123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- -- Generic utility functions
- module( ..., package.seeall )
- local lfs = require "lfs"
- local sf = string.format
- -- Taken from Lake
- dir_sep = package.config:sub( 1, 1 )
- is_os_windows = dir_sep == '\\'
- -- Converts a string with items separated by 'sep' into a table
- string_to_table = function( s, sep )
- if type( s ) ~= "string" then return end
- sep = sep or ' '
- if s:sub( -1, -1 ) ~= sep then s = s .. sep end
- s = s:gsub( sf( "^%s*", sep ), "" )
- local t = {}
- local fmt = sf( "(.-)%s+", sep )
- for w in s:gmatch( fmt ) do table.insert( t, w ) end
- return t
- end
- -- Split a file name into 'path part' and 'extension part'
- split_ext = function( s )
- local pos
- for i = #s, 1, -1 do
- if s:sub( i, i ) == "." then
- pos = i
- break
- end
- end
- if not pos or s:find( dir_sep, pos + 1 ) then return s end
- return s:sub( 1, pos - 1 ), s:sub( pos )
- end
- -- Replace the extension of a given file name
- replace_extension = function( s, newext )
- local p, e = split_ext( s )
- if e then
- if newext and #newext > 0 then
- s = p .. "." .. newext
- else
- s = p
- end
- end
- return s
- end
- -- Return 'true' if building from Windows, false otherwise
- is_windows = function()
- return is_os_windows
- end
- -- Prepend each component of a 'pat'-separated string with 'prefix'
- prepend_string = function( s, prefix, pat )
- if not s or #s == 0 then return "" end
- pat = pat or ' '
- local res = ''
- local st = string_to_table( s, pat )
- foreach( st, function( k, v ) res = res .. prefix .. v .. " " end )
- return res
- end
- -- Like above, but consider 'prefix' a path
- prepend_path = function( s, prefix, pat )
- return prepend_string( s, prefix .. dir_sep, pat )
- end
- -- full mkdir: create all the paths needed for a multipath
- full_mkdir = function( path )
- local ptables = string_to_table( path, dir_sep )
- local p, res = ''
- for i = 1, #ptables do
- p = ( i ~= 1 and p .. dir_sep or p ) .. ptables[ i ]
- res = lfs.mkdir( p )
- end
- return res
- end
- -- Concatenate the given paths to form a complete path
- concat_path = function( paths )
- return table.concat( paths, dir_sep )
- end
- -- Return true if the given array contains the given element, false otherwise
- array_element_index = function( arr, element )
- for i = 1, #arr do
- if arr[ i ] == element then return i end
- end
- end
- -- Linearize an array with (possibly) embedded arrays into a simple array
- _linearize_array = function( arr, res, filter )
- if type( arr ) ~= "table" then return end
- for i = 1, #arr do
- local e = arr[ i ]
- if type( e ) == 'table' and filter( e ) then
- _linearize_array( e, res, filter )
- else
- table.insert( res, e )
- end
- end
- end
- linearize_array = function( arr, filter )
- local res = {}
- filter = filter or function( v ) return true end
- _linearize_array( arr, res, filter )
- return res
- end
- -- Return an array with the keys of a table
- table_keys = function( t )
- local keys = {}
- foreach( t, function( k, v ) table.insert( keys, k ) end )
- return keys
- end
- -- Return an array with the values of a table
- table_values = function( t )
- local vals = {}
- foreach( t, function( k, v ) table.insert( vals, v ) end )
- return vals
- end
- -- Returns true if 'path' is a regular file, false otherwise
- is_file = function( path )
- return lfs.attributes( path, "mode" ) == "file"
- end
- -- Returns true if 'path' is a directory, false otherwise
- is_dir = function( path )
- return lfs.attributes( path, "mode" ) == "directory"
- end
- -- Return a list of files in the given directory matching a given mask
- get_files = function( path, mask, norec, level )
- local t = ''
- level = level or 0
- for f in lfs.dir( path ) do
- local fname = path .. dir_sep .. f
- if lfs.attributes( fname, "mode" ) == "file" then
- local include
- if type( mask ) == "string" then
- include = fname:find( mask )
- else
- include = mask( fname )
- end
- if include then t = t .. ' ' .. fname end
- elseif lfs.attributes( fname, "mode" ) == "directory" and not fname:find( "%.+$" ) and not norec then
- t = t .. " " .. get_files( fname, mask, norec, level + 1 )
- end
- end
- return level > 0 and t or t:gsub( "^%s+", "" )
- end
- -- Check if the given command can be executed properly
- check_command = function( cmd )
- local res = os.execute( cmd .. " > .build.temp 2>&1" )
- os.remove( ".build.temp" )
- return res
- end
- -- Execute a command and capture output
- -- From: http://stackoverflow.com/a/326715/105950
- exec_capture = function( cmd, raw )
- local f = assert(io.popen(cmd, 'r'))
- local s = assert(f:read('*a'))
- f:close()
- if raw then return s end
- s = string.gsub(s, '^%s+', '')
- s = string.gsub(s, '%s+$', '')
- s = string.gsub(s, '[\n\r]+', ' ')
- return s
- end
- -- Execute the given command for each value in a table
- foreach = function ( t, cmd )
- if type( t ) ~= "table" then return end
- for k, v in pairs( t ) do cmd( k, v ) end
- end
- -- Generate header with the given #defines, return result as string
- gen_header_string = function( name, defines )
- local s = "// eLua " .. name:lower() .. " definition\n\n"
- s = s .. "#ifndef __" .. name:upper() .. "_H__\n"
- s = s .. "#define __" .. name:upper() .. "_H__\n\n"
- for key,value in pairs(defines) do
- s = s .. string.format("#define %-25s%-19s\n",key:upper(),value)
- end
- s = s .. "\n#endif\n"
- return s
- end
- -- Generate header with the given #defines, save result to file
- gen_header_file = function( name, defines )
- local hname = concat_path{ "inc", name:lower() .. ".h" }
- local h = assert( io.open( hname, "w" ) )
- h:write( gen_header_string( name, defines ) )
- h:close()
- end
- -- Remove the given elements from an array
- remove_array_elements = function( arr, del )
- del = istable( del ) and del or { del }
- foreach( del, function( k, v )
- local pos = array_element_index( arr, v )
- if pos then table.remove( arr, pos ) end
- end )
- end
- -- Remove a directory recusively
- -- USE WITH CARE!! Doesn't do much checks :)
- rmdir_rec = function ( dirname )
- if lfs.attributes( dirname, "mode" ) ~= "directory" then return end
- for f in lfs.dir( dirname ) do
- local ename = string.format( "%s/%s", dirname, f )
- local attrs = lfs.attributes( ename )
- if attrs.mode == 'directory' and f ~= '.' and f ~= '..' then
- rmdir_rec( ename )
- elseif attrs.mode == 'file' or attrs.mode == 'named pipe' or attrs.mode == 'link' then
- os.remove( ename )
- end
- end
- lfs.rmdir( dirname )
- end
- -- Concatenates the second table into the first one
- concat_tables = function( dst, src )
- foreach( src, function( k, v ) dst[ k ] = v end )
- end
- -------------------------------------------------------------------------------
- -- Color-related funtions
- -- Currently disabled when running in Windows
- -- (they can be enabled by setting WIN_ANSI_TERM)
- local dcoltable = { 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' }
- local coltable = {}
- foreach( dcoltable, function( k, v ) coltable[ v ] = k - 1 end )
- local _col_builder = function( col )
- local _col_maker = function( s )
- if is_os_windows and not os.getenv( "WIN_ANSI_TERM" ) then
- return s
- else
- return( sf( "\027[%d;1m%s\027[m", coltable[ col ] + 30, s ) )
- end
- end
- return _col_maker
- end
- col_funcs = {}
- foreach( coltable, function( k, v )
- local fname = "col_" .. k
- _G[ fname ] = _col_builder( k )
- col_funcs[ k ] = _G[ fname ]
- end )
- -------------------------------------------------------------------------------
- -- Option handling
- local options = {}
- options.new = function()
- local self = {}
- self.options = {}
- setmetatable( self, { __index = options } )
- return self
- end
- -- Argument validator: boolean value
- options._bool_validator = function( v )
- if v == '0' or v:upper() == 'FALSE' then
- return false
- elseif v == '1' or v:upper() == 'TRUE' then
- return true
- end
- end
- -- Argument validator: choice value
- options._choice_validator = function( v, allowed )
- for i = 1, #allowed do
- if v:upper() == allowed[ i ]:upper() then return allowed[ i ] end
- end
- end
- -- Argument validator: choice map (argument value maps to something)
- options._choice_map_validator = function( v, allowed )
- for k, value in pairs( allowed ) do
- if v:upper() == k:upper() then return value end
- end
- end
- -- Argument validator: string value (no validation)
- options._string_validator = function( v )
- return v
- end
- -- Argument printer: boolean value
- options._bool_printer = function( o )
- return "true|false", o.default and "true" or "false"
- end
- -- Argument printer: choice value
- options._choice_printer = function( o )
- local clist, opts = '', o.data
- for i = 1, #opts do
- clist = clist .. ( i ~= 1 and "|" or "" ) .. opts[ i ]
- end
- return clist, o.default
- end
- -- Argument printer: choice map printer
- options._choice_map_printer = function( o )
- local clist, opts, def = '', o.data
- local i = 1
- for k, v in pairs( opts ) do
- clist = clist .. ( i ~= 1 and "|" or "" ) .. k
- if o.default == v then def = k end
- i = i + 1
- end
- return clist, def
- end
- -- Argument printer: string printer
- options._string_printer = function( o )
- return nil, o.default
- end
- -- Add an option of the specified type
- options._add_option = function( self, optname, opttype, help, default, data )
- local validators =
- {
- string = options._string_validator, choice = options._choice_validator,
- boolean = options._bool_validator, choice_map = options._choice_map_validator
- }
- local printers =
- {
- string = options._string_printer, choice = options._choice_printer,
- boolean = options._bool_printer, choice_map = options._choice_map_printer
- }
- if not validators[ opttype ] then
- print( sf( "[builder] Invalid option type '%s'", opttype ) )
- os.exit( 1 )
- end
- table.insert( self.options, { name = optname, help = help, validator = validators[ opttype ], printer = printers[ opttype ], data = data, default = default } )
- end
- -- Find an option with the given name
- options._find_option = function( self, optname )
- for i = 1, #self.options do
- local o = self.options[ i ]
- if o.name:upper() == optname:upper() then return self.options[ i ] end
- end
- end
- -- 'add option' helper (automatically detects option type)
- options.add_option = function( self, name, help, default, data )
- local otype
- if type( default ) == 'boolean' then
- otype = 'boolean'
- elseif data and type( data ) == 'table' and #data == 0 then
- otype = 'choice_map'
- elseif data and type( data ) == 'table' then
- otype = 'choice'
- data = linearize_array( data )
- elseif type( default ) == 'string' then
- otype = 'string'
- else
- print( sf( "Error: cannot detect option type for '%s'", name ) )
- os.exit( 1 )
- end
- self:_add_option( name, otype, help, default, data )
- end
- options.get_num_opts = function( self )
- return #self.options
- end
- options.get_option = function( self, i )
- return self.options[ i ]
- end
- -- Handle an option of type 'key=value'
- -- Returns both the key and the value or nil for error
- options.handle_arg = function( self, a )
- local si, ei, k, v = a:find( "([^=]+)=(.*)$" )
- if not k or not v then
- print( sf( "Error: invalid syntax in '%s'", a ) )
- return
- end
- local opt = self:_find_option( k )
- if not opt then
- print( sf( "Error: invalid option '%s'", k ) )
- return
- end
- local optv = opt.validator( v, opt.data )
- if optv == nil then
- print( sf( "Error: invalid value '%s' for option '%s'", v, k ) )
- return
- end
- return k, optv
- end
- -- Show help for all the registered options
- options.show_help = function( self )
- for i = 1, #self.options do
- local o = self.options[ i ]
- print( sf( "\n %s: %s", o.name, o.help ) )
- local values, default = o.printer( o )
- if values then
- print( sf( " Possible values: %s", values ) )
- end
- print( sf( " Default value: %s", default or "none (changes at runtime)" ) )
- end
- end
- -- Create a new option handler
- function options_handler()
- return options.new()
- end
|