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