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