X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fcbi%2Fluasrc%2Fcbi.lua;h=ff33604254f745148659ef4eccf6669fc7960ff4;hp=e94e1e2fa642ecc22be0858e820ef781f18fe685;hb=e72a526984982f6fa2b6f2ed5ce01523094bfe43;hpb=8f7f03a0c56190b02a8922f01f2c36c62d1dbfe7 diff --git a/libs/cbi/luasrc/cbi.lua b/libs/cbi/luasrc/cbi.lua index e94e1e2fa..ff3360425 100644 --- a/libs/cbi/luasrc/cbi.lua +++ b/libs/cbi/luasrc/cbi.lua @@ -2,7 +2,7 @@ LuCI - Configuration Bind Interface Description: -Offers an interface for binding confiugration values to certain +Offers an interface for binding configuration values to certain data types. Supports value and range validation and basic dependencies. FileId: @@ -13,9 +13,9 @@ Copyright 2008 Steven Barth Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -You may obtain a copy of the License at +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -31,38 +31,43 @@ require("luci.util") require("luci.http") require("luci.model.uci") +local uci = luci.model.uci local class = luci.util.class local instanceof = luci.util.instanceof + -- Loads a CBI map from given file, creating an environment and returns it -function load(cbimap) +function load(cbimap, ...) require("luci.fs") require("luci.i18n") require("luci.config") - require("luci.sys") - - local cbidir = luci.sys.libpath() .. "/model/cbi/" + require("luci.util") + + local cbidir = luci.util.libpath() .. "/model/cbi/" local func, err = loadfile(cbidir..cbimap..".lua") - + if not func then return nil end - + luci.i18n.loadc("cbi") - + 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) - - local map = func() - - if not instanceof(map, Map) then - error("CBI map returns no valid map object!") - return nil + luci.util.extfenv(func, "arg", {...}) + + local maps = {func()} + + for i, map in ipairs(maps) do + if not instanceof(map, Map) then + error("CBI map returns no valid map object!") + return nil + end end - - return map + + return maps end -- Node pseudo abstract class @@ -75,6 +80,22 @@ function Node.__init__(self, title, description) self.template = "cbi/node" end +-- i18n helper +function Node._i18n(self, config, section, option, title, description) + + -- i18n loaded? + if type(luci.i18n) == "table" then + + local key = config:gsub("[^%w]+", "") + + if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end + if option then key = key .. "_" .. 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", "" ) + end +end + -- Append child nodes function Node.append(self, obj) table.insert(self.children, obj) @@ -115,26 +136,53 @@ end --[[ -Map - A map describing a configuration file +Map - A map describing a configuration file ]]-- Map = class(Node) function Map.__init__(self, config, ...) Node.__init__(self, ...) + Node._i18n(self, config, nil, nil, ...) + self.config = config + self.parsechain = {self.config} self.template = "cbi/map" - self.uci = luci.model.uci.Session() - self.ucidata, self.uciorder = self.uci:sections(self.config) - if not self.ucidata or not self.uciorder then + if not uci.load(self.config) then error("Unable to read UCI data: " .. self.config) end end + +-- Chain foreign config +function Map.chain(self, config) + table.insert(self.parsechain, config) +end + -- Use optimized UCI writing function Map.parse(self, ...) - self.uci:t_load(self.config) Node.parse(self, ...) - self.uci:t_save(self.config) + for i, config in ipairs(self.parsechain) do + 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 + + -- Refresh data because commit changes section names + uci.unload(config) + uci.load(config) + end + + -- Reparse sections + Node.parse(self, ...) + + end + for i, config in ipairs(self.parsechain) do + uci.unload(config) + end end -- Creates a child section @@ -150,59 +198,35 @@ end -- UCI add function Map.add(self, sectiontype) - local name = self.uci:t_add(self.config, sectiontype) - if name then - self.ucidata[name] = {} - self.ucidata[name][".type"] = sectiontype - table.insert(self.uciorder, name) - end - return name + return uci.add(self.config, sectiontype) end -- UCI set function Map.set(self, section, option, value) - local stat = self.uci:t_set(self.config, section, option, value) - if stat then - local val = self.uci:t_get(self.config, section, option) - if option then - self.ucidata[section][option] = val - else - if not self.ucidata[section] then - self.ucidata[section] = {} - end - self.ucidata[section][".type"] = val - table.insert(self.uciorder, section) - end + if option then + return uci.set(self.config, section, option, value) + else + return uci.set(self.config, section, value) end - return stat end -- UCI del function Map.del(self, section, option) - local stat = self.uci:t_del(self.config, section, option) - if stat then - if option then - self.ucidata[section][option] = nil - else - self.ucidata[section] = nil - for i, k in ipairs(self.uciorder) do - if section == k then - table.remove(self.uciorder, i) - end - end - end + if option then + return uci.delete(self.config, section, option) + else + return uci.delete(self.config, section) end - return stat end --- UCI get (cached) +-- UCI get function Map.get(self, section, option) if not section then - return self.ucidata, self.uciorder - elseif option and self.ucidata[section] then - return self.ucidata[section][option] + return uci.get_all(self.config) + elseif option then + return uci.get(self.config, section, option) else - return self.ucidata[section] + return uci.get_all(self.config, section) end end @@ -218,21 +242,25 @@ function AbstractSection.__init__(self, map, sectiontype, ...) self.map = map self.config = map.config self.optionals = {} - + self.defaults = {} + self.optional = true self.addremove = false self.dynamic = false end -- Appends a new option -function AbstractSection.option(self, class, ...) +function AbstractSection.option(self, class, option, ...) if instanceof(class, AbstractValue) then - local obj = class(self.map, ...) + local obj = class(self.map, option, ...) + + Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...) + self:append(obj) return obj else error("class must be a descendent of AbstractValue") - end + end end -- Parse optional options @@ -240,9 +268,9 @@ function AbstractSection.parse_optionals(self, section) if not self.optional then return end - + self.optionals[section] = {} - + local field = luci.http.formvalue("cbi.opt."..self.config.."."..section) for k,v in ipairs(self.children) do if v.optional and not v:cfgvalue(section) then @@ -253,7 +281,7 @@ function AbstractSection.parse_optionals(self, section) end end end - + if field and #field > 0 and self.dynamic then self:add_dynamic(field) end @@ -270,27 +298,27 @@ function AbstractSection.parse_dynamic(self, section) if not self.dynamic then return end - + local arr = luci.util.clone(self:cfgvalue(section)) local form = luci.http.formvaluetable("cbid."..self.config.."."..section) for k, v in pairs(form) do arr[k] = v end - + for key,val in pairs(arr) do local create = true - + for i,c in ipairs(self.children) do if c.option == key then create = false end end - + if create and key:sub(1, 1) ~= "." then self:add_dynamic(key, true) end end -end +end -- Returns the section's UCI table function AbstractSection.cfgvalue(self, section) @@ -304,7 +332,28 @@ end -- Creates the section function AbstractSection.create(self, section) - return self.map:set(section, nil, self.sectiontype) + local stat + + if section then + stat = self.map:set(section, nil, self.sectiontype) + else + section = self.map:add(self.sectiontype) + stat = section + end + + if stat then + for k,v in pairs(self.children) do + if v.default then + self.map:set(section, v.option, v.default) + end + end + + for k,v in pairs(self.defaults) do + self.map:set(section, k, v) + end + end + + return stat end @@ -314,19 +363,20 @@ NamedSection - A fixed configuration section defined by its name ]]-- NamedSection = class(AbstractSection) -function NamedSection.__init__(self, map, section, ...) - AbstractSection.__init__(self, map, ...) +function NamedSection.__init__(self, map, section, type, ...) + AbstractSection.__init__(self, map, type, ...) + Node._i18n(self, map.config, section, nil, ...) + self.template = "cbi/nsection" - self.section = section self.addremove = false end function NamedSection.parse(self) - local s = self.section + local s = self.section local active = self:cfgvalue(s) - - + + if self.addremove then local path = self.config.."."..s if active then -- Remove the section @@ -334,21 +384,20 @@ function NamedSection.parse(self) return end else -- Create and apply default values - if luci.http.formvalue("cbi.cns."..path) and self:create(s) then - for k,v in pairs(self.children) do - v:write(s, v.default) - end + if luci.http.formvalue("cbi.cns."..path) then + self:create(s) + return end end end - + if active then AbstractSection.parse_dynamic(self, s) if luci.http.formvalue("cbi.submit") then Node.parse(self, s) end AbstractSection.parse_optionals(self, s) - end + end end @@ -356,48 +405,31 @@ end TypedSection - A (set of) configuration section(s) defined by the type addremove: Defines whether the user can add/remove sections of this type anonymous: Allow creating anonymous sections - validate: a validation function returning nil if the section is invalid + validate: a validation function returning nil if the section is invalid ]]-- TypedSection = class(AbstractSection) -function TypedSection.__init__(self, ...) - AbstractSection.__init__(self, ...) +function TypedSection.__init__(self, map, type, ...) + AbstractSection.__init__(self, map, type, ...) + Node._i18n(self, map.config, type, nil, ...) + self.template = "cbi/tsection" self.deps = {} - self.excludes = {} - + self.anonymous = false end -- Return all matching UCI sections for this TypedSection function TypedSection.cfgsections(self) local sections = {} - local map, order = self.map:get() - - for i, k in ipairs(order) do - if map[k][".type"] == self.sectiontype then - if self:checkscope(k) then - table.insert(sections, k) + uci.foreach(self.map.config, self.sectiontype, + function (section) + if self:checkscope(section[".name"]) then + table.insert(sections, section[".name"]) end - end - end - - return sections -end + end) --- Creates a new section of this type with the given name (or anonymous) -function TypedSection.create(self, name) - if name then - self.map:set(name, nil, self.sectiontype) - else - name = self.map:add(self.sectiontype) - end - - for k,v in pairs(self.children) do - if v.default then - self.map:set(name, v.option, v.default) - end - end + return sections end -- Limits scope to sections that have certain option => value pairs @@ -405,11 +437,6 @@ function TypedSection.depends(self, option, value) table.insert(self.deps, {option=option, value=value}) end --- Excludes several sections by name -function TypedSection.exclude(self, field) - self.excludes[field] = true -end - function TypedSection.parse(self) if self.addremove then -- Create @@ -419,25 +446,25 @@ function TypedSection.parse(self) if name then self:create() end - else + else if name then -- Ignore if it already exists if self:cfgvalue(name) then name = nil; end - + name = self:checkscope(name) - + if not name then self.err_invalid = true - end - + end + if name and name:len() > 0 then self:create(name) end end end - + -- Remove crval = "cbi.rts." .. self.config name = luci.http.formvaluetable(crval) @@ -445,9 +472,9 @@ function TypedSection.parse(self) if self:cfgvalue(k) and self:checkscope(k) then self:remove(k) end - end + end end - + for i, k in ipairs(self:cfgsections()) do AbstractSection.parse_dynamic(self, k) if luci.http.formvalue("cbi.submit") then @@ -460,25 +487,25 @@ end -- Verifies scope of sections function TypedSection.checkscope(self, section) -- Check if we are not excluded - if self.excludes[section] then + if self.filter and not self.filter(section) then return nil end - + -- Check if at least one dependency is met if #self.deps > 0 and self:cfgvalue(section) then local stat = false - + for k, v in ipairs(self.deps) do if self:cfgvalue(section)[v.option] == v.value then stat = true end end - + if not stat then return nil end end - + return self:validate(section) end @@ -492,7 +519,7 @@ end --[[ AbstractValue - An abstract Value Type null: Value can be empty - valid: A function returning the value if it is valid otherwise nil + valid: A function returning the value if it is valid otherwise nil depends: A table of option => value pairs of which one must be true default: The default value size: The size of the input fields @@ -508,7 +535,7 @@ function AbstractValue.__init__(self, map, option, ...) self.config = map.config self.tag_invalid = {} self.deps = {} - + self.rmempty = false self.default = nil self.size = nil @@ -534,7 +561,7 @@ end function AbstractValue.parse(self, section) local fvalue = self:formvalue(section) - + if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI fvalue = self:validate(fvalue) if not fvalue then @@ -542,7 +569,7 @@ function AbstractValue.parse(self, section) end if fvalue and not (fvalue == self:cfgvalue(section)) then self:write(section, fvalue) - end + end else -- Unset the UCI or error if self.rmempty or self.optional then self:remove(section) @@ -555,6 +582,28 @@ 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.ifattr = function(cond,key,val) + if cond then + return string.format( + ' %s="%s"', tostring(key), + tostring( val + or scope[key] + or (type(self[key]) ~= "function" and self[key]) + or "" ) + ) + else + return '' + end + end + + scope.attr = function(...) + return scope.ifattr( true, ... ) + end + Node.render(self, scope) end end @@ -585,28 +634,20 @@ end --[[ Value - A one-line value maxlength: The maximum length - isnumber: The value must be a valid (floating point) number - isinteger: The value must be a valid integer - ispositive: The value must be positive (and a number) ]]-- Value = class(AbstractValue) function Value.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/value" - - self.maxlength = nil - self.isnumber = false - self.isinteger = false + self.keylist = {} + self.vallist = {} end --- This validation is a bit more complex -function Value.validate(self, val) - if self.maxlength and tostring(val):len() > self.maxlength then - val = nil - end - - return luci.util.validate(val, self.isnumber, self.isinteger) +function Value.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) end @@ -620,11 +661,7 @@ function DummyValue.__init__(self, map, ...) end function DummyValue.parse(self) - -end -function DummyValue.render(self, s) - luci.template.render(self.template, {self=self, section=s}) end @@ -636,7 +673,7 @@ Flag = class(AbstractValue) function Flag.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/fvalue" - + self.enabled = "1" self.disabled = "0" end @@ -644,17 +681,17 @@ end -- A flag can only have two states: set or unset function Flag.parse(self, section) local fvalue = self:formvalue(section) - + if fvalue then fvalue = self.enabled else fvalue = self.disabled - end - - if fvalue == self.enabled or (not self.optional and not self.rmempty) then + end + + if fvalue == self.enabled or (not self.optional and not self.rmempty) then if not(fvalue == self:cfgvalue(section)) then self:write(section, fvalue) - end + end else self:remove(section) end @@ -673,7 +710,7 @@ function ListValue.__init__(self, ...) self.template = "cbi/lvalue" self.keylist = {} self.vallist = {} - + self.size = 1 self.widget = "select" end @@ -681,7 +718,7 @@ end function ListValue.value(self, key, val) val = val or key table.insert(self.keylist, tostring(key)) - table.insert(self.vallist, tostring(val)) + table.insert(self.vallist, tostring(val)) end function ListValue.validate(self, val) @@ -705,44 +742,46 @@ function MultiValue.__init__(self, ...) AbstractValue.__init__(self, ...) self.template = "cbi/mvalue" self.keylist = {} - self.vallist = {} - + self.vallist = {} + self.widget = "checkbox" self.delimiter = " " end +function MultiValue.render(self, ...) + if self.widget == "select" and not self.size then + self.size = #self.vallist + end + + AbstractValue.render(self, ...) +end + function MultiValue.value(self, key, val) val = val or key table.insert(self.keylist, tostring(key)) - table.insert(self.vallist, tostring(val)) + table.insert(self.vallist, tostring(val)) end function MultiValue.valuelist(self, section) local val = self:cfgvalue(section) - + if not(type(val) == "string") then return {} end - + return luci.util.split(val, self.delimiter) end function MultiValue.validate(self, val) - if not(type(val) == "string") then - return nil - end - - local result = "" - - for value in val:gmatch("[^\n]+") do + val = (type(val) == "table") and val or {val} + + local result + + for i, value in ipairs(val) do if luci.util.contains(self.keylist, value) then - result = result .. self.delimiter .. value - end - end - - if result:len() > 0 then - return result:sub(self.delimiter:len() + 1) - else - return nil + result = result and (result .. self.delimiter .. value) or value + end end -end \ No newline at end of file + + return result +end