root/luci/trunk/libs/core/luasrc/util.lua @ 3666

Revision 3666, 20.1 KB (checked in by Cyrus, 5 years ago)

luci.util.combine now also accepts single objects

  • Property svn:keywords set to Id
Line 
1--[[
2LuCI - Utility library
3
4Description:
5Several common useful Lua functions
6
7FileId:
8$Id$
9
10License:
11Copyright 2008 Steven Barth <steven@midlink.org>
12
13Licensed under the Apache License, Version 2.0 (the "License");
14you may not use this file except in compliance with the License.
15You may obtain a copy of the License at
16
17    http://www.apache.org/licenses/LICENSE-2.0
18
19Unless required by applicable law or agreed to in writing, software
20distributed under the License is distributed on an "AS IS" BASIS,
21WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22See the License for the specific language governing permissions and
23limitations under the License.
24
25]]--
26
27local io = require "io"
28local math = require "math"
29local table = require "table"
30local debug = require "debug"
31local ldebug = require "luci.debug"
32local string = require "string"
33local coroutine = require "coroutine"
34
35local getmetatable, setmetatable = getmetatable, setmetatable
36local rawget, rawset, unpack = rawget, rawset, unpack
37local tostring, type, assert = tostring, type, assert
38local ipairs, pairs, loadstring = ipairs, pairs, loadstring
39local require, pcall, xpcall = require, pcall, xpcall
40
41--- LuCI utility functions.
42module "luci.util"
43
44--
45-- Pythonic string formatting extension
46--
47getmetatable("").__mod = function(a, b)
48    if not b then
49        return a
50    elseif type(b) == "table" then
51        return a:format(unpack(b))
52    else
53        return a:format(b)
54    end
55end
56
57
58--
59-- Class helper routines
60--
61
62-- Instantiates a class
63local function _instantiate(class, ...)
64    local inst = setmetatable({}, {__index = class})
65
66    if inst.__init__ then
67        inst:__init__(...)
68    end
69
70    return inst
71end
72
73--- Create a Class object (Python-style object model).
74-- The class object can be instantiated by calling itself.
75-- Any class functions or shared parameters can be attached to this object.
76-- Attaching a table to the class object makes this table shared between
77-- all instances of this class. For object parameters use the __init__ function.
78-- Classes can inherit member functions and values from a base class.
79-- Class can be instantiated by calling them. All parameters will be passed
80-- to the __init__ function of this class - if such a function exists.
81-- The __init__ function must be used to set any object parameters that are not shared
82-- with other objects of this class. Any return values will be ignored.
83-- @param base  The base class to inherit from (optional)
84-- @return      A class object
85-- @see         instanceof
86-- @see         clone
87function class(base)
88    return setmetatable({}, {
89        __call  = _instantiate,
90        __index = base
91    })
92end
93
94--- Test whether the given object is an instance of the given class.
95-- @param object    Object instance
96-- @param class     Class object to test against
97-- @return          Boolean indicating whether the object is an instance
98-- @see             class
99-- @see             clone
100function instanceof(object, class)
101    local meta = getmetatable(object)
102    while meta and meta.__index do
103        if meta.__index == class then
104            return true
105        end
106        meta = getmetatable(meta.__index)
107    end
108    return false
109end
110
111
112--
113-- Scope manipulation routines
114--
115
116--- Create a new or get an already existing thread local store associated with
117-- the current active coroutine. A thread local store is private a table object
118-- whose values can't be accessed from outside of the running coroutine.
119-- @return  Table value representing the corresponding thread local store
120function threadlocal()
121    local tbl = {}
122
123    local function get(self, key)
124        local c = coroutine.running()
125        local thread = coxpt[c] or c or 0
126        if not rawget(self, thread) then
127            return nil
128        end
129        return rawget(self, thread)[key]
130    end
131
132    local function set(self, key, value)
133        local c = coroutine.running()
134        local thread = coxpt[c] or c or 0
135        if not rawget(self, thread) then
136            rawset(self, thread, {})
137        end
138        rawget(self, thread)[key] = value
139    end
140
141    setmetatable(tbl, {__index = get, __newindex = set, __mode = "k"})
142
143    return tbl
144end
145
146
147--
148-- Debugging routines
149--
150
151--- Write given object to stderr.
152-- @param obj   Value to write to stderr
153-- @return      Boolean indicating whether the write operation was successful
154function perror(obj)
155    return io.stderr:write(tostring(obj) .. "\n")
156end
157
158--- Recursively dumps a table to stdout, useful for testing and debugging.
159-- @param t Table value to dump
160-- @param maxdepth  Maximum depth
161-- @return  Always nil
162function dumptable(t, maxdepth, i, seen)
163    i = i or 0
164    seen = seen or setmetatable({}, {__mode="k"})
165
166    for k,v in pairs(t) do
167        perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v))
168        if type(v) == "table" and (not maxdepth or i < maxdepth) then
169            if not seen[v] then
170                seen[v] = true
171                dumptable(v, maxdepth, i+1, seen)
172            else
173                perror(string.rep("\t", i) .. "*** RECURSION ***")
174            end
175        end
176    end
177end
178
179
180--
181-- String and data manipulation routines
182--
183
184--- Escapes all occurrences of the given character in given string.
185-- @param s String value containing unescaped characters
186-- @param c String value with character to escape (optional, defaults to "\")
187-- @return  String value with each occurrence of character escaped with "\"
188function escape(s, c)
189    c = c or "\\"
190    return s:gsub(c, "\\" .. c)
191end
192
193--- Create valid XML PCDATA from given string.
194-- @param value String value containing the data to escape
195-- @return      String value containing the escaped data
196function pcdata(value)
197    return value and tostring(value):gsub("[&\"'<>]", {
198        ["&"] = "&#38;",
199        ['"'] = "&#34;",
200        ["'"] = "&#39;",
201        ["<"] = "&#60;",
202        [">"] = "&#62;"
203    })
204end
205
206--- Strip HTML tags from given string.
207-- @param value String containing the HTML text
208-- @return  String with HTML tags stripped of
209function striptags(s)
210    return pcdata(s:gsub("</?[A-Za-z][A-Za-z0-9:_%-]*[^>]*>", " "):gsub("%s+", " "))
211end
212
213--- Splits given string on a defined separator sequence and return a table
214-- containing the resulting substrings. The optional max parameter specifies
215-- the number of bytes to process, regardless of the actual length of the given
216-- string. The optional last parameter, regex, specifies whether the separator
217-- sequence is interpreted as regular expression.
218-- @param str       String value containing the data to split up
219-- @param pat       String with separator pattern (optional, defaults to "\n")
220-- @param max       Maximum times to split (optional)
221-- @param regex     Boolean indicating whether to interpret the separator
222--                  pattern as regular expression (optional, default is false)
223-- @return          Table containing the resulting substrings
224function split(str, pat, max, regex)
225    pat = pat or "\n"
226    max = max or #str
227
228    local t = {}
229    local c = 1
230
231    if #str == 0 then
232        return {""}
233    end
234
235    if #pat == 0 then
236        return nil
237    end
238
239    if max == 0 then
240        return str
241    end
242
243    repeat
244        local s, e = str:find(pat, c, not regex)
245        max = max - 1
246        if s and max < 0 then
247            t[#t+1] = str:sub(c)
248        else
249            t[#t+1] = str:sub(c, s and s - 1)
250        end
251        c = e and e + 1 or #str + 1
252    until not s or max < 0
253
254    return t
255end
256
257--- Remove leading and trailing whitespace from given string value.
258-- @param str   String value containing whitespace padded data
259-- @return      String value with leading and trailing space removed
260function trim(str)
261    return (str:gsub("^%s*(.-)%s*$", "%1"))
262end
263
264--- Parse certain units from the given string and return the canonical integer
265-- value or 0 if the unit is unknown. Upper- or lower case is irrelevant.
266-- Recognized units are:
267--  o "y"   - one year   (60*60*24*366)
268--  o "m"   - one month  (60*60*24*31)
269--  o "w"   - one week   (60*60*24*7)
270--  o "d"   - one day    (60*60*24)
271--  o "h"   - one hour   (60*60)
272--  o "min" - one minute (60)
273--  o "kb"  - one kilobyte (1024)
274--  o "mb"  - one megabyte (1024*1024)
275--  o "gb"  - one gigabyte (1024*1024*1024)
276--  o "kib" - one si kilobyte (1000)
277--  o "mib" - one si megabyte (1000*1000)
278--  o "gib" - one si gigabyte (1000*1000*1000)
279-- @param ustr  String containing a numerical value with trailing unit
280-- @return      Number containing the canonical value
281function parse_units(ustr)
282
283    local val = 0
284
285    -- unit map
286    local map = {
287        -- date stuff
288        y   = 60 * 60 * 24 * 366,
289        m   = 60 * 60 * 24 * 31,
290        w   = 60 * 60 * 24 * 7,
291        d   = 60 * 60 * 24,
292        h   = 60 * 60,
293        min = 60,
294
295        -- storage sizes
296        kb  = 1024,
297        mb  = 1024 * 1024,
298        gb  = 1024 * 1024 * 1024,
299
300        -- storage sizes (si)
301        kib = 1000,
302        mib = 1000 * 1000,
303        gib = 1000 * 1000 * 1000
304    }
305
306    -- parse input string
307    for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
308
309        local num = spec:gsub("[^0-9%.]+$","")
310        local spn = spec:gsub("^[0-9%.]+", "")
311
312        if map[spn] or map[spn:sub(1,1)] then
313            val = val + num * ( map[spn] or map[spn:sub(1,1)] )
314        else
315            val = val + num
316        end
317    end
318
319
320    return val
321end
322
323--- Combines two or more numerically indexed tables and single objects into one table.
324-- @param tbl1  Table value to combine
325-- @param tbl2  Table value to combine
326-- @param ...   More tables to combine
327-- @return      Table value containing all values of given tables
328function combine(...)
329    local result = {}
330    for i, a in ipairs({...}) do
331        if type(a) == "table" then
332            for j, v in ipairs(a) do
333                result[#result+1] = v
334            end
335        else
336            result[#result+1] = a
337        end
338    end
339    return result
340end
341
342--- Checks whether the given table contains the given value.
343-- @param table Table value
344-- @param value Value to search within the given table
345-- @return      Boolean indicating whether the given value occurs within table
346function contains(table, value)
347    for k, v in pairs(table) do
348        if value == v then
349            return k
350        end
351    end
352    return false
353end
354
355--- Update values in given table with the values from the second given table.
356-- Both table are - in fact - merged together.
357-- @param t         Table which should be updated
358-- @param updates   Table containing the values to update
359-- @return          Always nil
360function update(t, updates)
361    for k, v in pairs(updates) do
362        t[k] = v
363    end
364end
365
366--- Retrieve all keys of given associative table.
367-- @param t Table to extract keys from
368-- @return  Sorted table containing the keys
369function keys(t)
370    local keys = { }
371    if t then
372        for k, _ in kspairs(t) do
373            keys[#keys+1] = k
374        end
375    end
376    return keys
377end
378
379--- Clones the given object and return it's copy.
380-- @param object    Table value to clone
381-- @param deep      Boolean indicating whether to do recursive cloning
382-- @return          Cloned table value
383function clone(object, deep)
384    local copy = {}
385
386    for k, v in pairs(object) do
387        if deep and type(v) == "table" then
388            v = clone(v, deep)
389        end
390        copy[k] = v
391    end
392
393    return setmetatable(copy, getmetatable(object))
394end
395
396
397--- Create a dynamic table which automatically creates subtables.
398-- @return  Dynamic Table
399function dtable()
400        return setmetatable({}, { __index =
401                function(tbl, key)
402                        return rawget(tbl, key)
403                         or rawget(rawset(tbl, key, dtable()), key)
404                end
405        })
406end
407
408
409-- Serialize the contents of a table value.
410function _serialize_table(t, seen)
411    assert(not seen[t], "Recursion detected.")
412    seen[t] = true
413
414    local data  = ""
415    local idata = ""
416    local ilen  = 0
417
418    for k, v in pairs(t) do
419        if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then
420            k = serialize_data(k, seen)
421            v = serialize_data(v, seen)
422            data = data .. ( #data > 0 and ", " or "" ) ..
423                '[' .. k .. '] = ' .. v
424        elseif k > ilen then
425            ilen = k
426        end
427    end
428
429    for i = 1, ilen do
430        local v = serialize_data(t[i], seen)
431        idata = idata .. ( #idata > 0 and ", " or "" ) .. v
432    end
433
434    return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data
435end
436
437--- Recursively serialize given data to lua code, suitable for restoring
438-- with loadstring().
439-- @param val   Value containing the data to serialize
440-- @return      String value containing the serialized code
441-- @see         restore_data
442-- @see         get_bytecode
443function serialize_data(val, seen)
444    seen = seen or setmetatable({}, {__mode="k"})
445
446    if val == nil then
447        return "nil"
448    elseif type(val) == "number" then
449        return val
450    elseif type(val) == "string" then
451        return "%q" % val
452    elseif type(val) == "boolean" then
453        return val and "true" or "false"
454    elseif type(val) == "function" then
455        return "loadstring(%q)" % get_bytecode(val)
456    elseif type(val) == "table" then
457        return "{ " .. _serialize_table(val, seen) .. " }"
458    else
459        return '"[unhandled data type:' .. type(val) .. ']"'
460    end
461end
462
463--- Restore data previously serialized with serialize_data().
464-- @param str   String containing the data to restore
465-- @return      Value containing the restored data structure
466-- @see         serialize_data
467-- @see         get_bytecode
468function restore_data(str)
469    return loadstring("return " .. str)()
470end
471
472
473--
474-- Byte code manipulation routines
475--
476
477--- Return the current runtime bytecode of the given data. The byte code
478-- will be stripped before it is returned.
479-- @param val   Value to return as bytecode
480-- @return      String value containing the bytecode of the given data
481function get_bytecode(val)
482    local code
483
484    if type(val) == "function" then
485        code = string.dump(val)
486    else
487        code = string.dump( loadstring( "return " .. serialize_data(val) ) )
488    end
489
490    return code and strip_bytecode(code)
491end
492
493--- Strips unnescessary lua bytecode from given string. Information like line
494-- numbers and debugging numbers will be discarded. Original version by
495-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
496-- @param code  String value containing the original lua byte code
497-- @return      String value containing the stripped lua byte code
498function strip_bytecode(code)
499    local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
500    local subint
501    if endian == 1 then
502        subint = function(code, i, l)
503            local val = 0
504            for n = l, 1, -1 do
505                val = val * 256 + code:byte(i + n - 1)
506            end
507            return val, i + l
508        end
509    else
510        subint = function(code, i, l)
511            local val = 0
512            for n = 1, l, 1 do
513                val = val * 256 + code:byte(i + n - 1)
514            end
515            return val, i + l
516        end
517    end
518
519    local strip_function
520    strip_function = function(code)
521        local count, offset = subint(code, 1, size)
522        local stripped, dirty = string.rep("\0", size), offset + count
523        offset = offset + count + int * 2 + 4
524        offset = offset + int + subint(code, offset, int) * ins
525        count, offset = subint(code, offset, int)
526        for n = 1, count do
527            local t
528            t, offset = subint(code, offset, 1)
529            if t == 1 then
530                offset = offset + 1
531            elseif t == 4 then
532                offset = offset + size + subint(code, offset, size)
533            elseif t == 3 then
534                offset = offset + num
535            elseif t == 254 or t == 9 then
536                offset = offset + lnum
537            end
538        end
539        count, offset = subint(code, offset, int)
540        stripped = stripped .. code:sub(dirty, offset - 1)
541        for n = 1, count do
542            local proto, off = strip_function(code:sub(offset, -1))
543            stripped, offset = stripped .. proto, offset + off - 1
544        end
545        offset = offset + subint(code, offset, int) * int + int
546        count, offset = subint(code, offset, int)
547        for n = 1, count do
548            offset = offset + subint(code, offset, size) + size + int * 2
549        end
550        count, offset = subint(code, offset, int)
551        for n = 1, count do
552            offset = offset + subint(code, offset, size) + size
553        end
554        stripped = stripped .. string.rep("\0", int * 3)
555        return stripped, offset
556    end
557
558    return code:sub(1,12) .. strip_function(code:sub(13,-1))
559end
560
561
562--
563-- Sorting iterator functions
564--
565
566function _sortiter( t, f )
567    local keys = { }
568
569    for k, v in pairs(t) do
570        keys[#keys+1] = k
571    end
572
573    local _pos = 0
574
575    table.sort( keys, f )
576
577    return function()
578        _pos = _pos + 1
579        if _pos <= #keys then
580            return keys[_pos], t[keys[_pos]]
581        end
582    end
583end
584
585--- Return a key, value iterator which returns the values sorted according to
586-- the provided callback function.
587-- @param t The table to iterate
588-- @param f A callback function to decide the order of elements
589-- @return  Function value containing the corresponding iterator
590function spairs(t,f)
591    return _sortiter( t, f )
592end
593
594--- Return a key, value iterator for the given table.
595-- The table pairs are sorted by key.
596-- @param t The table to iterate
597-- @return  Function value containing the corresponding iterator
598function kspairs(t)
599    return _sortiter( t )
600end
601
602--- Return a key, value iterator for the given table.
603-- The table pairs are sorted by value.
604-- @param t The table to iterate
605-- @return  Function value containing the corresponding iterator
606function vspairs(t)
607    return _sortiter( t, function (a,b) return t[a] < t[b] end )
608end
609
610
611--
612-- System utility functions
613--
614
615--- Test whether the current system is operating in big endian mode.
616-- @return  Boolean value indicating whether system is big endian
617function bigendian()
618    return string.byte(string.dump(function() end), 7) == 0
619end
620
621--- Execute given commandline and gather stdout.
622-- @param command   String containing command to execute
623-- @return          String containing the command's stdout
624function exec(command)
625    local pp   = io.popen(command)
626    local data = pp:read("*a")
627    pp:close()
628
629    return data
630end
631
632--- Return a line-buffered iterator over the output of given command.
633-- @param command   String containing the command to execute
634-- @return          Iterator
635function execi(command)
636    local pp = io.popen(command)
637
638    return pp and function()
639        local line = pp:read()
640
641        if not line then
642            pp:close()
643        end
644
645        return line
646    end
647end
648
649-- Deprecated
650function execl(command)
651    local pp   = io.popen(command)
652    local line = ""
653    local data = {}
654
655    while true do
656        line = pp:read()
657        if (line == nil) then break end
658        data[#data+1] = line
659    end
660    pp:close()
661
662    return data
663end
664
665--- Returns the absolute path to LuCI base directory.
666-- @return      String containing the directory path
667function libpath()
668    return require "luci.fs".dirname(ldebug.__file__)
669end
670
671
672--
673-- Coroutine safe xpcall and pcall versions modified for Luci
674-- original version:
675-- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
676--
677-- Copyright © 2005 Kepler Project.
678-- Permission is hereby granted, free of charge, to any person obtaining a
679-- copy of this software and associated documentation files (the "Software"),
680-- to deal in the Software without restriction, including without limitation
681-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
682-- and/or sell copies of the Software, and to permit persons to whom the
683-- Software is furnished to do so, subject to the following conditions:
684--
685-- The above copyright notice and this permission notice shall be
686-- included in all copies or substantial portions of the Software.
687--
688-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
689-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
690-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
691-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
692-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
693-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
694-- OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
695
696local performResume, handleReturnValue
697local oldpcall, oldxpcall = pcall, xpcall
698coxpt = {}
699setmetatable(coxpt, {__mode = "kv"})
700
701-- Identity function for copcall
702local function copcall_id(trace, ...)
703  return ...
704end
705
706--- This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function
707-- @param f     Lua function to be called protected
708-- @param err   Custom error handler
709-- @param ...   Parameters passed to the function
710-- @return      A boolean whether the function call succeeded and the return
711--              values of either the function or the error handler
712function coxpcall(f, err, ...)
713    local res, co = oldpcall(coroutine.create, f)
714    if not res then
715        local params = {...}
716        local newf = function() return f(unpack(params)) end
717        co = coroutine.create(newf)
718    end
719    local c = coroutine.running()
720    coxpt[co] = coxpt[c] or c or 0
721
722    return performResume(err, co, ...)
723end
724
725--- This is a coroutine-safe drop-in replacement for Lua's "pcall"-function
726-- @param f     Lua function to be called protected
727-- @param ...   Parameters passed to the function
728-- @return      A boolean whether the function call succeeded and the returns
729--              values of the function or the error object
730function copcall(f, ...)
731    return coxpcall(f, copcall_id, ...)
732end
733
734-- Handle return value of protected call
735function handleReturnValue(err, co, status, ...)
736    if not status then
737        return false, err(debug.traceback(co, (...)), ...)
738    end
739    if coroutine.status(co) == 'suspended' then
740        return performResume(err, co, coroutine.yield(...))
741    else
742        return true, ...
743    end
744end
745
746-- Resume execution of protected function call
747function performResume(err, co, ...)
748    return handleReturnValue(err, co, coroutine.resume(co, ...))
749end
Note: See TracBrowser for help on using the browser.