| --- Generally useful routines. |
| -- See <a href="../../index.html#utils">the Guide</a>. |
| -- @class module |
| -- @name pl.utils |
| local format,gsub,byte = string.format,string.gsub,string.byte |
| local clock = os.clock |
| local stdout = io.stdout |
| local append = table.insert |
| |
| local collisions = {} |
| |
| local utils = {} |
| |
| utils._VERSION = "0.9.8" |
| |
| utils.dir_separator = _G.package.config:sub(1,1) |
| |
| --- end this program gracefully. |
| -- @param code The exit code or a message to be printed |
| -- @param ... extra arguments for message's format' |
| -- @see utils.fprintf |
| function utils.quit(code,...) |
| if type(code) == 'string' then |
| utils.fprintf(io.stderr,code,...) |
| code = -1 |
| else |
| utils.fprintf(io.stderr,...) |
| end |
| io.stderr:write('\n') |
| os.exit(code) |
| end |
| |
| --- print an arbitrary number of arguments using a format. |
| -- @param fmt The format (see string.format) |
| -- @param ... Extra arguments for format |
| function utils.printf(fmt,...) |
| utils.fprintf(stdout,fmt,...) |
| end |
| |
| --- write an arbitrary number of arguments to a file using a format. |
| -- @param f File handle to write to. |
| -- @param fmt The format (see string.format). |
| -- @param ... Extra arguments for format |
| function utils.fprintf(f,fmt,...) |
| utils.assert_string(2,fmt) |
| f:write(format(fmt,...)) |
| end |
| |
| local function import_symbol(T,k,v,libname) |
| local key = rawget(T,k) |
| -- warn about collisions! |
| if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then |
| utils.printf("warning: '%s.%s' overrides existing symbol\n",libname,k) |
| end |
| rawset(T,k,v) |
| end |
| |
| local function lookup_lib(T,t) |
| for k,v in pairs(T) do |
| if v == t then return k end |
| end |
| return '?' |
| end |
| |
| local already_imported = {} |
| |
| --- take a table and 'inject' it into the local namespace. |
| -- @param t The Table |
| -- @param T An optional destination table (defaults to callers environment) |
| function utils.import(t,T) |
| T = T or _G |
| t = t or utils |
| if type(t) == 'string' then |
| t = require (t) |
| end |
| local libname = lookup_lib(T,t) |
| if already_imported[t] then return end |
| already_imported[t] = libname |
| for k,v in pairs(t) do |
| import_symbol(T,k,v,libname) |
| end |
| end |
| |
| utils.patterns = { |
| FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', |
| INTEGER = '[+%-%d]%d*', |
| IDEN = '[%a_][%w_]*', |
| FILE = '[%a%.\\][:%][%w%._%-\\]*' |
| } |
| |
| --- escape any 'magic' characters in a string |
| -- @param s The input string |
| function utils.escape(s) |
| utils.assert_string(1,s) |
| return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')) |
| end |
| |
| --- return either of two values, depending on a condition. |
| -- @param cond A condition |
| -- @param value1 Value returned if cond is true |
| -- @param value2 Value returned if cond is false (can be optional) |
| function utils.choose(cond,value1,value2) |
| if cond then return value1 |
| else return value2 |
| end |
| end |
| |
| local raise |
| |
| --- return the contents of a file as a string |
| -- @param filename The file path |
| -- @param is_bin open in binary mode |
| -- @return file contents |
| function utils.readfile(filename,is_bin) |
| local mode = is_bin and 'b' or '' |
| utils.assert_string(1,filename) |
| local f,err = io.open(filename,'r'..mode) |
| if not f then return utils.raise (err) end |
| local res,err = f:read('*a') |
| f:close() |
| if not res then return raise (err) end |
| return res |
| end |
| |
| --- write a string to a file |
| -- @param filename The file path |
| -- @param str The string |
| -- @return true or nil |
| -- @return error message |
| -- @raise error if filename or str aren't strings |
| function utils.writefile(filename,str) |
| utils.assert_string(1,filename) |
| utils.assert_string(2,str) |
| local f,err = io.open(filename,'w') |
| if not f then return raise(err) end |
| f:write(str) |
| f:close() |
| return true |
| end |
| |
| --- return the contents of a file as a list of lines |
| -- @param filename The file path |
| -- @return file contents as a table |
| -- @raise errror if filename is not a string |
| function utils.readlines(filename) |
| utils.assert_string(1,filename) |
| local f,err = io.open(filename,'r') |
| if not f then return raise(err) end |
| local res = {} |
| for line in f:lines() do |
| append(res,line) |
| end |
| f:close() |
| return res |
| end |
| |
| --- split a string into a list of strings separated by a delimiter. |
| -- @param s The input string |
| -- @param re A Lua string pattern; defaults to '%s+' |
| -- @param plain don't use Lua patterns |
| -- @param n optional maximum number of splits |
| -- @return a list-like table |
| -- @raise error if s is not a string |
| function utils.split(s,re,plain,n) |
| utils.assert_string(1,s) |
| local find,sub,append = string.find, string.sub, table.insert |
| local i1,ls = 1,{} |
| if not re then re = '%s+' end |
| if re == '' then return {s} end |
| while true do |
| local i2,i3 = find(s,re,i1,plain) |
| if not i2 then |
| local last = sub(s,i1) |
| if last ~= '' then append(ls,last) end |
| if #ls == 1 and ls[1] == '' then |
| return {} |
| else |
| return ls |
| end |
| end |
| append(ls,sub(s,i1,i2-1)) |
| if n and #ls == n then |
| ls[#ls] = sub(s,i1) |
| return ls |
| end |
| i1 = i3+1 |
| end |
| end |
| |
| --- split a string into a number of values. |
| -- @param s the string |
| -- @param re the delimiter, default space |
| -- @return n values |
| -- @usage first,next = splitv('jane:doe',':') |
| -- @see split |
| function utils.splitv (s,re) |
| return unpack(utils.split(s,re)) |
| end |
| |
| local lua52 = table.pack ~= nil |
| local lua51_load = load |
| |
| if not lua52 then -- define Lua 5.2 style load() |
| function utils.load(str,src,mode,env) |
| local chunk,err |
| if type(str) == 'string' then |
| chunk,err = loadstring(str,src) |
| else |
| chunk,err = lua51_load(str,src) |
| end |
| if chunk and env then setfenv(chunk,env) end |
| return chunk,err |
| end |
| else |
| utils.load = load |
| -- setfenv/getfenv replacements for Lua 5.2 |
| -- by Sergey Rozhenko |
| -- http://lua-users.org/lists/lua-l/2010-06/msg00313.html |
| -- Roberto Ierusalimschy notes that it is possible for getfenv to return nil |
| -- in the case of a function with no globals: |
| -- http://lua-users.org/lists/lua-l/2010-06/msg00315.html |
| function setfenv(f, t) |
| f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) |
| local name |
| local up = 0 |
| repeat |
| up = up + 1 |
| name = debug.getupvalue(f, up) |
| until name == '_ENV' or name == nil |
| if name then |
| debug.upvaluejoin(f, up, function() return name end, 1) -- use unique upvalue |
| debug.setupvalue(f, up, t) |
| end |
| end |
| |
| function getfenv(f) |
| f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) |
| local name, val |
| local up = 0 |
| repeat |
| up = up + 1 |
| name, val = debug.getupvalue(f, up) |
| until name == '_ENV' or name == nil |
| return val |
| end |
| end |
| |
| |
| --- execute a shell command. |
| -- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2 |
| -- @param cmd a shell command |
| -- @return true if successful |
| -- @return actual return code |
| function utils.execute (cmd) |
| local res1,res2,res2 = os.execute(cmd) |
| if not lua52 then |
| return res1==0,res1 |
| else |
| return res1,res2 |
| end |
| end |
| |
| if not lua52 then |
| function table.pack (...) |
| local n = select('#',...) |
| return {n=n; ...},n |
| end |
| local sep = package.config:sub(1,1) |
| function package.searchpath (mod,path) |
| mod = mod:gsub('%.',sep) |
| for m in path:gmatch('[^;]+') do |
| local nm = m:gsub('?',mod) |
| local f = io.open(nm,'r') |
| if f then f:close(); return nm end |
| end |
| end |
| end |
| |
| if not table.pack then table.pack = _G.pack end |
| if not rawget(_G,"pack") then _G.pack = table.pack end |
| |
| --- take an arbitrary set of arguments and make into a table. |
| -- This returns the table and the size; works fine for nil arguments |
| -- @param ... arguments |
| -- @return table |
| -- @return table size |
| -- @usage local t,n = utils.args(...) |
| |
| --- 'memoize' a function (cache returned value for next call). |
| -- This is useful if you have a function which is relatively expensive, |
| -- but you don't know in advance what values will be required, so |
| -- building a table upfront is wasteful/impossible. |
| -- @param func a function of at least one argument |
| -- @return a function with at least one argument, which is used as the key. |
| function utils.memoize(func) |
| return setmetatable({}, { |
| __index = function(self, k, ...) |
| local v = func(k,...) |
| self[k] = v |
| return v |
| end, |
| __call = function(self, k) return self[k] end |
| }) |
| end |
| |
| --- is the object either a function or a callable object?. |
| -- @param obj Object to check. |
| function utils.is_callable (obj) |
| return type(obj) == 'function' or getmetatable(obj) and getmetatable(obj).__call |
| end |
| |
| --- is the object of the specified type?. |
| -- If the type is a string, then use type, otherwise compare with metatable |
| -- @param obj An object to check |
| -- @param tp String of what type it should be |
| function utils.is_type (obj,tp) |
| if type(tp) == 'string' then return type(obj) == tp end |
| local mt = getmetatable(obj) |
| return tp == mt |
| end |
| |
| local fileMT = getmetatable(io.stdout) |
| |
| --- a string representation of a type. |
| -- For tables with metatables, we assume that the metatable has a `_name` |
| -- field. Knows about Lua file objects. |
| -- @param obj an object |
| -- @return a string like 'number', 'table' or 'List' |
| function utils.type (obj) |
| local t = type(obj) |
| if t == 'table' or t == 'userdata' then |
| local mt = getmetatable(obj) |
| if mt == fileMT then |
| return 'file' |
| else |
| return mt._name or "unknown "..t |
| end |
| else |
| return t |
| end |
| end |
| |
| --- is this number an integer? |
| -- @param x a number |
| -- @raise error if x is not a number |
| function utils.is_integer (x) |
| return math.ceil(x)==x |
| end |
| |
| utils.stdmt = { |
| List = {_name='List'}, Map = {_name='Map'}, |
| Set = {_name='Set'}, MultiMap = {_name='MultiMap'} |
| } |
| |
| local _function_factories = {} |
| |
| --- associate a function factory with a type. |
| -- A function factory takes an object of the given type and |
| -- returns a function for evaluating it |
| -- @param mt metatable |
| -- @param fun a callable that returns a function |
| function utils.add_function_factory (mt,fun) |
| _function_factories[mt] = fun |
| end |
| |
| local function _string_lambda(f) |
| local raise = utils.raise |
| if f:find '^|' or f:find '_' then |
| local args,body = f:match '|([^|]*)|(.+)' |
| if f:find '_' then |
| args = '_' |
| body = f |
| else |
| if not args then return raise 'bad string lambda' end |
| end |
| local fstr = 'return function('..args..') return '..body..' end' |
| local fn,err = loadstring(fstr) |
| if not fn then return raise(err) end |
| fn = fn() |
| return fn |
| else return raise 'not a string lambda' |
| end |
| end |
| |
| --- an anonymous function as a string. This string is either of the form |
| -- '|args| expression' or is a function of one argument, '_' |
| -- @param lf function as a string |
| -- @return a function |
| -- @usage string_lambda '|x|x+1' (2) == 3 |
| -- @usage string_lambda '_+1 (2) == 3 |
| utils.string_lambda = utils.memoize(_string_lambda) |
| |
| local ops |
| |
| --- process a function argument. |
| -- This is used throughout Penlight and defines what is meant by a function: |
| -- Something that is callable, or an operator string as defined by <code>pl.operator</code>, |
| -- such as '>' or '#'. If a function factory has been registered for the type, it will |
| -- be called to get the function. |
| -- @param idx argument index |
| -- @param f a function, operator string, or callable object |
| -- @param msg optional error message |
| -- @return a callable |
| -- @raise if idx is not a number or if f is not callable |
| -- @see utils.is_callable |
| function utils.function_arg (idx,f,msg) |
| utils.assert_arg(1,idx,'number') |
| local tp = type(f) |
| if tp == 'function' then return f end -- no worries! |
| -- ok, a string can correspond to an operator (like '==') |
| if tp == 'string' then |
| if not ops then ops = require 'pl.operator'.optable end |
| local fn = ops[f] |
| if fn then return fn end |
| local fn, err = utils.string_lambda(f) |
| if not fn then error(err..': '..f) end |
| return fn |
| elseif tp == 'table' or tp == 'userdata' then |
| local mt = getmetatable(f) |
| if not mt then error('not a callable object',2) end |
| local ff = _function_factories[mt] |
| if not ff then |
| if not mt.__call then error('not a callable object',2) end |
| return f |
| else |
| return ff(f) -- we have a function factory for this type! |
| end |
| end |
| if not msg then msg = " must be callable" end |
| if idx > 0 then |
| error("argument "..idx..": "..msg,2) |
| else |
| error(msg,2) |
| end |
| end |
| |
| --- bind the first argument of the function to a value. |
| -- @param fn a function of at least two values (may be an operator string) |
| -- @param p a value |
| -- @return a function such that f(x) is fn(p,x) |
| -- @raise same as @{function_arg} |
| -- @see pl.func.curry |
| function utils.bind1 (fn,p) |
| fn = utils.function_arg(1,fn) |
| return function(...) return fn(p,...) end |
| end |
| |
| --- assert that the given argument is in fact of the correct type. |
| -- @param n argument index |
| -- @param val the value |
| -- @param tp the type |
| -- @param verify an optional verfication function |
| -- @param msg an optional custom message |
| -- @param lev optional stack position for trace, default 2 |
| -- @raise if the argument n is not the correct type |
| -- @usage assert_arg(1,t,'table') |
| -- @usage assert_arg(n,val,'string',path.isdir,'not a directory') |
| function utils.assert_arg (n,val,tp,verify,msg,lev) |
| if type(val) ~= tp then |
| error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),2) |
| end |
| if verify and not verify(val) then |
| error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2) |
| end |
| end |
| |
| --- assert the common case that the argument is a string. |
| -- @param n argument index |
| -- @param val a value that must be a string |
| -- @raise val must be a string |
| function utils.assert_string (n,val) |
| utils.assert_arg(n,val,'string',nil,nil,nil,3) |
| end |
| |
| local err_mode = 'default' |
| |
| --- control the error strategy used by Penlight. |
| -- Controls how <code>utils.raise</code> works; the default is for it |
| -- to return nil and the error string, but if the mode is 'error' then |
| -- it will throw an error. If mode is 'quit' it will immediately terminate |
| -- the program. |
| -- @param mode - either 'default', 'quit' or 'error' |
| -- @see utils.raise |
| function utils.on_error (mode) |
| err_mode = mode |
| end |
| |
| --- used by Penlight functions to return errors. Its global behaviour is controlled |
| -- by <code>utils.on_error</code> |
| -- @param err the error string. |
| -- @see utils.on_error |
| function utils.raise (err) |
| if err_mode == 'default' then return nil,err |
| elseif err_mode == 'quit' then utils.quit(err) |
| else error(err,2) |
| end |
| end |
| |
| raise = utils.raise |
| |
| --- load a code string or bytecode chunk. |
| -- @param code Lua code as a string or bytecode |
| -- @param name for source errors |
| -- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default) |
| -- @param env the environment for the new chunk (default nil) |
| -- @return compiled chunk |
| -- @return error message (chunk is nil) |
| -- @function utils.load |
| |
| |
| --- Lua 5.2 Compatible Functions |
| -- @section lua52 |
| |
| --- pack an argument list into a table. |
| -- @param ... any arguments |
| -- @return a table with field n set to the length |
| -- @return the length |
| -- @function table.pack |
| |
| ------ |
| -- return the full path where a Lua module name would be matched. |
| -- @param mod module name, possibly dotted |
| -- @param path a path in the same form as package.path or package.cpath |
| -- @see path.package_path |
| -- @function package.searchpath |
| |
| return utils |
| |
| |