X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcbi%2Fluasrc%2Fcbi.lua;h=12bf18c2adfc9be39ee0269537814e50957d863b;hp=4b4bf39059836a40c5be62b3375b7eab2ca6bca4;hb=d463d0b8357764ca9018549fa624c428f56fcd87;hpb=fbae92e522692d9bb34501ad9a805da794a47123 diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index 4b4bf3905..12bf18c2a 100644 --- a/libs/cbi/luasrc/cbi.lua +++ b/libs/cbi/luasrc/cbi.lua @@ -29,12 +29,20 @@ 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 +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, ...) @@ -61,7 +69,7 @@ function load(cbimap, ...) local maps = {func()} for i, map in ipairs(maps) do - if not instanceof(map, Map) then + if not instanceof(map, Node) then error("CBI map returns no valid map object!") return nil end @@ -70,6 +78,21 @@ function load(cbimap, ...) return maps end + +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() @@ -86,10 +109,10 @@ function Node._i18n(self, config, section, option, title, description) -- i18n loaded? if type(luci.i18n) == "table" then - local key = config:gsub("[^%w]+", "") + local key = config and config:gsub("[^%w]+", "") or "" if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end - if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end + if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end self.title = title or luci.i18n.translate( key, option or section or config ) self.description = description or luci.i18n.translate( key .. "_desc", "" ) @@ -134,6 +157,10 @@ function Template.__init__(self, template) self.template = template end +function Template.render(self) + luci.template.render(self.template, {self=self}) +end + --[[ Map - A map describing a configuration file @@ -147,9 +174,22 @@ function Map.__init__(self, config, ...) self.config = config self.parsechain = {self.config} self.template = "cbi/map" - if not uci.load(self.config) then + self.uci = uci.cursor() + 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) + if not option then + return self.scheme and self.scheme.sections[sectiontype] + else + return self.scheme and self.scheme.variables[sectiontype] + and self.scheme.variables[sectiontype][option] + end end @@ -161,19 +201,17 @@ end -- Use optimized UCI writing function Map.parse(self, ...) Node.parse(self, ...) + for i, config in ipairs(self.parsechain) do - uci.save(config) + self.uci:save(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 + self.uci:commit(config) + self.uci:apply(config) -- Refresh data because commit changes section names - uci.unload(config) - uci.load(config) + self.uci:load(config) end -- Reparse sections @@ -181,7 +219,7 @@ function Map.parse(self, ...) end for i, config in ipairs(self.parsechain) do - uci.unload(config) + self.uci:unload(config) end end @@ -198,40 +236,144 @@ 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 --[[ +Page - A simple node +]]-- + +Page = class(Node) +Page.__init__ = Node.__init__ +Page.parse = function() end + + +--[[ +SimpleForm - A Simple non-UCI form +]]-- +SimpleForm = class(Node) + +function SimpleForm.__init__(self, config, title, description, data) + Node.__init__(self, title, description) + self.config = config + self.data = data or {} + self.template = "cbi/simpleform" + self.dorender = true +end + +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 i, v in ipairs(j.children) do + 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 = + not luci.http.formvalue("cbi.submit") and 0 + or valid and 1 + or -1 + + self.dorender = not self.handle or self:handle(state, self.data) ~= false +end + +function SimpleForm.render(self, ...) + if self.dorender then + Node.render(self, ...) + end +end + +function SimpleForm.section(self, class, ...) + if instanceof(class, AbstractSection) then + local obj = class(self, ...) + self:append(obj) + return obj + else + error("class must be a descendent of AbstractSection") + end +end + +-- Creates a child field +function SimpleForm.field(self, class, ...) + local section + for k, v in ipairs(self.children) do + if instanceof(v, SimpleSection) then + section = v + break + end + end + if not section then + section = self:section(SimpleSection) + end + + if instanceof(class, AbstractValue) then + local obj = class(self, ...) + obj.track_missing = true + section:append(obj) + return obj + else + error("class must be a descendent of AbstractValue") + end +end + +function SimpleForm.set(self, section, option, value) + self.data[option] = value +end + + +function SimpleForm.del(self, section, option) + self.data[option] = nil +end + + +function SimpleForm.get(self, section, option) + return self.data[option] +end + + +function SimpleForm.get_scheme() + return nil +end + + + +--[[ AbstractSection ]]-- AbstractSection = class(Node) @@ -251,15 +393,31 @@ 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, option, ...) + local obj = class(self.map, self, option, ...) Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...) self:append(obj) return obj + elseif class == true then + error("No valid class was given and autodetection failed.") else - error("class must be a descendent of AbstractValue") + error("class must be a descendant of AbstractValue") end end @@ -333,7 +491,7 @@ end -- Creates the section function AbstractSection.create(self, section) local stat - + if section then stat = self.map:set(section, nil, self.sectiontype) else @@ -357,19 +515,82 @@ function AbstractSection.create(self, section) end +SimpleSection = class(AbstractSection) + +function SimpleSection.__init__(self, form, ...) + AbstractSection.__init__(self, form, nil, ...) + self.template = "cbi/nullsection" +end + + +Table = class(AbstractSection) + +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 + self.anonymous = true +end + +function Table.parse(self) + for i, k in ipairs(self:cfgsections()) do + if luci.http.formvalue("cbi.submit") then + Node.parse(self, k) + end + end +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 + + --[[ NamedSection - A fixed configuration section defined by its name ]]-- NamedSection = class(AbstractSection) -function NamedSection.__init__(self, map, section, type, ...) - AbstractSection.__init__(self, map, type, ...) +function NamedSection.__init__(self, map, section, stype, ...) + AbstractSection.__init__(self, map, stype, ...) Node._i18n(self, map.config, section, nil, ...) + -- 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 - self.addremove = false end function NamedSection.parse(self) @@ -415,14 +636,23 @@ function TypedSection.__init__(self, map, type, ...) 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 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"]) @@ -440,7 +670,7 @@ end function TypedSection.parse(self) if self.addremove then -- Create - local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype + local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype local name = luci.http.formvalue(crval) if self.anonymous then if name then @@ -459,14 +689,14 @@ function TypedSection.parse(self) self.err_invalid = true end - if name and name:len() > 0 then + if name and #name > 0 then self:create(name) end end end -- Remove - crval = "cbi.rts." .. self.config + crval = REMOVE_PREFIX .. self.config name = luci.http.formvaluetable(crval) for k,v in pairs(name) do if self:cfgvalue(k) and self:checkscope(k) then @@ -528,23 +758,60 @@ AbstractValue - An abstract Value Type ]]-- AbstractValue = class(Node) -function AbstractValue.__init__(self, map, option, ...) +function AbstractValue.__init__(self, map, section, option, ...) Node.__init__(self, ...) - self.option = option - self.map = map - self.config = map.config + self.section = section + self.option = option + self.map = map + self.config = map.config self.tag_invalid = {} + self.tag_missing = {} + self.tag_error = {} self.deps = {} - - self.rmempty = false - self.default = nil - self.size = nil - self.optional = false + self.cast = "string" + + self.track_missing = false + self.rmempty = false + self.default = nil + self.size = nil + self.optional = false + + -- 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" + self.title = self.title or vs.title + self.description = self.description or vs.descr + + 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 end -- Add a dependencie to another section field function AbstractValue.depends(self, field, value) - table.insert(self.deps, {field=field, value=value}) + local deps + if type(field) == "string" then + deps = {} + deps[field] = value + else + deps = field + end + + table.insert(self.deps, {deps=deps, add=""}) +end + +-- Generates the unique CBID +function AbstractValue.cbid(self, section) + return "cbid."..self.map.config.."."..section.."."..self.option end -- Return whether this object should be created @@ -555,24 +822,34 @@ end -- Returns the formvalue for this object function AbstractValue.formvalue(self, section) - local key = "cbid."..self.map.config.."."..section.."."..self.option - return luci.http.formvalue(key) + return luci.http.formvalue(self:cbid(section)) +end + +function AbstractValue.additional(self, value) + self.optional = value +end + +function AbstractValue.mandatory(self, value) + self.rmempty = not value end 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 - fvalue = self:validate(fvalue) + fvalue = self:transform(self:validate(fvalue, section)) if not fvalue then self.tag_invalid[section] = true end - if fvalue and not (fvalue == self:cfgvalue(section)) then + if fvalue and not (fvalue == cvalue) then self:write(section, fvalue) end else -- Unset the UCI or error if self.rmempty or self.optional then self:remove(section) + elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then + self.tag_missing[section] = true end end end @@ -581,19 +858,18 @@ end function AbstractValue.render(self, s, scope) if not self.optional or self:cfgvalue(s) or self:formcreated(s) then scope = scope or {} - scope.section = s - scope.cbid = "cbid." .. self.config .. - "." .. s .. - "." .. self.option + scope.section = s + scope.cbid = self:cbid(s) + scope.striptags = luci.util.striptags scope.ifattr = function(cond,key,val) if cond then return string.format( ' %s="%s"', tostring(key), - tostring( val + luci.util.pcdata(tostring( val or scope[key] or (type(self[key]) ~= "function" and self[key]) - or "" ) + or "" )) ) else return '' @@ -610,7 +886,16 @@ end -- Return the UCI value of this object function AbstractValue.cfgvalue(self, section) - return self.map:get(section, self.option) + local value = self.map:get(section, self.option) + if not self.cast or self.cast == type(value) then + return value + elseif self.cast == "string" then + if type(value) == "table" then + return value[1] + end + elseif self.cast == "table" then + return {value} + end end -- Validate the form value @@ -618,6 +903,9 @@ function AbstractValue.validate(self, value) return value end +AbstractValue.transform = AbstractValue.validate + + -- Write to UCI function AbstractValue.write(self, section, value) return self.map:set(section, self.option, value) @@ -654,8 +942,8 @@ end -- DummyValue - This does nothing except being there DummyValue = class(AbstractValue) -function DummyValue.__init__(self, map, ...) - AbstractValue.__init__(self, map, ...) +function DummyValue.__init__(self, ...) + AbstractValue.__init__(self, ...) self.template = "cbi/dvalue" self.value = nil end @@ -708,17 +996,38 @@ ListValue = class(AbstractValue) function ListValue.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/lvalue" + self.keylist = {} self.vallist = {} - self.size = 1 self.widget = "select" + + 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 + if self.rmempty or self.optional then + self:value("") + end + for k, v in pairs(vs.values) do + local deps = {} + if vs.enum_depends and vs.enum_depends[k] then + deps = _uvl_strip_remote_dependencies(vs.enum_depends[k]) + end + self:value(k, v, unpack(deps)) + end + end + end end -function ListValue.value(self, key, val) +function ListValue.value(self, key, val, ...) 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 end function ListValue.validate(self, val) @@ -741,6 +1050,7 @@ MultiValue = class(AbstractValue) function MultiValue.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/mvalue" + self.keylist = {} self.vallist = {} @@ -785,3 +1095,89 @@ function MultiValue.validate(self, val) return result end + + +StaticList = class(MultiValue) + +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) + value = (type(value) == "table") and value or {value} + + local valid = {} + for i, v in ipairs(value) do + if luci.util.contains(self.valuelist, v) then + table.insert(valid, v) + end + end + return valid +end + + +DynamicList = class(AbstractValue) + +function DynamicList.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/dynlist" + self.cast = "table" + self.keylist = {} + self.vallist = {} +end + +function DynamicList.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + +function DynamicList.validate(self, value, 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 + table.insert(valid, v) + end + end + + return valid +end + + +--[[ +TextValue - A multi-line value + rows: Rows +]]-- +TextValue = class(AbstractValue) + +function TextValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/tvalue" +end + +--[[ +Button +]]-- +Button = class(AbstractValue) + +function Button.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/button" + self.inputstyle = nil + self.rmempty = true +end