2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
32 require("luci.model.uci")
34 local uci = luci.model.uci
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
39 -- Loads a CBI map from given file, creating an environment and returns it
43 require("luci.config")
46 local cbidir = luci.sys.libpath() .. "/model/cbi/"
47 local func, err = loadfile(cbidir..cbimap..".lua")
53 luci.i18n.loadc("cbi")
55 luci.util.resfenv(func)
56 luci.util.updfenv(func, luci.cbi)
57 luci.util.extfenv(func, "translate", luci.i18n.translate)
58 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
62 for i, map in ipairs(maps) do
63 if not instanceof(map, Map) then
64 error("CBI map returns no valid map object!")
72 -- Node pseudo abstract class
75 function Node.__init__(self, title, description)
77 self.title = title or ""
78 self.description = description or ""
79 self.template = "cbi/node"
83 function Node._i18n(self, config, section, option, title, description)
86 if type(luci.i18n) == "table" then
88 local key = config:gsub("[^%w]+", "")
90 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
91 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
93 self.title = title or luci.i18n.translate( key, option or section or config )
94 self.description = description or luci.i18n.translate( key .. "_desc", "" )
99 function Node.append(self, obj)
100 table.insert(self.children, obj)
103 -- Parse this node and its children
104 function Node.parse(self, ...)
105 for k, child in ipairs(self.children) do
111 function Node.render(self, scope)
115 luci.template.render(self.template, scope)
118 -- Render the children
119 function Node.render_children(self, ...)
120 for k, node in ipairs(self.children) do
127 A simple template element
129 Template = class(Node)
131 function Template.__init__(self, template)
133 self.template = template
138 Map - A map describing a configuration file
142 function Map.__init__(self, config, ...)
143 Node.__init__(self, ...)
144 Node._i18n(self, config, nil, nil, ...)
147 self.template = "cbi/map"
148 if not uci.load(self.config) then
149 error("Unable to read UCI data: " .. self.config)
153 -- Use optimized UCI writing
154 function Map.parse(self, ...)
155 Node.parse(self, ...)
156 uci.save(self.config)
157 uci.unload(self.config)
160 -- Creates a child section
161 function Map.section(self, class, ...)
162 if instanceof(class, AbstractSection) then
163 local obj = class(self, ...)
167 error("class must be a descendent of AbstractSection")
172 function Map.add(self, sectiontype)
173 return uci.add(self.config, sectiontype)
177 function Map.set(self, section, option, value)
179 return uci.set(self.config, section, option, value)
181 return uci.set(self.config, section, value)
186 function Map.del(self, section, option)
188 return uci.delete(self.config, section, option)
190 return uci.delete(self.config, section)
195 function Map.get(self, section, option)
197 return uci.get_all(self.config)
199 return uci.get(self.config, section, option)
201 return uci.get_all(self.config, section)
209 AbstractSection = class(Node)
211 function AbstractSection.__init__(self, map, sectiontype, ...)
212 Node.__init__(self, ...)
213 self.sectiontype = sectiontype
215 self.config = map.config
220 self.addremove = false
224 -- Appends a new option
225 function AbstractSection.option(self, class, option, ...)
226 if instanceof(class, AbstractValue) then
227 local obj = class(self.map, option, ...)
229 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
234 error("class must be a descendent of AbstractValue")
238 -- Parse optional options
239 function AbstractSection.parse_optionals(self, section)
240 if not self.optional then
244 self.optionals[section] = {}
246 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
247 for k,v in ipairs(self.children) do
248 if v.optional and not v:cfgvalue(section) then
249 if field == v.option then
252 table.insert(self.optionals[section], v)
257 if field and #field > 0 and self.dynamic then
258 self:add_dynamic(field)
262 -- Add a dynamic option
263 function AbstractSection.add_dynamic(self, field, optional)
264 local o = self:option(Value, field, field)
265 o.optional = optional
268 -- Parse all dynamic options
269 function AbstractSection.parse_dynamic(self, section)
270 if not self.dynamic then
274 local arr = luci.util.clone(self:cfgvalue(section))
275 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
276 for k, v in pairs(form) do
280 for key,val in pairs(arr) do
283 for i,c in ipairs(self.children) do
284 if c.option == key then
289 if create and key:sub(1, 1) ~= "." then
290 self:add_dynamic(key, true)
295 -- Returns the section's UCI table
296 function AbstractSection.cfgvalue(self, section)
297 return self.map:get(section)
300 -- Removes the section
301 function AbstractSection.remove(self, section)
302 return self.map:del(section)
305 -- Creates the section
306 function AbstractSection.create(self, section)
307 local stat = self.map:set(section, nil, self.sectiontype)
310 for k,v in pairs(self.children) do
312 self.map:set(section, v.option, v.default)
316 for k,v in pairs(self.defaults) do
317 self.map:set(section, k, v)
327 NamedSection - A fixed configuration section defined by its name
329 NamedSection = class(AbstractSection)
331 function NamedSection.__init__(self, map, section, type, ...)
332 AbstractSection.__init__(self, map, type, ...)
333 Node._i18n(self, map.config, section, nil, ...)
335 self.template = "cbi/nsection"
336 self.section = section
337 self.addremove = false
340 function NamedSection.parse(self)
341 local s = self.section
342 local active = self:cfgvalue(s)
345 if self.addremove then
346 local path = self.config.."."..s
347 if active then -- Remove the section
348 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
351 else -- Create and apply default values
352 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
353 for k,v in pairs(self.children) do
354 v:write(s, v.default)
361 AbstractSection.parse_dynamic(self, s)
362 if luci.http.formvalue("cbi.submit") then
365 AbstractSection.parse_optionals(self, s)
371 TypedSection - A (set of) configuration section(s) defined by the type
372 addremove: Defines whether the user can add/remove sections of this type
373 anonymous: Allow creating anonymous sections
374 validate: a validation function returning nil if the section is invalid
376 TypedSection = class(AbstractSection)
378 function TypedSection.__init__(self, map, type, ...)
379 AbstractSection.__init__(self, map, type, ...)
380 Node._i18n(self, map.config, type, nil, ...)
382 self.template = "cbi/tsection"
386 self.anonymous = false
389 -- Return all matching UCI sections for this TypedSection
390 function TypedSection.cfgsections(self)
392 uci.foreach(self.map.config, self.sectiontype,
394 if self:checkscope(section[".name"]) then
395 table.insert(sections, section[".name"])
402 -- Creates a new section of this type with the given name (or anonymous)
403 function TypedSection.create(self, name)
404 name = name or self.map:add(self.sectiontype)
405 AbstractSection.create(self, name)
408 -- Limits scope to sections that have certain option => value pairs
409 function TypedSection.depends(self, option, value)
410 table.insert(self.deps, {option=option, value=value})
413 -- Excludes several sections by name
414 function TypedSection.exclude(self, field)
415 self.excludes[field] = true
418 function TypedSection.parse(self)
419 if self.addremove then
421 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
422 local name = luci.http.formvalue(crval)
423 if self.anonymous then
429 -- Ignore if it already exists
430 if self:cfgvalue(name) then
434 name = self:checkscope(name)
437 self.err_invalid = true
440 if name and name:len() > 0 then
447 crval = "cbi.rts." .. self.config
448 name = luci.http.formvaluetable(crval)
449 for k,v in pairs(name) do
450 if self:cfgvalue(k) and self:checkscope(k) then
456 for i, k in ipairs(self:cfgsections()) do
457 AbstractSection.parse_dynamic(self, k)
458 if luci.http.formvalue("cbi.submit") then
461 AbstractSection.parse_optionals(self, k)
465 -- Verifies scope of sections
466 function TypedSection.checkscope(self, section)
467 -- Check if we are not excluded
468 if self.excludes[section] then
472 -- Check if at least one dependency is met
473 if #self.deps > 0 and self:cfgvalue(section) then
476 for k, v in ipairs(self.deps) do
477 if self:cfgvalue(section)[v.option] == v.value then
487 return self:validate(section)
491 -- Dummy validate function
492 function TypedSection.validate(self, section)
498 AbstractValue - An abstract Value Type
499 null: Value can be empty
500 valid: A function returning the value if it is valid otherwise nil
501 depends: A table of option => value pairs of which one must be true
502 default: The default value
503 size: The size of the input fields
504 rmempty: Unset value if empty
505 optional: This value is optional (see AbstractSection.optionals)
507 AbstractValue = class(Node)
509 function AbstractValue.__init__(self, map, option, ...)
510 Node.__init__(self, ...)
513 self.config = map.config
514 self.tag_invalid = {}
520 self.optional = false
523 -- Add a dependencie to another section field
524 function AbstractValue.depends(self, field, value)
525 table.insert(self.deps, {field=field, value=value})
528 -- Return whether this object should be created
529 function AbstractValue.formcreated(self, section)
530 local key = "cbi.opt."..self.config.."."..section
531 return (luci.http.formvalue(key) == self.option)
534 -- Returns the formvalue for this object
535 function AbstractValue.formvalue(self, section)
536 local key = "cbid."..self.map.config.."."..section.."."..self.option
537 return luci.http.formvalue(key)
540 function AbstractValue.parse(self, section)
541 local fvalue = self:formvalue(section)
543 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
544 fvalue = self:validate(fvalue)
546 self.tag_invalid[section] = true
548 if fvalue and not (fvalue == self:cfgvalue(section)) then
549 self:write(section, fvalue)
551 else -- Unset the UCI or error
552 if self.rmempty or self.optional then
558 -- Render if this value exists or if it is mandatory
559 function AbstractValue.render(self, s, scope)
560 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
563 scope.cbid = "cbid." .. self.config ..
567 scope.ifattr = function(cond,key,val)
569 return string.format(
570 ' %s="%s"', tostring(key),
571 tostring( val or scope[key] or self[key] or "" )
578 scope.attr = function(...)
579 return scope.ifattr( true, ... )
582 Node.render(self, scope)
586 -- Return the UCI value of this object
587 function AbstractValue.cfgvalue(self, section)
588 return self.map:get(section, self.option)
591 -- Validate the form value
592 function AbstractValue.validate(self, value)
597 function AbstractValue.write(self, section, value)
598 return self.map:set(section, self.option, value)
602 function AbstractValue.remove(self, section)
603 return self.map:del(section, self.option)
610 Value - A one-line value
611 maxlength: The maximum length
613 Value = class(AbstractValue)
615 function Value.__init__(self, ...)
616 AbstractValue.__init__(self, ...)
617 self.template = "cbi/value"
622 -- This validation is a bit more complex
623 function Value.validate(self, val)
624 if self.maxlength and tostring(val):len() > self.maxlength then
632 -- DummyValue - This does nothing except being there
633 DummyValue = class(AbstractValue)
635 function DummyValue.__init__(self, map, ...)
636 AbstractValue.__init__(self, map, ...)
637 self.template = "cbi/dvalue"
641 function DummyValue.parse(self)
645 function DummyValue.render(self, s)
646 luci.template.render(self.template, {self=self, section=s})
651 Flag - A flag being enabled or disabled
653 Flag = class(AbstractValue)
655 function Flag.__init__(self, ...)
656 AbstractValue.__init__(self, ...)
657 self.template = "cbi/fvalue"
663 -- A flag can only have two states: set or unset
664 function Flag.parse(self, section)
665 local fvalue = self:formvalue(section)
668 fvalue = self.enabled
670 fvalue = self.disabled
673 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
674 if not(fvalue == self:cfgvalue(section)) then
675 self:write(section, fvalue)
685 ListValue - A one-line value predefined in a list
686 widget: The widget that will be used (select, radio)
688 ListValue = class(AbstractValue)
690 function ListValue.__init__(self, ...)
691 AbstractValue.__init__(self, ...)
692 self.template = "cbi/lvalue"
697 self.widget = "select"
700 function ListValue.value(self, key, val)
702 table.insert(self.keylist, tostring(key))
703 table.insert(self.vallist, tostring(val))
706 function ListValue.validate(self, val)
707 if luci.util.contains(self.keylist, val) then
717 MultiValue - Multiple delimited values
718 widget: The widget that will be used (select, checkbox)
719 delimiter: The delimiter that will separate the values (default: " ")
721 MultiValue = class(AbstractValue)
723 function MultiValue.__init__(self, ...)
724 AbstractValue.__init__(self, ...)
725 self.template = "cbi/mvalue"
729 self.widget = "checkbox"
733 function MultiValue.render(self, ...)
734 if self.widget == "select" and not self.size then
735 self.size = #self.vallist
738 AbstractValue.render(self, ...)
741 function MultiValue.value(self, key, val)
743 table.insert(self.keylist, tostring(key))
744 table.insert(self.vallist, tostring(val))
747 function MultiValue.valuelist(self, section)
748 local val = self:cfgvalue(section)
750 if not(type(val) == "string") then
754 return luci.util.split(val, self.delimiter)
757 function MultiValue.validate(self, val)
758 val = (type(val) == "table") and val or {val}
762 for i, value in ipairs(val) do
763 if luci.util.contains(self.keylist, value) then
764 result = result and (result .. self.delimiter .. value) or value