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

Revision 3667, 20.2 KB (checked in by Cyrus, 5 years ago)

Add luci.util.append

  • 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--- Appends numerically indexed tables or single objects to a given table.
324-- @param src   Target table
325-- @param ...   Objects to insert
326-- @return      Target table
327function append(src, ...)
328    for i, a in ipairs({...}) do
329        if type(a) == "table" then
330            for j, v in ipairs(a) do
331                src[#src+1] = v
332            end
333        else
334            src[#src+1] = a
335        end
336    end
337    return src
338end
339
340--- Combines two or more numerically indexed tables and single objects into one table.
341-- @param tbl1  Table value to combine
342-- @param tbl2  Table value to combine
343-- @param ...   More tables to combine
344-- @return      Table value containing all values of given tables
345function combine(...)
346    return append({}, ...)
347end
348
349--- Checks whether the given table contains the given value.
350-- @param table Table value
351-- @param value Value to search within the given table
352-- @return      Boolean indicating whether the given value occurs within table
353function contains(table, value)
354    for k, v in pairs(table) do
355        if value == v then
356            return k
357        end
358    end
359    return false
360end
361
362--- Update values in given table with the values from the second given table.
363-- Both table are - in fact - merged together.
364-- @param t         Table which should be updated
365-- @param updates   Table containing the values to update
366-- @return          Always nil
367function update(t, updates)
368    for k, v in pairs(updates) do
369        t[k] = v
370    end
371end
372
373--- Retrieve all keys of given associative table.
374-- @param t Table to extract keys from
375-- @return  Sorted table containing the keys
376function keys(t)
377    local keys = { }
378    if t then
379        for k, _ in kspairs(t) do
380            keys[#keys+1] = k
381        end
382    end
383    return keys
384end
385
386--- Clones the given object and return it's copy.
387-- @param object    Table value to clone
388-- @param deep      Boolean indicating whether to do recursive cloning
389-- @return          Cloned table value
390function clone(object, deep)
391    local copy = {}
392
393    for k, v in pairs(object) do
394        if deep and type(v) == "table" then
395            v = clone(v, deep)
396        end
397        copy[k] = v
398    end
399
400    return setmetatable(copy, getmetatable(object))
401end
402
403
404--- Create a dynamic table which automatically creates subtables.
405-- @return  Dynamic Table
406function dtable()
407        return setmetatable({}, { __index =
408                function(tbl, key)
409                        return rawget(tbl, key)
410                         or rawget(rawset(tbl, key, dtable()), key)
411                end
412        })
413end
414
415
416-- Serialize the contents of a table value.
417function _serialize_table(t, seen)
418    assert(not seen[t], "Recursion detected.")
419    seen[t] = true
420
421    local data  = ""
422    local idata = ""
423    local ilen  = 0
424
425    for k, v in pairs(t) do
426        if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then
427            k = serialize_data(k, seen)
428            v = serialize_data(v, seen)
429            data = data .. ( #data > 0 and ", " or "" ) ..
430                '[' .. k .. '] = ' .. v
431        elseif k > ilen then
432            ilen = k
433        end
434    end
435
436    for i = 1, ilen do
437        local v = serialize_data(t[i], seen)
438        idata = idata .. ( #idata > 0 and ", " or "" ) .. v
439    end
440
441    return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data
442end
443
444--- Recursively serialize given data to lua code, suitable for restoring
445-- with loadstring().
446-- @param val   Value containing the data to serialize
447-- @return      String value containing the serialized code
448-- @see         restore_data
449-- @see         get_bytecode
450function serialize_data(val, seen)
451    seen = seen or setmetatable({}, {__mode="k"})
452
453    if val == nil then
454        return "nil"
455    elseif type(val) == "number" then
456        return val
457    elseif type(val) == "string" then
458        return "%q" % val
459    elseif type(val) == "boolean" then
460        return val and "true" or "false"
461    elseif type(val) == "function" then
462        return "loadstring(%q)" % get_bytecode(val)
463    elseif type(val) == "table" then
464        return "{ " .. _serialize_table(val, seen) .. " }"
465    else
466        return '"[unhandled data type:' .. type(val) .. ']"'
467    end
468end
469
470--- Restore data previously serialized with serialize_data().
471-- @param str   String containing the data to restore
472-- @return      Value containing the restored data structure
473-- @see         serialize_data
474-- @see         get_bytecode
475function restore_data(str)
476    return loadstring("return " .. str)()
477end
478
479
480--
481-- Byte code manipulation routines
482--
483
484--- Return the current runtime bytecode of the given data. The byte code
485-- will be stripped before it is returned.
486-- @param val   Value to return as bytecode
487-- @return      String value containing the bytecode of the given data
488function get_bytecode(val)
489    local code
490
491    if type(val) == "function" then
492        code = string.dump(val)
493    else
494        code = string.dump( loadstring( "return " .. serialize_data(val) ) )
495    end
496
497    return code and strip_bytecode(code)
498end
499
500--- Strips unnescessary lua bytecode from given string. Information like line
501-- numbers and debugging numbers will be discarded. Original version by
502-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
503-- @param code  String value containing the original lua byte code
504-- @return      String value containing the stripped lua byte code
505function strip_bytecode(code)
506    local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
507    local subint
508    if endian == 1 then
509        subint = function(code, i, l)
510            local val = 0
511            for n = l, 1, -1 do
512                val = val * 256 + code:byte(i + n - 1)
513            end
514            return val, i + l
515        end
516    else
517        subint = function(code, i, l)
518            local val = 0
519            for n = 1, l, 1 do
520                val = val * 256 + code:byte(i + n - 1)
521            end
522            return val, i + l
523        end
524    end
525
526    local strip_function
527    strip_function = function(code)
528        local count, offset = subint(code, 1, size)
529        local stripped, dirty = string.rep("\0", size), offset + count
530        offset = offset + count + int * 2 + 4
531        offset = offset + int + subint(code, offset, int) * ins
532        count, offset = subint(code, offset, int)
533        for n = 1, count do
534            local t
535            t, offset = subint(code, offset, 1)
536            if t == 1 then
537                offset = offset + 1
538            elseif t == 4 then
539                offset = offset + size + subint(code, offset, size)
540            elseif t == 3 then
541                offset = offset + num
542            elseif t == 254 or t == 9 then
543                offset = offset + lnum
544            end
545        end
546        count, offset = subint(code, offset, int)
547        stripped = stripped .. code:sub(dirty, offset - 1)
548        for n = 1, count do
549            local proto, off = strip_function(code:sub(offset, -1))
550            stripped, offset = stripped .. proto, offset + off - 1
551        end
552        offset = offset + subint(code, offset, int) * int + int
553        count, offset = subint(code, offset, int)
554        for n = 1, count do
555            offset = offset + subint(code, offset, size) + size + int * 2
556        end
557        count, offset = subint(code, offset, int)
558        for n = 1, count do
559            offset = offset + subint(code, offset, size) + size
560        end
561        stripped = stripped .. string.rep("\0", int * 3)
562        return stripped, offset
563    end
564
565    return code:sub(1,12) .. strip_function(code:sub(13,-1))
566end
567
568
569--
570-- Sorting iterator functions
571--
572
573function _sortiter( t, f )
574    local keys = { }
575
576    for k, v in pairs(t) do
577        keys[#keys+1] = k
578    end
579
580    local _pos = 0
581
582    table.sort( keys, f )
583
584    return function()
585        _pos = _pos + 1
586        if _pos <= #keys then
587            return keys[_pos], t[keys[_pos]]
588        end
589    end
590end
591
592--- Return a key, value iterator which returns the values sorted according to
593-- the provided callback function.
594-- @param t The table to iterate
595-- @param f A callback function to decide the order of elements
596-- @return  Function value containing the corresponding iterator
597function spairs(t,f)
598    return _sortiter( t, f )
599end
600
601--- Return a key, value iterator for the given table.
602-- The table pairs are sorted by key.
603-- @param t The table to iterate
604-- @return  Function value containing the corresponding iterator
605function kspairs(t)
606    return _sortiter( t )
607end
608
609--- Return a key, value iterator for the given table.
610-- The table pairs are sorted by value.
611-- @param t The table to iterate
612-- @return  Function value containing the corresponding iterator
613function vspairs(t)
614    return _sortiter( t, function (a,b) return t[a] < t[b] end )
615end
616
617
618--
619-- System utility functions
620--
621
622--- Test whether the current system is operating in big endian mode.
623-- @return  Boolean value indicating whether system is big endian
624function bigendian()
625    return string.byte(string.dump(function() end), 7) == 0
626end
627
628--- Execute given commandline and gather stdout.
629-- @param command   String containing command to execute
630-- @return          String containing the command's stdout
631function exec(command)
632    local pp   = io.popen(command)
633    local data = pp:read("*a")
634    pp:close()
635
636    return data
637end
638
639--- Return a line-buffered iterator over the output of given command.
640-- @param command   String containing the command to execute
641-- @return          Iterator
642function execi(command)
643    local pp = io.popen(command)
644
645    return pp and function()
646        local line = pp:read()
647
648        if not line then
649            pp:close()
650        end
651
652        return line
653    end
654end
655
656-- Deprecated
657function execl(command)
658    local pp   = io.popen(command)
659    local line = ""
660    local data = {}
661
662    while true do
663        line = pp:read()
664        if (line == nil) then break end
665        data[#data+1] = line
666    end
667    pp:close()
668
669    return data
670end
671
672--- Returns the absolute path to LuCI base directory.
673-- @return      String containing the directory path
674function libpath()
675    return require "luci.fs".dirname(ldebug.__file__)
676end
677
678
679--
680-- Coroutine safe xpcall and pcall versions modified for Luci
681-- original version:
682-- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
683--
684-- Copyright © 2005 Kepler Project.
685-- Permission is hereby granted, free of charge, to any person obtaining a
686-- copy of this software and associated documentation files (the "Software"),
687-- to deal in the Software without restriction, including without limitation
688-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
689-- and/or sell copies of the Software, and to permit persons to whom the
690-- Software is furnished to do so, subject to the following conditions:
691--
692-- The above copyright notice and this permission notice shall be
693-- included in all copies or substantial portions of the Software.
694--
695-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
696-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
697-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
698-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
699-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
700-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
701-- OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
702
703local performResume, handleReturnValue
704local oldpcall, oldxpcall = pcall, xpcall
705coxpt = {}
706setmetatable(coxpt, {__mode = "kv"})
707
708-- Identity function for copcall
709local function copcall_id(trace, ...)
710  return ...
711end
712
713--- This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function
714-- @param f     Lua function to be called protected
715-- @param err   Custom error handler
716-- @param ...   Parameters passed to the function
717-- @return      A boolean whether the function call succeeded and the return
718--              values of either the function or the error handler
719function coxpcall(f, err, ...)
720    local res, co = oldpcall(coroutine.create, f)
721    if not res then
722        local params = {...}
723        local newf = function() return f(unpack(params)) end
724        co = coroutine.create(newf)
725    end
726    local c = coroutine.running()
727    coxpt[co] = coxpt[c] or c or 0
728
729    return performResume(err, co, ...)
730end
731
732--- This is a coroutine-safe drop-in replacement for Lua's "pcall"-function
733-- @param f     Lua function to be called protected
734-- @param ...   Parameters passed to the function
735-- @return      A boolean whether the function call succeeded and the returns
736--              values of the function or the error object
737function copcall(f, ...)
738    return coxpcall(f, copcall_id, ...)
739end
740
741-- Handle return value of protected call
742function handleReturnValue(err, co, status, ...)
743    if not status then
744        return false, err(debug.traceback(co, (...)), ...)
745    end
746    if coroutine.status(co) == 'suspended' then
747        return performResume(err, co, coroutine.yield(...))
748    else
749        return true, ...
750    end
751end
752
753-- Resume execution of protected function call
754function performResume(err, co, ...)
755    return handleReturnValue(err, co, coroutine.resume(co, ...))
756end
Note: See TracBrowser for help on using the browser.