Added TODO's for geometry.Point

Added TODO's for geometry.Point

export ^

-- Implementation of the AS3 Point. The API interface was used for function
-- names, but the code is original work.
class Point
	new: (x = 0, y = 0) =>
		-- Add the clone mixin
		clone = require "LunoPunk.utils.mixins.clone"
		clone @
		@x, @y = x, y

	-- The length of the line from (0, 0) to the point
	length: => math.sqrt @x^2 + @y^2

	-- Add two points together
	add: (p) => Point @x + p.x, @y + p.y

	__add: (p) => @add p

	-- Copies point data from p to this Point
	copyFrom: (p) => @x, @y = p.x, p.y

	-- The distance between two points
	distance: (p1, p2) -> math.sqrt (p1.x - p2.x)^2 + (p1.y - p2.y)^2

	-- determine if two points are equal
	equals: (p) => @x == p.x and @y == p.y

	-- determines a point between two points
	-- TODO
	-- interpolate: (p1, p2) =>

	-- scales the line between (0,0) and current point to a set length
	-- TODO
	-- normalize: (thinkness) =>

	-- offsets the point by an amount
	offset: (dx, dy) =>
		@x += dx
		@y += dy

	-- Converts polar coordinates to a new point
	-- TODO
	-- polar: (length, angle) ->

	-- set the point to a new value
	set: (x, y) => @x, @y = x, y

	-- Subtracts p from this point to create a new Point
	subtract: (p) => Point @x - p.x, @y - p.y

	toString: => string.format "(%f, %f)", @x, @y

	__tostring: => @toString!

export ^

-- Implementation of the AS3 Point. The API interface was used for function
-- names, but the code is original work.
class Point
	new: (x = 0, y = 0) =>
		-- Add the clone mixin
		clone = require "LunoPunk.utils.mixins.clone"
		clone @
		@x, @y = x, y

	-- The length of the line from (0, 0) to the point
	length: => math.sqrt @x^2 + @y^2

	-- Add two points together
	add: (p) => Point @x + p.x, @y + p.y

	__add: (p) => @add p

	-- Copies point data from p to this Point
	copyFrom: (p) => @x, @y = p.x, p.y

	-- The distance between two points
	distance: (p1, p2) -> math.sqrt (p1.x - p2.x)^2 + (p1.y - p2.y)^2

	-- determine if two points are equal
	equals: (p) => @x == p.x and @y == p.y

	-- offsets the point by an amount
	offset: (dx, dy) =>
		@x += dx
		@y += dy

	-- set the point to a new value
	set: (x, y) => @x, @y = x, y

	-- Subtracts p from this point to create a new Point
	subtract: (p) => Point @x - p.x, @y - p.y

	toString: => string.format "(%f, %f)", @x, @y

	__tostring: => @toString!


Implement LP.clamp

Implement LP.clamp

require "LunoPunk.geometry.Point"
require "LunoPunk.geometry.Rectangle"
require "LunoPunk.geometry.Matrix"
require "LunoPunk.Scene"
require "LunoPunk.Tweener"

export ^

class lp
	-- Private
	BASELAYER = 10
	DEG = -180 / math.pi
	INT_MAX_VALUE = nil
	NUMBER_MAX_VALUE = nil
	RAD = math.pi / -180
	VERSION = "0.0.1"
	__scene = nil

	_gameTime: nil
	_renderTime: nil
	_systemTime: nil
	_updateTime: nil
	assignedFrameRate: nil
	blackColor: 0x00000000
	bounds: nil
	buffer: nil
	camera: Point!
	console: nil
	defaultFont: "default"
	elapsed: 0
	engine: nil
	entity: nil
	fixed: false
	focused: nil
	frameRate: 0
	log: nil
	matrix: Matrix!
	orientations: nil
	pan: nil
	point: Point!
	point2: Point!
	random: -> math.random!
	randomSeed: nil
	rate: 0
	rect: Rectangle!
	renderMode: nil
	scene: Scene!
	screen: nil
	sprite: nil
	tweener: Tweener!
	volume: nil
	watch: nil
	world: nil
	zero: Point!

	-- Window/resolution related fields
	fullscreen: false
	halfHeight: -> LP.height/2
	halfWidth: -> LP.width/2
	height: 0
	width: 0
	windowHeight: 0
	windowWidth: 0

	alarm: (delay, complete, typeweenType, tweener) ->
	anchorTo: (object, anchor, distance) ->
	angle: (x1, y1, x2, y2) ->
	angleDifference: (angle1, angle2) ->
	angleXY: (object, angle, length, x, y) ->
	approach: (value, target, amount) ->
	choose: (objs) ->
	clamp: (value, min, max) -> if value < min
			min
		else if value > max
			max
		else
			value
	clampInRect: (object, x, y, width, height, padding) ->
	clear: (t) -> while #t > 0 do table.remove t
	colorLerp: (fromColor, toColor, t) ->
	consoleEnabled: ->
	createBitmap: (width, height, transparent, color) ->
	distance: (x1, y1, x2, y2) ->
	istanceRectPoint: (px, py, rx, ry, rw, rh) ->
	distanceRects: (x1, y1, w1, h1, x2, y2, w2, h2) ->
	distanceSquared: (x1, y1, xprint2, y2) ->
	frames: (from_frame, to_frame, skip) ->
	getImage: (source) -> love.graphics.newImage source
	getColorHSV: (h, s, v) ->
	getColorHue: (color) ->

	getColorSaturation: (color) ->
	getColorValue: (color) ->

	convertColor: (color) -> { lp.getRed(color), lp.getGreen(color), lp.getBlue(color) }
	getColorRGB: (r, g, b) -> r * 0x10000 + g * 0x100 + b
	getRed: (color) -> math.floor color / 0x10000
	getGreen: (color) -> math.floor color / 0x100 % 0x100
	getBlue: (color) -> math.floor color % 0x100

	indexOf: (tbl, item) -> for i, v in ipairs tbl do return i if item == v

	-- read-only
	DEG: -> DEG

	-- read-only
	NUMBER_MAX_VALUE: -> NUMBER_MAX_VALUE

	-- read-only
	RAD: -> RAD

	-- read-only
	VERSION: -> VERSION

	-- read-only
	BASELAYER: -> BASELAYER

	sceneIsNull: => @scene != @__scene or @__scene == nil

	lerp: (a, b, t) ->
	next: (current, options, loop) ->
	prev: (current, options, loop) ->

	rand: (amount) -> math.random(amount)
	randomizeSeed: -> math.randomseed os.time()

	resetCamera: -> camera.x, camera.y = 0, 0

	resize: (width, height) ->
		-- resize scene to scale
		width /= LP.screen\fullScaleX!
		height = height / LP.screen\fullScaleY!
		LP.width, LP.height = width, height
		LP.bounds.width, LP.bounds.height = width, height
		LP.screen\resize!

	rotateAround: (object, anchor, angle, relative) ->
	round: (num, precision) ->
	scale: (value, min, max, min2, max2) ->
	scaleClamp: (value, min, max, min2, max2) ->
	setCamera: (x, y) -> LP.camera.x, LP.camera.y = x, y
	shuffle: (a) ->

	-- Finds the sign of the provided value.
	-- @param	value		The Float to evaluate.
	-- @return	1 if value > 0, -1 if value < 0, and 0 when value == 0.
	sign: (value) -> if value < 0 then -1 else if value == 0 then 0 else 1

	stepTowards: (object, x, y, distance) ->
	swap: (current, a, b) ->
	swapScene: => @__scene = @scene
	timeFlag: ->
	tween: (object, values, duration, options) ->

	-- Used to determine the Löve version that's being used
	__love: (version) -> version == string.format "%d.%d", love._version_major, love._version_minor

