X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcore%2Fluasrc%2Futil.lua;h=adeb99fb11d25efa6932f6e46ad9fa3e950d2f6a;hp=b2a683772ef933e581cc75352c84b1a0ef5e48f8;hb=128437f67103c980a8c963bf2510fe38ba1f4e54;hpb=b4ac19ca76f3d3816f3d92041ed6f38dad0c09eb diff --git a/libs/core/luasrc/util.lua b/libs/core/luasrc/util.lua index b2a683772..adeb99fb1 100644 --- a/libs/core/luasrc/util.lua +++ b/libs/core/luasrc/util.lua @@ -24,15 +24,54 @@ limitations under the License. ]]-- --- LuCI utility functions. -module("luci.util", package.seeall) +local io = require "io" +local math = require "math" +local table = require "table" +local debug = require "debug" +local ldebug = require "luci.debug" +local string = require "string" +local coroutine = require "coroutine" + +local getmetatable, setmetatable = getmetatable, setmetatable +local rawget, rawset, unpack = rawget, rawset, unpack +local tostring, type, assert = tostring, type, assert +local ipairs, pairs, loadstring = ipairs, pairs, loadstring +local require, pcall, xpcall = require, pcall, xpcall + +--- LuCI utility functions. +module "luci.util" + +-- +-- Pythonic string formatting extension +-- +getmetatable("").__mod = function(a, b) + if not b then + return a + elseif type(b) == "table" then + return a:format(unpack(b)) + else + return a:format(b) + end +end + -- -- Class helper routines -- ---- Creates a Class object (Python-style object model) --- Creates a new class object which can be instantiated by calling itself. +-- Instantiates a class +local function _instantiate(class, ...) + local inst = setmetatable({}, {__index = class}) + + if inst.__init__ then + inst:__init__(...) + end + + return inst +end + +--- Create a Class object (Python-style object model). +-- The class object can be instantiated by calling itself. -- Any class functions or shared parameters can be attached to this object. -- Attaching a table to the class object makes this table shared between -- all instances of this class. For object parameters use the __init__ function. @@ -46,36 +85,16 @@ module("luci.util", package.seeall) -- @see instanceof -- @see clone function class(base) - local class = {} - - local create = function(class, ...) - local inst = {} - setmetatable(inst, {__index = class}) - - if inst.__init__ then - local stat, err = copcall(inst.__init__, inst, ...) - if not stat then - error(err) - end - end - - return inst - end - - local classmeta = {__call = create} - - if base then - classmeta.__index = base - end - - setmetatable(class, classmeta) - return class + return setmetatable({}, { + __call = _instantiate, + __index = base + }) end --- Test whether the given object is an instance of the given class. -- @param object Object instance -- @param class Class object to test against --- @return Boolean indicating wheather the object is an instance +-- @return Boolean indicating whether the object is an instance -- @see class -- @see clone function instanceof(object, class) @@ -94,37 +113,6 @@ end -- Scope manipulation routines -- ---- Replace a function scope with a shallow copy of itself --- This is useful if you want to get rid of several unwanted side effects --- while changing the scope of a certain Lua function. --- @param f Lua function -function resfenv(f) - setfenv(f, clone(getfenv(f))) -end - ---- Store given object associated with given key in the scope of a function --- @param f Lua function --- @param key String value containg the key of the object to store --- @param obj Object to store in the scope --- @see updfenv --- @see resfenv --- @return Always nil -function extfenv(f, key, obj) - local scope = getfenv(f) - scope[key] = obj -end - ---- Extend the scope of a function with the contents of a table --- @param f Lua function --- @param key String value containg the key of the object to store --- @param obj Object to store in the scope --- @see extfenv --- @see resfenv --- @return Always nil -function updfenv(f, extscope) - update(getfenv(f), extscope) -end - --- Create a new or get an already existing thread local store associated with -- the current active coroutine. A thread local store is private a table object -- whose values can't be accessed from outside of the running coroutine. @@ -162,21 +150,28 @@ end --- Write given object to stderr. -- @param obj Value to write to stderr --- @return Boolean indicating wheather the write operation was successful +-- @return Boolean indicating whether the write operation was successful function perror(obj) return io.stderr:write(tostring(obj) .. "\n") end --- Recursively dumps a table to stdout, useful for testing and debugging. -- @param t Table value to dump --- @param i Number of tabs to prepend to each line +-- @param maxdepth Maximum depth -- @return Always nil -function dumptable(t, i) +function dumptable(t, maxdepth, i, seen) i = i or 0 + seen = seen or setmetatable({}, {__mode="k"}) + for k,v in pairs(t) do - print(string.rep("\t", i) .. tostring(k), tostring(v)) - if type(v) == "table" then - dumptable(v, i+1) + perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v)) + if type(v) == "table" and (not maxdepth or i < maxdepth) then + if not seen[v] then + seen[v] = true + dumptable(v, maxdepth, i+1, seen) + else + perror(string.rep("\t", i) .. "*** RECURSION ***") + end end end end @@ -186,10 +181,10 @@ end -- String and data manipulation routines -- ---- Escapes all occurences of the given character in given string. +--- Escapes all occurrences of the given character in given string. -- @param s String value containing unescaped characters -- @param c String value with character to escape (optional, defaults to "\") --- @return String value with each occurence of character escaped with "\" +-- @return String value with each occurrence of character escaped with "\" function escape(s, c) c = c or "\\" return s:gsub(c, "\\" .. c) @@ -198,23 +193,43 @@ end --- Create valid XML PCDATA from given string. -- @param value String value containing the data to escape -- @return String value containing the escaped data +local function _pcdata_repl(c) + local i = string.byte(c) + + if ( i >= 0x00 and i <= 0x08 ) or ( i >= 0x0B and i <= 0x0C ) or + ( i >= 0x0E and i <= 0x1F ) or ( i == 0x7F ) + then + return "" + + elseif ( i == 0x26 ) or ( i == 0x27 ) or ( i == 0x22 ) or + ( i == 0x3C ) or ( i == 0x3E ) + then + return string.format("&#%i;", i) + end + + return c +end + function pcdata(value) - value = value:gsub("&", "&") - value = value:gsub('"', """) - value = value:gsub("'", "'") - value = value:gsub("<", "<") - return value:gsub(">", ">") + return value and tostring(value):gsub("[&\"'<>%c]", _pcdata_repl) end ---- Splits given string on a defined seperator sequence and return a table +--- Strip HTML tags from given string. +-- @param value String containing the HTML text +-- @return String with HTML tags stripped of +function striptags(s) + return pcdata(s:gsub("]*>", " "):gsub("%s+", " ")) +end + +--- Splits given string on a defined separator sequence and return a table -- containing the resulting substrings. The optional max parameter specifies -- the number of bytes to process, regardless of the actual length of the given --- string. The optional last parameter, regex, sepcifies wheather the separator +-- string. The optional last parameter, regex, specifies whether the separator -- sequence is interpreted as regular expression. -- @param str String value containing the data to split up -- @param pat String with separator pattern (optional, defaults to "\n") --- @param max Num of bytes to process (optional, default is string length) --- @param regexp Boolean indicating wheather to interprete the separator +-- @param max Maximum times to split (optional) +-- @param regex Boolean indicating whether to interpret the separator -- pattern as regular expression (optional, default is false) -- @return Table containing the resulting substrings function split(str, pat, max, regex) @@ -240,9 +255,9 @@ function split(str, pat, max, regex) local s, e = str:find(pat, c, not regex) max = max - 1 if s and max < 0 then - table.insert(t, str:sub(c)) + t[#t+1] = str:sub(c) else - table.insert(t, str:sub(c, s and s - 1)) + t[#t+1] = str:sub(c, s and s - 1) end c = e and e + 1 or #str + 1 until not s or max < 0 @@ -254,12 +269,21 @@ end -- @param str String value containing whitespace padded data -- @return String value with leading and trailing space removed function trim(str) - local s = str:gsub("^%s*(.-)%s*$", "%1") - return s + return (str:gsub("^%s*(.-)%s*$", "%1")) +end + +--- Count the occurences of given substring in given string. +-- @param str String to search in +-- @param pattern String containing pattern to find +-- @return Number of found occurences +function cmatch(str, pat) + local count = 0 + for _ in str:gmatch(pat) do count = count + 1 end + return count end --- Parse certain units from the given string and return the canonical integer --- value or 0 if the unit is unknown. Upper- or lowercase is irrelevant. +-- value or 0 if the unit is unknown. Upper- or lower case is irrelevant. -- Recognized units are: -- o "y" - one year (60*60*24*366) -- o "m" - one month (60*60*24*31) @@ -317,25 +341,46 @@ function parse_units(ustr) return val end ---- Combines two or more numerically indexed tables into one. +-- also register functions above in the central string class for convenience +string.escape = escape +string.pcdata = pcdata +string.striptags = striptags +string.split = split +string.trim = trim +string.cmatch = cmatch +string.parse_units = parse_units + + +--- Appends numerically indexed tables or single objects to a given table. +-- @param src Target table +-- @param ... Objects to insert +-- @return Target table +function append(src, ...) + for i, a in ipairs({...}) do + if type(a) == "table" then + for j, v in ipairs(a) do + src[#src+1] = v + end + else + src[#src+1] = a + end + end + return src +end + +--- Combines two or more numerically indexed tables and single objects into one table. -- @param tbl1 Table value to combine -- @param tbl2 Table value to combine --- @param tblN More values to combine +-- @param ... More tables to combine -- @return Table value containing all values of given tables function combine(...) - local result = {} - for i, a in ipairs(arg) do - for j, v in ipairs(a) do - table.insert(result, v) - end - end - return result + return append({}, ...) end --- Checks whether the given table contains the given value. -- @param table Table value -- @param value Value to search within the given table --- @return Boolean indicating wheather the given value occurs within table +-- @return Boolean indicating whether the given value occurs within table function contains(table, value) for k, v in pairs(table) do if value == v then @@ -356,9 +401,22 @@ function update(t, updates) end end +--- Retrieve all keys of given associative table. +-- @param t Table to extract keys from +-- @return Sorted table containing the keys +function keys(t) + local keys = { } + if t then + for k, _ in kspairs(t) do + keys[#keys+1] = k + end + end + return keys +end + --- Clones the given object and return it's copy. -- @param object Table value to clone --- @param deep Boolean indicating wheather to do recursive cloning +-- @param deep Boolean indicating whether to do recursive cloning -- @return Cloned table value function clone(object, deep) local copy = {} @@ -370,9 +428,83 @@ function clone(object, deep) copy[k] = v end - setmetatable(copy, getmetatable(object)) + return setmetatable(copy, getmetatable(object)) +end - return copy + +--- Create a dynamic table which automatically creates subtables. +-- @return Dynamic Table +function dtable() + return setmetatable({}, { __index = + function(tbl, key) + return rawget(tbl, key) + or rawget(rawset(tbl, key, dtable()), key) + end + }) +end + + +-- Serialize the contents of a table value. +function _serialize_table(t, seen) + assert(not seen[t], "Recursion detected.") + seen[t] = true + + local data = "" + local idata = "" + local ilen = 0 + + for k, v in pairs(t) do + if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then + k = serialize_data(k, seen) + v = serialize_data(v, seen) + data = data .. ( #data > 0 and ", " or "" ) .. + '[' .. k .. '] = ' .. v + elseif k > ilen then + ilen = k + end + end + + for i = 1, ilen do + local v = serialize_data(t[i], seen) + idata = idata .. ( #idata > 0 and ", " or "" ) .. v + end + + return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data +end + +--- Recursively serialize given data to lua code, suitable for restoring +-- with loadstring(). +-- @param val Value containing the data to serialize +-- @return String value containing the serialized code +-- @see restore_data +-- @see get_bytecode +function serialize_data(val, seen) + seen = seen or setmetatable({}, {__mode="k"}) + + if val == nil then + return "nil" + elseif type(val) == "number" then + return val + elseif type(val) == "string" then + return "%q" % val + elseif type(val) == "boolean" then + return val and "true" or "false" + elseif type(val) == "function" then + return "loadstring(%q)" % get_bytecode(val) + elseif type(val) == "table" then + return "{ " .. _serialize_table(val, seen) .. " }" + else + return '"[unhandled data type:' .. type(val) .. ']"' + end +end + +--- Restore data previously serialized with serialize_data(). +-- @param str String containing the data to restore +-- @return Value containing the restored data structure +-- @see serialize_data +-- @see get_bytecode +function restore_data(str) + return loadstring("return " .. str)() end @@ -380,13 +512,20 @@ end -- Byte code manipulation routines -- ---- Return the current runtime bytecode of the given function. The byte code +--- Return the current runtime bytecode of the given data. The byte code -- will be stripped before it is returned. --- @param f Function value to return as bytecode --- @return String value containing the bytecode of the given function -function get_bytecode(f) - local d = string.dump(f) - return d and strip_bytecode(d) +-- @param val Value to return as bytecode +-- @return String value containing the bytecode of the given data +function get_bytecode(val) + local code + + if type(val) == "function" then + code = string.dump(val) + else + code = string.dump( loadstring( "return " .. serialize_data(val) ) ) + end + + return code and strip_bytecode(code) end --- Strips unnescessary lua bytecode from given string. Information like line @@ -415,10 +554,10 @@ function strip_bytecode(code) end end - local strip_function - strip_function = function(code) + local function strip_function(code) local count, offset = subint(code, 1, size) - local stripped, dirty = string.rep("\0", size), offset + count + local stripped = { string.rep("\0", size) } + local dirty = offset + count offset = offset + count + int * 2 + 4 offset = offset + int + subint(code, offset, int) * ins count, offset = subint(code, offset, int) @@ -436,10 +575,11 @@ function strip_bytecode(code) end end count, offset = subint(code, offset, int) - stripped = stripped .. code:sub(dirty, offset - 1) + stripped[#stripped+1] = code:sub(dirty, offset - 1) for n = 1, count do local proto, off = strip_function(code:sub(offset, -1)) - stripped, offset = stripped .. proto, offset + off - 1 + stripped[#stripped+1] = proto + offset = offset + off - 1 end offset = offset + subint(code, offset, int) * int + int count, offset = subint(code, offset, int) @@ -450,8 +590,8 @@ function strip_bytecode(code) for n = 1, count do offset = offset + subint(code, offset, size) + size end - stripped = stripped .. string.rep("\0", int * 3) - return stripped, offset + stripped[#stripped+1] = string.rep("\0", int * 3) + return table.concat(stripped), offset end return code:sub(1,12) .. strip_function(code:sub(13,-1)) @@ -466,17 +606,16 @@ function _sortiter( t, f ) local keys = { } for k, v in pairs(t) do - table.insert( keys, k ) + keys[#keys+1] = k end local _pos = 0 - local _len = table.getn( keys ) table.sort( keys, f ) return function() _pos = _pos + 1 - if _pos <= _len then + if _pos <= #keys then return keys[_pos], t[keys[_pos]] end end @@ -509,10 +648,89 @@ end -- +-- System utility functions +-- + +--- Test whether the current system is operating in big endian mode. +-- @return Boolean value indicating whether system is big endian +function bigendian() + return string.byte(string.dump(function() end), 7) == 0 +end + +--- Execute given commandline and gather stdout. +-- @param command String containing command to execute +-- @return String containing the command's stdout +function exec(command) + local pp = io.popen(command) + local data = pp:read("*a") + pp:close() + + return data +end + +--- Return a line-buffered iterator over the output of given command. +-- @param command String containing the command to execute +-- @return Iterator +function execi(command) + local pp = io.popen(command) + + return pp and function() + local line = pp:read() + + if not line then + pp:close() + end + + return line + end +end + +-- Deprecated +function execl(command) + local pp = io.popen(command) + local line = "" + local data = {} + + while true do + line = pp:read() + if (line == nil) then break end + data[#data+1] = line + end + pp:close() + + return data +end + +--- Returns the absolute path to LuCI base directory. +-- @return String containing the directory path +function libpath() + return require "luci.fs".dirname(ldebug.__file__) +end + + +-- -- Coroutine safe xpcall and pcall versions modified for Luci -- original version: -- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org) -- +-- Copyright © 2005 Kepler Project. +-- Permission is hereby granted, free of charge, to any person obtaining a +-- copy of this software and associated documentation files (the "Software"), +-- to deal in the Software without restriction, including without limitation +-- the rights to use, copy, modify, merge, publish, distribute, sublicense, +-- and/or sell copies of the Software, and to permit persons to whom the +-- Software is furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +-- OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local performResume, handleReturnValue local oldpcall, oldxpcall = pcall, xpcall @@ -525,10 +743,11 @@ local function copcall_id(trace, ...) end --- This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function --- @param f Lua function to be called protected --- @param err Custom error handler --- @param ... parameters passed to the function --- @return a boolean whether the function call succeeded and the return values of either the function or the error handler +-- @param f Lua function to be called protected +-- @param err Custom error handler +-- @param ... Parameters passed to the function +-- @return A boolean whether the function call succeeded and the return +-- values of either the function or the error handler function coxpcall(f, err, ...) local res, co = oldpcall(coroutine.create, f) if not res then @@ -543,9 +762,10 @@ function coxpcall(f, err, ...) end --- This is a coroutine-safe drop-in replacement for Lua's "pcall"-function --- @param f Lua function to be called protected --- @param ... parameters passed to the function --- @return a boolean whether the function call succeeded and the returns values of the function or the error object +-- @param f Lua function to be called protected +-- @param ... Parameters passed to the function +-- @return A boolean whether the function call succeeded and the returns +-- values of the function or the error object function copcall(f, ...) return coxpcall(f, copcall_id, ...) end