utils.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. -- Generic utility functions
  2. module( ..., package.seeall )
  3. local lfs = require "lfs"
  4. local sf = string.format
  5. -- Taken from Lake
  6. dir_sep = package.config:sub( 1, 1 )
  7. is_os_windows = dir_sep == '\\'
  8. -- Converts a string with items separated by 'sep' into a table
  9. string_to_table = function( s, sep )
  10. if type( s ) ~= "string" then return end
  11. sep = sep or ' '
  12. if s:sub( -1, -1 ) ~= sep then s = s .. sep end
  13. s = s:gsub( sf( "^%s*", sep ), "" )
  14. local t = {}
  15. local fmt = sf( "(.-)%s+", sep )
  16. for w in s:gmatch( fmt ) do table.insert( t, w ) end
  17. return t
  18. end
  19. -- Split a file name into 'path part' and 'extension part'
  20. split_ext = function( s )
  21. local pos
  22. for i = #s, 1, -1 do
  23. if s:sub( i, i ) == "." then
  24. pos = i
  25. break
  26. end
  27. end
  28. if not pos or s:find( dir_sep, pos + 1 ) then return s end
  29. return s:sub( 1, pos - 1 ), s:sub( pos )
  30. end
  31. -- Replace the extension of a given file name
  32. replace_extension = function( s, newext )
  33. local p, e = split_ext( s )
  34. if e then
  35. if newext and #newext > 0 then
  36. s = p .. "." .. newext
  37. else
  38. s = p
  39. end
  40. end
  41. return s
  42. end
  43. -- Return 'true' if building from Windows, false otherwise
  44. is_windows = function()
  45. return is_os_windows
  46. end
  47. -- Prepend each component of a 'pat'-separated string with 'prefix'
  48. prepend_string = function( s, prefix, pat )
  49. if not s or #s == 0 then return "" end
  50. pat = pat or ' '
  51. local res = ''
  52. local st = string_to_table( s, pat )
  53. foreach( st, function( k, v ) res = res .. prefix .. v .. " " end )
  54. return res
  55. end
  56. -- Like above, but consider 'prefix' a path
  57. prepend_path = function( s, prefix, pat )
  58. return prepend_string( s, prefix .. dir_sep, pat )
  59. end
  60. -- full mkdir: create all the paths needed for a multipath
  61. full_mkdir = function( path )
  62. local ptables = string_to_table( path, dir_sep )
  63. local p, res = ''
  64. for i = 1, #ptables do
  65. p = ( i ~= 1 and p .. dir_sep or p ) .. ptables[ i ]
  66. res = lfs.mkdir( p )
  67. end
  68. return res
  69. end
  70. -- Concatenate the given paths to form a complete path
  71. concat_path = function( paths )
  72. return table.concat( paths, dir_sep )
  73. end
  74. -- Return true if the given array contains the given element, false otherwise
  75. array_element_index = function( arr, element )
  76. for i = 1, #arr do
  77. if arr[ i ] == element then return i end
  78. end
  79. end
  80. -- Linearize an array with (possibly) embedded arrays into a simple array
  81. _linearize_array = function( arr, res, filter )
  82. if type( arr ) ~= "table" then return end
  83. for i = 1, #arr do
  84. local e = arr[ i ]
  85. if type( e ) == 'table' and filter( e ) then
  86. _linearize_array( e, res, filter )
  87. else
  88. table.insert( res, e )
  89. end
  90. end
  91. end
  92. linearize_array = function( arr, filter )
  93. local res = {}
  94. filter = filter or function( v ) return true end
  95. _linearize_array( arr, res, filter )
  96. return res
  97. end
  98. -- Return an array with the keys of a table
  99. table_keys = function( t )
  100. local keys = {}
  101. foreach( t, function( k, v ) table.insert( keys, k ) end )
  102. return keys
  103. end
  104. -- Return an array with the values of a table
  105. table_values = function( t )
  106. local vals = {}
  107. foreach( t, function( k, v ) table.insert( vals, v ) end )
  108. return vals
  109. end
  110. -- Returns true if 'path' is a regular file, false otherwise
  111. is_file = function( path )
  112. return lfs.attributes( path, "mode" ) == "file"
  113. end
  114. -- Returns true if 'path' is a directory, false otherwise
  115. is_dir = function( path )
  116. return lfs.attributes( path, "mode" ) == "directory"
  117. end
  118. -- Return a list of files in the given directory matching a given mask
  119. get_files = function( path, mask, norec, level )
  120. local t = ''
  121. level = level or 0
  122. for f in lfs.dir( path ) do
  123. local fname = path .. dir_sep .. f
  124. if lfs.attributes( fname, "mode" ) == "file" then
  125. local include
  126. if type( mask ) == "string" then
  127. include = fname:find( mask )
  128. else
  129. include = mask( fname )
  130. end
  131. if include then t = t .. ' ' .. fname end
  132. elseif lfs.attributes( fname, "mode" ) == "directory" and not fname:find( "%.+$" ) and not norec then
  133. t = t .. " " .. get_files( fname, mask, norec, level + 1 )
  134. end
  135. end
  136. return level > 0 and t or t:gsub( "^%s+", "" )
  137. end
  138. -- Check if the given command can be executed properly
  139. check_command = function( cmd )
  140. local res = os.execute( cmd .. " > .build.temp 2>&1" )
  141. os.remove( ".build.temp" )
  142. return res
  143. end
  144. -- Execute a command and capture output
  145. -- From: http://stackoverflow.com/a/326715/105950
  146. exec_capture = function( cmd, raw )
  147. local f = assert(io.popen(cmd, 'r'))
  148. local s = assert(f:read('*a'))
  149. f:close()
  150. if raw then return s end
  151. s = string.gsub(s, '^%s+', '')
  152. s = string.gsub(s, '%s+$', '')
  153. s = string.gsub(s, '[\n\r]+', ' ')
  154. return s
  155. end
  156. -- Execute the given command for each value in a table
  157. foreach = function ( t, cmd )
  158. if type( t ) ~= "table" then return end
  159. for k, v in pairs( t ) do cmd( k, v ) end
  160. end
  161. -- Generate header with the given #defines, return result as string
  162. gen_header_string = function( name, defines )
  163. local s = "// eLua " .. name:lower() .. " definition\n\n"
  164. s = s .. "#ifndef __" .. name:upper() .. "_H__\n"
  165. s = s .. "#define __" .. name:upper() .. "_H__\n\n"
  166. for key,value in pairs(defines) do
  167. s = s .. string.format("#define %-25s%-19s\n",key:upper(),value)
  168. end
  169. s = s .. "\n#endif\n"
  170. return s
  171. end
  172. -- Generate header with the given #defines, save result to file
  173. gen_header_file = function( name, defines )
  174. local hname = concat_path{ "inc", name:lower() .. ".h" }
  175. local h = assert( io.open( hname, "w" ) )
  176. h:write( gen_header_string( name, defines ) )
  177. h:close()
  178. end
  179. -- Remove the given elements from an array
  180. remove_array_elements = function( arr, del )
  181. del = istable( del ) and del or { del }
  182. foreach( del, function( k, v )
  183. local pos = array_element_index( arr, v )
  184. if pos then table.remove( arr, pos ) end
  185. end )
  186. end
  187. -- Remove a directory recusively
  188. -- USE WITH CARE!! Doesn't do much checks :)
  189. rmdir_rec = function ( dirname )
  190. if lfs.attributes( dirname, "mode" ) ~= "directory" then return end
  191. for f in lfs.dir( dirname ) do
  192. local ename = string.format( "%s/%s", dirname, f )
  193. local attrs = lfs.attributes( ename )
  194. if attrs.mode == 'directory' and f ~= '.' and f ~= '..' then
  195. rmdir_rec( ename )
  196. elseif attrs.mode == 'file' or attrs.mode == 'named pipe' or attrs.mode == 'link' then
  197. os.remove( ename )
  198. end
  199. end
  200. lfs.rmdir( dirname )
  201. end
  202. -- Concatenates the second table into the first one
  203. concat_tables = function( dst, src )
  204. foreach( src, function( k, v ) dst[ k ] = v end )
  205. end
  206. -------------------------------------------------------------------------------
  207. -- Color-related funtions
  208. -- Currently disabled when running in Windows
  209. -- (they can be enabled by setting WIN_ANSI_TERM)
  210. local dcoltable = { 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' }
  211. local coltable = {}
  212. foreach( dcoltable, function( k, v ) coltable[ v ] = k - 1 end )
  213. local _col_builder = function( col )
  214. local _col_maker = function( s )
  215. if is_os_windows and not os.getenv( "WIN_ANSI_TERM" ) then
  216. return s
  217. else
  218. return( sf( "\027[%d;1m%s\027[m", coltable[ col ] + 30, s ) )
  219. end
  220. end
  221. return _col_maker
  222. end
  223. col_funcs = {}
  224. foreach( coltable, function( k, v )
  225. local fname = "col_" .. k
  226. _G[ fname ] = _col_builder( k )
  227. col_funcs[ k ] = _G[ fname ]
  228. end )
  229. -------------------------------------------------------------------------------
  230. -- Option handling
  231. local options = {}
  232. options.new = function()
  233. local self = {}
  234. self.options = {}
  235. setmetatable( self, { __index = options } )
  236. return self
  237. end
  238. -- Argument validator: boolean value
  239. options._bool_validator = function( v )
  240. if v == '0' or v:upper() == 'FALSE' then
  241. return false
  242. elseif v == '1' or v:upper() == 'TRUE' then
  243. return true
  244. end
  245. end
  246. -- Argument validator: choice value
  247. options._choice_validator = function( v, allowed )
  248. for i = 1, #allowed do
  249. if v:upper() == allowed[ i ]:upper() then return allowed[ i ] end
  250. end
  251. end
  252. -- Argument validator: choice map (argument value maps to something)
  253. options._choice_map_validator = function( v, allowed )
  254. for k, value in pairs( allowed ) do
  255. if v:upper() == k:upper() then return value end
  256. end
  257. end
  258. -- Argument validator: string value (no validation)
  259. options._string_validator = function( v )
  260. return v
  261. end
  262. -- Argument printer: boolean value
  263. options._bool_printer = function( o )
  264. return "true|false", o.default and "true" or "false"
  265. end
  266. -- Argument printer: choice value
  267. options._choice_printer = function( o )
  268. local clist, opts = '', o.data
  269. for i = 1, #opts do
  270. clist = clist .. ( i ~= 1 and "|" or "" ) .. opts[ i ]
  271. end
  272. return clist, o.default
  273. end
  274. -- Argument printer: choice map printer
  275. options._choice_map_printer = function( o )
  276. local clist, opts, def = '', o.data
  277. local i = 1
  278. for k, v in pairs( opts ) do
  279. clist = clist .. ( i ~= 1 and "|" or "" ) .. k
  280. if o.default == v then def = k end
  281. i = i + 1
  282. end
  283. return clist, def
  284. end
  285. -- Argument printer: string printer
  286. options._string_printer = function( o )
  287. return nil, o.default
  288. end
  289. -- Add an option of the specified type
  290. options._add_option = function( self, optname, opttype, help, default, data )
  291. local validators =
  292. {
  293. string = options._string_validator, choice = options._choice_validator,
  294. boolean = options._bool_validator, choice_map = options._choice_map_validator
  295. }
  296. local printers =
  297. {
  298. string = options._string_printer, choice = options._choice_printer,
  299. boolean = options._bool_printer, choice_map = options._choice_map_printer
  300. }
  301. if not validators[ opttype ] then
  302. print( sf( "[builder] Invalid option type '%s'", opttype ) )
  303. os.exit( 1 )
  304. end
  305. table.insert( self.options, { name = optname, help = help, validator = validators[ opttype ], printer = printers[ opttype ], data = data, default = default } )
  306. end
  307. -- Find an option with the given name
  308. options._find_option = function( self, optname )
  309. for i = 1, #self.options do
  310. local o = self.options[ i ]
  311. if o.name:upper() == optname:upper() then return self.options[ i ] end
  312. end
  313. end
  314. -- 'add option' helper (automatically detects option type)
  315. options.add_option = function( self, name, help, default, data )
  316. local otype
  317. if type( default ) == 'boolean' then
  318. otype = 'boolean'
  319. elseif data and type( data ) == 'table' and #data == 0 then
  320. otype = 'choice_map'
  321. elseif data and type( data ) == 'table' then
  322. otype = 'choice'
  323. data = linearize_array( data )
  324. elseif type( default ) == 'string' then
  325. otype = 'string'
  326. else
  327. print( sf( "Error: cannot detect option type for '%s'", name ) )
  328. os.exit( 1 )
  329. end
  330. self:_add_option( name, otype, help, default, data )
  331. end
  332. options.get_num_opts = function( self )
  333. return #self.options
  334. end
  335. options.get_option = function( self, i )
  336. return self.options[ i ]
  337. end
  338. -- Handle an option of type 'key=value'
  339. -- Returns both the key and the value or nil for error
  340. options.handle_arg = function( self, a )
  341. local si, ei, k, v = a:find( "([^=]+)=(.*)$" )
  342. if not k or not v then
  343. print( sf( "Error: invalid syntax in '%s'", a ) )
  344. return
  345. end
  346. local opt = self:_find_option( k )
  347. if not opt then
  348. print( sf( "Error: invalid option '%s'", k ) )
  349. return
  350. end
  351. local optv = opt.validator( v, opt.data )
  352. if optv == nil then
  353. print( sf( "Error: invalid value '%s' for option '%s'", v, k ) )
  354. return
  355. end
  356. return k, optv
  357. end
  358. -- Show help for all the registered options
  359. options.show_help = function( self )
  360. for i = 1, #self.options do
  361. local o = self.options[ i ]
  362. print( sf( "\n %s: %s", o.name, o.help ) )
  363. local values, default = o.printer( o )
  364. if values then
  365. print( sf( " Possible values: %s", values ) )
  366. end
  367. print( sf( " Default value: %s", default or "none (changes at runtime)" ) )
  368. end
  369. end
  370. -- Create a new option handler
  371. function options_handler()
  372. return options.new()
  373. end