LP = lp!

-- Don't allow unknown Löve versions
if not (LP.__love("0.8") or LP.__love("0.9"))
	assert false, "Unsupported Löve version"

require "LunoPunk.geometry.Point"
require "LunoPunk.geometry.Rectangle"
require "LunoPunk.geometry.Matrix"
require "LunoPunk.Scene"
require "LunoPunk.Tweener"

export ^

class lp
	-- Private
	BASELAYER = 10
	DEG = -180 / math.pi
	INT_MAX_VALUE = nil
	NUMBER_MAX_VALUE = nil
	RAD = math.pi / -180
	VERSION = "0.0.1"
	__scene = nil

	_gameTime: nil
	_renderTime: nil
	_systemTime: nil
	_updateTime: nil
	assignedFrameRate: nil
	blackColor: 0x00000000
	bounds: nil
	buffer: nil
	camera: Point!
	console: nil
	defaultFont: "default"
	elapsed: 0
	engine: nil
	entity: nil
	fixed: false
	focused: nil
	frameRate: 0
	log: nil
	matrix: Matrix!
	orientations: nil
	pan: nil
	point: Point!
	point2: Point!
	random: -> math.random!
	randomSeed: nil
	rate: 0
	rect: Rectangle!
	renderMode: nil
	scene: Scene!
	screen: nil
	sprite: nil
	tweener: Tweener!
	volume: nil
	watch: nil
	world: nil
	zero: Point!

	-- Window/resolution related fields
	fullscreen: false
	halfHeight: -> LP.height/2
	halfWidth: -> LP.width/2
	height: 0
	width: 0
	windowHeight: 0
	windowWidth: 0

	alarm: (delay, complete, typeweenType, tweener) ->
	anchorTo: (object, anchor, distance) ->
	angle: (x1, y1, x2, y2) ->
	angleDifference: (angle1, angle2) ->
	angleXY: (object, angle, length, x, y) ->
	approach: (value, target, amount) ->
	choose: (objs) ->
	clamp: (value, min, max) ->
	clampInRect: (object, x, y, width, height, padding) ->
	clear: (t) -> while #t > 0 do table.remove t
	colorLerp: (fromColor, toColor, t) ->
	consoleEnabled: ->
	createBitmap: (width, height, transparent, color) ->
	distance: (x1, y1, x2, y2) ->
	istanceRectPoint: (px, py, rx, ry, rw, rh) ->
	distanceRects: (x1, y1, w1, h1, x2, y2, w2, h2) ->
	distanceSquared: (x1, y1, xprint2, y2) ->
	frames: (from_frame, to_frame, skip) ->
	getImage: (source) -> love.graphics.newImage source
	getColorHSV: (h, s, v) ->
	getColorHue: (color) ->

	getColorSaturation: (color) ->
	getColorValue: (color) ->

	convertColor: (color) -> { lp.getRed(color), lp.getGreen(color), lp.getBlue(color) }
	getColorRGB: (r, g, b) -> r * 0x10000 + g * 0x100 + b
	getRed: (color) -> math.floor color / 0x10000
	getGreen: (color) -> math.floor color / 0x100 % 0x100
	getBlue: (color) -> math.floor color % 0x100

	indexOf: (tbl, item) -> for i, v in ipairs tbl do return i if item == v

	-- read-only
	DEG: -> DEG

	-- read-only
	NUMBER_MAX_VALUE: -> NUMBER_MAX_VALUE

	-- read-only
	RAD: -> RAD

	-- read-only
	VERSION: -> VERSION

	-- read-only
	BASELAYER: -> BASELAYER

	sceneIsNull: => @scene != @__scene or @__scene == nil

	lerp: (a, b, t) ->
	next: (current, options, loop) ->
	prev: (current, options, loop) ->

	rand: (amount) -> math.random(amount)
	randomizeSeed: -> math.randomseed os.time()

	resetCamera: -> camera.x, camera.y = 0, 0

	resize: (width, height) ->
		-- resize scene to scale
		width /= LP.screen\fullScaleX!
		height = height / LP.screen\fullScaleY!
		LP.width, LP.height = width, height
		LP.bounds.width, LP.bounds.height = width, height
		LP.screen\resize!

	rotateAround: (object, anchor, angle, relative) ->
	round: (num, precision) ->
	scale: (value, min, max, min2, max2) ->
	scaleClamp: (value, min, max, min2, max2) ->
	setCamera: (x, y) -> LP.camera.x, LP.camera.y = x, y
	shuffle: (a) ->

	-- Finds the sign of the provided value.
	-- @param	value		The Float to evaluate.
	-- @return	1 if value > 0, -1 if value < 0, and 0 when value == 0.
	sign: (value) -> if value < 0 then -1 else if value == 0 then 0 else 1

	stepTowards: (object, x, y, distance) ->
	swap: (current, a, b) ->
	swapScene: => @__scene = @scene
	timeFlag: ->
	tween: (object, values, duration, options) ->

	-- Used to determine the Löve version that's being used
	__love: (version) -> version == string.format "%d.%d", love._version_major, love._version_minor

