PageRenderTime 26ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/Src/Modules/penlight/lua/pl/utils.lua

https://github.com/jjensen/luaplus51-all
Lua | 475 lines | 272 code | 46 blank | 157 comment | 63 complexity | ef5e764786b1dc09e4feb279bc0579f0 MD5 | raw file
  1. --- Generally useful routines.
  2. -- See @{01-introduction.md.Generally_useful_functions|the Guide}.
  3. -- @module pl.utils
  4. local format,gsub,byte = string.format,string.gsub,string.byte
  5. local compat = require 'pl.compat'
  6. local clock = os.clock
  7. local stdout = io.stdout
  8. local append = table.insert
  9. local unpack = rawget(_G,'unpack') or rawget(table,'unpack')
  10. local collisions = {}
  11. local utils = {
  12. _VERSION = "1.2.1",
  13. lua51 = compat.lua51,
  14. setfenv = compat.setfenv,
  15. getfenv = compat.getfenv,
  16. load = compat.load,
  17. execute = compat.execute,
  18. dir_separator = _G.package.config:sub(1,1),
  19. unpack = unpack
  20. }
  21. --- end this program gracefully.
  22. -- @param code The exit code or a message to be printed
  23. -- @param ... extra arguments for message's format'
  24. -- @see utils.fprintf
  25. function utils.quit(code,...)
  26. if type(code) == 'string' then
  27. utils.fprintf(io.stderr,code,...)
  28. code = -1
  29. else
  30. utils.fprintf(io.stderr,...)
  31. end
  32. io.stderr:write('\n')
  33. os.exit(code)
  34. end
  35. --- print an arbitrary number of arguments using a format.
  36. -- @param fmt The format (see string.format)
  37. -- @param ... Extra arguments for format
  38. function utils.printf(fmt,...)
  39. utils.assert_string(1,fmt)
  40. utils.fprintf(stdout,fmt,...)
  41. end
  42. --- write an arbitrary number of arguments to a file using a format.
  43. -- @param f File handle to write to.
  44. -- @param fmt The format (see string.format).
  45. -- @param ... Extra arguments for format
  46. function utils.fprintf(f,fmt,...)
  47. utils.assert_string(2,fmt)
  48. f:write(format(fmt,...))
  49. end
  50. local function import_symbol(T,k,v,libname)
  51. local key = rawget(T,k)
  52. -- warn about collisions!
  53. if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then
  54. utils.printf("warning: '%s.%s' overrides existing symbol\n",libname,k)
  55. end
  56. rawset(T,k,v)
  57. end
  58. local function lookup_lib(T,t)
  59. for k,v in pairs(T) do
  60. if v == t then return k end
  61. end
  62. return '?'
  63. end
  64. local already_imported = {}
  65. --- take a table and 'inject' it into the local namespace.
  66. -- @param t The Table
  67. -- @param T An optional destination table (defaults to callers environment)
  68. function utils.import(t,T)
  69. T = T or _G
  70. t = t or utils
  71. if type(t) == 'string' then
  72. t = require (t)
  73. end
  74. local libname = lookup_lib(T,t)
  75. if already_imported[t] then return end
  76. already_imported[t] = libname
  77. for k,v in pairs(t) do
  78. import_symbol(T,k,v,libname)
  79. end
  80. end
  81. utils.patterns = {
  82. FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*',
  83. INTEGER = '[+%-%d]%d*',
  84. IDEN = '[%a_][%w_]*',
  85. FILE = '[%a%.\\][:%][%w%._%-\\]*'
  86. }
  87. --- escape any 'magic' characters in a string
  88. -- @param s The input string
  89. function utils.escape(s)
  90. utils.assert_string(1,s)
  91. return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1'))
  92. end
  93. --- return either of two values, depending on a condition.
  94. -- @param cond A condition
  95. -- @param value1 Value returned if cond is true
  96. -- @param value2 Value returned if cond is false (can be optional)
  97. function utils.choose(cond,value1,value2)
  98. if cond then return value1
  99. else return value2
  100. end
  101. end
  102. local raise
  103. --- return the contents of a file as a string
  104. -- @param filename The file path
  105. -- @param is_bin open in binary mode
  106. -- @return file contents
  107. function utils.readfile(filename,is_bin)
  108. local mode = is_bin and 'b' or ''
  109. utils.assert_string(1,filename)
  110. local f,err = io.open(filename,'r'..mode)
  111. if not f then return utils.raise (err) end
  112. local res,err = f:read('*a')
  113. f:close()
  114. if not res then return raise (err) end
  115. return res
  116. end
  117. --- write a string to a file
  118. -- @param filename The file path
  119. -- @param str The string
  120. -- @return true or nil
  121. -- @return error message
  122. -- @raise error if filename or str aren't strings
  123. function utils.writefile(filename,str)
  124. utils.assert_string(1,filename)
  125. utils.assert_string(2,str)
  126. local f,err = io.open(filename,'w')
  127. if not f then return raise(err) end
  128. f:write(str)
  129. f:close()
  130. return true
  131. end
  132. --- return the contents of a file as a list of lines
  133. -- @param filename The file path
  134. -- @return file contents as a table
  135. -- @raise errror if filename is not a string
  136. function utils.readlines(filename)
  137. utils.assert_string(1,filename)
  138. local f,err = io.open(filename,'r')
  139. if not f then return raise(err) end
  140. local res = {}
  141. for line in f:lines() do
  142. append(res,line)
  143. end
  144. f:close()
  145. return res
  146. end
  147. --- split a string into a list of strings separated by a delimiter.
  148. -- @param s The input string
  149. -- @param re A Lua string pattern; defaults to '%s+'
  150. -- @param plain don't use Lua patterns
  151. -- @param n optional maximum number of splits
  152. -- @return a list-like table
  153. -- @raise error if s is not a string
  154. function utils.split(s,re,plain,n)
  155. utils.assert_string(1,s)
  156. local find,sub,append = string.find, string.sub, table.insert
  157. local i1,ls = 1,{}
  158. if not re then re = '%s+' end
  159. if re == '' then return {s} end
  160. while true do
  161. local i2,i3 = find(s,re,i1,plain)
  162. if not i2 then
  163. local last = sub(s,i1)
  164. if last ~= '' then append(ls,last) end
  165. if #ls == 1 and ls[1] == '' then
  166. return {}
  167. else
  168. return ls
  169. end
  170. end
  171. append(ls,sub(s,i1,i2-1))
  172. if n and #ls == n then
  173. ls[#ls] = sub(s,i1)
  174. return ls
  175. end
  176. i1 = i3+1
  177. end
  178. end
  179. --- split a string into a number of values.
  180. -- @param s the string
  181. -- @param re the delimiter, default space
  182. -- @return n values
  183. -- @usage first,next = splitv('jane:doe',':')
  184. -- @see split
  185. function utils.splitv (s,re)
  186. return unpack(utils.split(s,re))
  187. end
  188. --- convert an array of values to strings.
  189. -- @param t a list-like table
  190. -- @param temp buffer to use, otherwise allocate
  191. -- @param tostr custom tostring function, called with (value,index).
  192. -- Otherwise use `tostring`
  193. -- @return the converted buffer
  194. function utils.array_tostring (t,temp,tostr)
  195. temp, tostr = temp or {}, tostr or tostring
  196. for i = 1,#t do
  197. temp[i] = tostr(t[i],i)
  198. end
  199. return temp
  200. end
  201. --- execute a shell command and return the output.
  202. -- This function redirects the output to tempfiles and returns the content of those files.
  203. -- @param cmd a shell command
  204. -- @param bin boolean, if true, read output as binary file
  205. -- @return true if successful
  206. -- @return actual return code
  207. -- @return stdout output (string)
  208. -- @return errout output (string)
  209. function utils.executeex(cmd, bin)
  210. local mode
  211. local outfile = os.tmpname()
  212. local errfile = os.tmpname()
  213. if utils.dir_separator == '\\' then
  214. outfile = os.getenv('TEMP')..outfile
  215. errfile = os.getenv('TEMP')..errfile
  216. end
  217. cmd = cmd .. [[ >"]]..outfile..[[" 2>"]]..errfile..[["]]
  218. local success, retcode = utils.execute(cmd)
  219. local outcontent = utils.readfile(outfile, bin)
  220. local errcontent = utils.readfile(errfile, bin)
  221. os.remove(outfile)
  222. os.remove(errfile)
  223. return success, retcode, (outcontent or ""), (errcontent or "")
  224. end
  225. --- 'memoize' a function (cache returned value for next call).
  226. -- This is useful if you have a function which is relatively expensive,
  227. -- but you don't know in advance what values will be required, so
  228. -- building a table upfront is wasteful/impossible.
  229. -- @param func a function of at least one argument
  230. -- @return a function with at least one argument, which is used as the key.
  231. function utils.memoize(func)
  232. return setmetatable({}, {
  233. __index = function(self, k, ...)
  234. local v = func(k,...)
  235. self[k] = v
  236. return v
  237. end,
  238. __call = function(self, k) return self[k] end
  239. })
  240. end
  241. utils.stdmt = {
  242. List = {_name='List'}, Map = {_name='Map'},
  243. Set = {_name='Set'}, MultiMap = {_name='MultiMap'}
  244. }
  245. local _function_factories = {}
  246. --- associate a function factory with a type.
  247. -- A function factory takes an object of the given type and
  248. -- returns a function for evaluating it
  249. -- @tab mt metatable
  250. -- @func fun a callable that returns a function
  251. function utils.add_function_factory (mt,fun)
  252. _function_factories[mt] = fun
  253. end
  254. local function _string_lambda(f)
  255. local raise = utils.raise
  256. if f:find '^|' or f:find '_' then
  257. local args,body = f:match '|([^|]*)|(.+)'
  258. if f:find '_' then
  259. args = '_'
  260. body = f
  261. else
  262. if not args then return raise 'bad string lambda' end
  263. end
  264. local fstr = 'return function('..args..') return '..body..' end'
  265. local fn,err = utils.load(fstr)
  266. if not fn then return raise(err) end
  267. fn = fn()
  268. return fn
  269. else return raise 'not a string lambda'
  270. end
  271. end
  272. --- an anonymous function as a string. This string is either of the form
  273. -- '|args| expression' or is a function of one argument, '_'
  274. -- @param lf function as a string
  275. -- @return a function
  276. -- @usage string_lambda '|x|x+1' (2) == 3
  277. -- @usage string_lambda '_+1 (2) == 3
  278. -- @function utils.string_lambda
  279. utils.string_lambda = utils.memoize(_string_lambda)
  280. local ops
  281. --- process a function argument.
  282. -- This is used throughout Penlight and defines what is meant by a function:
  283. -- Something that is callable, or an operator string as defined by <code>pl.operator</code>,
  284. -- such as '>' or '#'. If a function factory has been registered for the type, it will
  285. -- be called to get the function.
  286. -- @param idx argument index
  287. -- @param f a function, operator string, or callable object
  288. -- @param msg optional error message
  289. -- @return a callable
  290. -- @raise if idx is not a number or if f is not callable
  291. function utils.function_arg (idx,f,msg)
  292. utils.assert_arg(1,idx,'number')
  293. local tp = type(f)
  294. if tp == 'function' then return f end -- no worries!
  295. -- ok, a string can correspond to an operator (like '==')
  296. if tp == 'string' then
  297. if not ops then ops = require 'pl.operator'.optable end
  298. local fn = ops[f]
  299. if fn then return fn end
  300. local fn, err = utils.string_lambda(f)
  301. if not fn then error(err..': '..f) end
  302. return fn
  303. elseif tp == 'table' or tp == 'userdata' then
  304. local mt = getmetatable(f)
  305. if not mt then error('not a callable object',2) end
  306. local ff = _function_factories[mt]
  307. if not ff then
  308. if not mt.__call then error('not a callable object',2) end
  309. return f
  310. else
  311. return ff(f) -- we have a function factory for this type!
  312. end
  313. end
  314. if not msg then msg = " must be callable" end
  315. if idx > 0 then
  316. error("argument "..idx..": "..msg,2)
  317. else
  318. error(msg,2)
  319. end
  320. end
  321. --- bind the first argument of the function to a value.
  322. -- @param fn a function of at least two values (may be an operator string)
  323. -- @param p a value
  324. -- @return a function such that f(x) is fn(p,x)
  325. -- @raise same as @{function_arg}
  326. -- @see func.bind1
  327. function utils.bind1 (fn,p)
  328. fn = utils.function_arg(1,fn)
  329. return function(...) return fn(p,...) end
  330. end
  331. --- bind the second argument of the function to a value.
  332. -- @param fn a function of at least two values (may be an operator string)
  333. -- @param p a value
  334. -- @return a function such that f(x) is fn(x,p)
  335. -- @raise same as @{function_arg}
  336. function utils.bind2 (fn,p)
  337. fn = utils.function_arg(1,fn)
  338. return function(x,...) return fn(x,p,...) end
  339. end
  340. --- assert that the given argument is in fact of the correct type.
  341. -- @param n argument index
  342. -- @param val the value
  343. -- @param tp the type
  344. -- @param verify an optional verfication function
  345. -- @param msg an optional custom message
  346. -- @param lev optional stack position for trace, default 2
  347. -- @raise if the argument n is not the correct type
  348. -- @usage assert_arg(1,t,'table')
  349. -- @usage assert_arg(n,val,'string',path.isdir,'not a directory')
  350. function utils.assert_arg (n,val,tp,verify,msg,lev)
  351. if type(val) ~= tp then
  352. error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),lev or 2)
  353. end
  354. if verify and not verify(val) then
  355. error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2)
  356. end
  357. end
  358. --- assert the common case that the argument is a string.
  359. -- @param n argument index
  360. -- @param val a value that must be a string
  361. -- @raise val must be a string
  362. function utils.assert_string (n,val)
  363. utils.assert_arg(n,val,'string',nil,nil,3)
  364. end
  365. local err_mode = 'default'
  366. --- control the error strategy used by Penlight.
  367. -- Controls how <code>utils.raise</code> works; the default is for it
  368. -- to return nil and the error string, but if the mode is 'error' then
  369. -- it will throw an error. If mode is 'quit' it will immediately terminate
  370. -- the program.
  371. -- @param mode - either 'default', 'quit' or 'error'
  372. -- @see utils.raise
  373. function utils.on_error (mode)
  374. if ({['default'] = 1, ['quit'] = 2, ['error'] = 3})[mode] then
  375. err_mode = mode
  376. else
  377. -- fail loudly
  378. if err_mode == 'default' then err_mode = 'error' end
  379. utils.raise("Bad argument expected string; 'default', 'quit', or 'error'. Got '"..tostring(mode).."'")
  380. end
  381. end
  382. --- used by Penlight functions to return errors. Its global behaviour is controlled
  383. -- by <code>utils.on_error</code>
  384. -- @param err the error string.
  385. -- @see utils.on_error
  386. function utils.raise (err)
  387. if err_mode == 'default' then return nil,err
  388. elseif err_mode == 'quit' then utils.quit(err)
  389. else error(err,2)
  390. end
  391. end
  392. --- is the object of the specified type?.
  393. -- If the type is a string, then use type, otherwise compare with metatable
  394. -- @param obj An object to check
  395. -- @param tp String of what type it should be
  396. function utils.is_type (obj,tp)
  397. if type(tp) == 'string' then return type(obj) == tp end
  398. local mt = getmetatable(obj)
  399. return tp == mt
  400. end
  401. raise = utils.raise
  402. --- load a code string or bytecode chunk.
  403. -- @param code Lua code as a string or bytecode
  404. -- @param name for source errors
  405. -- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default)
  406. -- @param env the environment for the new chunk (default nil)
  407. -- @return compiled chunk
  408. -- @return error message (chunk is nil)
  409. -- @function utils.load
  410. ---------------
  411. -- Get environment of a function.
  412. -- With Lua 5.2, may return nil for a function with no global references!
  413. -- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html)
  414. -- @param f a function or a call stack reference
  415. -- @function utils.setfenv
  416. ---------------
  417. -- Set environment of a function
  418. -- @param f a function or a call stack reference
  419. -- @param env a table that becomes the new environment of `f`
  420. -- @function utils.setfenv
  421. --- execute a shell command.
  422. -- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2
  423. -- @param cmd a shell command
  424. -- @return true if successful
  425. -- @return actual return code
  426. -- @function utils.execute
  427. return utils