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 if luci.http.formvalue("cbi.apply") then
158 uci.commit(self.config)
160 uci.unload(self.config)
163 -- Creates a child section
164 function Map.section(self, class, ...)
165 if instanceof(class, AbstractSection) then
166 local obj = class(self, ...)
170 error("class must be a descendent of AbstractSection")
175 function Map.add(self, sectiontype)
176 return uci.add(self.config, sectiontype)
180 function Map.set(self, section, option, value)
182 return uci.set(self.config, section, option, value)
184 return uci.set(self.config, section, value)
189 function Map.del(self, section, option)
191 return uci.delete(self.config, section, option)
193 return uci.delete(self.config, section)
198 function Map.get(self, section, option)
200 return uci.get_all(self.config)
202 return uci.get(self.config, section, option)
204 return uci.get_all(self.config, section)
212 AbstractSection = class(Node)
214 function AbstractSection.__init__(self, map, sectiontype, ...)
215 Node.__init__(self, ...)
216 self.sectiontype = sectiontype
218 self.config = map.config
223 self.addremove = false
227 -- Appends a new option
228 function AbstractSection.option(self, class, option, ...)
229 if instanceof(class, AbstractValue) then
230 local obj = class(self.map, option, ...)
232 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
237 error("class must be a descendent of AbstractValue")
241 -- Parse optional options
242 function AbstractSection.parse_optionals(self, section)
243 if not self.optional then
247 self.optionals[section] = {}
249 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
250 for k,v in ipairs(self.children) do
251 if v.optional and not v:cfgvalue(section) then
252 if field == v.option then
255 table.insert(self.optionals[section], v)
260 if field and #field > 0 and self.dynamic then
261 self:add_dynamic(field)
265 -- Add a dynamic option
266 function AbstractSection.add_dynamic(self, field, optional)
267 local o = self:option(Value, field, field)
268 o.optional = optional
271 -- Parse all dynamic options
272 function AbstractSection.parse_dynamic(self, section)
273 if not self.dynamic then
277 local arr = luci.util.clone(self:cfgvalue(section))
278 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
279 for k, v in pairs(form) do
283 for key,val in pairs(arr) do
286 for i,c in ipairs(self.children) do
287 if c.option == key then
292 if create and key:sub(1, 1) ~= "." then
293 self:add_dynamic(key, true)
298 -- Returns the section's UCI table
299 function AbstractSection.cfgvalue(self, section)
300 return self.map:get(section)
303 -- Removes the section
304 function AbstractSection.remove(self, section)
305 return self.map:del(section)
308 -- Creates the section
309 function AbstractSection.create(self, section)
313 stat = self.map:set(section, nil, self.sectiontype)
315 section = self.map:add(self.sectiontype)
320 for k,v in pairs(self.children) do
322 self.map:set(section, v.option, v.default)
326 for k,v in pairs(self.defaults) do
327 self.map:set(section, k, v)
337 NamedSection - A fixed configuration section defined by its name
339 NamedSection = class(AbstractSection)
341 function NamedSection.__init__(self, map, section, type, ...)
342 AbstractSection.__init__(self, map, type, ...)
343 Node._i18n(self, map.config, section, nil, ...)
345 self.template = "cbi/nsection"
346 self.section = section
347 self.addremove = false
350 function NamedSection.parse(self)
351 local s = self.section
352 local active = self:cfgvalue(s)
355 if self.addremove then
356 local path = self.config.."."..s
357 if active then -- Remove the section
358 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
361 else -- Create and apply default values
362 if luci.http.formvalue("cbi.cns."..path) then
370 AbstractSection.parse_dynamic(self, s)
371 if luci.http.formvalue("cbi.submit") then
374 AbstractSection.parse_optionals(self, s)
380 TypedSection - A (set of) configuration section(s) defined by the type
381 addremove: Defines whether the user can add/remove sections of this type
382 anonymous: Allow creating anonymous sections
383 validate: a validation function returning nil if the section is invalid
385 TypedSection = class(AbstractSection)
387 function TypedSection.__init__(self, map, type, ...)
388 AbstractSection.__init__(self, map, type, ...)
389 Node._i18n(self, map.config, type, nil, ...)
391 self.template = "cbi/tsection"
395 self.anonymous = false
398 -- Return all matching UCI sections for this TypedSection
399 function TypedSection.cfgsections(self)
401 uci.foreach(self.map.config, self.sectiontype,
403 if self:checkscope(section[".name"]) then
404 table.insert(sections, section[".name"])
411 -- Limits scope to sections that have certain option => value pairs
412 function TypedSection.depends(self, option, value)
413 table.insert(self.deps, {option=option, value=value})
416 -- Excludes several sections by name
417 function TypedSection.exclude(self, field)
418 self.excludes[field] = true
421 function TypedSection.parse(self)
422 if self.addremove then
424 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
425 local name = luci.http.formvalue(crval)
426 if self.anonymous then
432 -- Ignore if it already exists
433 if self:cfgvalue(name) then
437 name = self:checkscope(name)
440 self.err_invalid = true
443 if name and name:len() > 0 then
450 crval = "cbi.rts." .. self.config
451 name = luci.http.formvaluetable(crval)
452 for k,v in pairs(name) do
453 if self:cfgvalue(k) and self:checkscope(k) then
459 for i, k in ipairs(self:cfgsections()) do
460 AbstractSection.parse_dynamic(self, k)
461 if luci.http.formvalue("cbi.submit") then
464 AbstractSection.parse_optionals(self, k)
468 -- Verifies scope of sections
469 function TypedSection.checkscope(self, section)
470 -- Check if we are not excluded
471 if self.excludes[section] then
475 -- Check if at least one dependency is met
476 if #self.deps > 0 and self:cfgvalue(section) then
479 for k, v in ipairs(self.deps) do
480 if self:cfgvalue(section)[v.option] == v.value then
490 return self:validate(section)
494 -- Dummy validate function
495 function TypedSection.validate(self, section)
501 AbstractValue - An abstract Value Type
502 null: Value can be empty
503 valid: A function returning the value if it is valid otherwise nil
504 depends: A table of option => value pairs of which one must be true
505 default: The default value
506 size: The size of the input fields
507 rmempty: Unset value if empty
508 optional: This value is optional (see AbstractSection.optionals)
510 AbstractValue = class(Node)
512 function AbstractValue.__init__(self, map, option, ...)
513 Node.__init__(self, ...)
516 self.config = map.config
517 self.tag_invalid = {}
523 self.optional = false
526 -- Add a dependencie to another section field
527 function AbstractValue.depends(self, field, value)
528 table.insert(self.deps, {field=field, value=value})
531 -- Return whether this object should be created
532 function AbstractValue.formcreated(self, section)
533 local key = "cbi.opt."..self.config.."."..section
534 return (luci.http.formvalue(key) == self.option)
537 -- Returns the formvalue for this object
538 function AbstractValue.formvalue(self, section)
539 local key = "cbid."..self.map.config.."."..section.."."..self.option
540 return luci.http.formvalue(key)
543 function AbstractValue.parse(self, section)
544 local fvalue = self:formvalue(section)
546 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
547 fvalue = self:validate(fvalue)
549 self.tag_invalid[section] = true
551 if fvalue and not (fvalue == self:cfgvalue(section)) then
552 self:write(section, fvalue)
554 else -- Unset the UCI or error
555 if self.rmempty or self.optional then
561 -- Render if this value exists or if it is mandatory
562 function AbstractValue.render(self, s, scope)
563 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
566 scope.cbid = "cbid." .. self.config ..
570 scope.ifattr = function(cond,key,val)
572 return string.format(
573 ' %s="%s"', tostring(key),
574 tostring( val or scope[key] or self[key] or "" )
581 scope.attr = function(...)
582 return scope.ifattr( true, ... )
585 Node.render(self, scope)
589 -- Return the UCI value of this object
590 function AbstractValue.cfgvalue(self, section)
591 return self.map:get(section, self.option)
594 -- Validate the form value
595 function AbstractValue.validate(self, value)
600 function AbstractValue.write(self, section, value)
601 return self.map:set(section, self.option, value)
605 function AbstractValue.remove(self, section)
606 return self.map:del(section, self.option)
613 Value - A one-line value
614 maxlength: The maximum length
616 Value = class(AbstractValue)
618 function Value.__init__(self, ...)
619 AbstractValue.__init__(self, ...)
620 self.template = "cbi/value"
625 -- This validation is a bit more complex
626 function Value.validate(self, val)
627 if self.maxlength and tostring(val):len() > self.maxlength then
635 -- DummyValue - This does nothing except being there
636 DummyValue = class(AbstractValue)
638 function DummyValue.__init__(self, map, ...)
639 AbstractValue.__init__(self, map, ...)
640 self.template = "cbi/dvalue"
644 function DummyValue.parse(self)
650 Flag - A flag being enabled or disabled
652 Flag = class(AbstractValue)
654 function Flag.__init__(self, ...)
655 AbstractValue.__init__(self, ...)
656 self.template = "cbi/fvalue"
662 -- A flag can only have two states: set or unset
663 function Flag.parse(self, section)
664 local fvalue = self:formvalue(section)
667 fvalue = self.enabled
669 fvalue = self.disabled
672 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
673 if not(fvalue == self:cfgvalue(section)) then
674 self:write(section, fvalue)
684 ListValue - A one-line value predefined in a list
685 widget: The widget that will be used (select, radio)
687 ListValue = class(AbstractValue)
689 function ListValue.__init__(self, ...)
690 AbstractValue.__init__(self, ...)
691 self.template = "cbi/lvalue"
696 self.widget = "select"
699 function ListValue.value(self, key, val)
701 table.insert(self.keylist, tostring(key))
702 table.insert(self.vallist, tostring(val))
705 function ListValue.validate(self, val)
706 if luci.util.contains(self.keylist, val) then
716 MultiValue - Multiple delimited values
717 widget: The widget that will be used (select, checkbox)
718 delimiter: The delimiter that will separate the values (default: " ")
720 MultiValue = class(AbstractValue)
722 function MultiValue.__init__(self, ...)
723 AbstractValue.__init__(self, ...)
724 self.template = "cbi/mvalue"
728 self.widget = "checkbox"
732 function MultiValue.render(self, ...)
733 if self.widget == "select" and not self.size then
734 self.size = #self.vallist
737 AbstractValue.render(self, ...)
740 function MultiValue.value(self, key, val)
742 table.insert(self.keylist, tostring(key))
743 table.insert(self.vallist, tostring(val))
746 function MultiValue.valuelist(self, section)
747 local val = self:cfgvalue(section)
749 if not(type(val) == "string") then
753 return luci.util.split(val, self.delimiter)
756 function MultiValue.validate(self, val)
757 val = (type(val) == "table") and val or {val}
761 for i, value in ipairs(val) do
762 if luci.util.contains(self.keylist, value) then
763 result = result and (result .. self.delimiter .. value) or value