* luci/libs/core: added inline documentation to luci.util, reordered and renamed functions to logical groups

* luci/libs/web:	rename dump() to get_bytecode()
This commit is contained in:
Jo-Philipp Wich 2008-07-22 20:33:04 +00:00
parent 33b8cf5506
commit 70b119b8ee
3 changed files with 347 additions and 248 deletions

View file

@ -12,9 +12,9 @@ Copyright 2008 Steven Barth <steven@midlink.org>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -24,8 +24,12 @@ limitations under the License.
]]-- ]]--
-- LuCI utility functions.
module("luci.util", package.seeall) module("luci.util", package.seeall)
--
-- Class helper routines
--
--- Creates a Class object (Python-style object model) --- Creates a Class object (Python-style object model)
-- Creates a new class object which can be instantiated by calling itself. -- Creates a new class object which can be instantiated by calling itself.
@ -37,86 +41,127 @@ module("luci.util", package.seeall)
-- to the __init__ function of this class - if such a function exists. -- to the __init__ function of this class - if such a function exists.
-- The __init__ function must be used to set any object parameters that are not shared -- The __init__ function must be used to set any object parameters that are not shared
-- with other objects of this class. Any return values will be ignored. -- with other objects of this class. Any return values will be ignored.
-- @see instanceof -- @param base The base class to inherit from (optional)
-- @see clone -- @return A class object
-- @param base the base class to inherit from (optional) -- @see instanceof
-- @return class object -- @see clone
function class(base) function class(base)
local class = {} local class = {}
local create = function(class, ...) local create = function(class, ...)
local inst = {} local inst = {}
setmetatable(inst, {__index = class}) setmetatable(inst, {__index = class})
if inst.__init__ then if inst.__init__ then
local stat, err = copcall(inst.__init__, inst, ...) local stat, err = copcall(inst.__init__, inst, ...)
if not stat then if not stat then
error(err) error(err)
end end
end end
return inst return inst
end end
local classmeta = {__call = create} local classmeta = {__call = create}
if base then if base then
classmeta.__index = base classmeta.__index = base
end end
setmetatable(class, classmeta) setmetatable(class, classmeta)
return class return class
end end
--- Test whether the given object is an instance of the given class.
-- Clones an object (deep on-demand) -- @param object Object instance
function clone(object, deep) -- @param class Class object to test against
local copy = {} -- @return Boolean indicating wheather the object is an instance
-- @see class
for k, v in pairs(object) do -- @see clone
if deep and type(v) == "table" then function instanceof(object, class)
v = clone(v, deep) local meta = getmetatable(object)
end while meta and meta.__index do
copy[k] = v if meta.__index == class then
end return true
setmetatable(copy, getmetatable(object))
return copy
end
-- Combines two or more numerically indexed tables into one
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
end
-- Checks whether a table has an object "value" in it
function contains(table, value)
for k, v in pairs(table) do
if value == v then
return k
end end
meta = getmetatable(meta.__index)
end end
return false return false
end end
-- Dumps and strips a Lua-Function --
function dump(f) -- Scope manipulation routines
local d = string.dump(f) --
return d and strip_bytecode(d)
--- Resets the scope of f doing a shallow copy of its scope into a new table
-- (ToDo: @param and @return)
function resfenv(f)
setfenv(f, clone(getfenv(f)))
end
--- Store given object associated with given key in the scope associated with
-- the given identifier.
-- @param f Value containing the scope identifier
-- @param key String value containg the key of the object to store
-- @param obj Object to store in the scope
-- @return Always nil
function extfenv(f, key, obj)
local scope = getfenv(f)
scope[key] = obj
end
--- Updates the scope of f with "extscope" (ToDo: docu)
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.
-- @return Table value representing the corresponding thread local store
function threadlocal()
local tbl = {}
local function get(self, key)
local c = coroutine.running()
local thread = coxpt[c] or c or 0
if not rawget(self, thread) then
return nil
end
return rawget(self, thread)[key]
end
local function set(self, key, value)
local c = coroutine.running()
local thread = coxpt[c] or c or 0
if not rawget(self, thread) then
rawset(self, thread, {})
end
rawget(self, thread)[key] = value
end
setmetatable(tbl, {__index = get, __newindex = set, __mode = "k"})
return tbl
end end
-- Dumps a table to stdout (useful for testing and debugging) --
-- Debugging routines
--
--- Write given object to stderr.
-- @param obj Value to write to stderr
-- @return Boolean indicating wheather the write operation was successful
function perror(obj)
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
-- @return Always nil
function dumptable(t, i) function dumptable(t, i)
i = i or 0 i = i or 0
for k,v in pairs(t) do for k,v in pairs(t) do
@ -128,75 +173,60 @@ function dumptable(t, i)
end end
-- Escapes all occurences of c in s --
-- String and data manipulation routines
--
--- Escapes all occurences 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 "\"
function escape(s, c) function escape(s, c)
c = c or "\\" c = c or "\\"
return s:gsub(c, "\\" .. c) return s:gsub(c, "\\" .. c)
end end
--- Create valid XML PCDATA from given string.
-- Populate obj in the scope of f as key -- @param value String value containing the data to escape
function extfenv(f, key, obj) -- @return String value containing the escaped data
local scope = getfenv(f)
scope[key] = obj
end
-- Checks whether an object is an instanceof class
function instanceof(object, class)
local meta = getmetatable(object)
while meta and meta.__index do
if meta.__index == class then
return true
end
meta = getmetatable(meta.__index)
end
return false
end
-- Creates valid XML PCDATA from a string
function pcdata(value) function pcdata(value)
value = value:gsub("&", "&amp;") value = value:gsub("&", "&amp;")
value = value:gsub('"', "&quot;") value = value:gsub('"', "&quot;")
value = value:gsub("'", "&apos;") value = value:gsub("'", "&apos;")
value = value:gsub("<", "&lt;") value = value:gsub("<", "&lt;")
return value:gsub(">", "&gt;") return value:gsub(">", "&gt;")
end end
--- Splits given string on a defined seperator sequence and return a table
-- Returns an error message to stdout -- containing the resulting substrings. The optional max parameter specifies
function perror(obj) -- the number of bytes to process, regardless of the actual length of the given
io.stderr:write(tostring(obj) .. "\n") -- string. The optional last parameter, regex, sepcifies wheather the separator
end -- 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")
-- Resets the scope of f doing a shallow copy of its scope into a new table -- @param max Num of bytes to process (optional, default is string length)
function resfenv(f) -- @param regexp Boolean indicating wheather to interprete the separator
setfenv(f, clone(getfenv(f))) -- pattern as regular expression (optional, default is false)
end -- @return Table containing the resulting substrings
-- Splits a string into an array
function split(str, pat, max, regex) function split(str, pat, max, regex)
pat = pat or "\n" pat = pat or "\n"
max = max or #str max = max or #str
local t = {} local t = {}
local c = 1 local c = 1
if #str == 0 then if #str == 0 then
return {""} return {""}
end end
if #pat == 0 then if #pat == 0 then
return nil return nil
end end
if max == 0 then if max == 0 then
return str return str
end end
repeat repeat
local s, e = str:find(pat, c, not regex) local s, e = str:find(pat, c, not regex)
max = max - 1 max = max - 1
@ -207,171 +237,222 @@ function split(str, pat, max, regex)
end end
c = e and e + 1 or #str + 1 c = e and e + 1 or #str + 1
until not s or max < 0 until not s or max < 0
return t return t
end 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
end
-- Strips lua bytecode --- Parse certain units from the given string and return the canonical integer
-- Original version by Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html) -- value or 0 if the unit is unknown. Upper- or lowercase is irrelevant.
function strip_bytecode(dump) -- Recognized units are:
local version, format, endian, int, size, ins, num, lnum = dump:byte(5, 12) -- o "y" - one year (60*60*24*366)
-- o "m" - one month (60*60*24*31)
-- o "w" - one week (60*60*24*7)
-- o "d" - one day (60*60*24)
-- o "h" - one hour (60*60)
-- o "min" - one minute (60)
-- o "kb" - one kilobyte (1024)
-- o "mb" - one megabyte (1024*1024)
-- o "gb" - one gigabyte (1024*1024*1024)
-- o "kib" - one si kilobyte (1000)
-- o "mib" - one si megabyte (1000*1000)
-- o "gib" - one si gigabyte (1000*1000*1000)
-- @param ustr String containing a numerical value with trailing unit
-- @return Number containing the canonical value
function parse_units(ustr)
local val = 0
-- unit map
local map = {
-- date stuff
y = 60 * 60 * 24 * 366,
m = 60 * 60 * 24 * 31,
w = 60 * 60 * 24 * 7,
d = 60 * 60 * 24,
h = 60 * 60,
min = 60,
-- storage sizes
kb = 1024,
mb = 1024 * 1024,
gb = 1024 * 1024 * 1024,
-- storage sizes (si)
kib = 1000,
mib = 1000 * 1000,
gib = 1000 * 1000 * 1000
}
-- parse input string
for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
local num = spec:gsub("[^0-9%.]+$","")
local spn = spec:gsub("^[0-9%.]+", "")
if map[spn] or map[spn:sub(1,1)] then
val = val + num * ( map[spn] or map[spn:sub(1,1)] )
else
val = val + num
end
end
return val
end
--- 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
-- @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
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
function contains(table, value)
for k, v in pairs(table) do
if value == v then
return k
end
end
return false
end
--- Update values in given table with the values from the second given table.
-- Both table are - in fact - merged together.
-- @param t Table which should be updated
-- @param updates Table containing the values to update
-- @return Always nil
function update(t, updates)
for k, v in pairs(updates) do
t[k] = v
end
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
-- @return Cloned table value
function clone(object, deep)
local copy = {}
for k, v in pairs(object) do
if deep and type(v) == "table" then
v = clone(v, deep)
end
copy[k] = v
end
setmetatable(copy, getmetatable(object))
return copy
end
--
-- Byte code manipulation routines
--
--- Return the current runtime bytecode of the given function. 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)
end
--- Strips unnescessary lua bytecode from given string. Information like line
-- numbers and debugging numbers will be discarded. Original version by
-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
-- @param code String value containing the original lua byte code
-- @return String value containing the stripped lua byte code
function strip_bytecode(code)
local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
local subint local subint
if endian == 1 then if endian == 1 then
subint = function(dump, i, l) subint = function(code, i, l)
local val = 0 local val = 0
for n = l, 1, -1 do for n = l, 1, -1 do
val = val * 256 + dump:byte(i + n - 1) val = val * 256 + code:byte(i + n - 1)
end end
return val, i + l return val, i + l
end end
else else
subint = function(dump, i, l) subint = function(code, i, l)
local val = 0 local val = 0
for n = 1, l, 1 do for n = 1, l, 1 do
val = val * 256 + dump:byte(i + n - 1) val = val * 256 + code:byte(i + n - 1)
end end
return val, i + l return val, i + l
end end
end end
local strip_function local strip_function
strip_function = function(dump) strip_function = function(code)
local count, offset = subint(dump, 1, size) local count, offset = subint(code, 1, size)
local stripped, dirty = string.rep("\0", size), offset + count local stripped, dirty = string.rep("\0", size), offset + count
offset = offset + count + int * 2 + 4 offset = offset + count + int * 2 + 4
offset = offset + int + subint(dump, offset, int) * ins offset = offset + int + subint(code, offset, int) * ins
count, offset = subint(dump, offset, int) count, offset = subint(code, offset, int)
for n = 1, count do for n = 1, count do
local t local t
t, offset = subint(dump, offset, 1) t, offset = subint(code, offset, 1)
if t == 1 then if t == 1 then
offset = offset + 1 offset = offset + 1
elseif t == 4 then elseif t == 4 then
offset = offset + size + subint(dump, offset, size) offset = offset + size + subint(code, offset, size)
elseif t == 3 then elseif t == 3 then
offset = offset + num offset = offset + num
elseif t == 254 or t == 9 then elseif t == 254 or t == 9 then
offset = offset + lnum offset = offset + lnum
end end
end end
count, offset = subint(dump, offset, int) count, offset = subint(code, offset, int)
stripped = stripped .. dump:sub(dirty, offset - 1) stripped = stripped .. code:sub(dirty, offset - 1)
for n = 1, count do for n = 1, count do
local proto, off = strip_function(dump:sub(offset, -1)) local proto, off = strip_function(code:sub(offset, -1))
stripped, offset = stripped .. proto, offset + off - 1 stripped, offset = stripped .. proto, offset + off - 1
end end
offset = offset + subint(dump, offset, int) * int + int offset = offset + subint(code, offset, int) * int + int
count, offset = subint(dump, offset, int) count, offset = subint(code, offset, int)
for n = 1, count do for n = 1, count do
offset = offset + subint(dump, offset, size) + size + int * 2 offset = offset + subint(code, offset, size) + size + int * 2
end end
count, offset = subint(dump, offset, int) count, offset = subint(code, offset, int)
for n = 1, count do for n = 1, count do
offset = offset + subint(dump, offset, size) + size offset = offset + subint(code, offset, size) + size
end end
stripped = stripped .. string.rep("\0", int * 3) stripped = stripped .. string.rep("\0", int * 3)
return stripped, offset return stripped, offset
end end
return dump:sub(1,12) .. strip_function(dump:sub(13,-1)) return code:sub(1,12) .. strip_function(code:sub(13,-1))
end end
-- Creates a new threadlocal store --
function threadlocal() -- Sorting iterator functions
local tbl = {} --
local function get(self, key)
local c = coroutine.running()
local thread = coxpt[c] or c or 0
if not rawget(self, thread) then
return nil
end
return rawget(self, thread)[key]
end
local function set(self, key, value)
local c = coroutine.running()
local thread = coxpt[c] or c or 0
if not rawget(self, thread) then
rawset(self, thread, {})
end
rawget(self, thread)[key] = value
end
setmetatable(tbl, {__index = get, __newindex = set, __mode = "k"})
return tbl
end
-- Removes whitespace from beginning and end of a string
function trim(str)
local s = str:gsub("^%s*(.-)%s*$", "%1")
return s
end
-- Updates given table with new values
function update(t, updates)
for k, v in pairs(updates) do
t[k] = v
end
end
-- Updates the scope of f with "extscope"
function updfenv(f, extscope)
update(getfenv(f), extscope)
end
-- Parse units from a string and return integer value
function parse_units(ustr)
local val = 0
-- unit map
local map = {
-- date stuff
y = 60 * 60 * 24 * 366,
m = 60 * 60 * 24 * 31,
w = 60 * 60 * 24 * 7,
d = 60 * 60 * 24,
h = 60 * 60,
min = 60,
-- storage sizes
kb = 1024,
mb = 1024 * 1024,
gb = 1024 * 1024 * 1024,
-- storage sizes (si)
kib = 1000,
mib = 1000 * 1000,
gib = 1000 * 1000 * 1000
}
-- parse input string
for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
local num = spec:gsub("[^0-9%.]+$","")
local spn = spec:gsub("^[0-9%.]+", "")
if map[spn] or map[spn:sub(1,1)] then
val = val + num * ( map[spn] or map[spn:sub(1,1)] )
else
val = val + num
end
end
return val
end
-- Provide various sorting iterators
function _sortiter( t, f ) function _sortiter( t, f )
local keys = { } local keys = { }
@ -392,62 +473,80 @@ function _sortiter( t, f )
end end
end end
-- Return key, value pairs sorted by provided callback function --- Return a key, value iterator which returns the values sorted according to
-- the provided callback function.
-- @param t The table to iterate
-- @param f A callback function to decide the order of elements
-- @return Function value containing the corresponding iterator
function spairs(t,f) function spairs(t,f)
return _sortiter( t, f ) return _sortiter( t, f )
end end
-- Return key, value pairs sorted by keys --- Return a key, value iterator for the given table.
-- The table pairs are sorted by key.
-- @param t The table to iterate
-- @return Function value containing the corresponding iterator
function kspairs(t) function kspairs(t)
return _sortiter( t ) return _sortiter( t )
end end
-- Return key, value pairs sorted by values --- Return a key, value iterator for the given table.
-- The table pairs are sorted by value.
-- @param t The table to iterate
-- @return Function value containing the corresponding iterator
function vspairs(t) function vspairs(t)
return _sortiter( t, function (a,b) return t[a] < t[b] end ) return _sortiter( t, function (a,b) return t[a] < t[b] end )
end end
--
-- Coroutine safe xpcall and pcall versions modified for Luci -- Coroutine safe xpcall and pcall versions modified for Luci
-- original version: -- original version:
-- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org) -- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
--
local performResume, handleReturnValue local performResume, handleReturnValue
local oldpcall, oldxpcall = pcall, xpcall local oldpcall, oldxpcall = pcall, xpcall
coxpt = {} coxpt = {}
setmetatable(coxpt, {__mode = "kv"}) setmetatable(coxpt, {__mode = "kv"})
function handleReturnValue(err, co, status, ...) --- (ToDo: docu)
if not status then
return false, err(debug.traceback(co, (...)), ...)
end
if coroutine.status(co) == 'suspended' then
return performResume(err, co, coroutine.yield(...))
else
return true, ...
end
end
function performResume(err, co, ...)
return handleReturnValue(err, co, coroutine.resume(co, ...))
end
function coxpcall(f, err, ...) function coxpcall(f, err, ...)
local res, co = oldpcall(coroutine.create, f) local res, co = oldpcall(coroutine.create, f)
if not res then if not res then
local params = {...} local params = {...}
local newf = function() return f(unpack(params)) end local newf = function() return f(unpack(params)) end
co = coroutine.create(newf) co = coroutine.create(newf)
end end
local c = coroutine.running() local c = coroutine.running()
coxpt[co] = coxpt[c] or c or 0 coxpt[co] = coxpt[c] or c or 0
return performResume(err, co, ...) return performResume(err, co, ...)
end end
--- (ToDo: docu)
function copcall(f, ...)
return coxpcall(f, id, ...)
end
--- (ToDo: docu)
local function id(trace, ...) local function id(trace, ...)
return ... return ...
end end
function copcall(f, ...) --- (ToDo: docu)
return coxpcall(f, id, ...) function handleReturnValue(err, co, status, ...)
if not status then
return false, err(debug.traceback(co, (...)), ...)
end
if coroutine.status(co) == 'suspended' then
return performResume(err, co, coroutine.yield(...))
else
return true, ...
end
end
--- (ToDo: docu)
function performResume(err, co, ...)
return handleReturnValue(err, co, coroutine.resume(co, ...))
end end

View file

@ -256,7 +256,7 @@ function createindex_plain(path, suffix)
index[module] = mod.index index[module] = mod.index
if cache then if cache then
luci.fs.writefile(cachefile, luci.util.dump(mod.index)) luci.fs.writefile(cachefile, luci.util.get_bytecode(mod.index))
end end
end end
else else

View file

@ -180,7 +180,7 @@ function Template.__init__(self, name)
if source then if source then
local compiled, err = compile(source) local compiled, err = compile(source)
luci.fs.writefile(compiledfile, luci.util.dump(compiled)) luci.fs.writefile(compiledfile, luci.util.get_bytecode(compiled))
self.template = compiled self.template = compiled
end end
else else