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)
310 stat = self.map:set(section, nil, self.sectiontype)
312 section = self.map:add(self.sectiontype)
317 for k,v in pairs(self.children) do
319 self.map:set(section, v.option, v.default)
323 for k,v in pairs(self.defaults) do
324 self.map:set(section, k, v)
334 NamedSection - A fixed configuration section defined by its name
336 NamedSection = class(AbstractSection)
338 function NamedSection.__init__(self, map, section, type, ...)
339 AbstractSection.__init__(self, map, type, ...)
340 Node._i18n(self, map.config, section, nil, ...)
342 self.template = "cbi/nsection"
343 self.section = section
344 self.addremove = false
347 function NamedSection.parse(self)
348 local s = self.section
349 local active = self:cfgvalue(s)
352 if self.addremove then
353 local path = self.config.."."..s
354 if active then -- Remove the section
355 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
358 else -- Create and apply default values
359 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
360 for k,v in pairs(self.children) do
361 v:write(s, v.default)
368 AbstractSection.parse_dynamic(self, s)
369 if luci.http.formvalue("cbi.submit") then
372 AbstractSection.parse_optionals(self, s)
378 TypedSection - A (set of) configuration section(s) defined by the type
379 addremove: Defines whether the user can add/remove sections of this type
380 anonymous: Allow creating anonymous sections
381 validate: a validation function returning nil if the section is invalid
383 TypedSection = class(AbstractSection)
385 function TypedSection.__init__(self, map, type, ...)
386 AbstractSection.__init__(self, map, type, ...)
387 Node._i18n(self, map.config, type, nil, ...)
389 self.template = "cbi/tsection"
393 self.anonymous = false
396 -- Return all matching UCI sections for this TypedSection
397 function TypedSection.cfgsections(self)
399 uci.foreach(self.map.config, self.sectiontype,
401 if self:checkscope(section[".name"]) then
402 table.insert(sections, section[".name"])
409 -- Limits scope to sections that have certain option => value pairs
410 function TypedSection.depends(self, option, value)
411 table.insert(self.deps, {option=option, value=value})
414 -- Excludes several sections by name
415 function TypedSection.exclude(self, field)
416 self.excludes[field] = true
419 function TypedSection.parse(self)
420 if self.addremove then
422 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
423 local name = luci.http.formvalue(crval)
424 if self.anonymous then
430 -- Ignore if it already exists
431 if self:cfgvalue(name) then
435 name = self:checkscope(name)
438 self.err_invalid = true
441 if name and name:len() > 0 then
448 crval = "cbi.rts." .. self.config
449 name = luci.http.formvaluetable(crval)
450 for k,v in pairs(name) do
451 if self:cfgvalue(k) and self:checkscope(k) then
457 for i, k in ipairs(self:cfgsections()) do
458 AbstractSection.parse_dynamic(self, k)
459 if luci.http.formvalue("cbi.submit") then
462 AbstractSection.parse_optionals(self, k)
466 -- Verifies scope of sections
467 function TypedSection.checkscope(self, section)
468 -- Check if we are not excluded
469 if self.excludes[section] then
473 -- Check if at least one dependency is met
474 if #self.deps > 0 and self:cfgvalue(section) then
477 for k, v in ipairs(self.deps) do
478 if self:cfgvalue(section)[v.option] == v.value then
488 return self:validate(section)
492 -- Dummy validate function
493 function TypedSection.validate(self, section)
499 AbstractValue - An abstract Value Type
500 null: Value can be empty
501 valid: A function returning the value if it is valid otherwise nil
502 depends: A table of option => value pairs of which one must be true
503 default: The default value
504 size: The size of the input fields
505 rmempty: Unset value if empty
506 optional: This value is optional (see AbstractSection.optionals)
508 AbstractValue = class(Node)
510 function AbstractValue.__init__(self, map, option, ...)
511 Node.__init__(self, ...)
514 self.config = map.config
515 self.tag_invalid = {}
521 self.optional = false
524 -- Add a dependencie to another section field
525 function AbstractValue.depends(self, field, value)
526 table.insert(self.deps, {field=field, value=value})
529 -- Return whether this object should be created
530 function AbstractValue.formcreated(self, section)
531 local key = "cbi.opt."..self.config.."."..section
532 return (luci.http.formvalue(key) == self.option)
535 -- Returns the formvalue for this object
536 function AbstractValue.formvalue(self, section)
537 local key = "cbid."..self.map.config.."."..section.."."..self.option
538 return luci.http.formvalue(key)
541 function AbstractValue.parse(self, section)
542 local fvalue = self:formvalue(section)
544 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
545 fvalue = self:validate(fvalue)
547 self.tag_invalid[section] = true
549 if fvalue and not (fvalue == self:cfgvalue(section)) then
550 self:write(section, fvalue)
552 else -- Unset the UCI or error
553 if self.rmempty or self.optional then
559 -- Render if this value exists or if it is mandatory
560 function AbstractValue.render(self, s, scope)
561 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
564 scope.cbid = "cbid." .. self.config ..
568 scope.ifattr = function(cond,key,val)
570 return string.format(
571 ' %s="%s"', tostring(key),
572 tostring( val or scope[key] or self[key] or "" )
579 scope.attr = function(...)
580 return scope.ifattr( true, ... )
583 Node.render(self, scope)
587 -- Return the UCI value of this object
588 function AbstractValue.cfgvalue(self, section)
589 return self.map:get(section, self.option)
592 -- Validate the form value
593 function AbstractValue.validate(self, value)
598 function AbstractValue.write(self, section, value)
599 return self.map:set(section, self.option, value)
603 function AbstractValue.remove(self, section)
604 return self.map:del(section, self.option)
611 Value - A one-line value
612 maxlength: The maximum length
614 Value = class(AbstractValue)
616 function Value.__init__(self, ...)
617 AbstractValue.__init__(self, ...)
618 self.template = "cbi/value"
623 -- This validation is a bit more complex
624 function Value.validate(self, val)
625 if self.maxlength and tostring(val):len() > self.maxlength then
633 -- DummyValue - This does nothing except being there
634 DummyValue = class(AbstractValue)
636 function DummyValue.__init__(self, map, ...)
637 AbstractValue.__init__(self, map, ...)
638 self.template = "cbi/dvalue"
642 function DummyValue.parse(self)
646 function DummyValue.render(self, s)
647 luci.template.render(self.template, {self=self, section=s})
652 Flag - A flag being enabled or disabled
654 Flag = class(AbstractValue)
656 function Flag.__init__(self, ...)
657 AbstractValue.__init__(self, ...)
658 self.template = "cbi/fvalue"
664 -- A flag can only have two states: set or unset
665 function Flag.parse(self, section)
666 local fvalue = self:formvalue(section)
669 fvalue = self.enabled
671 fvalue = self.disabled
674 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
675 if not(fvalue == self:cfgvalue(section)) then
676 self:write(section, fvalue)
686 ListValue - A one-line value predefined in a list
687 widget: The widget that will be used (select, radio)
689 ListValue = class(AbstractValue)
691 function ListValue.__init__(self, ...)
692 AbstractValue.__init__(self, ...)
693 self.template = "cbi/lvalue"
698 self.widget = "select"
701 function ListValue.value(self, key, val)
703 table.insert(self.keylist, tostring(key))
704 table.insert(self.vallist, tostring(val))
707 function ListValue.validate(self, val)
708 if luci.util.contains(self.keylist, val) then
718 MultiValue - Multiple delimited values
719 widget: The widget that will be used (select, checkbox)
720 delimiter: The delimiter that will separate the values (default: " ")
722 MultiValue = class(AbstractValue)
724 function MultiValue.__init__(self, ...)
725 AbstractValue.__init__(self, ...)
726 self.template = "cbi/mvalue"
730 self.widget = "checkbox"
734 function MultiValue.render(self, ...)
735 if self.widget == "select" and not self.size then
736 self.size = #self.vallist
739 AbstractValue.render(self, ...)
742 function MultiValue.value(self, key, val)
744 table.insert(self.keylist, tostring(key))
745 table.insert(self.vallist, tostring(val))
748 function MultiValue.valuelist(self, section)
749 local val = self:cfgvalue(section)
751 if not(type(val) == "string") then
755 return luci.util.split(val, self.delimiter)
758 function MultiValue.validate(self, val)
759 val = (type(val) == "table") and val or {val}
763 for i, value in ipairs(val) do
764 if luci.util.contains(self.keylist, value) then
765 result = result and (result .. self.delimiter .. value) or value