X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcore%2Fluasrc%2Futil.lua;h=53e46a99dc85176b1461720d0446954ad1512e85;hp=d8fc666233da71e8056bf55a8f4483dbf993e6bb;hb=a3cf596d37e4116a7068daaa724270a0898cb7da;hpb=ab71972c7e418be3a2a4d523c2f4ae5e9c5bfdd4 diff --git a/libs/core/luasrc/util.lua b/libs/core/luasrc/util.lua index d8fc66623..53e46a99d 100644 --- a/libs/core/luasrc/util.lua +++ b/libs/core/luasrc/util.lua @@ -24,13 +24,57 @@ limitations under the License. ]]-- +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 cutil = require "luci.cutil" + +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", package.seeall) +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 -- +-- 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. @@ -45,32 +89,15 @@ module("luci.util", package.seeall) -- @return A class object -- @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 +]]-- +class = cutil.class --- Test whether the given object is an instance of the given class. -- @param object Object instance @@ -78,6 +105,7 @@ end -- @return Boolean indicating whether the object is an instance -- @see class -- @see clone +--[[ function instanceof(object, class) local meta = getmetatable(object) while meta and meta.__index do @@ -88,43 +116,14 @@ function instanceof(object, class) end return false end +]]-- +instanceof = cutil.instanceof -- -- 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 --- @return Always nil --- @see updfenv --- @see resfenv -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 --- @return Always nil --- @see extfenv --- @see resfenv -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. @@ -169,14 +168,21 @@ 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 @@ -198,15 +204,18 @@ end --- Create valid XML PCDATA from given string. -- @param value String value containing the data to escape -- @return String value containing the escaped data +--[[ function pcdata(value) - if not value then return end - value = tostring(value) - value = value:gsub("&", "&") - value = value:gsub('"', """) - value = value:gsub("'", "'") - value = value:gsub("<", "<") - return value:gsub(">", ">") + return value and tostring(value):gsub("[&\"'<>]", { + ["&"] = "&", + ['"'] = """, + ["'"] = "'", + ["<"] = "<", + [">"] = ">" + }) end +]]-- +pcdata = cutil.pcdata --- Strip HTML tags from given string. -- @param value String containing the HTML text @@ -249,9 +258,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 @@ -262,9 +271,21 @@ end --- Remove leading and trailing whitespace from given string value. -- @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 +]]-- +trim = cutil.trim + +--- 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 @@ -326,19 +347,40 @@ 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 ... 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. @@ -372,7 +414,7 @@ function keys(t) local keys = { } if t then for k, _ in kspairs(t) do - table.insert( keys, k ) + keys[#keys+1] = k end end return keys @@ -392,9 +434,7 @@ function clone(object, deep) copy[k] = v end - setmetatable(copy, getmetatable(object)) - - return copy + return setmetatable(copy, getmetatable(object)) end @@ -411,15 +451,31 @@ end -- Serialize the contents of a table value. -function _serialize_table(t) - local data = "" +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 - k = serialize_data(k) - v = serialize_data(v) - data = data .. ( #data > 0 and ", " or "" ) .. - '[' .. k .. '] = ' .. v + 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 - return data + + 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 @@ -428,19 +484,21 @@ end -- @return String value containing the serialized code -- @see restore_data -- @see get_bytecode -function serialize_data(val) +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 string.format("%q", val) + return "%q" % val elseif type(val) == "boolean" then return val and "true" or "false" elseif type(val) == "function" then - return string.format("loadstring(%q)", get_bytecode(val)) + return "loadstring(%q)" % get_bytecode(val) elseif type(val) == "table" then - return "{ " .. _serialize_table(val) .. " }" + return "{ " .. _serialize_table(val, seen) .. " }" else return '"[unhandled data type:' .. type(val) .. ']"' end @@ -553,17 +611,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 @@ -624,11 +681,11 @@ function execi(command) return pp and function() local line = pp:read() - + if not line then pp:close() end - + return line end end @@ -642,7 +699,7 @@ function execl(command) while true do line = pp:read() if (line == nil) then break end - table.insert(data, line) + data[#data+1] = line end pp:close() @@ -652,7 +709,7 @@ end --- Returns the absolute path to LuCI base directory. -- @return String containing the directory path function libpath() - return luci.fs.dirname(require("luci.debug").__file__) + return require "luci.fs".dirname(ldebug.__file__) end