LP = lp!

-- Don't allow unknown Löve versions
if not (LP.__love("0.8") or LP.__love("0.9"))
	assert false, "Unsupported Löve version"


Clean up Aegisub example module.

Clean up Aegisub example module.

-- This library is unlicensed under CC0

export script_name        = "ASSInspector Example"
export script_description = "Calculates bounding rectangles on the start and end times of selected events."
export script_author      = "torque"
export script_version     = 0x000003

ffi  = require( 'ffi' )

ffi.cdef( [[
typedef struct {
	int x, y;
	unsigned int w, h;
} ASSI_Rect;

uint32_t    assi_getVersion( void );
const char* assi_getErrorString( void* );
void*       assi_init( int, int, const char*, const char* );
int         assi_setHeader( void*, const char* );
int         assi_setScript( void*, const char* );
int         assi_calculateBounds( void*, ASSI_Rect*, const int32_t*, const uint32_t );
void        assi_cleanup( void* );
]] )

-- Figure out a better way to load this?
extension = ('OSX' == ffi.os and '.dylib' or 'Windows' == ffi.os and '.dll' or '.so')
libraryPath = "?user/automation/include/ASSInspector"
success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
if not success
	libraryPath = "?data/automation/include/ASSInspector"
	success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
	if not success
		error( "Could not load required libASSInspector library." )

-- Inspector is not actually a property of the class but a property of
-- the script. Since we don't need to worry about concurrency or
-- anything silly like that, we can initialize ASSInspector once and
-- keep that reference alive until Aegisub is closed or scripts are all
-- reloaded.
state = {
	inspector: nil
	fontDir:   nil
	resX:      nil
	resY:      nil
}

collectHeader = ( subtitles ) =>
	scriptHeader = {
		"[Script Info]"
	}

	-- These are the only header fields that actually affect the way ASS
	-- is rendered. I don't actually know if ScriptType matters.
	infoFields = {
		PlayResX:   true
		PlayResY:   true
		WrapStyle:  true
		ScriptType: true
		ScaledBorderAndShadow: true
	}

	styles = { }

	seenStyles = false
	resX = 640
	resY = 480

	-- This loop assumes the subtitle script is nicely organized in the
	-- order [Script Info] -> [V4+ Styles] -> [Events]. This isn't
	-- guaranteed for scripts floating around in the wild, but fortunately
	-- Aegisub 3.2 seems to do a very good job of ensuring this order when
	-- a script is loaded. In other words, this loop is probably only safe
	-- to do from within automation.
	for index = 1, #subtitles
		with line = subtitles[index]
			if "info" == .class
				if infoFields[.key]
					table.insert( scriptHeader, .raw )

				if "PlayResX" == .key
					resX = tonumber( .value )
				elseif "PlayResY" == .key
					resY = tonumber( .value )

			elseif "style" == .class
				unless seenStyles
					table.insert( scriptHeader, "[V4+ Styles]\n" )
					seenStyles = true

				styles[.name] = .raw

			elseif "dialogue" == .class
				break

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution. Don't use low-resolution workraws for typesetting!
	vidResX, vidResY = aegisub.video_size( )
	@resX = vidResX or resX
	@resY = vidResY or resY

	@header = table.concat( scriptHeader, '\n' )
	@styles = styles

initializeInspector = ( fontDirectory = state.fontDir ) =>
	if nil == state.inspector
		libVersion = ASSInspector.assi_getVersion!
		if libVersion < script_version
			return nil, "ASSInspector C library is out of date."
		elseif libVersion > script_version
			return nil, "ASSInspector moonscript library is out of date."

		state.inspector = ffi.gc( ASSInspector.assi_init( @resX, @resY, aegisub.decode_path( libraryPath .. "/fonts.conf" ), fontDirectory ), ASSInspector.assi_cleanup )

		if nil == state.inspector
			return nil, "ASSInspector library initialization failed."

		state.resX = @resX
		state.resY = @resY
		state.fontDir = fontDirectory

	return true

validateRect = ( rect ) ->
	bounds = {
		x: tonumber( rect.x )
		y: tonumber( rect.y )
		w: tonumber( rect.w )
		h: tonumber( rect.h )
	}

	if bounds.w == 0 or bounds.h == 0
		return false
	else
		return bounds

