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) then
367 AbstractSection.parse_dynamic(self, s)
368 if luci.http.formvalue("cbi.submit") then
371 AbstractSection.parse_optionals(self, s)
377 TypedSection - A (set of) configuration section(s) defined by the type
378 addremove: Defines whether the user can add/remove sections of this type
379 anonymous: Allow creating anonymous sections
380 validate: a validation function returning nil if the section is invalid
382 TypedSection = class(AbstractSection)
384 function TypedSection.__init__(self, map, type, ...)
385 AbstractSection.__init__(self, map, type, ...)
386 Node._i18n(self, map.config, type, nil, ...)
388 self.template = "cbi/tsection"
392 self.anonymous = false
395 -- Return all matching UCI sections for this TypedSection
396 function TypedSection.cfgsections(self)
398 uci.foreach(self.map.config, self.sectiontype,
400 if self:checkscope(section[".name"]) then
401 table.insert(sections, section[".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)
647 Flag - A flag being enabled or disabled
649 Flag = class(AbstractValue)
651 function Flag.__init__(self, ...)
652 AbstractValue.__init__(self, ...)
653 self.template = "cbi/fvalue"
659 -- A flag can only have two states: set or unset
660 function Flag.parse(self, section)
661 local fvalue = self:formvalue(section)
664 fvalue = self.enabled
666 fvalue = self.disabled
669 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
670 if not(fvalue == self:cfgvalue(section)) then
671 self:write(section, fvalue)
681 ListValue - A one-line value predefined in a list
682 widget: The widget that will be used (select, radio)
684 ListValue = class(AbstractValue)
686 function ListValue.__init__(self, ...)
687 AbstractValue.__init__(self, ...)
688 self.template = "cbi/lvalue"
693 self.widget = "select"
696 function ListValue.value(self, key, val)
698 table.insert(self.keylist, tostring(key))
699 table.insert(self.vallist, tostring(val))
702 function ListValue.validate(self, val)
703 if luci.util.contains(self.keylist, val) then
713 MultiValue - Multiple delimited values
714 widget: The widget that will be used (select, checkbox)
715 delimiter: The delimiter that will separate the values (default: " ")
717 MultiValue = class(AbstractValue)
719 function MultiValue.__init__(self, ...)
720 AbstractValue.__init__(self, ...)
721 self.template = "cbi/mvalue"
725 self.widget = "checkbox"
729 function MultiValue.render(self, ...)
730 if self.widget == "select" and not self.size then
731 self.size = #self.vallist
734 AbstractValue.render(self, ...)
737 function MultiValue.value(self, key, val)
739 table.insert(self.keylist, tostring(key))
740 table.insert(self.vallist, tostring(val))
743 function MultiValue.valuelist(self, section)
744 local val = self:cfgvalue(section)
746 if not(type(val) == "string") then
750 return luci.util.split(val, self.delimiter)
753 function MultiValue.validate(self, val)
754 val = (type(val) == "table") and val or {val}
758 for i, value in ipairs(val) do
759 if luci.util.contains(self.keylist, value) then
760 result = result and (result .. self.delimiter .. value) or value