libs/core: Add in-line documentation for luci.util.class
[project/luci.git] / libs / core / luasrc / util.lua
1 --[[
2 LuCI - Utility library
3
4 Description:
5 Several common useful Lua functions
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at 
16
17         http://www.apache.org/licenses/LICENSE-2.0 
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27 module("luci.util", package.seeall)
28
29
30 --- Creates a Class object (Python-style object model)
31 -- Creates a new class object which can be instantiated by calling itself.
32 -- Any class functions or shared parameters can be attached to this object.
33 -- Attaching a table to the class object makes this table shared between
34 -- all instances of this class. For object paramters use the __init__ function.
35 -- Classes can inherit member functions and values from a base class.
36 -- Class can be instantiated by calling them. All parameters will be passed
37 -- to the __init__ function of this class - if such a function exists.
38 -- The __init__ function must be used to set any object parameters that are not shared
39 -- with other objects of this class. Any return values will be ignored.
40 -- @see instanceof
41 -- @see clone
42 -- @param base the base class to inherit from (optional)
43 -- @return class object
44 function class(base)
45         local class = {}
46         
47         local create = function(class, ...)
48                 local inst = {}
49                 setmetatable(inst, {__index = class})
50                 
51                 if inst.__init__ then
52                         local stat, err = copcall(inst.__init__, inst, ...)
53                         if not stat then
54                                 error(err)
55                         end
56                 end
57                 
58                 return inst
59         end
60         
61         local classmeta = {__call = create}
62         
63         if base then
64                 classmeta.__index = base
65         end
66         
67         setmetatable(class, classmeta)
68         return class
69 end
70
71
72 -- Clones an object (deep on-demand)
73 function clone(object, deep)
74         local copy = {}
75         
76         for k, v in pairs(object) do
77                 if deep and type(v) == "table" then
78                         v = clone(v, deep)
79                 end
80                 copy[k] = v
81         end
82         
83         setmetatable(copy, getmetatable(object))
84         
85         return copy
86 end
87
88
89 -- Combines two or more numerically indexed tables into one
90 function combine(...)
91         local result = {}
92         for i, a in ipairs(arg) do
93                 for j, v in ipairs(a) do
94                         table.insert(result, v)
95                 end
96         end
97         return result
98 end
99
100
101 -- Checks whether a table has an object "value" in it
102 function contains(table, value)
103         for k, v in pairs(table) do
104                 if value == v then
105                         return k
106                 end
107         end
108         return false
109 end
110
111
112 -- Dumps and strips a Lua-Function
113 function dump(f)
114         local d = string.dump(f)
115         return d and strip_bytecode(d)
116 end
117
118
119 -- Dumps a table to stdout (useful for testing and debugging)
120 function dumptable(t, i)
121         i = i or 0
122         for k,v in pairs(t) do
123                 print(string.rep("\t", i) .. tostring(k), tostring(v))
124                 if type(v) == "table" then
125                         dumptable(v, i+1)
126                 end
127         end
128 end
129
130
131 -- Escapes all occurences of c in s
132 function escape(s, c)
133         c = c or "\\"
134         return s:gsub(c, "\\" .. c)
135 end
136
137
138 -- Populate obj in the scope of f as key 
139 function extfenv(f, key, obj)
140         local scope = getfenv(f)
141         scope[key] = obj
142 end
143
144
145 -- Checks whether an object is an instanceof class
146 function instanceof(object, class)
147         local meta = getmetatable(object)
148     while meta and meta.__index do 
149         if meta.__index == class then
150                 return true
151         end
152         meta = getmetatable(meta.__index)
153     end
154     return false        
155 end
156
157
158 -- Creates valid XML PCDATA from a string
159 function pcdata(value)
160         value = value:gsub("&", "&amp;")        
161         value = value:gsub('"', "&quot;")
162         value = value:gsub("'", "&apos;")
163         value = value:gsub("<", "&lt;") 
164         return value:gsub(">", "&gt;")
165 end
166
167
168 -- Returns an error message to stdout
169 function perror(obj)
170         io.stderr:write(tostring(obj) .. "\n")
171 end
172
173
174 -- Resets the scope of f doing a shallow copy of its scope into a new table
175 function resfenv(f)
176         setfenv(f, clone(getfenv(f)))
177 end 
178
179
180 -- Splits a string into an array
181 function split(str, pat, max, regex)
182         pat = pat or "\n"
183         max = max or #str
184         
185         local t = {}
186         local c = 1
187         
188         if #str == 0 then
189                 return {""}
190         end
191         
192         if #pat == 0 then
193                 return nil
194         end
195         
196         if max == 0 then
197                 return str
198         end
199         
200         repeat
201                 local s, e = str:find(pat, c, not regex)
202                 max = max - 1
203                 if s and max < 0 then
204                         table.insert(t, str:sub(c))
205                 else
206                         table.insert(t, str:sub(c, s and s - 1))
207                 end
208                 c = e and e + 1 or #str + 1
209         until not s or max < 0
210         
211         return t
212 end
213
214
215 -- Strips lua bytecode
216 -- Original version by Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
217 function strip_bytecode(dump)
218         local version, format, endian, int, size, ins, num, lnum = dump:byte(5, 12)
219         local subint
220         if endian == 1 then
221                 subint = function(dump, i, l)
222                         local val = 0
223                         for n = l, 1, -1 do
224                                 val = val * 256 + dump:byte(i + n - 1)
225                         end
226                         return val, i + l
227                 end
228         else
229                 subint = function(dump, i, l)
230                         local val = 0
231                         for n = 1, l, 1 do
232                                 val = val * 256 + dump:byte(i + n - 1)
233                         end
234                         return val, i + l
235                 end
236         end
237     
238         local strip_function
239         strip_function = function(dump)
240                 local count, offset = subint(dump, 1, size)
241                 local stripped, dirty = string.rep("\0", size), offset + count
242                 offset = offset + count + int * 2 + 4
243                 offset = offset + int + subint(dump, offset, int) * ins
244                 count, offset = subint(dump, offset, int)
245                 for n = 1, count do
246                         local t
247                         t, offset = subint(dump, offset, 1)
248                         if t == 1 then
249                                 offset = offset + 1
250                         elseif t == 4 then
251                                 offset = offset + size + subint(dump, offset, size)
252                         elseif t == 3 then
253                                 offset = offset + num
254                         elseif t == 254 or t == 9 then
255                                 offset = offset + lnum
256                         end
257                 end
258                 count, offset = subint(dump, offset, int)
259                 stripped = stripped .. dump:sub(dirty, offset - 1)
260                 for n = 1, count do
261                         local proto, off = strip_function(dump:sub(offset, -1))
262                         stripped, offset = stripped .. proto, offset + off - 1
263                 end
264                 offset = offset + subint(dump, offset, int) * int + int
265                 count, offset = subint(dump, offset, int)
266                 for n = 1, count do
267                         offset = offset + subint(dump, offset, size) + size + int * 2
268                 end
269                 count, offset = subint(dump, offset, int)
270                 for n = 1, count do
271                         offset = offset + subint(dump, offset, size) + size
272                 end
273                 stripped = stripped .. string.rep("\0", int * 3)
274                 return stripped, offset
275         end
276         
277         return dump:sub(1,12) .. strip_function(dump:sub(13,-1))
278 end
279
280
281 -- Creates a new threadlocal store
282 function threadlocal()
283         local tbl = {}
284         
285         local function get(self, key)
286                 local c = coroutine.running()
287                 local thread = coxpt[c] or c or 0
288                 if not rawget(self, thread) then
289                         return nil
290                 end
291                 return rawget(self, thread)[key]
292         end
293                 
294         local function set(self, key, value)
295                 local c = coroutine.running()
296                 local thread = coxpt[c] or c or 0
297                 if not rawget(self, thread) then
298                         rawset(self, thread, {})
299                 end
300                 rawget(self, thread)[key] = value
301         end
302         
303         setmetatable(tbl, {__index = get, __newindex = set, __mode = "k"})
304         
305         return tbl
306 end
307
308
309 -- Removes whitespace from beginning and end of a string
310 function trim(str)
311         local s = str:gsub("^%s*(.-)%s*$", "%1")
312         return s
313 end
314
315
316 -- Updates given table with new values
317 function update(t, updates)
318         for k, v in pairs(updates) do
319                 t[k] = v
320         end     
321 end
322
323
324 -- Updates the scope of f with "extscope"
325 function updfenv(f, extscope)
326         update(getfenv(f), extscope)
327 end
328
329
330 -- Parse units from a string and return integer value
331 function parse_units(ustr)
332
333         local val = 0
334
335         -- unit map
336         local map = {
337                 -- date stuff
338                 y   = 60 * 60 * 24 * 366,
339                 m   = 60 * 60 * 24 * 31,
340                 w   = 60 * 60 * 24 * 7,
341                 d   = 60 * 60 * 24,
342                 h   = 60 * 60,
343                 min = 60,
344
345                 -- storage sizes
346                 kb  = 1024,
347                 mb  = 1024 * 1024,
348                 gb  = 1024 * 1024 * 1024,
349
350                 -- storage sizes (si)
351                 kib = 1000,
352                 mib = 1000 * 1000,
353                 gib = 1000 * 1000 * 1000
354         }
355
356         -- parse input string
357         for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
358
359                 local num = spec:gsub("[^0-9%.]+$","")
360                 local spn = spec:gsub("^[0-9%.]+", "")
361
362                 if map[spn] or map[spn:sub(1,1)] then
363                         val = val + num * ( map[spn] or map[spn:sub(1,1)] )
364                 else
365                         val = val + num
366                 end
367         end
368
369
370         return val
371 end
372
373
374 -- Provide various sorting iterators
375 function _sortiter( t, f )
376         local keys = { }
377
378         for k, v in pairs(t) do
379                 table.insert( keys, k )
380         end
381
382         local _pos = 0
383         local _len = table.getn( keys )
384
385         table.sort( keys, f )
386
387         return function()
388                 _pos = _pos + 1
389                 if _pos <= _len then
390                         return keys[_pos], t[keys[_pos]]
391                 end
392         end
393 end
394
395 -- Return key, value pairs sorted by provided callback function
396 function spairs(t,f)
397         return _sortiter( t, f )                                        
398 end
399
400 -- Return key, value pairs sorted by keys
401 function kspairs(t)
402         return _sortiter( t )
403 end
404
405 -- Return key, value pairs sorted by values
406 function vspairs(t)
407         return _sortiter( t, function (a,b) return t[a] < t[b] end )
408 end
409
410
411 -- Coroutine safe xpcall and pcall versions modified for Luci
412 -- original version:
413 -- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
414 local performResume, handleReturnValue
415 local oldpcall, oldxpcall = pcall, xpcall
416 coxpt = {}
417 setmetatable(coxpt, {__mode = "kv"})
418
419 function handleReturnValue(err, co, status, ...)
420     if not status then
421         return false, err(debug.traceback(co, (...)), ...)
422     end
423     if coroutine.status(co) == 'suspended' then
424         return performResume(err, co, coroutine.yield(...))
425     else
426         return true, ...
427     end
428 end
429
430 function performResume(err, co, ...)
431     return handleReturnValue(err, co, coroutine.resume(co, ...))
432 end    
433
434 function coxpcall(f, err, ...)
435     local res, co = oldpcall(coroutine.create, f)
436     if not res then
437         local params = {...}
438         local newf = function() return f(unpack(params)) end
439         co = coroutine.create(newf)
440     end
441     local c = coroutine.running()
442     coxpt[co] = coxpt[c] or c or 0
443     
444     return performResume(err, co, ...)
445 end
446
447 local function id(trace, ...)
448   return ...
449 end
450
451 function copcall(f, ...)
452     return coxpcall(f, id, ...)
453 end