defaultTimes = ( lines ) ->
	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame

	seenTimes = { }
	times = { }
	hasFrames = ffms( 0 )

	for line in *lines
		with line
			if (true == .assi_exhaustive) and hasFrames
				for frame = ffms( .start_time ), ffms( .end_time ) - 1
					frameTime = math.floor( 0.5*( msff( frame ) + msff( frame + 1 ) ) )
					unless seenTimes[frameTime]
						table.insert( times, frameTime )
						seenTimes[frameTime] = true
			else
				unless seenTimes[.start_time]
					table.insert( times, frameTime )
					seenTimes[.start_time] = true

	-- This will only happen if all lines are displayed for 0 frames.
	unless 0 < #times
		return false
	else
		return times

addStyles = ( line, scriptText, seenStyles ) =>
	if @styles[line.style] and not seenStyles[line.style]
		table.insert( scriptText, @styles[line.style] )
		seenStyles[line.style] = true

	line.text\gsub "{(.-)}", ( tagBlock ) ->
		tagBlock\gsub "\\r([^\\}]*)", ( styleName ) ->
			if 0 < #styleName and @styles[styleName] and not seenStyles[styleName]
				table.insert( scriptText, @styles[styleName] )
				seenStyles[styleName] = true

class Inspector

	new: ( subtitles, fontDirectory = aegisub.decode_path( '?script/fonts' ) ) =>
		if '?script/fonts' == fontDirectory
			fontDirectory = nil

		collectHeader( @, subtitles )
		success, message = initializeInspector( @, fontDirectory )
		if nil == success
			return success, message

		@updateHeader!

	updateHeader: ( subtitles ) =>
		if nil != subtitles
			collectHeader( @, subtitles )

		if @resX != state.resX or @resY != state.resY
			@forceLibraryReload!

		if 0 < ASSInspector.assi_setHeader( state.inspector, @header )
			return nil, "Failed to set header.\n" .. ffi.string( ASSInspector.assi_getErrorString( state.inspector ) )

	forceLibraryReload: ( fontDirectory ) =>
		state.inspector = nil
		initializeInspector( @, fontDirectory )

	-- Arguments:
	-- subtitles: the subtitles object that Aegisub passes to the macro.

	-- line: an array-like table of line tables that have at least the
	--       fields `start_time`, `text`, `end_time` and `raw`. `raw` is
	--       mandatory in all cases. `start_time` and `end_time` are only
	--       necessary if a table of times to render the line is not
	--       provided and a video is loaded. Allows rendering 1 or more
	--       lines simultaneously.

	-- times: a table of times (in milliseconds) to render the line at.
	--        Defaults to the start time of the line unless there is a video
	--        loaded and the line contains the field line.assi_exhaustive =
	--        true, in which case it defaults to every single frame that the
	--        line is displayed.

	-- Returns:
	-- An array-like table of bounding boxes, and an array-like table of
	-- render times, the two tables are the same length. If multiple lines
	-- are passed, the bounding box for a given time is the combined
	-- bounding boxes of all the lines.

	-- Error Handling:
	-- If an error is encounted, getBounds returns nil and an error string,
	-- which is typical for lua error reporting. In order to distinguish
	-- between an error and a valid false return, users should make sure
	-- they actually compare result to nil and false. Rather than just
	-- checking that the result is not falsy.

	getBounds: ( lines, times = defaultTimes( lines ) ) =>
		unless times
			return false

		scriptText = { }
		seenStyles = { }
		if @styles.Default
			seenStyles.Default = true
			table.insert( scriptText, @styles.Default )

		for line in *lines
			addStyles( @, line, scriptText, seenStyles )

		table.insert( scriptText, '[Events]' )

		for line in *lines
			table.insert( scriptText, line.raw )

		if 0 < ASSInspector.assi_setScript( state.inspector, table.concat( scriptText, '\n' ) )
			return nil, "Could not set script" .. ffi.string( ASSInspector.assi_getErrorString( state.inspector ) )

		renderCount = #times
		cTimes = ffi.new( 'int32_t[?]', renderCount )
		cRects = ffi.new( 'ASSI_Rect[?]', renderCount )

		for i = 0, renderCount - 1
			cTimes[i] = times[i + 1]

		if 0 < ASSInspector.assi_calculateBounds( state.inspector, cRects, cTimes, renderCount )
			return nil, "Error calculating bounds" .. ffi.string( ASSInspector.assi_getErrorString( state.inspector ) )

		rects = { }
		for i = 0, renderCount - 1
			table.insert( rects, validateRect( cRects[i] ) )

		return rects, times

return Inspector

-- This library is unlicensed under CC0

export script_name        = "ASSInspector Example"
export script_description = "Calculates bounding rectangles on the start and end times of selected events."
export script_author      = "torque"
export script_version     = 0x000003

ffi  = require( 'ffi' )
util = require( 'aegisub.util' )
log  = require( 'a-mo.log' )

ffi.cdef( [[
typedef struct {
	int x, y;
	unsigned int w, h;
} ASSI_Rect;

uint32_t    assi_getVersion( void );
const char* assi_getErrorString( void* );
void*       assi_init( int, int, const char*, const char* );
int         assi_setHeader( void*, const char* );
int         assi_setScript( void*, const char* );
int         assi_calculateBounds( void*, ASSI_Rect*, const int32_t*, const uint32_t );
void        assi_cleanup( void* );
]] )

-- Figure out a better way to load this?
extension = ('OSX' == ffi.os and '.dylib' or 'Windows' == ffi.os and '.dll' or '.so')
libraryPath = "?user/automation/include/ASSInspector"
success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
if not success
	libraryPath = "?data/automation/include/ASSInspector"
	success, ASSInspector = pcall( ffi.load, aegisub.decode_path( libraryPath .. "/libASSInspector" .. extension ) )
	if not success
		error( "Could not load required libASSInspector library." )

