mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 19:13:49 +01:00
Add simple unit test framework and various other improvements/changes:
* Move logger.lua; * update quotation style in several files; * add proposed configuration key layout to conf_defaults.lua; * move dump function to utils.lua; * implement (very) basic unit testing environment; * fix bugs in various functions in utils.lua; * checkin of preliminary REST API test code.
This commit is contained in:
parent
3726063b99
commit
9a8ac55b8f
34
src/conf_defaults.lua
Normal file
34
src/conf_defaults.lua
Normal file
@ -0,0 +1,34 @@
|
||||
local M = {}
|
||||
|
||||
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
||||
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
||||
M.DEBUG_PCALLS = true
|
||||
|
||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||
|
||||
M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
||||
M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
||||
M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
||||
|
||||
|
||||
--NOTE: proposed notation for baseline configuration (containing defaults as well as type and constraint information)
|
||||
--the table name is the configuration key; min, max and regex are all optional; type is one of: {int, float, string, ...?}
|
||||
M.temperature = {
|
||||
default = 230,
|
||||
type = 'int',
|
||||
description = '...xyzzy',
|
||||
min = 0,
|
||||
max = 350
|
||||
}
|
||||
|
||||
M.ssid = {
|
||||
default = 'd3d-ap-%%MAC_TAIL%%',
|
||||
type = 'int', --one of: {int, float, string, ...?}
|
||||
min = 1,
|
||||
max = 32,
|
||||
regex = '[a-zA-Z0-9 -=+]+'
|
||||
}
|
||||
|
||||
|
||||
return M
|
@ -1,14 +0,0 @@
|
||||
local M = {}
|
||||
|
||||
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
||||
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
||||
M.DEBUG_PCALLS = true
|
||||
|
||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||
|
||||
M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
||||
M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
||||
M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
||||
|
||||
return M
|
25
src/main.lua
25
src/main.lua
@ -1,11 +1,12 @@
|
||||
package.path = package.path .. ';/usr/share/lua/wifibox/?.lua'
|
||||
|
||||
local l = require("logger")
|
||||
local RequestClass = require("rest.request")
|
||||
local ResponseClass = require("rest.response")
|
||||
local wifi = require("network.wlanconfig")
|
||||
local netconf = require("network.netconfig")
|
||||
local config = require("config")
|
||||
local config = require('config')
|
||||
local u = require('util.utils')
|
||||
local l = require('util.logger')
|
||||
local wifi = require('network.wlanconfig')
|
||||
local netconf = require('network.netconfig')
|
||||
local RequestClass = require('rest.request')
|
||||
local ResponseClass = require('rest.response')
|
||||
|
||||
local postData = nil
|
||||
|
||||
@ -22,8 +23,8 @@ local function init()
|
||||
else l:info("Wifibox CGI handler started")
|
||||
end
|
||||
|
||||
if (os.getenv("REQUEST_METHOD") == "POST") then
|
||||
local n = tonumber(os.getenv("CONTENT_LENGTH"))
|
||||
if (os.getenv('REQUEST_METHOD') == 'POST') then
|
||||
local n = tonumber(os.getenv('CONTENT_LENGTH'))
|
||||
postData = io.read(n)
|
||||
end
|
||||
|
||||
@ -41,14 +42,14 @@ end
|
||||
local rq = RequestClass.new(postData, config.DEBUG_PCALLS)
|
||||
|
||||
l:info("received request of type " .. rq:getRequestMethod() .. " for " .. (rq:getRequestedApiModule() or "<unknown>")
|
||||
.. "/" .. (rq:getRealApiFunctionName() or "<unknown>") .. " with arguments: " .. l:dump(rq:getAll()))
|
||||
if rq:getRequestMethod() ~= "CMDLINE" then
|
||||
.. "/" .. (rq:getRealApiFunctionName() or "<unknown>") .. " with arguments: " .. u.dump(rq:getAll()))
|
||||
if rq:getRequestMethod() ~= 'CMDLINE' then
|
||||
l:info("remote IP/port: " .. rq:getRemoteHost() .. "/" .. rq:getRemotePort())
|
||||
l:debug("user agent: " .. rq:getUserAgent())
|
||||
end
|
||||
|
||||
if (not config.DEBUG_PCALLS and rq:getRequestMethod() == "CMDLINE") then
|
||||
if rq:get("autowifi") ~= nil then
|
||||
if (not config.DEBUG_PCALLS and rq:getRequestMethod() == 'CMDLINE') then
|
||||
if rq:get('autowifi') ~= nil then
|
||||
setupAutoWifiMode()
|
||||
else
|
||||
l:info("Nothing to do...bye.\n")
|
||||
|
@ -1,6 +1,6 @@
|
||||
local config = require("config")
|
||||
local u = require("util.utils")
|
||||
local l = require("logger")
|
||||
local l = require("util.logger")
|
||||
local uci = require("uci").cursor()
|
||||
|
||||
local M = {}
|
||||
|
@ -1,5 +1,5 @@
|
||||
local util = require("util.utils")
|
||||
local l = require("logger")
|
||||
local l = require("util.logger")
|
||||
local uci = require("uci").cursor()
|
||||
local iwinfo = require("iwinfo")
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
local config = require("config")
|
||||
local l = require("logger")
|
||||
local u = require("util.utils")
|
||||
local l = require("util.logger")
|
||||
local netconf = require("network.netconfig")
|
||||
local wifi = require("network.wlanconfig")
|
||||
local ResponseClass = require("rest.response")
|
||||
@ -36,7 +36,7 @@ function M.available(request, response)
|
||||
netInfo["signal"] = se.signal
|
||||
netInfo["quality"] = se.quality
|
||||
netInfo["quality_max"] = se.quality_max
|
||||
if withRaw then netInfo["_raw"] = l:dump(se) end
|
||||
if withRaw then netInfo["_raw"] = u.dump(se) end
|
||||
|
||||
table.insert(netInfoList, netInfo)
|
||||
end
|
||||
@ -63,7 +63,7 @@ function M.known(request, response)
|
||||
netInfo["bssid"] = net.bssid or ""
|
||||
netInfo["channel"] = net.channel or ""
|
||||
netInfo["encryption"] = net.encryption
|
||||
if withRaw then netInfo["_raw"] = l:dump(net) end
|
||||
if withRaw then netInfo["_raw"] = u.dump(net) end
|
||||
table.insert(netInfoList, netInfo)
|
||||
end
|
||||
end
|
||||
@ -87,7 +87,7 @@ function M.state(request, response)
|
||||
response:addData("txpower", ds.txpower)
|
||||
response:addData("signal", ds.signal)
|
||||
response:addData("noise", ds.noise)
|
||||
if withRaw then response:addData("_raw", l:dump(ds)) end
|
||||
if withRaw then response:addData("_raw", u.dump(ds)) end
|
||||
end
|
||||
|
||||
--UNTESTED
|
||||
|
@ -1,4 +1,4 @@
|
||||
local l = require("logger")
|
||||
local l = require("util.logger")
|
||||
local ResponseClass = require("rest.response")
|
||||
|
||||
local M = {}
|
||||
|
63
src/test/ansicolors.lua
Normal file
63
src/test/ansicolors.lua
Normal file
@ -0,0 +1,63 @@
|
||||
-- adapted from: http://lua-users.org/wiki/AnsiTerminalColors
|
||||
local pairs = pairs
|
||||
local tostring = tostring
|
||||
local setmetatable = setmetatable
|
||||
local schar = string.char
|
||||
|
||||
local colormt = {}
|
||||
|
||||
function colormt:__tostring()
|
||||
return self.value
|
||||
end
|
||||
|
||||
function colormt:__concat(other)
|
||||
return tostring(self) .. tostring(other)
|
||||
end
|
||||
|
||||
function colormt:__call(s)
|
||||
return self .. s .. colormt.reset
|
||||
end
|
||||
|
||||
colormt.__metatable = {}
|
||||
|
||||
local function makecolor(value)
|
||||
return setmetatable({ value = schar(27) .. '[' .. tostring(value) .. 'm' }, colormt)
|
||||
end
|
||||
|
||||
local colors = {
|
||||
-- attributes
|
||||
reset = 0,
|
||||
clear = 0,
|
||||
bright = 1,
|
||||
dim = 2,
|
||||
underscore = 4,
|
||||
blink = 5,
|
||||
reverse = 7,
|
||||
hidden = 8,
|
||||
|
||||
-- foreground
|
||||
black = 30,
|
||||
red = 31,
|
||||
green = 32,
|
||||
yellow = 33,
|
||||
blue = 34,
|
||||
magenta = 35,
|
||||
cyan = 36,
|
||||
white = 37,
|
||||
|
||||
-- background
|
||||
onblack = 40,
|
||||
onred = 41,
|
||||
ongreen = 42,
|
||||
onyellow = 43,
|
||||
onblue = 44,
|
||||
onmagenta = 45,
|
||||
oncyan = 46,
|
||||
onwhite = 47,
|
||||
}
|
||||
|
||||
for c, v in pairs(colors) do
|
||||
colormt[c] = makecolor(v)
|
||||
end
|
||||
|
||||
return colormt
|
105
src/test/test_utils.lua
Normal file
105
src/test/test_utils.lua
Normal file
@ -0,0 +1,105 @@
|
||||
local utils = require("util.utils")
|
||||
|
||||
local M = {
|
||||
_is_test = true,
|
||||
_skip = { 'dump', 'symlink', 'getUciSectionName', 'symlinkInRoot' },
|
||||
_wifibox_only = { 'getUciSectionName', 'symlinkInRoot' }
|
||||
}
|
||||
|
||||
local function compareTables(t1, t2)
|
||||
if #t1 ~= #t2 then return false end
|
||||
for i=1,#t1 do
|
||||
if t1[i] ~= t2[i] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Returns a string representation of the argument, with 'real' strings enclosed in single quotes
|
||||
local function stringRepresentation(v)
|
||||
return type(v) == 'string' and ("'"..v.."'") or tostring(v)
|
||||
end
|
||||
|
||||
|
||||
local filename = "/tmp/somefile12345.txt"
|
||||
|
||||
|
||||
function M:_setup()
|
||||
os.execute("rm -f " .. filename) -- make sure the file does not exist
|
||||
end
|
||||
|
||||
function M:_teardown()
|
||||
os.execute("rm -f " .. filename) -- make sure the file gets removed again
|
||||
end
|
||||
|
||||
|
||||
function M:test_splitString()
|
||||
local input1, input2, input3 = ':a:b::', '/a/b//', '$a$b$$'
|
||||
local expected = { '', 'a', 'b', '', '' }
|
||||
|
||||
local result1 = input1:split()
|
||||
local result2 = input2:split('/')
|
||||
local result3 = input3:split('$')
|
||||
|
||||
assert(#result1 == 5)
|
||||
assert(compareTables(result1, expected))
|
||||
assert(#result2 == 5)
|
||||
assert(compareTables(result2, expected))
|
||||
assert(#result3 == 5)
|
||||
assert(compareTables(result3, expected))
|
||||
end
|
||||
|
||||
function M:test_toboolean()
|
||||
local trues = { true, 1, 'true', 'True', 'T', '1' }
|
||||
local falses = { nil, false, 0, 'false', 'False' , 'f', {} }
|
||||
|
||||
for _,v in pairs(trues) do assert(utils.toboolean(v), "expected true: " .. stringRepresentation(v)) end
|
||||
for _,v in pairs(falses) do assert(not utils.toboolean(v), "expected false: " .. stringRepresentation(v)) end
|
||||
end
|
||||
|
||||
function M:test_dump()
|
||||
--test handling of reference loops
|
||||
assert(false, 'not implemented')
|
||||
end
|
||||
|
||||
function M:test_getUciSectionName()
|
||||
assert(false, 'not implemented')
|
||||
end
|
||||
|
||||
function M:test_exists()
|
||||
assert(utils.exists() == nil)
|
||||
assert(utils.exists(nil) == nil)
|
||||
|
||||
assert(not utils.exists(filename))
|
||||
os.execute("touch " .. filename)
|
||||
assert(utils.exists(filename))
|
||||
end
|
||||
|
||||
function M:test_create()
|
||||
local f, testContents = nil, 'test text'
|
||||
|
||||
assert(utils.create() == nil)
|
||||
assert(utils.create(nil) == nil)
|
||||
|
||||
assert(not io.open(filename, 'r'))
|
||||
utils.create(filename)
|
||||
assert(io.open(filename, 'r'))
|
||||
|
||||
f = io.open(filename, 'w')
|
||||
f:write(testContents)
|
||||
f:close()
|
||||
|
||||
utils.create(filename)
|
||||
f = io.open(filename, 'r')
|
||||
local actualContents = f:read('*all')
|
||||
assert(actualContents == testContents)
|
||||
end
|
||||
|
||||
function M:test_symlink()
|
||||
assert(false, 'not implemented')
|
||||
end
|
||||
|
||||
function M:test_symlinkInRoot()
|
||||
assert(false, 'not implemented')
|
||||
end
|
||||
|
||||
return M
|
87
src/test/testrunner.lua
Normal file
87
src/test/testrunner.lua
Normal file
@ -0,0 +1,87 @@
|
||||
package.path = package.path .. ';./test/?.lua'
|
||||
local ansicolors = require('test.ansicolors')
|
||||
|
||||
local testFunctionPrefix = 'test_'
|
||||
|
||||
local function tableIndexOf(t, val)
|
||||
for k,v in ipairs(t) do
|
||||
if v == val then return k end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
local function runningOnWifibox()
|
||||
local f,e = io.open('/etc/openwrt_release', 'r')
|
||||
return f ~= nil
|
||||
end
|
||||
|
||||
local function runTestFile(filename, showStackTraces)
|
||||
local r,tf = pcall(require, filename)
|
||||
local stackTrace, errorMessage
|
||||
|
||||
local function errorHandler(msg)
|
||||
stackTrace = debug.traceback()
|
||||
errorMessage = msg
|
||||
end
|
||||
|
||||
if not r then return nil,tf end
|
||||
if not tf._is_test then return nil,"not a test file" end
|
||||
|
||||
local setupFunc, teardownFunc = tf._setup, tf._teardown
|
||||
|
||||
print("======= running test file '" .. filename .. "' =======")
|
||||
|
||||
for k,v in pairs(tf) do
|
||||
--if type(v) == 'function' and k ~= '_setup' and k ~= '_teardown' then
|
||||
if type(v) == 'function' and k:find(testFunctionPrefix) == 1 then
|
||||
local baseName = k:sub(testFunctionPrefix:len() + 1)
|
||||
local skip = (tableIndexOf(tf._skip, baseName) > -1)
|
||||
local wifiboxOnly = (tableIndexOf(tf._wifibox_only, baseName) > -1)
|
||||
|
||||
if not skip and (not wifiboxOnly or runningOnWifibox()) then
|
||||
pcall(setupFunc)
|
||||
local testResult = xpcall(v, errorHandler)
|
||||
pcall(teardownFunc)
|
||||
|
||||
if testResult then
|
||||
print(ansicolors.green .. "[OK ] " .. ansicolors.reset .. k)
|
||||
else
|
||||
print(ansicolors.red .. "[ERR] " .. ansicolors.reset .. k)
|
||||
if errorMessage then print(" " .. errorMessage) end
|
||||
if showStackTraces and stackTrace then print(" " .. stackTrace) end
|
||||
end
|
||||
else
|
||||
if skip then
|
||||
print(ansicolors.bright .. ansicolors.black .. "[SKP] " .. ansicolors.reset .. k)
|
||||
else
|
||||
print(ansicolors.yellow .. "[WBO] " .. ansicolors.reset .. k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print("")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function main()
|
||||
if #arg < 1 then
|
||||
print("Please specify at least one lua test file")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
for i=1,#arg do
|
||||
local modName = 'test_'..arg[i]
|
||||
local r,e = runTestFile(modName, true)
|
||||
|
||||
if not r then
|
||||
io.stderr:write("test file '" .. modName .. "' could not be loaded or is not a test file ('" .. e .. "')\n")
|
||||
end
|
||||
end
|
||||
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
main(arg)
|
43
src/test/www/restapi.css
Normal file
43
src/test/www/restapi.css
Normal file
@ -0,0 +1,43 @@
|
||||
body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#content {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
div.module {
|
||||
border: 1px solid #eee;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-left: 2em;
|
||||
font-size: larger;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.resp_success {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.resp_status {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.resp_fail {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.resp_error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.resp_unknown {
|
||||
color: purple;
|
||||
}
|
||||
|
||||
span.resp_msg {
|
||||
padding-left: 1em;
|
||||
}
|
22
src/test/www/restapi.html
Normal file
22
src/test/www/restapi.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<title>Test page for the Doodle3D WiFiBox REST API</title>
|
||||
<link type="text/css" rel="stylesheet" href="restapi.css" />
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="restapi.js"></script>
|
||||
</head>
|
||||
<body><div id="content">
|
||||
<div id="module_test" class="module">
|
||||
<span class="title">Test API</span>
|
||||
</div>
|
||||
<div id="module_network" class="module">
|
||||
<span class="title">Network API</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="title">Run arbitrary requests</span>
|
||||
</div>
|
||||
</div></body>
|
||||
</html>
|
40
src/test/www/restapi.js
Normal file
40
src/test/www/restapi.js
Normal file
@ -0,0 +1,40 @@
|
||||
const API_ROOT = "/cgi-bin/d3dapi";
|
||||
|
||||
$(document).ready(function(){
|
||||
performRequest('test', '123', "GET", {});
|
||||
performRequest('test', '', "POST", {});
|
||||
performRequest('test', 'success', "GET", {});
|
||||
performRequest('test', 'fail', "GET", {});
|
||||
performRequest('test', 'error', "GET", {});
|
||||
performRequest('test', 'echo', "GET", {});
|
||||
performRequest('test', 'write', "POST", {});
|
||||
performRequest('test', 'read', "GET", {});
|
||||
});
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* - change function signature to: apiPath, data, expected
|
||||
* - create request divs before ajax request so it's visible what 'threads' have been put out
|
||||
* - function always tries both GET and POST and matches this with expected.methods
|
||||
* - expected.methods can be 'GET', 'POST' or 'BOTH'
|
||||
* - expected.status indicates what status was expected
|
||||
* - expected.data.{...} indicates what fields should look like (regex/literal/...?)
|
||||
* - anything not specified in expected.data is ignored
|
||||
*/
|
||||
function performRequest(mod, func, method, data) {
|
||||
$.ajax({
|
||||
type: method,
|
||||
context: $("#module_" + mod),
|
||||
url: API_ROOT + "/" + mod + "/" + func,
|
||||
dataType: 'json',
|
||||
data: data
|
||||
}).done(function(response){
|
||||
var status = response.status == 'success' ? '<span class="resp_status resp_success">+</span>'
|
||||
: response.status == 'fail' ? '<span class="resp_status resp_fail">-</span>'
|
||||
: response.status == 'error' ? '<span class="resp_status resp_error">!</span>'
|
||||
: '<span class="resp_status resp_unknown">?</span>';
|
||||
var modFunc = '<span class="resp_mod_func">' + mod + '/' + func + '</span><br/>';
|
||||
this.append('<div id="resp_' + mod + '_' + func + '">' + status + modFunc
|
||||
+ '<span class="resp_msg">' + response.msg + '</span></div>');
|
||||
});
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
local utils = require('util.utils')
|
||||
|
||||
local M = {}
|
||||
|
||||
local logLevel, logVerbose, logStream
|
||||
|
||||
M.LEVEL = {"debug", "info", "warn", "error", "fatal"}
|
||||
M.LEVEL = {'debug', 'info', 'warn', 'error', 'fatal'}
|
||||
|
||||
-- M.LEVEL already has idx=>name entries, now create name=>idx entries
|
||||
for i,v in ipairs(M.LEVEL) do
|
||||
@ -22,31 +24,18 @@ end
|
||||
|
||||
local function log(level, msg, verbose)
|
||||
if level >= logLevel then
|
||||
local now = os.date("%m-%d %H:%M:%S")
|
||||
local now = os.date('%m-%d %H:%M:%S')
|
||||
local i = debug.getinfo(3) --the stack frame just above the logger call
|
||||
local v = verbose
|
||||
if v == nil then v = logVerbose end
|
||||
local name = i.name or "(nil)"
|
||||
local vVal = "nil"
|
||||
local m = (type(msg) == "string") and msg or M:dump(msg)
|
||||
local vVal = 'nil'
|
||||
local m = (type(msg) == 'string') and msg or utils.dump(msg)
|
||||
if v then logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. " [" .. name .. "@" .. i.short_src .. ":" .. i.linedefined .. "]\n")
|
||||
else logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. "\n") end
|
||||
end
|
||||
end
|
||||
|
||||
function M:dump(o)
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. M:dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
function M:debug(msg, verbose) log(M.LEVEL.debug, msg, verbose); return true end
|
||||
function M:info(msg, verbose) log(M.LEVEL.info, msg, verbose); return true end
|
||||
function M:warn(msg, verbose) log(M.LEVEL.warn, msg, verbose); return true end
|
@ -1,9 +1,9 @@
|
||||
local uci = require("uci").cursor()
|
||||
local uci = require('uci').cursor()
|
||||
|
||||
local M = {}
|
||||
|
||||
function string:split(div)
|
||||
local div, pos, arr = div or ":", 0, {}
|
||||
local div, pos, arr = div or ':', 0, {}
|
||||
for st,sp in function() return self:find(div, pos, true) end do
|
||||
table.insert(arr, self:sub(pos, st - 1))
|
||||
pos = sp + 1
|
||||
@ -15,26 +15,63 @@ end
|
||||
function M.toboolean(s)
|
||||
if not s then return false end
|
||||
|
||||
local b = s:lower()
|
||||
return (b == "1" or b == "t" or b == "true") and true or false
|
||||
local b = type(s) == 'string' and s:lower() or s
|
||||
local textTrue = (b == '1' or b == 't' or b == 'true')
|
||||
local boolTrue = (type(b) == 'boolean' and b == true)
|
||||
local numTrue = (type(b) == 'number' and b > 0)
|
||||
return textTrue or boolTrue or numTrue
|
||||
end
|
||||
|
||||
function M.dump(o)
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. M.dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
function M.getUciSectionName(config, type)
|
||||
local sname = nil
|
||||
uci:foreach(config, type, function(s) sname = s[".name"] end)
|
||||
uci:foreach(config, type, function(s) sname = s['.name'] end)
|
||||
return sname
|
||||
end
|
||||
|
||||
function M.exists(file)
|
||||
local r = io.open(file) --ignore returned message
|
||||
if r ~= nil then io.close(r) end
|
||||
if not file or type(file) ~= 'string' or file:len() == 0 then
|
||||
return nil, "file must be a non-empty string"
|
||||
end
|
||||
|
||||
local r = io.open(file, 'r') -- ignore returned message
|
||||
if r then r:close() end
|
||||
return r ~= nil
|
||||
end
|
||||
|
||||
--creates and returns true if not exists, returns false it does, nil+msg on error
|
||||
function M.create(file)
|
||||
local r,m = M.exists(file)
|
||||
|
||||
if r == nil then
|
||||
return r,m
|
||||
elseif r == true then
|
||||
return true
|
||||
end
|
||||
|
||||
r,m = io.open(file, 'a') -- append mode is probably safer in case the file does exist after all
|
||||
if not r then return r,m end
|
||||
|
||||
r:close()
|
||||
return true
|
||||
end
|
||||
|
||||
--FIXME: somehow protect this function from running arbitrary commands
|
||||
function M.symlink(from, to)
|
||||
if from == nil or from == "" or to == nil or to == "" then return -1 end
|
||||
local x = "ln -s " .. from .. " " .. to
|
||||
if from == nil or from == '' or to == nil or to == '' then return -1 end
|
||||
local x = 'ln -s ' .. from .. ' ' .. to
|
||||
return os.execute(x)
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user