1 --- Generally useful routines.
2 -- See @{01-introduction.md.Generally_useful_functions|the Guide}.
4 -- Dependencies: `pl.compat`
7 local format = string.format
8 local compat = require 'pl.compat'
9 local stdout = io.stdout
10 local append = table.insert
11 local unpack = rawget(_G,'unpack') or rawget(table,'unpack')
16 setfenv = compat.setfenv,
17 getfenv = compat.getfenv,
19 execute = compat.execute,
20 dir_separator = _G.package.config:sub(1,1),
24 --- end this program gracefully.
25 -- @param code The exit code or a message to be printed
26 -- @param ... extra arguments for message's format'
28 function utils.quit(code,...)
29 if type(code) == 'string' then
30 utils.fprintf(io.stderr,code,...)
33 utils.fprintf(io.stderr,...)
39 --- print an arbitrary number of arguments using a format.
40 -- @param fmt The format (see string.format)
41 -- @param ... Extra arguments for format
42 function utils.printf(fmt,...)
43 utils.assert_string(1,fmt)
44 utils.fprintf(stdout,fmt,...)
47 --- write an arbitrary number of arguments to a file using a format.
48 -- @param f File handle to write to.
49 -- @param fmt The format (see string.format).
50 -- @param ... Extra arguments for format
51 function utils.fprintf(f,fmt,...)
52 utils.assert_string(2,fmt)
53 f:write(format(fmt,...))
56 local function import_symbol(T,k,v,libname)
57 local key = rawget(T,k)
58 -- warn about collisions!
59 if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then
60 utils.fprintf(io.stderr,"warning: '%s.%s' will not override existing symbol\n",libname,k)
66 local function lookup_lib(T,t)
67 for k,v in pairs(T) do
68 if v == t then return k end
73 local already_imported = {}
75 --- take a table and 'inject' it into the local namespace.
77 -- @param T An optional destination table (defaults to callers environment)
78 function utils.import(t,T)
81 if type(t) == 'string' then
84 local libname = lookup_lib(T,t)
85 if already_imported[t] then return end
86 already_imported[t] = libname
87 for k,v in pairs(t) do
88 import_symbol(T,k,v,libname)
93 FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*',
94 INTEGER = '[+%-%d]%d*',
96 FILE = '[%a%.\\][:%][%w%._%-\\]*'
99 --- escape any 'magic' characters in a string
100 -- @param s The input string
101 function utils.escape(s)
102 utils.assert_string(1,s)
103 return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1'))
106 --- return either of two values, depending on a condition.
107 -- @param cond A condition
108 -- @param value1 Value returned if cond is true
109 -- @param value2 Value returned if cond is false (can be optional)
110 function utils.choose(cond,value1,value2)
111 if cond then return value1
118 --- return the contents of a file as a string
119 -- @param filename The file path
120 -- @param is_bin open in binary mode
121 -- @return file contents
122 function utils.readfile(filename,is_bin)
123 local mode = is_bin and 'b' or ''
124 utils.assert_string(1,filename)
125 local f,open_err = io.open(filename,'r'..mode)
126 if not f then return utils.raise (open_err) end
127 local res,read_err = f:read('*a')
130 -- Errors in io.open have "filename: " prefix,
131 -- error in file:read don't, add it.
132 return raise (filename..": "..read_err)
137 --- write a string to a file
138 -- @param filename The file path
139 -- @param str The string
140 -- @param is_bin open in binary mode
141 -- @return true or nil
142 -- @return error message
143 -- @raise error if filename or str aren't strings
144 function utils.writefile(filename,str,is_bin)
145 local mode = is_bin and 'b' or ''
146 utils.assert_string(1,filename)
147 utils.assert_string(2,str)
148 local f,err = io.open(filename,'w'..mode)
149 if not f then return raise(err) end
155 --- return the contents of a file as a list of lines
156 -- @param filename The file path
157 -- @return file contents as a table
158 -- @raise errror if filename is not a string
159 function utils.readlines(filename)
160 utils.assert_string(1,filename)
161 local f,err = io.open(filename,'r')
162 if not f then return raise(err) end
164 for line in f:lines() do
171 --- split a string into a list of strings separated by a delimiter.
172 -- @param s The input string
173 -- @param re A Lua string pattern; defaults to '%s+'
174 -- @param plain don't use Lua patterns
175 -- @param n optional maximum number of splits
176 -- @return a list-like table
177 -- @raise error if s is not a string
178 function utils.split(s,re,plain,n)
179 utils.assert_string(1,s)
180 local find,sub,append = string.find, string.sub, table.insert
182 if not re then re = '%s+' end
183 if re == '' then return {s} end
185 local i2,i3 = find(s,re,i1,plain)
187 local last = sub(s,i1)
188 if last ~= '' then append(ls,last) end
189 if #ls == 1 and ls[1] == '' then
195 append(ls,sub(s,i1,i2-1))
196 if n and #ls == n then
204 --- split a string into a number of values.
205 -- @param s the string
206 -- @param re the delimiter, default space
208 -- @usage first,next = splitv('jane:doe',':')
210 function utils.splitv (s,re)
211 return unpack(utils.split(s,re))
214 --- convert an array of values to strings.
215 -- @param t a list-like table
216 -- @param temp buffer to use, otherwise allocate
217 -- @param tostr custom tostring function, called with (value,index).
218 -- Otherwise use `tostring`
219 -- @return the converted buffer
220 function utils.array_tostring (t,temp,tostr)
221 temp, tostr = temp or {}, tostr or tostring
223 temp[i] = tostr(t[i],i)
228 local is_windows = package.config:sub(1, 1) == "\\"
230 --- Quote an argument of a command.
231 -- Quotes a single argument of a command to be passed
232 -- to `os.execute`, `pl.utils.execute` or `pl.utils.executeex`.
233 -- @string argument the argument.
234 -- @return quoted argument.
235 function utils.quote_arg(argument)
237 if argument == "" or argument:find('[ \f\t\v]') then
238 -- Need to quote the argument.
239 -- Quotes need to be escaped with backslashes;
240 -- additionally, backslashes before a quote, escaped or not,
241 -- need to be doubled.
242 -- See documentation for CommandLineToArgvW Windows function.
243 argument = '"' .. argument:gsub([[(\*)"]], [[%1%1\"]]):gsub([[\+$]], "%0%0") .. '"'
246 -- os.execute() uses system() C function, which on Windows passes command
247 -- to cmd.exe. Escape its special characters.
248 return (argument:gsub('["^<>!|&%%]', "^%0"))
250 if argument == "" or argument:find('[^a-zA-Z0-9_@%+=:,./-]') then
251 -- To quote arguments on posix-like systems use single quotes.
252 -- To represent an embedded single quote close quoted string ('),
253 -- add escaped quote (\'), open quoted string again (').
254 argument = "'" .. argument:gsub("'", [['\'']]) .. "'"
261 --- execute a shell command and return the output.
262 -- This function redirects the output to tempfiles and returns the content of those files.
263 -- @param cmd a shell command
264 -- @param bin boolean, if true, read output as binary file
265 -- @return true if successful
266 -- @return actual return code
267 -- @return stdout output (string)
268 -- @return errout output (string)
269 function utils.executeex(cmd, bin)
271 local outfile = os.tmpname()
272 local errfile = os.tmpname()
274 if is_windows and not outfile:find(':') then
275 outfile = os.getenv('TEMP')..outfile
276 errfile = os.getenv('TEMP')..errfile
278 cmd = cmd .. " > " .. utils.quote_arg(outfile) .. " 2> " .. utils.quote_arg(errfile)
280 local success, retcode = utils.execute(cmd)
281 local outcontent = utils.readfile(outfile, bin)
282 local errcontent = utils.readfile(errfile, bin)
285 return success, retcode, (outcontent or ""), (errcontent or "")
288 --- 'memoize' a function (cache returned value for next call).
289 -- This is useful if you have a function which is relatively expensive,
290 -- but you don't know in advance what values will be required, so
291 -- building a table upfront is wasteful/impossible.
292 -- @param func a function of at least one argument
293 -- @return a function with at least one argument, which is used as the key.
294 function utils.memoize(func)
308 List = {_name='List'}, Map = {_name='Map'},
309 Set = {_name='Set'}, MultiMap = {_name='MultiMap'}
312 local _function_factories = {}
314 --- associate a function factory with a type.
315 -- A function factory takes an object of the given type and
316 -- returns a function for evaluating it
318 -- @func fun a callable that returns a function
319 function utils.add_function_factory (mt,fun)
320 _function_factories[mt] = fun
323 local function _string_lambda(f)
324 local raise = utils.raise
325 if f:find '^|' or f:find '_' then
326 local args,body = f:match '|([^|]*)|(.+)'
331 if not args then return raise 'bad string lambda' end
333 local fstr = 'return function('..args..') return '..body..' end'
334 local fn,err = utils.load(fstr)
335 if not fn then return raise(err) end
338 else return raise 'not a string lambda'
342 --- an anonymous function as a string. This string is either of the form
343 -- '|args| expression' or is a function of one argument, '_'
344 -- @param lf function as a string
345 -- @return a function
346 -- @usage string_lambda '|x|x+1' (2) == 3
347 -- @usage string_lambda '_+1' (2) == 3
348 -- @function utils.string_lambda
349 utils.string_lambda = utils.memoize(_string_lambda)
353 --- process a function argument.
354 -- This is used throughout Penlight and defines what is meant by a function:
355 -- Something that is callable, or an operator string as defined by <code>pl.operator</code>,
356 -- such as '>' or '#'. If a function factory has been registered for the type, it will
357 -- be called to get the function.
358 -- @param idx argument index
359 -- @param f a function, operator string, or callable object
360 -- @param msg optional error message
361 -- @return a callable
362 -- @raise if idx is not a number or if f is not callable
363 function utils.function_arg (idx,f,msg)
364 utils.assert_arg(1,idx,'number')
366 if tp == 'function' then return f end -- no worries!
367 -- ok, a string can correspond to an operator (like '==')
368 if tp == 'string' then
369 if not ops then ops = require 'pl.operator'.optable end
371 if fn then return fn end
372 local fn, err = utils.string_lambda(f)
373 if not fn then error(err..': '..f) end
375 elseif tp == 'table' or tp == 'userdata' then
376 local mt = getmetatable(f)
377 if not mt then error('not a callable object',2) end
378 local ff = _function_factories[mt]
380 if not mt.__call then error('not a callable object',2) end
383 return ff(f) -- we have a function factory for this type!
386 if not msg then msg = " must be callable" end
388 error("argument "..idx..": "..msg,2)
394 --- bind the first argument of the function to a value.
395 -- @param fn a function of at least two values (may be an operator string)
397 -- @return a function such that f(x) is fn(p,x)
398 -- @raise same as @{function_arg}
400 function utils.bind1 (fn,p)
401 fn = utils.function_arg(1,fn)
402 return function(...) return fn(p,...) end
405 --- bind the second argument of the function to a value.
406 -- @param fn a function of at least two values (may be an operator string)
408 -- @return a function such that f(x) is fn(x,p)
409 -- @raise same as @{function_arg}
410 function utils.bind2 (fn,p)
411 fn = utils.function_arg(1,fn)
412 return function(x,...) return fn(x,p,...) end
416 --- assert that the given argument is in fact of the correct type.
417 -- @param n argument index
418 -- @param val the value
419 -- @param tp the type
420 -- @param verify an optional verification function
421 -- @param msg an optional custom message
422 -- @param lev optional stack position for trace, default 2
423 -- @raise if the argument n is not the correct type
424 -- @usage assert_arg(1,t,'table')
425 -- @usage assert_arg(n,val,'string',path.isdir,'not a directory')
426 function utils.assert_arg (n,val,tp,verify,msg,lev)
427 if type(val) ~= tp then
428 error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),lev or 2)
430 if verify and not verify(val) then
431 error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2)
435 --- assert the common case that the argument is a string.
436 -- @param n argument index
437 -- @param val a value that must be a string
438 -- @raise val must be a string
439 function utils.assert_string (n,val)
440 utils.assert_arg(n,val,'string',nil,nil,3)
443 local err_mode = 'default'
445 --- control the error strategy used by Penlight.
446 -- Controls how <code>utils.raise</code> works; the default is for it
447 -- to return nil and the error string, but if the mode is 'error' then
448 -- it will throw an error. If mode is 'quit' it will immediately terminate
450 -- @param mode - either 'default', 'quit' or 'error'
452 function utils.on_error (mode)
453 if ({['default'] = 1, ['quit'] = 2, ['error'] = 3})[mode] then
457 if err_mode == 'default' then err_mode = 'error' end
458 utils.raise("Bad argument expected string; 'default', 'quit', or 'error'. Got '"..tostring(mode).."'")
462 --- used by Penlight functions to return errors. Its global behaviour is controlled
463 -- by <code>utils.on_error</code>
464 -- @param err the error string.
465 -- @see utils.on_error
466 function utils.raise (err)
467 if err_mode == 'default' then return nil,err
468 elseif err_mode == 'quit' then utils.quit(err)
473 --- is the object of the specified type?.
474 -- If the type is a string, then use type, otherwise compare with metatable
475 -- @param obj An object to check
476 -- @param tp String of what type it should be
477 function utils.is_type (obj,tp)
478 if type(tp) == 'string' then return type(obj) == tp end
479 local mt = getmetatable(obj)
485 --- load a code string or bytecode chunk.
486 -- @param code Lua code as a string or bytecode
487 -- @param name for source errors
488 -- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default)
489 -- @param env the environment for the new chunk (default nil)
490 -- @return compiled chunk
491 -- @return error message (chunk is nil)
492 -- @function utils.load
495 -- Get environment of a function.
496 -- With Lua 5.2, may return nil for a function with no global references!
497 -- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html)
498 -- @param f a function or a call stack reference
499 -- @function utils.getfenv
502 -- Set environment of a function
503 -- @param f a function or a call stack reference
504 -- @param env a table that becomes the new environment of `f`
505 -- @function utils.setfenv
507 --- execute a shell command.
508 -- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2
509 -- @param cmd a shell command
510 -- @return true if successful
511 -- @return actual return code
512 -- @function utils.execute