-- Inspector is not actually a property of the class but a property of
-- the script. Since we don't need to worry about concurrency or
-- anything silly like that, we can initialize ASSInspector once and
-- keep that reference alive until Aegisub is closed or scripts are all
-- reloaded.
state = {
	inspector: nil
	fontDir:   nil
	resX:      nil
	resY:      nil
}

collectHeader = ( subtitles ) =>
	scriptHeader = {
		"[Script Info]"
	}

	-- These are the only header fields that actually affect the way ASS
	-- is rendered. I don't actually know if ScriptType matters.
	infoFields = {
		PlayResX:   true
		PlayResY:   true
		WrapStyle:  true
		ScriptType: true
		ScaledBorderAndShadow: true
	}

	styles = { }

	seenStyles = false
	resX = 640
	resY = 480

	-- This loop assumes the subtitle script is nicely organized in the
	-- order [Script Info] -> [V4+ Styles] -> [Events]. This isn't
	-- guaranteed for scripts floating around in the wild, but fortunately
	-- Aegisub 3.2 seems to do a very good job of ensuring this order when
	-- a script is loaded. In other words, this loop is probably only safe
	-- to do from within automation.
	for index = 1, #subtitles
		with line = subtitles[index]
			if "info" == .class
				if infoFields[.key]
					table.insert( scriptHeader, .raw )

				if "PlayResX" == .key
					resX = tonumber( .value )
				elseif "PlayResY" == .key
					resY = tonumber( .value )

			elseif "style" == .class
				unless seenStyles
					table.insert( scriptHeader, "[V4+ Styles]\n" )
					seenStyles = true

				styles[.name] = .raw

			elseif "dialogue" == .class
				break

	-- If a video is loaded, use its resolution instead of the script's
	-- resolution. Don't use low-resolution workraws for typesetting!
	vidResX, vidResY = aegisub.video_size( )
	@resX = vidResX or resX
	@resY = vidResY or resY

	@header = table.concat( scriptHeader, '\n' )
	@styles = styles

initializeInspector = ( fontDirectory = state.fontDir ) =>
	if nil == state.inspector
		libVersion = ASSInspector.assi_getVersion!
		if libVersion < script_version
			return nil, "ASSInspector C library is out of date."
		elseif libVersion > script_version
			return nil, "ASSInspector moonscript library is out of date."

		state.inspector = ffi.gc( ASSInspector.assi_init( @resX, @resY, aegisub.decode_path( libraryPath .. "/fonts.conf" ), fontDirectory ), ASSInspector.assi_cleanup )

		if nil == state.inspector
			return nil, "ASSInspector library initialization failed."

		state.resX = @resX
		state.resY = @resY
		state.fontDir = fontDirectory

	return true

validateRect = ( rect ) ->
	bounds = {
		x: tonumber( rect.x )
		y: tonumber( rect.y )
		w: tonumber( rect.w )
		h: tonumber( rect.h )
	}

	log.dump bounds

	if bounds.w == 0 or bounds.h == 0
		return false
	else
		return bounds

defaultTimes = ( lines ) ->
	ffms = aegisub.frame_from_ms
	msff = aegisub.ms_from_frame

	seenTimes = { }
	times = { }
	hasFrames = ffms( 0 )

	for line in *lines
		with line
			if (true == .assi_exhaustive) and hasFrames
				for frame = ffms( .start_time ), ffms( .end_time ) - 1
					frameTime = math.floor( 0.5*( msff( frame ) + msff( frame + 1 ) ) )
					unless seenTimes[frameTime]
						table.insert( times, frameTime )
						seenTimes[frameTime] = true
			else
				unless seenTimes[.start_time]
					table.insert( times, frameTime )
					seenTimes[.start_time] = true

	-- This will only happen if all lines are displayed for 0 frames.
	unless 0 < #times
		return false
	else
		return times

addStyles = ( line, scriptText, seenStyles ) =>
	if @styles[line.style] and not seenStyles[line.style]
		table.insert( scriptText, @styles[line.style] )
		seenStyles[line.style] = true

	line.text\gsub "{(.-)}", ( tagBlock ) ->
		tagBlock\gsub "\\r([^\\}]*)", ( styleName ) ->
			if 0 < #styleName and @styles[styleName] and not seenStyles[styleName]
				table.insert( scriptText, @styles[styleName] )
				seenStyles[styleName] = true

