luci/libs/cbi/luasrc/cbi.lua
2008-08-30 12:47:14 +00:00

1246 lines
27 KiB
Lua

--[[
LuCI - Configuration Bind Interface
Description:
Offers an interface for binding configuration values to certain
data types. Supports value and range validation and basic dependencies.
FileId:
$Id$
License:
Copyright 2008 Steven Barth <steven@midlink.org>
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
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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--
module("luci.cbi", package.seeall)
require("luci.template")
require("luci.util")
require("luci.http")
require("luci.uvl")
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, ...)
require("luci.fs")
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
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)
luci.util.extfenv(func, "arg", {...})
local maps = {func()}
for i, map in ipairs(maps) do
if not instanceof(map, Node) then
error("CBI map returns no valid map object!")
return nil
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 stat, err = node.map.validator:validate_section(node.config, name, co)
if err then
node.map.save = false
if err.code == luci.uvl.errors.ERR_DEPENDENCY then
node.tag_deperror[name] = true
else
node.tag_invalid[name] = true
end
for i, v in ipairs(err.childs) do
if v.option and node.fields[v.option] then
if v.code == luci.uvl.errors.ERR_OPTION then
local subcode = v.childs and v.childs[1] and v.childs[1].code
if subcode == luci.uvl.errors.ERR_DEPENDENCY then
node.fields[v.option].tag_reqerror[name] = true
elseif subcode == luci.uvl.errors.ERR_OPT_REQUIRED then
node.fields[v.option].tag_missing[name] = true
node.tag_deperror[name] = true
else
node.fields[v.option].tag_invalid[name] = true
end
end
end
end
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()
function Node.__init__(self, title, description)
self.children = {}
self.title = title or ""
self.description = description or ""
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 and config:gsub("[^%w]+", "") or ""
if section then key = key .. "_" .. section: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", "" )
end
end
-- Append child nodes
function Node.append(self, obj)
table.insert(self.children, obj)
end
-- Parse this node and its children
function Node.parse(self, ...)
for k, child in ipairs(self.children) do
child:parse(...)
end
end
-- Render this node
function Node.render(self, scope)
scope = scope or {}
scope.self = self
luci.template.render(self.template, scope)
end
-- Render the children
function Node.render_children(self, ...)
for k, node in ipairs(self.children) do
node:render(...)
end
end
--[[
A simple template element
]]--
Template = class(Node)
function Template.__init__(self, template)
Node.__init__(self)
self.template = template
end
function Template.render(self)
luci.template.render(self.template, {self=self})
end
--[[
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 = 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)
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
-- Chain foreign config
function Map.chain(self, config)
table.insert(self.parsechain, config)
end
-- Use optimized UCI writing
function Map.parse(self, ...)
Node.parse(self, ...)
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)
self.uci:apply(config)
-- Refresh data because commit changes section names
self.uci:load(config)
end
-- Reparse sections
Node.parse(self, ...)
end
for i, config in ipairs(self.parsechain) do
self.uci:unload(config)
end
end
end
-- Creates a child section
function Map.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
-- UCI add
function Map.add(self, sectiontype)
return self.uci:add(self.config, sectiontype)
end
-- UCI set
function Map.set(self, section, option, value)
if option then
return self.uci:set(self.config, section, option, value)
else
return self.uci:set(self.config, section, value)
end
end
-- UCI del
function Map.del(self, section, option)
if option then
return self.uci:delete(self.config, section, option)
else
return self.uci:delete(self.config, section)
end
end
-- UCI get
function Map.get(self, section, option)
if not section then
return self.uci:get_all(self.config)
elseif option then
return self.uci:get(self.config, section, option)
else
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, section, ...)
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)
function AbstractSection.__init__(self, map, sectiontype, ...)
Node.__init__(self, ...)
self.sectiontype = sectiontype
self.map = map
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
self.dynamic = false
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, ...)
Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
self:append(obj)
self.fields[option] = obj
return obj
elseif class == true then
error("No valid class was given and autodetection failed.")
else
error("class must be a descendant of AbstractValue")
end
end
-- Parse optional options
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
if field == v.option then
field = nil
else
table.insert(self.optionals[section], v)
end
end
end
if field and #field > 0 and self.dynamic then
self:add_dynamic(field)
end
end
-- Add a dynamic option
function AbstractSection.add_dynamic(self, field, optional)
local o = self:option(Value, field, field)
o.optional = optional
end
-- Parse all dynamic options
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
-- Returns the section's UCI table
function AbstractSection.cfgvalue(self, section)
return self.map:get(section)
end
-- Removes the section
function AbstractSection.remove(self, section)
return self.map:del(section)
end
-- Creates the section
function AbstractSection.create(self, section)
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
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, 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
end
function NamedSection.parse(self)
local s = self.section
local active = self:cfgvalue(s)
if self.addremove then
local path = self.config.."."..s
if active then -- Remove the section
if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
return
end
else -- Create and apply default values
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)
if not self.override_scheme and self.map.scheme then
_uvl_validate_section(self, s)
end
end
AbstractSection.parse_optionals(self, s)
end
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
]]--
TypedSection = class(AbstractSection)
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.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 = {}
self.map.uci:foreach(self.map.config, self.sectiontype,
function (section)
if self:checkscope(section[".name"]) then
table.insert(sections, section[".name"])
end
end)
return sections
end
-- Limits scope to sections that have certain option => value pairs
function TypedSection.depends(self, option, value)
table.insert(self.deps, {option=option, value=value})
end
function TypedSection.parse(self)
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 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 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 crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
local name = luci.http.formvalue(crval)
if self.anonymous then
if name then
self:create()
end
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
if name and #name > 0 then
self:create(name)
end
end
end
end
end
-- Verifies scope of sections
function TypedSection.checkscope(self, section)
-- Check if we are not excluded
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
-- Dummy validate function
function TypedSection.validate(self, section)
return section
end
--[[
AbstractValue - An abstract Value Type
null: Value can be empty
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
rmempty: Unset value if empty
optional: This value is optional (see AbstractSection.optionals)
]]--
AbstractValue = class(Node)
function AbstractValue.__init__(self, map, section, option, ...)
Node.__init__(self, ...)
self.section = section
self.option = option
self.map = map
self.config = map.config
self.tag_invalid = {}
self.tag_missing = {}
self.tag_reqerror = {}
self.tag_error = {}
self.deps = {}
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)
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
function AbstractValue.formcreated(self, section)
local key = "cbi.opt."..self.config.."."..section
return (luci.http.formvalue(key) == self.option)
end
-- Returns the formvalue for this object
function AbstractValue.formvalue(self, section)
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:transform(self:validate(fvalue, section))
if not fvalue then
self.tag_invalid[section] = true
end
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
-- 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
scope = scope or {}
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),
luci.util.pcdata(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
-- 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
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
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)
end
-- Remove from UCI
function AbstractValue.remove(self, section)
return self.map:del(section, self.option)
end
--[[
Value - A one-line value
maxlength: The maximum length
]]--
Value = class(AbstractValue)
function Value.__init__(self, ...)
AbstractValue.__init__(self, ...)
self.template = "cbi/value"
self.keylist = {}
self.vallist = {}
end
function Value.value(self, key, val)
val = val or key
table.insert(self.keylist, tostring(key))
table.insert(self.vallist, tostring(val))
end
-- DummyValue - This does nothing except being there
DummyValue = class(AbstractValue)
function DummyValue.__init__(self, ...)
AbstractValue.__init__(self, ...)
self.template = "cbi/dvalue"
self.value = nil
end
function DummyValue.parse(self)
end
--[[
Flag - A flag being enabled or disabled
]]--
Flag = class(AbstractValue)
function Flag.__init__(self, ...)
AbstractValue.__init__(self, ...)
self.template = "cbi/fvalue"
self.enabled = "1"
self.disabled = "0"
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
if not(fvalue == self:cfgvalue(section)) then
self:write(section, fvalue)
end
else
self:remove(section)
end
end
--[[
ListValue - A one-line value predefined in a list
widget: The widget that will be used (select, radio)
]]--
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 not self.override_dependencies
and vs.enum_depends and vs.enum_depends[k] then
for i, dep in ipairs(vs.enum_depends[k]) do
table.insert(deps, _uvl_strip_remote_dependencies(dep))
end
end
self:value(k, v, 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
end
function ListValue.validate(self, val)
if luci.util.contains(self.keylist, val) then
return val
else
return nil
end
end
--[[
MultiValue - Multiple delimited values
widget: The widget that will be used (select, checkbox)
delimiter: The delimiter that will separate the values (default: " ")
]]--
MultiValue = class(AbstractValue)
function MultiValue.__init__(self, ...)
AbstractValue.__init__(self, ...)
self.template = "cbi/mvalue"
self.keylist = {}
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)
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))
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)
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 and (result .. self.delimiter .. value) or value
end
end
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