-- @class module
-- @cstyle instance
-local fs = require "luci.fs"
+local fs = require "nixio.fs"
local uci = require "luci.model.uci"
local util = require "luci.util"
+local nutil = require "nixio.util"
local table = require "table"
local string = require "string"
local require, pcall, ipairs, pairs = require, pcall, ipairs, pairs
local type, error, tonumber, tostring = type, error, tonumber, tostring
-local unpack, loadfile = unpack, loadfile
+local unpack, loadfile, collectgarbage = unpack, loadfile, collectgarbage
module "luci.uvl"
-local ERR = require "luci.uvl.errors"
+local ERR = require "luci.uvl.errors".error
local datatypes = require "luci.uvl.datatypes"
local validation = require "luci.uvl.validation"
local dependencies = require "luci.uvl.dependencies"
local TYPE_OPTION = 0x03
local TYPE_ENUM = 0x04
+local PAT_EXPR1 = "^%$?[%w_]+$"
+local PAT_EXPR2 = "^%$?[%w_]+%.%$?[%w_]+$"
+local PAT_EXPR3 = "^%$?[%w_]+%.%$?[%w_]+%.%$?[%w_]+$"
+
--- Boolean; default true;
-- treat sections found in config but not in scheme as error
STRICT_UNKNOWN_SECTIONS = true
-- @class function
-- @name UVL
-- @param schemedir Path to the scheme directory (optional)
+-- @param configdir Override config directory (optional)
-- @return Instance object
UVL = util.class()
-function UVL.__init__( self, schemedir )
+function UVL.__init__( self, schemedir, configdir )
self.schemedir = schemedir or default_schemedir
+ self.configdir = configdir
self.packages = { }
self.beenthere = { }
self.depseen = { }
for k, v in pairs(co:config()) do
local so = co:section(k)
if not self.beenthere[so:cid()] then
- co:error(ERR.SECT_UNKNOWN(so))
+ co:error(ERR('SECT_UNKNOWN', so))
end
end
end
for _, k in ipairs(util.keys(sc)) do
local so = co:section(k)
if so:scheme('required') and sc[k] == 0 then
- co:error(ERR.SECT_REQUIRED(so))
+ co:error(ERR('SECT_REQUIRED', so))
elseif so:scheme('unique') and sc[k] > 1 then
- co:error(ERR.SECT_UNIQUE(so))
+ co:error(ERR('SECT_UNIQUE', so))
end
end
if so:config() then
return self:_validate_section( so )
else
- return false, ERR.SECT_NOTFOUND(so)
+ return false, ERR('SECT_NOTFOUND', so)
end
end
if so:config() and oo:config() then
return self:_validate_option( oo )
else
- return false, ERR.OPT_NOTFOUND(oo)
+ return false, ERR('OPT_NOTFOUND', oo)
end
end
if section:scheme('named') == true and
section:config('.anonymous') == true
then
- return false, ERR.SECT_NAMED(section)
+ return false, ERR('SECT_NAMED', section)
end
for _, v in ipairs(section:variables()) do
local ok, err = self:_validate_option( v )
if not ok and (
v:scheme('required') or v:scheme('type') == "enum" or (
- not err:is(ERR.ERR_DEP_NOTEQUAL) and
- not err:is(ERR.ERR_DEP_NOVALUE)
+ not err:is('DEP_NOTEQUAL') and
+ not err:is('DEP_NOVALUE')
)
) then
section:error(err)
section:error(err)
end
else
- return false, ERR.SECT_NOTFOUND(section)
+ return false, ERR('SECT_NOTFOUND', section)
end
if STRICT_UNKNOWN_OPTIONS and not section:scheme('dynamic') then
for k, v in pairs(section:config()) do
local oo = section:option(k)
- if k:sub(1,1) ~= "." and not self.beenthere[oo:cid()] then
- section:error(ERR.OPT_UNKNOWN(oo))
+ if k:byte(1) == 46 and not self.beenthere[oo:cid()] then
+ section:error(ERR('OPT_UNKNOWN', oo))
end
end
end
if not option:scheme() and not option:parent():scheme('dynamic') then
if STRICT_UNKNOWN_OPTIONS then
- return false, option:error(ERR.OPT_UNKNOWN(option))
+ return false, option:error(ERR('OPT_UNKNOWN', option))
else
return true
end
local ok, err = dependencies.check( self, option )
if not ok then
if not err:is_all(
- ERR.ERR_OPT_REQUIRED,
- ERR.ERR_DEP_NOTEQUAL,
- ERR.ERR_DEP_NOVALUE
+ 'OPT_REQUIRED',
+ 'DEP_NOTEQUAL',
+ 'DEP_NOVALUE'
) then
option:error(err)
return false, option:errors()
end
if option:scheme('required') and not option:value() then
- return false, option:error(ERR.OPT_REQUIRED(option))
+ return false, option:error(ERR('OPT_REQUIRED', option))
elseif option:value() then
local val = option:value()
local config_values = ( type(val) == "table" and val or { val } )
for _, v in ipairs(config_values) do
if not scheme_values[v] then
- return false, option:error( ERR.OPT_BADVALUE(
+ return false, option:error( ERR(
+ 'OPT_BADVALUE',
option, { v, util.serialize_data(
util.keys(scheme_values)
) }
end
elseif option:scheme('type') == "list" then
if type(val) ~= "table" and STRICT_LIST_TYPE then
- return false, option:error(ERR.OPT_NOTLIST(option))
+ return false, option:error(ERR('OPT_NOTLIST', option))
end
end
for i, v in ipairs(val) do
if not self.datatypes[dt]( v ) then
return false, option:error(
- ERR.OPT_INVVALUE(option, { v, dt })
+ ERR('OPT_INVVALUE', option, { v, dt })
)
end
end
else
- return false, option:error(ERR.OPT_DATATYPE(option, dt))
+ return false, option:error(ERR('OPT_DATATYPE', option, dt))
end
end
for _, v in ipairs(val) do
if option:scheme('minlength') then
if #v < option:scheme('minlength') then
- return false, option:error(ERR.OPT_RANGE(option))
+ return false, option:error(ERR('OPT_RANGE', option))
end
end
if option:scheme('maxlength') then
if #v > option:scheme('maxlength') then
- return false, option:error(ERR.OPT_RANGE(option))
+ return false, option:error(ERR('OPT_RANGE', option))
end
end
if option:scheme('minimum') then
if not w or w < option:scheme('minimum') then
- return false, option:error(ERR.OPT_RANGE(option))
+ return false, option:error(ERR('OPT_RANGE', option))
end
end
if option:scheme('maximum') then
if not w or w > option:scheme('maximum') then
- return false, option:error(ERR.OPT_RANGE(option))
+ return false, option:error(ERR('OPT_RANGE', option))
end
end
end
local bc = "%s/bytecode/%s.lua" %{ self.schemedir, shm }
if not fs.access(bc) then
- local files = fs.glob(self.schemedir .. '/*/' .. shm)
+ local files = nutil.consume((fs.glob(self.schemedir .. '/*/' .. shm)))
- if files then
+ if #files > 0 then
local ok, err
- for i, file in ipairs( files ) do
+ for _, file in ipairs(files) do
if not fs.access(file) then
- return false, so:error(ERR.SME_READ(so,file))
+ return false, so:error(ERR('SME_READ', so,file))
end
local uci = uci.cursor( fs.dirname(file), default_savedir )
local sd, err = uci:load( sname )
if not sd then
- return false, ERR.UCILOAD(so, err)
+ return false, ERR('UCILOAD', so, err)
end
ok, err = pcall(function()
if ok and alias then self.packages[alias] = self.packages[shm] end
return ok and self, err
else
- return false, so:error(ERR.SME_FIND(so, self.schemedir))
+ return false, so:error(ERR('SME_FIND', so, self.schemedir))
end
else
local sc = loadfile(bc)
self.packages[shm] = sc()
return true
else
- return false, so:error(ERR.SME_READ(so,bc))
+ return false, so:error(ERR('SME_READ',so,bc))
end
end
end
o = enum( scheme, nil, p, '(nil)', '(nil)', n )
end
- return false, ERR.SME_REQFLD(o,v)
+ return false, ERR('SME_REQFLD',o,v)
end
end
return true
r[1] = ( #r[1] > 0 and r[1] or scheme:sid() )
if #r ~= n then
- return false, ERR.SME_BADREF(scheme, k)
+ return false, ERR('SME_BADREF', scheme, k)
end
return r
local so = scheme:section(v.name)
for k, v2 in pairs(v) do
- if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
+ if k ~= "name" and k ~= "package" and k:byte(1) == 46 then
if k == "depends" then
s.depends = self:_read_dependency( v2, s.depends )
if not s.depends then
return false, scheme:error(
- ERR.SME_BADDEP(so, util.serialize_data(s.depends))
+ ERR('SME_BADDEP', so, util.serialize_data(s.depends))
)
end
elseif k == "dynamic" or k == "unique" or
local p = self.packages[r[1]]
if not p then
error(scheme:error(
- ERR.SME_VBADPACK({scheme:sid(), '', v.name}, r[1])
+ ERR('SME_VBADPACK', {scheme:sid(), '', v.name}, r[1])
))
end
local s = p.variables[r[2]]
if not s then
error(scheme:error(
- ERR.SME_VBADSECT({scheme:sid(), '', v.name}, r[2])
+ ERR('SME_VBADSECT', {scheme:sid(), '', v.name}, r[2])
))
end
local to = so:option(v.name)
for k, v2 in pairs(v) do
- if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
+ if k ~= "name" and k ~= "section" and k:byte(1) == 46 then
if k == "depends" then
t.depends = self:_read_dependency( v2, t.depends )
if not t.depends then
error(scheme:error(so:error(
- ERR.SME_BADDEP(to, util.serialize_data(v2))
+ ERR('SME_BADDEP', to, util.serialize_data(v2))
)))
end
elseif k == "validator" then
t.validators = self:_read_validator( v2, t.validators )
if not t.validators then
error(scheme:error(so:error(
- ERR.SME_BADVAL(to, util.serialize_data(v2))
+ ERR('SME_BADVAL', to, util.serialize_data(v2))
)))
end
elseif k == "valueof" then
local values, err = self:_read_reference( v2 )
if err then
error(scheme:error(so:error(
- ERR.REFERENCE(to, util.serialize_data(v2)):child(err)
+ ERR('REFERENCE', to, util.serialize_data(v2)):child(err)
)))
end
t.type = "reference"
local p = self.packages[r[1]]
if not p then
error(scheme:error(
- ERR.SME_EBADPACK({scheme:sid(), '', '', v.value}, r[1])
+ ERR('SME_EBADPACK', {scheme:sid(), '', '', v.value}, r[1])
))
end
local s = p.variables[r[2]]
if not s then
error(scheme:error(
- ERR.SME_EBADSECT({scheme:sid(), '', '', v.value}, r[2])
+ ERR('SME_EBADSECT', {scheme:sid(), '', '', v.value}, r[2])
))
end
local t = s[r[3]]
if not t then
error(scheme:error(
- ERR.SME_EBADOPT({scheme:sid(), '', '', v.value}, r[3])
+ ERR('SME_EBADOPT', {scheme:sid(), '', '', v.value}, r[3])
))
end
local eo = oo:enum(v.value)
if t.type ~= "enum" and t.type ~= "reference" then
- error(scheme:error(ERR.SME_EBADTYPE(eo)))
+ error(scheme:error(ERR('SME_EBADTYPE', eo)))
end
if not t.values then
if v.default then
if t.default then
- error(scheme:error(ERR.SME_EBADDEF(eo)))
+ error(scheme:error(ERR('SME_EBADDEF', eo)))
end
t.default = v.value
end
if not t.enum_depends[v.value] then
error(scheme:error(so:error(oo:error(
- ERR.SME_BADDEP(eo, util.serialize_data(v.depends))
+ ERR('SME_BADDEP', eo, util.serialize_data(v.depends))
))))
end
end
local k, e, v = val:match("%s*([%w$_.]+)%s*(=?)%s*(.*)")
if k and (
- k:match("^"..expr.."%."..expr.."%."..expr.."$") or
- k:match("^"..expr.."%."..expr.."$") or
- k:match("^"..expr.."$")
+ k:match(PAT_EXPR1) or k:match(PAT_EXPR2) or k:match(PAT_EXPR3)
) then
condition[k] = (e == '=') and v or true
else
validator = self:_resolve_function( (value:gsub("^lua:","") ) )
elseif value:match("^regexp:") then
local pattern = value:gsub("^regexp:","")
- validator = function( type, dtype, pack, sect, optn, ... )
- local values = { ... }
+ validator = function( type, dtype, pack, sect, optn, arg1, arg2, arg3, arg4, arg5 )
+ local values = { arg1, arg2, arg3, arg4, arg5 }
for _, v in ipairs(values) do
local ok, match =
pcall( string.match, v, pattern )
if v['.type'] == ref[2] then
if #ref == 2 then
if v['.anonymous'] == true then
- return false, ERR.SME_INVREF('', value)
+ return false, ERR('SME_INVREF', '', value)
end
val[k] = k -- XXX: title/description would be nice
elseif v[ref[3]] then
end
end
else
- return false, ERR.SME_BADREF('', value)
+ return false, ERR('SME_BADREF', '', value)
end
end
end
end
-function uvlitem.error(self, ...)
+function uvlitem.error(self, arg1, arg2, arg3, arg4, arg5)
if not self.e then
- local errconst = { ERR.CONFIG, ERR.SECTION, ERR.OPTION, ERR.OPTION }
- self.e = errconst[#self.cref]( self )
+ local errconst = { 'CONFIG', 'SECTION', 'OPTION', 'OPTION' }
+ self.e = ERR( errconst[#self.cref], self )
end
- return self.e:child( ... )
+ return self.e:child( arg1, arg2, arg3, arg4, arg5 )
end
function uvlitem.errors(self)
end
end
-function uvlitem._loadconf(self, co, c)
+function uvlitem._loadconf(self, co, c, configdir)
co = co or self._configcache
if not co then
local err
- co, err = uci.cursor():get_all(c)
+ co, err = uci.cursor(configdir):get_all(c)
if err then
- self:error(ERR.UCILOAD(self, err))
+ self:error(ERR('UCILOAD', self, err))
end
self._configcache = co
self.cref = { c }
self.sref = { c }
- self.c = self:_loadconf(co, c)
+ self.c = self:_loadconf(co, c, scheme.configdir)
self.s = scheme
self.t = TYPE_SCHEME
end
--- Add an error to scheme.
-- @return Scheme error context
-function scheme.error(self, ...)
- if not self.e then self.e = ERR.SCHEME( self ) end
- return self.e:child( ... )
+function scheme.error(self, arg1, arg2, arg3, arg4, arg5)
+ if not self.e then self.e = ERR( 'SCHEME', self ) end
+ return self.e:child( arg1, arg2, arg3, arg4, arg5 )
end
--- Get an associated config object.
if not c then
c, co = co, nil
end
-
self.cref = { c }
self.sref = { c }
- self.c = self:_loadconf(co, c)
+ self.c = self:_loadconf(co, c, scheme.configdir)
self.s = scheme
self.t = TYPE_CONFIG
end
function section.__init__(self, scheme, co, c, s)
self.cref = { c, s }
self.sref = { c, co and co[s] and co[s]['.type'] or s }
- self.c = self:_loadconf(co, c)
+ self.c = self:_loadconf(co, c, scheme.configdir)
self.s = scheme
self.t = TYPE_SECTION
end
function option.__init__(self, scheme, co, c, s, o)
self.cref = { c, s, o }
self.sref = { c, co and co[s] and co[s]['.type'] or s, o }
- self.c = self:_loadconf(co, c)
+ self.c = self:_loadconf(co, c, scheme.configdir)
self.s = scheme
self.t = TYPE_OPTION
end
function enum.__init__(self, scheme, co, c, s, o, v)
self.cref = { c, s, o, v }
self.sref = { c, co and co[s] and co[s]['.type'] or s, o, v }
- self.c = self:_loadconf(co, c)
+ self.c = self:_loadconf(co, c, scheme.configdir)
self.s = scheme
self.t = TYPE_ENUM
end