class Inspector

	new: ( subtitles, fontDirectory = aegisub.decode_path( '?script/fonts' ) ) =>
		if '?script/fonts' == fontDirectory
			fontDirectory = nil

		collectHeader( @, subtitles )
		success, message = initializeInspector( @, fontDirectory )
		if nil == success
			return success, message

		@updateHeader!

	updateHeader: ( subtitles ) =>
		if nil != subtitles
			collectHeader( @, subtitles )

		if @resX != state.resX or @resY != state.resY
			@forceLibraryReload!

		if 0 < ASSInspector.assi_setHeader( state.inspector, @header )
			return nil, "Failed to set header.\n" .. ffi.string( ASSInspector.assi_getErrorString( state.inspector ) )

	forceLibraryReload: ( fontDirectory ) =>
		state.inspector = nil
		initializeInspector( @, fontDirectory )

	-- Arguments:
	-- subtitles: the subtitles object that Aegisub passes to the macro.

	-- line: an array-like table of line tables that have at least the
	--       fields `start_time`, `text`, `end_time` and `raw`. `raw` is
	--       mandatory in all cases. `start_time` and `end_time` are only
	--       necessary if a table of times to render the line is not
	--       provided and a video is loaded. Allows rendering 1 or more
	--       lines simultaneously.

	-- times: a table of times (in milliseconds) to render the line at.
	--        Defaults to the start time of the line unless there is a video
	--        loaded and the line contains the field line.assi_exhaustive =
	--        true, in which case it defaults to every single frame that the
	--        line is displayed.

	-- Returns:
	-- False or an array-like table of bounding boxes of the form {:x, :y,
	-- :w, :h }. If false, then the line is not displayed on screen. If
	-- multiple times are rendered, each index of the return table will be
	-- false or a bounding box. If an index is false, the line is not
	-- displayed on that frame.

	-- Error Handling:
	-- If an error is encounted, getBounds returns nil and an error string,
	-- which is typical for lua error reporting. In order to distinguish
	-- between an error and a valid false return, users should make sure
	-- they actually compare result to nil and false. Rather than just
	-- checking that the result is not falsy.
	getBounds: ( lines, times = defaultTimes( lines ) ) =>
		unless times
			return false

		scriptText = { }
		seenStyles = { }
		if @styles.Default
			seenStyles.Default = true
			table.insert( scriptText, @styles.Default )

		for line in *lines
			addStyles( @, line, scriptText, seenStyles )

		table.insert( scriptText, '[Events]' )

		for line in *lines
			table.insert( scriptText, line.raw )

		if 0 < ASSInspector.assi_setScript( state.inspector, table.concat( scriptText, '\n' ) )
			return nil, "Could not set script" .. ffi.string( ASSInspector.assi_getErrorString( state.inspector ) )

		renderCount = #times
		cTimes = ffi.new( 'int32_t[?]', renderCount )
		cRects = ffi.new( 'ASSI_Rect[?]', renderCount )

		for i = 0, renderCount - 1
			cTimes[i] = times[i + 1]

		if 0 < ASSInspector.assi_calculateBounds( state.inspector, cRects, cTimes, renderCount )
			return nil, "Error calculating bounds" .. ffi.string( ASSInspector.assi_getErrorString( state.inspector ) )

		rects = { }
		for i = 0, renderCount - 1
			table.insert( rects, validateRect( cRects[i] ) )

		return rects, times

return Inspector


utf8: remove extra space

utf8: remove extra space

Former-commit-id: 388a98741a9c3afcc2be2c02f7f6acda0238af66 [formerly ed57c9bc2a5f0cb171b6d46503af66353783a18e]
Former-commit-id: 26ac23fc0c8536c4ec8534f5bb806650e9360009

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in string.gfind(chars, "([%z\1-\127\194-\244][\128-\191]*)")
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[ ⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in string.gfind(chars, "([%z\1-\127\194-\244][\128-\191]*)")
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)


Fix: Another fix to lua5.1 compat

Fix: Another fix to lua5.1 compat

-- The MIT License (MIT)
-- Copyright (c) 2016 Yanghe Sun

-- freeze lar files to build a self-running program
-- Required module:
-- lua_cliargs, moonscript

cli   = require("cliargs")
path  = require("path")
fs    = require("path.fs")
moon  = require("moon")
load  = loadstring or load

import split, splitext from path

COPYRIGHT = "Copyright (C) 1994-2017 Yanghe Sun"
VERSION = "freeze packager 0.1b"

print_version = ->
    print(VERSION .. " " .. COPYRIGHT)
    print("lua_cliargs: version " .. cli.VERSION)
    os.exit(0)

cli\set_name("freeze.moon")
cli\argument("ORIG", "orig executeable file")
cli\argument("MAIN", "main lar file to execute")
cli\argument("OUTPUT", "path to the output executeable file")
cli\splat("INPUTS", "lar files to package", "", 100)
cli\flag("-v, --version", "prints the program's version and exits", print_version)
cli\flag("--verbose", "the script output will be very verbose")
args, err = cli\parse(arg)

if err then
    print(err)
    os.exit(0)

get_sizegroup = (ori) ->
    ret = {}
    for u, v in ipairs(ori)
        ret[u] = fs.fsize(v)
    return ret

dumps = (tbl, main) ->
    tbcode = "{"
    for _, v in ipairs(tbl)
        subcode = "{"
        subcode ..= string.format("size=%d,", v.size)
        subcode ..= string.format("id=\"%s\"", v.id)
        subcode ..= "},"
        tbcode ..= subcode
    tbcode ..= "}"
    funcode = string.format("return %s, \"%s\"", tbcode, main)
    return string.dump(load(funcode), true)

