X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcbi%2Fluasrc%2Fcbi.lua;h=6432a2590d034782121e85d14b144e6905511cf5;hp=9a94aba1d43d467c7a24a6adf446df7a8ec45444;hb=d0442f8aab131cb53deaed1da81e09001cc33a82;hpb=d68d03085a8cd7100b1b7f9c0e599f9329d6010c diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index 9a94aba1d..6432a2590 100644 --- a/libs/cbi/luasrc/cbi.lua +++ b/libs/cbi/luasrc/cbi.lua @@ -29,10 +29,9 @@ module("luci.cbi", package.seeall) require("luci.template") require("luci.util") require("luci.http") -require("luci.model.uci") require("luci.uvl") -local uci = luci.model.uci +local uci = require("luci.model.uci") local class = luci.util.class local instanceof = luci.util.instanceof @@ -40,30 +39,35 @@ FORM_NODATA = 0 FORM_VALID = 1 FORM_INVALID = -1 +AUTO = true + CREATE_PREFIX = "cbi.cts." REMOVE_PREFIX = "cbi.rts." -- Loads a CBI map from given file, creating an environment and returns it function load(cbimap, ...) require("luci.fs") - require("luci.i18n") + local i18n = require "luci.i18n" require("luci.config") require("luci.util") local cbidir = luci.util.libpath() .. "/model/cbi/" - local func, err = loadfile(cbidir..cbimap..".lua") - - if not func then - return nil - end + local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua") + assert(func, err) luci.i18n.loadc("cbi") + luci.i18n.loadc("uvl") + + local env = { + translate=i18n.translate, + translatef=i18n.translatef, + arg={...} + } - luci.util.resfenv(func) - luci.util.updfenv(func, luci.cbi) - luci.util.extfenv(func, "translate", luci.i18n.translate) - luci.util.extfenv(func, "translatef", luci.i18n.translatef) - luci.util.extfenv(func, "arg", {...}) + setfenv(func, setmetatable(env, {__index = + function(tbl, key) + return rawget(tbl, key) or _M[key] or _G[key] + end})) local maps = {func()} @@ -71,23 +75,69 @@ function load(cbimap, ...) if not instanceof(map, Node) then error("CBI map returns no valid map object!") return nil + else + map:prepare() end end 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) 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 -function _uvl_strip_remote_dependencies(deps) + 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_]+$") then + if k:match("^[%w_]+$") and type(v) == "string" then clean[k] = v end end - + return clean end @@ -118,6 +168,13 @@ function Node._i18n(self, config, section, option, title, description) end end +-- Prepare nodes +function Node.prepare(self, ...) + for k, child in ipairs(self.children) do + child:prepare(...) + end +end + -- Append child nodes function Node.append(self, obj) table.insert(self.children, obj) @@ -173,12 +230,16 @@ function Map.__init__(self, config, ...) self.config = config self.parsechain = {self.config} self.template = "cbi/map" - if not uci.load_config(self.config) then + self.apply_on_parse = nil + self.uci = uci.cursor() + self.save = true + 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.get_scheme(self, sectiontype, option) @@ -190,15 +251,6 @@ function Map.get_scheme(self, sectiontype, option) end end -function Map.render(self, ...) - if self.stateful then - uci.load_state(self.config) - else - uci.load_config(self.config) - end - Node.render(self, ...) -end - -- Chain foreign config function Map.chain(self, config) @@ -206,35 +258,45 @@ function Map.chain(self, config) end -- Use optimized UCI writing -function Map.parse(self, ...) - if self.stateful then - uci.load_state(self.config) - else - uci.load_config(self.config) - end - - Node.parse(self, ...) - - for i, config in ipairs(self.parsechain) do - uci.save_config(config) - end - if luci.http.formvalue("cbi.apply") then - for i, config in ipairs(self.parsechain) do - uci.commit(config) - if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then - luci.util.exec(luci.config.uci_oncommit[config]) - end +function Map.parse(self) + Node.parse(self) - -- Refresh data because commit changes section names - uci.load_config(config) + if self.save then + for i, config in ipairs(self.parsechain) do + self.uci:save(config) end + if luci.http.formvalue("cbi.apply") then + for i, config in ipairs(self.parsechain) do + self.uci:commit(config) + + -- Refresh data because commit changes section names + self.uci:load(config) + end + if self.apply_on_parse then + self.uci:apply(self.parsechain) + else + self._apply = function() + local cmd = self.uci:apply(self.parsechain, true) + return io.popen(cmd) + end + end - -- Reparse sections - Node.parse(self, ...) + -- Reparse sections + Node.parse(self, true) + end + for i, config in ipairs(self.parsechain) do + self.uci:unload(config) + end end - for i, config in ipairs(self.parsechain) do - uci.unload(config) +end + +function Map.render(self, ...) + Node.render(self, ...) + if self._apply then + local fp = self._apply() + fp:read("*a") + fp:close() end end @@ -251,35 +313,35 @@ end -- UCI add function Map.add(self, sectiontype) - return uci.add(self.config, sectiontype) + return self.uci:add(self.config, sectiontype) end -- UCI set function Map.set(self, section, option, value) if option then - return uci.set(self.config, section, option, value) + return self.uci:set(self.config, section, option, value) else - return uci.set(self.config, section, value) + return self.uci:set(self.config, section, value) end end -- UCI del function Map.del(self, section, option) if option then - return uci.delete(self.config, section, option) + return self.uci:delete(self.config, section, option) else - return uci.delete(self.config, section) + return self.uci:delete(self.config, section) end end -- UCI get function Map.get(self, section, option) if not section then - return uci.get_all(self.config) + return self.uci:get_all(self.config) elseif option then - return uci.get(self.config, section, option) + return self.uci:get(self.config, section, option) else - return uci.get_all(self.config, section) + return self.uci:get_all(self.config, section) end end @@ -310,17 +372,17 @@ function SimpleForm.parse(self, ...) if luci.http.formvalue("cbi.submit") then Node.parse(self, 1, ...) end - + local valid = true - for k, j in ipairs(self.children) do + for k, j in ipairs(self.children) do for i, v in ipairs(j.children) do - valid = valid + valid = valid and (not v.tag_missing or not v.tag_missing[1]) and (not v.tag_invalid or not v.tag_invalid[1]) end end - - local state = + + local state = not luci.http.formvalue("cbi.submit") and 0 or valid and 1 or -1 @@ -356,9 +418,9 @@ function SimpleForm.field(self, class, ...) if not section then section = self:section(SimpleSection) end - + if instanceof(class, AbstractValue) then - local obj = class(self, ...) + local obj = class(self, section, ...) obj.track_missing = true section:append(obj) return obj @@ -382,6 +444,11 @@ function SimpleForm.get(self, section, option) end +function SimpleForm.get_scheme() + return nil +end + + --[[ AbstractSection @@ -395,6 +462,10 @@ function AbstractSection.__init__(self, map, sectiontype, ...) self.config = map.config self.optionals = {} self.defaults = {} + self.fields = {} + self.tag_error = {} + self.tag_invalid = {} + self.tag_deperror = {} self.optional = true self.addremove = false @@ -403,29 +474,29 @@ end -- Appends a new option function AbstractSection.option(self, class, option, ...) - -- Autodetect form UVL - if not class or type(class) == "boolean" - and self.map:get_scheme(self.sectiontype, option) then + -- 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" + class = Flag elseif vs.type == "list" then - class = "DynamicList" + class = DynamicList elseif vs.type == "enum" or vs.type == "reference" then - class = "ListValue" + class = ListValue else - class = "Value" + class = Value end end - + if instanceof(class, AbstractValue) then local obj = class(self.map, self, option, ...) Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...) self:append(obj) + self.fields[option] = obj return obj - elseif not class or type(class) == "boolean" then + elseif class == true then error("No valid class was given and autodetection failed.") else error("class must be a descendant of AbstractValue") @@ -502,9 +573,9 @@ end -- Creates the section function AbstractSection.create(self, section) local stat - + if section then - stat = self.map:set(section, nil, self.sectiontype) + stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype) else section = self.map:add(self.sectiontype) stat = section @@ -540,15 +611,19 @@ function Table.__init__(self, form, data, ...) local datasource = {} datasource.config = "table" self.data = data - + function datasource.get(self, section, option) return data[section] and data[section][option] end - + function datasource.del(...) return true end - + + function datasource.get_scheme() + return nil + end + AbstractSection.__init__(self, datasource, "table", ...) self.template = "cbi/tblsection" self.rowcolors = true @@ -565,11 +640,11 @@ end function Table.cfgsections(self) local sections = {} - + for i, v in luci.util.kspairs(self.data) do table.insert(sections, i) end - + return sections end @@ -600,11 +675,10 @@ function NamedSection.__init__(self, map, section, stype, ...) self.section = section end -function NamedSection.parse(self) +function NamedSection.parse(self, novld) local s = self.section local active = self:cfgvalue(s) - if self.addremove then local path = self.config.."."..s if active then -- Remove the section @@ -623,6 +697,10 @@ function NamedSection.parse(self) AbstractSection.parse_dynamic(self, s) if luci.http.formvalue("cbi.submit") 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) end @@ -659,7 +737,7 @@ end -- Return all matching UCI sections for this TypedSection function TypedSection.cfgsections(self) local sections = {} - uci.foreach(self.map.config, self.sectiontype, + self.map.uci:foreach(self.map.config, self.sectiontype, function (section) if self:checkscope(section[".name"]) then table.insert(sections, section[".name"]) @@ -674,14 +752,42 @@ function TypedSection.depends(self, option, value) table.insert(self.deps, {option=option, value=value}) end -function TypedSection.parse(self) +function TypedSection.parse(self, novld) + if self.addremove then + -- Remove + local crval = REMOVE_PREFIX .. self.config + local name = luci.http.formvaluetable(crval) + for k,v in pairs(name) do + if k:sub(-2) == ".x" then + k = k:sub(1, #k - 2) + end + if self:cfgvalue(k) and self:checkscope(k) then + self:remove(k) + end + end + end + + local co + for i, k in ipairs(self:cfgsections()) do + AbstractSection.parse_dynamic(self, k) + if luci.http.formvalue("cbi.submit") then + Node.parse(self, k) + + 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 + if self.addremove then -- Create + local created local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype local name = luci.http.formvalue(crval) if self.anonymous then if name then - self:create() + created = self:create() end else if name then @@ -696,32 +802,19 @@ function TypedSection.parse(self) self.err_invalid = true end - if name and name:len() > 0 then - self:create(name) + if name and #name > 0 then + created = self:create(name) and name + if not created then + self.invalid_cts = true + end end end end - -- Remove - crval = REMOVE_PREFIX .. self.config - name = luci.http.formvaluetable(crval) - for k,v in pairs(name) do - luci.util.perror(k) - luci.util.perror(self:cfgvalue(k)) - luci.util.perror(self:checkscope(k)) - if self:cfgvalue(k) and self:checkscope(k) then - self:remove(k) - end + if created then + AbstractSection.parse_optionals(self, created) end end - - for i, k in ipairs(self:cfgsections()) do - AbstractSection.parse_dynamic(self, k) - if luci.http.formvalue("cbi.submit") then - Node.parse(self, k) - end - AbstractSection.parse_optionals(self, k) - end end -- Verifies scope of sections @@ -776,25 +869,35 @@ function AbstractValue.__init__(self, map, section, option, ...) self.config = map.config self.tag_invalid = {} self.tag_missing = {} + self.tag_reqerror = {} self.tag_error = {} self.deps = {} - self.cast = "string" + --self.cast = "string" self.track_missing = false - self.rmempty = false + --self.rmempty = false self.default = nil self.size = nil self.optional = 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) - self.rmempty = not vs.required - self.cast = (vs.type == "list") and "list" or "string" + if self.rmempty == nil then + self.rmempty = not vs.required + end + 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) @@ -803,13 +906,9 @@ function AbstractValue.__init__(self, map, section, option, ...) end end end - - if self.value and vs.values and not self.override_values then - for k, v in pairs(vs.values) do - self:value(k, v) - end - end end + + self.cast = self.cast or "string" end -- Add a dependencie to another section field @@ -821,7 +920,7 @@ function AbstractValue.depends(self, field, value) else deps = field end - + table.insert(self.deps, {deps=deps, add=""}) end @@ -853,7 +952,7 @@ function AbstractValue.parse(self, section) local fvalue = self:formvalue(section) local cvalue = self:cfgvalue(section) - if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI + if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI fvalue = self:transform(self:validate(fvalue, section)) if not fvalue then self.tag_invalid[section] = true @@ -903,7 +1002,9 @@ end -- Return the UCI value of this object function AbstractValue.cfgvalue(self, section) local value = self.map:get(section, self.option) - if not self.cast or self.cast == type(value) then + if not value then + return nil + elseif not self.cast or self.cast == type(value) then return value elseif self.cast == "string" then if type(value) == "table" then @@ -1012,18 +1113,42 @@ ListValue = class(AbstractValue) function ListValue.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/lvalue" + self.keylist = {} self.vallist = {} - self.size = 1 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 + end + val = val or key table.insert(self.keylist, tostring(key)) table.insert(self.vallist, tostring(val)) - + for i, deps in ipairs({...}) do table.insert(self.deps, {add = "-"..key, deps=deps}) end @@ -1049,6 +1174,7 @@ MultiValue = class(AbstractValue) function MultiValue.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/mvalue" + self.keylist = {} self.vallist = {} @@ -1065,6 +1191,10 @@ function MultiValue.render(self, ...) end function MultiValue.value(self, key, val) + if luci.util.contains(self.keylist, key) then + return + end + val = val or key table.insert(self.keylist, tostring(key)) table.insert(self.vallist, tostring(val)) @@ -1101,6 +1231,16 @@ function StaticList.__init__(self, ...) MultiValue.__init__(self, ...) self.cast = "table" self.valuelist = self.cfgvalue + + 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.values and not self.override_values then + for k, v in pairs(vs.values) do + self:value(k, v) + end + end + end end function StaticList.validate(self, value) @@ -1108,7 +1248,7 @@ function StaticList.validate(self, value) local valid = {} for i, v in ipairs(value) do - if luci.util.contains(self.valuelist, v) then + if luci.util.contains(self.vallist, v) then table.insert(valid, v) end end @@ -1122,7 +1262,6 @@ function DynamicList.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/dynlist" self.cast = "table" - self.keylist = {} self.vallist = {} end @@ -1133,17 +1272,19 @@ function DynamicList.value(self, key, val) table.insert(self.vallist, tostring(val)) end -function DynamicList.validate(self, value, section) +function DynamicList.formvalue(self, section) + local value = AbstractValue.formvalue(self, section) value = (type(value) == "table") and value or {value} - + local valid = {} for i, v in ipairs(value) do - if v and #v > 0 and - not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) then + if v and #v > 0 + and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i) + and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then table.insert(valid, v) end end - + return valid end @@ -1169,4 +1310,4 @@ function Button.__init__(self, ...) self.template = "cbi/button" self.inputstyle = nil self.rmempty = true -end \ No newline at end of file +end