]]--
+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
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
-- 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.
--- 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
-- @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("'", "'")
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("</?[A-Za-z][A-Za-z0-9:_%-]*[^>]*>", " "):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
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
-- @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
--- Combines two or more numerically indexed tables into one.
-- @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)
+ result[#result+1] = v
end
end
return result
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 whether to do recursive cloning
copy[k] = v
end
- setmetatable(copy, getmetatable(object))
-
- return copy
+ return setmetatable(copy, getmetatable(object))
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
-- @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
local keys = { }
for k, v in pairs(t) do
- table.insert( keys, k )
+ keys[#keys+1] = k
end
local _pos = 0
--
+-- 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