create_meta = (group, main)->
    size = get_sizegroup(group)
    ret  = {}
    for i= 1, #group
        ret[i] = { size: size[i],
                   id: splitext(select(2, split(group[i]))) }
    meta = dumps(ret, main)
    info = string.format("<larpr%010d>", #meta)
    return meta, info

meta, info = create_meta(args.INPUTS, args.MAIN)
f = io.open(args.OUTPUT, "wb")
f1 = io.open(args.ORIG, "rb")
f\write(f1\read("*a"))

for v in *args.INPUTS
    f1 = io.open(v, "rb")
    f\write(f1\read("*a"))
    f1\close()

f\write(meta)
f\write(info)
f\close()

-- The MIT License (MIT)
-- Copyright (c) 2016 Yanghe Sun

-- freeze lar files to build a self-running program
-- Required module:
-- lua_cliargs, moonscript

cli   = require("cliargs")
path  = require("path")
fs    = require("path.fs")
moon  = require("moon")
load  = load or loadstring

import split, splitext from path

COPYRIGHT = "Copyright (C) 1994-2017 Yanghe Sun"
VERSION = "freeze packager 0.1b"

print_version = ->
    print(VERSION .. " " .. COPYRIGHT)
    print("lua_cliargs: version " .. cli.VERSION)
    os.exit(0)

cli\set_name("freeze.moon")
cli\argument("ORIG", "orig executeable file")
cli\argument("MAIN", "main lar file to execute")
cli\argument("OUTPUT", "path to the output executeable file")
cli\splat("INPUTS", "lar files to package", "", 100)
cli\flag("-v, --version", "prints the program's version and exits", print_version)
cli\flag("--verbose", "the script output will be very verbose")
args, err = cli\parse(arg)

if err then
    print(err)
    os.exit(0)

get_sizegroup = (ori) ->
    ret = {}
    for u, v in ipairs(ori)
        ret[u] = fs.fsize(v)
    return ret

dumps = (tbl, main) ->
    tbcode = "{"
    for _, v in ipairs(tbl)
        subcode = "{"
        subcode ..= string.format("size=%d,", v.size)
        subcode ..= string.format("id=\"%s\"", v.id)
        subcode ..= "},"
        tbcode ..= subcode
    tbcode ..= "}"
    funcode = string.format("return %s, \"%s\"", tbcode, main)
    return string.dump(load(funcode), true)

create_meta = (group, main)->
    size = get_sizegroup(group)
    ret  = {}
    for i= 1, #group
        ret[i] = { size: size[i],
                   id: splitext(select(2, split(group[i]))) }
    meta = dumps(ret, main)
    info = string.format("<larpr%010d>", #meta)
    return meta, info

meta, info = create_meta(args.INPUTS, args.MAIN)
f = io.open(args.OUTPUT, "wb")
f1 = io.open(args.ORIG, "rb")
f\write(f1\read("*a"))

for v in *args.INPUTS
    f1 = io.open(v, "rb")
    f\write(f1\read("*a"))
    f1\close()

f\write(meta)
f\write(info)
f\close()


Im having too much fun with this.

Im having too much fun with this.

rex = require "rex_pcre"
cli = require "cliargs"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	results = {}
	for k, v in pairs structure
		res = func(v, map_listing)
		insert(results, res) if res
	results

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "mkdir"
			"[mkdir]: Woo?"
		when "symlink", "hardlink"
			lfs.link(old, new, action == "hardlink" or false) if not debug
			"[link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')

			isdir = (md) -> if md == 'directory' then true else false

			in_f = parent.in .. '/' .. file
			out_f = if isdir(mode)
				do
					f = file
					->
					f = rex.gsub(f, regex, '')
					f = f\match("^%s*(.-)%s*$")
					parent.out .. '/' .. f
			else
				do
					f = file
					->
					f = rex.gsub(f, regex, '')
					f = rex.match(f, [[(.*)(v\d)]]) or f
					f = f\gsub('[ ]', '')
					f = rex.gsub(f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
					f ..= file_ext(file)
					parent.out .. '/' .. f

			action = if isdir(mode) then "mkdir" else args["a"]
			print perform_action(in_f, out_f, action)
			process_files(data, regex, {in: in_f, out: out_f}) if data

		else
			print "ERR: ", file, mode

cli\set_name("regexulon.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-e, --existing", "skip creating previously existing target directories.")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")

export args = cli\parse_args()
return if not args

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

inputdir = args["DIR"]
targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

--map_listing(files, print_listing)
processed = process_files(files, regex, {in: inputdir, out: targetdir})

rex = require "rex_pcre"
cli = require "cliargs"
lfs = require "lfs"

import insert from table

contains = (t, item) ->
	for k, v in pairs t
		return true if v == item
	false

local dir_list, dir_listing
dir_listing = (path, file) ->
	f = path .. '/' .. file
	attr = lfs.attributes(f).mode
	{file, attr, if attr == 'directory' then dir_list(f)}

dir_list = (path) ->
	[dir_listing(path, file) for file in lfs.dir(path) when not contains({'.', '..'}, file)]

map_listing = (structure, func) ->
	results = {}
	for k, v in pairs structure
		res = func(v, map_listing)
		insert(results, res) if res
	results

print_listing = (value, func) ->
	{file, mode, data} = value
	print file, mode
	func(data, print_listing) if data

file_ext = (str) ->
	"." .. str\match(".([^%.]+)$")

dbg = (b) ->
	"[DBG: #{(b and 'Y') or 'N'}]"

perform_action = (old, new, action) ->
	dbg(debug) .. switch action
		when "copy"
			"[cp]: Not yet implemented."
		when "move"
			"[mv]: Not yet implemented."
		when "symlink", "hardlink"
			lfs.link(old, new, action == "hardlink" or false) if not debug
			"[link]: #{old} to: #{new}"
		else
			"unknown action: #{action}"

process_files = (filedata, regex, parent) ->
	for k, {file, mode, data} in pairs filedata
		if rex.gsub(file, regex, '')
			if mode == 'directory'
				in_f = parent.in .. '/' .. file
				out_f = do
					f = file
					->
					f = rex.gsub(f, regex, '')
					f = f\match("^%s*(.-)%s*$")
					parent.out .. '/' .. f
				lfs.mkdir(out_f)
				process_files(data, regex, {in: in_f, out: out_f})
			else
				ext = file_ext(file)
				newer_f = rex.gsub(file, regex, '')
				new_f = rex.match(newer_f, [[(.*)(v\d)]]) or newer_f
				new_f = new_f\gsub('[ ]', '')
				new_f = rex.gsub(new_f, [[([^\d]*)(\d+)[^\d]*$]], [[%1_ep%2]], 1)
				old_f, new_f = parent.in .. '/' .. file, parent.out .. '/' .. new_f .. ext
				print perform_action(old_f, new_f, args["a"])

		else
			print "ERR: ", file, mode

cli\set_name("regexulon.lua")
cli\add_argument("DIR", "directory to scan")
cli\add_argument("TARGET", "target directory")
cli\optarg("IGNORED", "ignored files/directories", "")
cli\add_option("-a, --action=ACTION", "action to take: cp, mv, symlink, hardlink", "hardlink")
cli\add_flag("-e, --existing", "skip creating previously existing target directories.")
cli\add_flag("-d, --debug", "script will simulate execution.")
cli\add_flag("-V, --version", "prints the program version")
cli\add_flag("-v, --verbose", "verbose output")

export args = cli\parse_args()
return if not args

ignored_files = switch type(args["IGNORED"])
	when "string" then {}
	else args["IGNORED"]

inputdir = args["DIR"]
targetdir = args["TARGET"]
export debug = args["d"]

files = dir_list(args["DIR"])
regex = [==[\(.*?\)|\[.*?\]]==]

--map_listing(files, print_listing)
processed = process_files(files, regex, {in: inputdir, out: targetdir})


added possibility to turn rooms

added possibility to turn rooms

export ^

require "rooms/rock"
require "rooms/corridor"
require "ui/button"
shapes = require "rooms/shape"

class UI
    -- todo? separate roomPlacer and crewScheduler?
    new: (@spacebase) =>
        @roomTypes = {Rock, Corridor}
        @nextShapes = {}
        @hRatio = 0.1 -- space taken on screen
        -- fill with random shapes
        for i=1,4
            table.insert(@nextShapes, shapes.random())

        @roomButtons = {}
        @selectedRoomType = nil
        @currentLeftTurns = 0
        offset = 1/6 * wScr()
        width = 70
        margin_w = 10
        margin_h = 0
        for i, room in ipairs(@roomTypes)
            i -= 1 -- starts at 0
            x = offset + i * width + i * margin_w
            y = margin_h
            w = width
            h = @hRatio * hScr()
            butt = Button(x, y, w, h, room)
            table.insert(@roomButtons, butt)

    draw: =>
        w, h = wScr(), hScr()
        love.graphics.setColor(0,0,0, 100)
        love.graphics.rectangle("fill", 0, 0, w, @hRatio * h)
        love.graphics.rectangle("fill", 0, (1 - @hRatio) * h, w, @hRatio * h)

        -- buttons
        for i, butt in ipairs(@roomButtons)
            butt\draw(i == @selectedRoomType)

    isInside: (x, y) =>
        return y < @hRatio * hScr() or y > (1 - @hRatio) * hScr()

    nextShape: =>
        return @nextShapes[#@nextShapes]

    updateShapes: =>
        table.remove(@nextShapes, 1)
        table.insert(@nextShapes, shapes.random())

    selectedRoom: =>
        return @roomTypes[@selectedRoomType](@nextShape(), {0, 0}, @currentLeftTurns)

    selectNewRoomType: (n) =>
        @selectedRoomType = n
        @spacebase.floatingRoom = @selectedRoom()

    unselectRoomType: =>
        @selectedRoomType = nil
        @spacebase.floatingRoom = nil

    turnSelectedRoom: =>
        @currentLeftTurns += 1
        @currentLeftTurns %= 4
        @spacebase.floatingRoom = @selectedRoom()

    placeRoom: =>
        @spacebase\placeFloatingRoom()
        @selectedRoomType = nil
        @updateShapes()

    mousepressed: (x, y, button) =>
        switch button
            when "l"
                if @selectedRoomType ~= nil
                    @placeRoom()
                for i, butt in ipairs(@roomButtons)
                    if butt\isInside(x, y)
                        @selectNewRoomType(i)
            when "r"
                @turnSelectedRoom()

export ^

require "rooms/rock"
require "rooms/corridor"
require "ui/button"
shapes = require "rooms/shape"

class UI
    -- todo? separate roomPlacer and crewScheduler?
    new: (@spacebase) =>
        @roomTypes = {Rock, Corridor}
        @nextShapes = {}
        @hRatio = 0.1 -- space taken on screen
        -- fill with random shapes
        for i=1,4
            table.insert(@nextShapes, shapes.random())

        @roomButtons = {}
        @selectedRoomType = nil
        @currentLeftTurns = 0
        offset = 1/6 * wScr()
        width = 70
        margin_w = 10
        margin_h = 0
        for i, room in ipairs(@roomTypes)
            i -= 1 -- starts at 0
            x = offset + i * width + i * margin_w
            y = margin_h
            w = width
            h = @hRatio * hScr()
            butt = Button(x, y, w, h, room)
            table.insert(@roomButtons, butt)

    draw: =>
        w, h = wScr(), hScr()
        love.graphics.setColor(0,0,0, 100)
        love.graphics.rectangle("fill", 0, 0, w, @hRatio * h)
        love.graphics.rectangle("fill", 0, (1 - @hRatio) * h, w, @hRatio * h)

        -- buttons
        for i, butt in ipairs(@roomButtons)
            butt\draw(i == @selectedRoomType)

    isInside: (x, y) =>
        return y < @hRatio * hScr() or y > (1 - @hRatio) * hScr()

    nextShape: =>
        return @nextShapes[#@nextShapes]

    updateShapes: =>
        table.remove(@nextShapes, 1)
        table.insert(@nextShapes, shapes.random())

    selectedRoom: =>
        return @roomTypes[@selectedRoomType](@nextShape(), {0, 0}, @currentLeftTurns)

    selectNewRoomType: (n) =>
        @selectedRoomType = n
        @spacebase.floatingRoom = @selectedRoom()

    unselectRoomType: =>
        @selectedRoomType = nil
        @spacebase.floatingRoom = nil

    turnSelectedRoom: =>
        @currentLeftTurns += 1
        @spacebase.floatingRoom = @selectedRoom

    placeRoom: =>
        @spacebase\placeFloatingRoom()
        @selectedRoomType = nil
        @updateShapes()

    mousepressed: (x, y, button) =>
        switch button
            when "l"
                if @selectedRoomType ~= nil
                    @placeRoom()
                for i, butt in ipairs(@roomButtons)
                    if butt\isInside(x, y)
                        @selectNewRoomType(i)
            when "r"
                roomLeftTurn()