require("luci.fs")
require("luci.util")
-require("luci.model.uci")
+local uci = require("luci.model.uci")
require("luci.uvl.errors")
require("luci.uvl.datatypes")
require("luci.uvl.validation")
require("luci.uvl.dependencies")
+local cursor = uci.cursor()
TYPE_SCHEME = 0x00
TYPE_CONFIG = 0x01
TYPE_SECTION = 0x02
-TYPE_VARIABLE = 0x03
-TYPE_OPTION = 0x04
-TYPE_ENUM = 0x05
+TYPE_OPTION = 0x03
+TYPE_ENUM = 0x04
--- Boolean; default true;
-- treat sections found in config but not in scheme as error
self.beenthere[option:cid()] = true
if not option:scheme() and not option:parent():scheme('dynamic') then
- return false, option:error(ERR.OPT_UNKNOWN(option))
+ if STRICT_UNKNOWN_OPTIONS then
+ return false, option:error(ERR.OPT_UNKNOWN(option))
+ else
+ return true
+ end
elseif option:scheme() then
if option:scheme('required') and not option:value() then
if option:scheme('type') == "reference" or
option:scheme('type') == "enum"
then
- if not option:scheme('values') or
- not option:scheme('values')[val]
- then
- return false, option:error( ERR.OPT_BADVALUE(
- option, luci.util.serialize_data(
- luci.util.keys(option:scheme('values') or {})
- )
- ) )
+ local scheme_values = option:scheme('values') or { }
+ 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(
+ option, { v, luci.util.serialize_data(
+ luci.util.keys(scheme_values)
+ ) }
+ ) )
+ end
end
elseif option:scheme('type') == "list" then
if type(val) ~= "table" and STRICT_LIST_TYPE then
return false, option:error(ERR.OPT_NOTLIST(option))
end
- elseif option:scheme('datatype') then
+ end
+
+ if option:scheme('datatype') then
local dt = option:scheme('datatype')
if self.datatypes[dt] then
for i, v in ipairs(val) do
if not self.datatypes[dt]( v ) then
return false, option:error(
- ERR.OPT_INVVALUE(option, dt)
+ ERR.OPT_INVVALUE(option, { v, dt })
)
end
end
-- This is normally done on demand, so you don't have to call this function
-- by yourself.
-- @param scheme Name of the scheme to parse
-function UVL.read_scheme( self, scheme )
+-- @param alias Create an alias for the loaded scheme
+function UVL.read_scheme( self, scheme, alias )
local so = luci.uvl.scheme( self, scheme )
+ local bc = "%s/bytecode/%s.lua" %{ self.schemedir, scheme }
- local schemes = { }
- local files = luci.fs.glob(self.schemedir .. '/*/' .. scheme)
+ if not luci.fs.access(bc) then
+ local schemes = { }
+ local files = luci.fs.glob(self.schemedir .. '/*/' .. scheme)
- if files then
- for i, file in ipairs( files ) do
- if not luci.fs.access(file) then
- return so:error(ERR.SME_READ(so,file))
- end
+ if files then
+ for i, file in ipairs( files ) do
+ if not luci.fs.access(file) then
+ return false, so:error(ERR.SME_READ(so,file))
+ end
- local uci = luci.model.uci.cursor( luci.fs.dirname(file), default_savedir )
+ local uci = luci.model.uci.cursor( luci.fs.dirname(file), default_savedir )
- local sd, err = uci:get_all( luci.fs.basename(file) )
+ local sd, err = uci:get_all( luci.fs.basename(file) )
+
+ if not sd then
+ return false, ERR.UCILOAD(so, err)
+ end
- if not sd then
- return false, ERR.UCILOAD(so, err)
+ table.insert( schemes, sd )
end
- table.insert( schemes, sd )
+ local ok, err = self:_read_scheme_parts( so, schemes )
+ if ok and alias then self.packages[alias] = self.packages[scheme] end
+ return ok, err
+ else
+ return false, so:error(ERR.SME_FIND(so, self.schemedir))
end
-
- return self:_read_scheme_parts( so, schemes )
else
- return false, so:error(ERR.SME_FIND(so, self.schemedir))
+ local sc = loadfile(bc)
+ if sc then
+ self.packages[scheme] = sc()
+ return true
+ else
+ return false, so:error(ERR.SME_READ(so,bc))
+ end
end
end
function UVL._read_scheme_parts( self, scheme, schemes )
-- helper function to check for required fields
- local function _req( c, t, r )
+ local function _req( t, n, c, r )
for i, v in ipairs(r) do
- if not t[v] then
- return false, ERR.SME_REQFLD({c,t}, v)
+ if not c[v] then
+ local p, o = scheme:sid(), nil
+
+ if t == TYPE_SECTION then
+ o = section( scheme, nil, p, n )
+ elseif t == TYPE_OPTION then
+ o = option( scheme, nil, p, '(nil)', n )
+ elseif t == TYPE_ENUM then
+ o = enum( scheme, nil, p, '(nil)', '(nil)', n )
+ end
+
+ return false, ERR.SME_REQFLD(o,v)
end
end
return true
if c == TYPE_SECTION then
k = "package"
n = 1
- elseif c == TYPE_VARIABLE then
+ elseif c == TYPE_OPTION then
k = "section"
n = 2
elseif c == TYPE_ENUM then
for k, v in pairs( conf ) do
if v['.type'] == 'section' then
- ok, err = _req( TYPE_SECTION, v, { "name", "package" } )
+ ok, err = _req( TYPE_SECTION, k, v, { "name", "package" } )
if err then return false, scheme:error(err) end
local r, err = _ref( TYPE_SECTION, v )
for k, v in pairs( conf ) do
if v['.type'] == "variable" then
- ok, err = _req( TYPE_VARIABLE, v, { "name", "section" } )
+ ok, err = _req( TYPE_OPTION, k, v, { "name", "section" } )
if err then return false, scheme:error(err) end
- local r, err = _ref( TYPE_VARIABLE, v )
+ local r, err = _ref( TYPE_OPTION, v )
if err then return false, scheme:error(err) end
local p = self.packages[r[1]]
for k, v in pairs( conf ) do
if v['.type'] == "enum" then
- ok, err = _req( TYPE_ENUM, v, { "value", "variable" } )
+ ok, err = _req( TYPE_ENUM, k, v, { "value", "variable" } )
if err then return false, scheme:error(err) end
local r, err = _ref( TYPE_ENUM, v )
local oo = so:option(r[3])
local eo = oo:enum(v.value)
- if t.type ~= "enum" then
+ if t.type ~= "enum" and t.type ~= "reference" then
return false, scheme:error(ERR.SME_EBADTYPE(eo))
end
validator = value:gsub("^exec:","")
elseif value:match("^lua:") then
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 = { ... }
+ for _, v in ipairs(values) do
+ local ok, match =
+ luci.util.copcall( string.match, v, pattern )
+
+ if not ok then
+ return false, match
+ elseif not match then
+ return false,
+ 'Value "%s" does not match pattern "%s"' % {
+ v, pattern
+ }
+ end
+ end
+ return true
+ end
end
if validator then
uvlitem = luci.util.class()
function uvlitem.cid(self)
- return table.concat( self.cref, '.' )
+ if #self.cref == 1 then
+ return self.cref[1]
+ else
+ local r = { unpack(self.cref) }
+ local c = self.c
+ if c and c[r[2]] and c[r[2]]['.anonymous'] and c[r[2]]['.index'] then
+ r[2] = '@' .. c[r[2]]['.type'] ..
+ '[' .. tostring(c[r[2]]['.index']) .. ']'
+ end
+ return table.concat( r, '.' )
+ end
end
function uvlitem.sid(self)
end
function uvlitem.scheme(self, opt)
- local s
-
- if #self.sref == 4 or #self.sref == 3 then
- s = self.s
- .packages[self.sref[1]]
- .variables[self.sref[2]][self.sref[3]]
- elseif #self.sref == 2 then
- s = self.s
- .packages[self.sref[1]]
- .sections[self.sref[2]]
- else
- s = self.s
- .packages[self.sref[1]]
+ local s = self._scheme
+
+ if not s then
+ s = self.s and self.s.packages and self.s.packages[self.sref[1]]
+ if #self.sref == 2 then
+ s = s and s.sections and s.sections[self.sref[2]]
+ elseif #self.sref > 2 then
+ s = s and s.variables and s.variables[self.sref[2]]
+ and s.variables[self.sref[2]][self.sref[3]]
+ end
+ self._scheme = s
end
- if s and opt then
- return s[opt]
- elseif s then
- return s
- end
+ return opt and (s and s[opt]) or s
end
function uvlitem.config(self, opt)
- local c
+ local c = self._config
- if #self.cref == 4 or #self.cref == 3 then
- c = self.c and self.c[self.cref[2]] or nil
- c = c and c[self.cref[3]] or nil
- elseif #self.cref == 2 then
- c = self.c and self.c[self.cref[2]] or nil
- else
+ if not c then
c = self.c
+ if #self.cref > 1 then
+ c = c and self.c[self.cref[2]]
+ end
+ if #self.cref > 2 then
+ c = c and c[self.cref[3]]
+ end
+ self._config = c
end
- if c and opt then
- return c[opt]
- elseif c then
- return c
- end
+ return opt and (c and c[opt]) or c
end
function uvlitem.title(self)
end
function uvlitem.type(self)
- if self.t == luci.uvl.TYPE_CONFIG then
- return 'config'
- elseif self.t == luci.uvl.TYPE_SECTION then
- return 'section'
- elseif self.t == luci.uvl.TYPE_OPTION then
- return 'option'
- elseif self.t == luci.uvl.TYPE_ENUM then
- return 'enum'
- end
+ local _t = {
+ [TYPE_CONFIG] = 'config',
+ [TYPE_SECTION] = 'section',
+ [TYPE_OPTION] = 'option',
+ [TYPE_ENUM] = 'enum'
+ }
+
+ return _t[self.t]
end
function uvlitem.error(self, ...)
if self.p then
return self.p
elseif #self.cref == 3 or #self.cref == 4 then
- return luci.uvl.section( self.s, self.c, self.cref[1], self.cref[2] )
+ return section( self.s, self.c, self.cref[1], self.cref[2] )
elseif #self.cref == 2 then
- return luci.uvl.config( self.s, self.c, self.cref[1] )
+ return config( self.s, self.c, self.cref[1] )
else
return nil
end
end
+-- Shared cache
+uvlitem._ucicache = {}
+
function uvlitem._loadconf(self, co, c)
+ co = co or self._ucicache[c]
if not co then
- local uci, err = luci.model.uci.cursor(), nil
- co, err = uci:get_all(c)
+ local err
+ co, err = cursor:get_all(c)
if err then
self:error(ERR.UCILOAD(self, err))
end
+
+ self._ucicache[c] = co
end
return co
end
self.sref = { c }
self.c = self:_loadconf(co, c)
self.s = scheme
- self.t = luci.uvl.TYPE_SCHEME
+ self.t = TYPE_SCHEME
end
--- Add an error to scheme.
--- Get an associated config object.
-- @return Config instance
function scheme.config(self)
- local co = luci.uvl.config( self.s, self.cref[1] )
+ local co = config( self.s, self.cref[1] )
co.p = self
return co
local v = { }
if self.s.packages[self.sref[1]].sections then
for o, _ in pairs( self.s.packages[self.sref[1]].sections ) do
- table.insert( v, luci.uvl.option(
+ table.insert( v, option(
self.s, self.c, self.cref[1], self.cref[2], o
) )
end
-- @param s Section to select
-- @return Section instance
function scheme.section(self, s)
- local so = luci.uvl.section( self.s, self.c, self.cref[1], s )
+ local so = section( self.s, self.c, self.cref[1], s )
so.p = self
return so
self.sref = { c }
self.c = self:_loadconf(co, c)
self.s = scheme
- self.t = luci.uvl.TYPE_CONFIG
+ self.t = TYPE_CONFIG
end
--- Get all section objects associated with this config.
self.sref = { c, co and co[s] and co[s]['.type'] or s }
self.c = self:_loadconf(co, c)
self.s = scheme
- self.t = luci.uvl.TYPE_SECTION
+ self.t = TYPE_SECTION
end
--- Get all option objects associated with this section.
self.sref = { c, co and co[s] and co[s]['.type'] or s, o }
self.c = self:_loadconf(co, c)
self.s = scheme
- self.t = luci.uvl.TYPE_OPTION
+ self.t = TYPE_OPTION
end
--- Get the value of this option.
-- @return The associated configuration value
function option.value(self)
- return self:config()
+ local v = self:config() or self:scheme('default')
+ if v and self:scheme('multival') then
+ v = luci.util.split( v, "%s+", nil, true )
+ end
+ return v
end
--- Get the associated section information in scheme.
self.sref = { c, co and co[s] and co[s]['.type'] or s, o, v }
self.c = self:_loadconf(co, c)
self.s = scheme
- self.t = luci.uvl.TYPE_ENUM
+ self.t = TYPE_ENUM
end