X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcore%2Fluasrc%2Futil.lua;h=235b0c22c2db59c61148988098d7046fbcc9e9f0;hp=a4ea2d29f889059f5176c8880f533300885ada2b;hb=8e00d27ae72245aa26feebfcf5cc0338ba1167d0;hpb=8c3ee6f9b7e5ab0b4edbf4838c7c1a9ee6d7df5d diff --git a/libs/core/luasrc/util.lua b/libs/core/luasrc/util.lua index a4ea2d29f..235b0c22c 100644 --- a/libs/core/luasrc/util.lua +++ b/libs/core/luasrc/util.lua @@ -24,8 +24,36 @@ 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 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 @@ -49,14 +77,10 @@ function class(base) local class = {} local create = function(class, ...) - local inst = {} - setmetatable(inst, {__index = class}) + local inst = setmetatable({}, {__index = class}) if inst.__init__ then - local stat, err = copcall(inst.__init__, inst, ...) - if not stat then - error(err) - end + inst:__init__(...) end return inst @@ -94,37 +118,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 --- @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 +162,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 @@ -199,6 +199,8 @@ end -- @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("'", "'") @@ -206,6 +208,13 @@ function pcdata(value) return value:gsub(">", ">") end +--- 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 @@ -254,8 +263,7 @@ 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 --- Parse certain units from the given string and return the canonical integer @@ -356,6 +364,19 @@ 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 + table.insert( keys, 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 whether to do recursive cloning @@ -370,9 +391,7 @@ function clone(object, deep) copy[k] = v end - setmetatable(copy, getmetatable(object)) - - return copy + return setmetatable(copy, getmetatable(object)) end @@ -389,15 +408,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 @@ -406,19 +441,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 @@ -574,10 +611,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 + table.insert(data, 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