require("luci.template")
local util = require("luci.util")
require("luci.http")
-require("luci.uvl")
--local event = require "luci.sys.event"
local fs = require("nixio.fs")
local uci = require("luci.model.uci")
+local datatypes = require("luci.cbi.datatypes")
local class = util.class
local instanceof = util.instanceof
local cbidir = luci.util.libpath() .. "/model/cbi/"
local func, err
- if fs.access(cbimap) then
- func, err = loadfile(cbimap)
- elseif fs.access(cbidir..cbimap..".lua") then
+ if fs.access(cbidir..cbimap..".lua") then
func, err = loadfile(cbidir..cbimap..".lua")
- elseif fs.access(cbidir..cbimap..".lua.gz") then
- func, err = loadfile(cbidir..cbimap..".lua.gz")
+ elseif fs.access(cbimap) then
+ func, err = loadfile(cbimap)
else
func, err = nil, "Model '" .. cbimap .. "' not found!"
end
assert(func, err)
- luci.i18n.loadc("cbi")
- luci.i18n.loadc("uvl")
+ luci.i18n.loadc("base")
local env = {
translate=i18n.translate,
return maps
end
-local function _uvl_validate_section(node, name)
- local co = node.map:get()
-
- luci.uvl.STRICT_UNKNOWN_OPTIONS = false
- luci.uvl.STRICT_UNKNOWN_SECTIONS = false
-
- local function tag_fields(e)
- if e.option and node.fields[e.option] then
- if node.fields[e.option].error then
- node.fields[e.option].error[name] = e
- else
- node.fields[e.option].error = { [name] = e }
- end
- elseif e.childs then
- for _, c in ipairs(e.childs) do tag_fields(c) end
- end
- end
-
- local function tag_section(e)
- local s = { }
- for _, c in ipairs(e.childs or { e }) do
- if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
- table.insert( s, c.childs[1]:string() )
- else
- table.insert( s, c:string() )
- end
- end
- if #s > 0 then
- if node.error then
- node.error[name] = s
- else
- node.error = { [name] = s }
- end
- end
- end
-
- local stat, err = node.map.validator:validate_section(node.config, name, co)
- if err then
- node.map.save = false
- tag_fields(err)
- tag_section(err)
- end
-
-end
-
-local function _uvl_strip_remote_dependencies(deps)
- local clean = {}
-
- for k, v in pairs(deps) do
- k = k:gsub("%$config%.%$section%.", "")
- if k:match("^[%w_]+$") and type(v) == "string" then
- clean[k] = v
- end
- end
-
- return clean
-end
-
-- Node pseudo abstract class
Node = class()
end
-- hook helper
+function Node._run_hook(self, hook)
+ if type(self[hook]) == "function" then
+ return self[hook](self)
+ end
+end
+
function Node._run_hooks(self, ...)
local f
local r = false
if not self.uci:load(self.config) then
error("Unable to read UCI data: " .. self.config)
end
-
- self.validator = luci.uvl.UVL()
- self.scheme = self.validator:get_scheme(self.config)
end
function Map.formvalue(self, key)
-- Use optimized UCI writing
function Map.parse(self, readinput, ...)
self.readinput = (readinput ~= false)
+ self:_run_hooks("on_parse")
if self:formvalue("cbi.skip") then
self.state = FORM_SKIP
end
function Delegator.set(self, name, node)
- if type(node) == "table" and getmetatable(node) == nil then
- node = Compound(unpack(node))
- end
- assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
assert(not self.nodes[name], "Duplicate entry")
self.nodes[name] = node
end
function Delegator.insert_after(self, name, after)
- local n = #self.chain
+ local n = #self.chain + 1
for k, v in ipairs(self.chain) do
- if v == state then
+ if v == after then
n = k + 1
break
end
end
function Delegator.get(self, name)
- return self.nodes[name]
+ local node = self.nodes[name]
+
+ if type(node) == "string" then
+ node = load(node, name)
+ end
+
+ if type(node) == "table" and getmetatable(node) == nil then
+ node = Compound(unpack(node))
+ end
+
+ return node
end
function Delegator.parse(self, ...)
return FORM_NODATA
elseif stat > FORM_PROCEED
and (not newcurrent or not self:get(newcurrent)) then
- self:_run_hooks("on_done")
- return FORM_DONE
+ return self:_run_hook("on_done") or FORM_DONE
else
self.current = newcurrent or self.current
self.active = self:get(self.current)
if type(self.active) ~= "function" then
- self.active:parse(false)
- return FROM_PROCEED
+ self.active:populate_delegator(self)
+ local stat = self.active:parse(false)
+ if stat == FORM_SKIP then
+ return self:parse(...)
+ else
+ return FORM_PROCEED
+ end
else
return self:parse(...)
end
return FORM_SKIP
end
+ if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
+ return FORM_DONE
+ end
+
if self:submitstate() then
Node.parse(self, 1, ...)
end
}
end
+-- Check whether the section has tabs
+function AbstractSection.has_tabs(self)
+ return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
+end
+
-- Appends a new option
function AbstractSection.option(self, class, option, ...)
- -- Autodetect from UVL
- if class == true and self.map:get_scheme(self.sectiontype, option) then
- local vs = self.map:get_scheme(self.sectiontype, option)
- if vs.type == "boolean" then
- class = Flag
- elseif vs.type == "list" then
- class = DynamicList
- elseif vs.type == "enum" or vs.type == "reference" then
- class = ListValue
- else
- class = Value
- end
- end
-
if instanceof(class, AbstractValue) then
local obj = class(self.map, self, option, ...)
self:append(obj)
local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
for k,v in ipairs(self.children) do
- if v.optional and not v:cfgvalue(section) then
+ if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
if field == v.option then
field = nil
self.map.proceed = true
-- Defaults
self.addremove = false
-
- -- Use defaults from UVL
- if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
- local vs = self.map:get_scheme(self.sectiontype)
- self.addremove = not vs.unique and not vs.required
- self.dynamic = vs.dynamic
- self.title = self.title or vs.title
- self.description = self.description or vs.descr
- end
-
self.template = "cbi/nsection"
self.section = section
end
AbstractSection.parse_dynamic(self, s)
if self.map:submitstate() then
Node.parse(self, s)
-
- if not novld and not self.override_scheme and self.map.scheme then
- _uvl_validate_section(self, s)
- end
end
AbstractSection.parse_optionals(self, s)
function TypedSection.__init__(self, map, type, ...)
AbstractSection.__init__(self, map, type, ...)
- self.template = "cbi/tsection"
+ self.template = "cbi/tsection"
self.deps = {}
self.anonymous = false
-
- -- Use defaults from UVL
- if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
- local vs = self.map:get_scheme(self.sectiontype)
- self.addremove = not vs.unique and not vs.required
- self.dynamic = vs.dynamic
- self.anonymous = not vs.named
- self.title = self.title or vs.title
- self.description = self.description or vs.descr
- end
end
-- Return all matching UCI sections for this TypedSection
AbstractSection.parse_dynamic(self, k)
if self.map:submitstate() then
Node.parse(self, k, novld)
-
- if not novld and not self.override_scheme and self.map.scheme then
- _uvl_validate_section(self, k)
- end
end
AbstractSection.parse_optionals(self, k)
end
self.tag_reqerror = {}
self.tag_error = {}
self.deps = {}
+ self.subdeps = {}
--self.cast = "string"
self.track_missing = false
end
function AbstractValue.prepare(self)
- -- Use defaults from UVL
- if not self.override_scheme
- and self.map:get_scheme(self.section.sectiontype, self.option) then
- local vs = self.map:get_scheme(self.section.sectiontype, self.option)
- if self.cast == nil then
- self.cast = (vs.type == "list") and "list" or "string"
- end
- self.title = self.title or vs.title
- self.description = self.description or vs.descr
- if self.default == nil then
- self.default = vs.default
- end
-
- if vs.depends and not self.override_dependencies then
- for i, deps in ipairs(vs.depends) do
- deps = _uvl_strip_remote_dependencies(deps)
- if next(deps) then
- self:depends(deps)
- end
- end
- end
- end
-
self.cast = self.cast or "string"
end
-- Render if this value exists or if it is mandatory
function AbstractValue.render(self, s, scope)
- if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
+ if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
scope = scope or {}
scope.section = s
scope.cbid = self:cbid(s)
-- Return the UCI value of this object
function AbstractValue.cfgvalue(self, section)
- local value = self.map:get(section, self.option)
+ local value = (self.error and self.error[section] == "invalid")
+ and self:formvalue(section) or self.map:get(section, self.option)
if not value then
return nil
elseif not self.cast or self.cast == type(value) then
-- Validate the form value
function AbstractValue.validate(self, value)
+ if self.datatype and value and datatypes[self.datatype] then
+ if type(value) == "table" then
+ local v
+ for _, v in ipairs(value) do
+ if v and #v > 0 and not datatypes[self.datatype](v) then
+ return nil
+ end
+ end
+ else
+ if not datatypes[self.datatype](value) then
+ return nil
+ end
+ end
+ end
return value
end
self.widget = "select"
end
-function ListValue.prepare(self, ...)
- AbstractValue.prepare(self, ...)
- if not self.override_scheme
- and self.map:get_scheme(self.section.sectiontype, self.option) then
- local vs = self.map:get_scheme(self.section.sectiontype, self.option)
- if self.value and vs.valuelist and not self.override_values then
- for k, v in ipairs(vs.valuelist) do
- local deps = {}
- if not self.override_dependencies
- and vs.enum_depends and vs.enum_depends[v.value] then
- for i, dep in ipairs(vs.enum_depends[v.value]) do
- table.insert(deps, _uvl_strip_remote_dependencies(dep))
- end
- end
- self:value(v.value, v.title or v.value, unpack(deps))
- end
- end
- end
-end
-
function ListValue.value(self, key, val, ...)
if luci.util.contains(self.keylist, key) then
return
table.insert(self.vallist, tostring(val))
for i, deps in ipairs({...}) do
- table.insert(self.deps, {add = "-"..key, deps=deps})
+ self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
end
end