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
219 self.addremove = false
223 -- Appends a new option
224 function AbstractSection.option(self, class, option, ...)
225 if instanceof(class, AbstractValue) then
226 local obj = class(self.map, option, ...)
228 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
233 error("class must be a descendent of AbstractValue")
237 -- Parse optional options
238 function AbstractSection.parse_optionals(self, section)
239 if not self.optional then
243 self.optionals[section] = {}
245 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
246 for k,v in ipairs(self.children) do
247 if v.optional and not v:cfgvalue(section) then
248 if field == v.option then
251 table.insert(self.optionals[section], v)
256 if field and #field > 0 and self.dynamic then
257 self:add_dynamic(field)
261 -- Add a dynamic option
262 function AbstractSection.add_dynamic(self, field, optional)
263 local o = self:option(Value, field, field)
264 o.optional = optional
267 -- Parse all dynamic options
268 function AbstractSection.parse_dynamic(self, section)
269 if not self.dynamic then
273 local arr = luci.util.clone(self:cfgvalue(section))
274 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
275 for k, v in pairs(form) do
279 for key,val in pairs(arr) do
282 for i,c in ipairs(self.children) do
283 if c.option == key then
288 if create and key:sub(1, 1) ~= "." then
289 self:add_dynamic(key, true)
294 -- Returns the section's UCI table
295 function AbstractSection.cfgvalue(self, section)
296 return self.map:get(section)
299 -- Removes the section
300 function AbstractSection.remove(self, section)
301 return self.map:del(section)
304 -- Creates the section
305 function AbstractSection.create(self, section)
306 return self.map:set(section, nil, self.sectiontype)
312 NamedSection - A fixed configuration section defined by its name
314 NamedSection = class(AbstractSection)
316 function NamedSection.__init__(self, map, section, type, ...)
317 AbstractSection.__init__(self, map, type, ...)
318 Node._i18n(self, map.config, section, nil, ...)
320 self.template = "cbi/nsection"
321 self.section = section
322 self.addremove = false
325 function NamedSection.parse(self)
326 local s = self.section
327 local active = self:cfgvalue(s)
330 if self.addremove then
331 local path = self.config.."."..s
332 if active then -- Remove the section
333 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
336 else -- Create and apply default values
337 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
338 for k,v in pairs(self.children) do
339 v:write(s, v.default)
346 AbstractSection.parse_dynamic(self, s)
347 if luci.http.formvalue("cbi.submit") then
350 AbstractSection.parse_optionals(self, s)
356 TypedSection - A (set of) configuration section(s) defined by the type
357 addremove: Defines whether the user can add/remove sections of this type
358 anonymous: Allow creating anonymous sections
359 validate: a validation function returning nil if the section is invalid
361 TypedSection = class(AbstractSection)
363 function TypedSection.__init__(self, map, type, ...)
364 AbstractSection.__init__(self, map, type, ...)
365 Node._i18n(self, map.config, type, nil, ...)
367 self.template = "cbi/tsection"
371 self.anonymous = false
374 -- Return all matching UCI sections for this TypedSection
375 function TypedSection.cfgsections(self)
377 uci.foreach(self.map.config, self.sectiontype,
379 if self:checkscope(section[".name"]) then
380 table.insert(sections, section[".name"])
387 -- Creates a new section of this type with the given name (or anonymous)
388 function TypedSection.create(self, name)
390 self.map:set(name, nil, self.sectiontype)
392 name = self.map:add(self.sectiontype)
395 for k,v in pairs(self.children) do
397 self.map:set(name, v.option, v.default)
402 -- Limits scope to sections that have certain option => value pairs
403 function TypedSection.depends(self, option, value)
404 table.insert(self.deps, {option=option, value=value})
407 -- Excludes several sections by name
408 function TypedSection.exclude(self, field)
409 self.excludes[field] = true
412 function TypedSection.parse(self)
413 if self.addremove then
415 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
416 local name = luci.http.formvalue(crval)
417 if self.anonymous then
423 -- Ignore if it already exists
424 if self:cfgvalue(name) then
428 name = self:checkscope(name)
431 self.err_invalid = true
434 if name and name:len() > 0 then
441 crval = "cbi.rts." .. self.config
442 name = luci.http.formvaluetable(crval)
443 for k,v in pairs(name) do
444 if self:cfgvalue(k) and self:checkscope(k) then
450 for i, k in ipairs(self:cfgsections()) do
451 AbstractSection.parse_dynamic(self, k)
452 if luci.http.formvalue("cbi.submit") then
455 AbstractSection.parse_optionals(self, k)
459 -- Verifies scope of sections
460 function TypedSection.checkscope(self, section)
461 -- Check if we are not excluded
462 if self.excludes[section] then
466 -- Check if at least one dependency is met
467 if #self.deps > 0 and self:cfgvalue(section) then
470 for k, v in ipairs(self.deps) do
471 if self:cfgvalue(section)[v.option] == v.value then
481 return self:validate(section)
485 -- Dummy validate function
486 function TypedSection.validate(self, section)
492 AbstractValue - An abstract Value Type
493 null: Value can be empty
494 valid: A function returning the value if it is valid otherwise nil
495 depends: A table of option => value pairs of which one must be true
496 default: The default value
497 size: The size of the input fields
498 rmempty: Unset value if empty
499 optional: This value is optional (see AbstractSection.optionals)
501 AbstractValue = class(Node)
503 function AbstractValue.__init__(self, map, option, ...)
504 Node.__init__(self, ...)
507 self.config = map.config
508 self.tag_invalid = {}
514 self.optional = false
517 -- Add a dependencie to another section field
518 function AbstractValue.depends(self, field, value)
519 table.insert(self.deps, {field=field, value=value})
522 -- Return whether this object should be created
523 function AbstractValue.formcreated(self, section)
524 local key = "cbi.opt."..self.config.."."..section
525 return (luci.http.formvalue(key) == self.option)
528 -- Returns the formvalue for this object
529 function AbstractValue.formvalue(self, section)
530 local key = "cbid."..self.map.config.."."..section.."."..self.option
531 return luci.http.formvalue(key)
534 function AbstractValue.parse(self, section)
535 local fvalue = self:formvalue(section)
537 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
538 fvalue = self:validate(fvalue)
540 self.tag_invalid[section] = true
542 if fvalue and not (fvalue == self:cfgvalue(section)) then
543 self:write(section, fvalue)
545 else -- Unset the UCI or error
546 if self.rmempty or self.optional then
552 -- Render if this value exists or if it is mandatory
553 function AbstractValue.render(self, s, scope)
554 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
557 scope.cbid = "cbid." .. self.config ..
561 scope.ifattr = function(cond,key,val)
563 return string.format(
564 ' %s="%s"', tostring(key),
565 tostring( val or scope[key] or self[key] or "" )
572 scope.attr = function(...)
573 return scope.ifattr( true, ... )
576 Node.render(self, scope)
580 -- Return the UCI value of this object
581 function AbstractValue.cfgvalue(self, section)
582 return self.map:get(section, self.option)
585 -- Validate the form value
586 function AbstractValue.validate(self, value)
591 function AbstractValue.write(self, section, value)
592 return self.map:set(section, self.option, value)
596 function AbstractValue.remove(self, section)
597 return self.map:del(section, self.option)
604 Value - A one-line value
605 maxlength: The maximum length
607 Value = class(AbstractValue)
609 function Value.__init__(self, ...)
610 AbstractValue.__init__(self, ...)
611 self.template = "cbi/value"
616 -- This validation is a bit more complex
617 function Value.validate(self, val)
618 if self.maxlength and tostring(val):len() > self.maxlength then
626 -- DummyValue - This does nothing except being there
627 DummyValue = class(AbstractValue)
629 function DummyValue.__init__(self, map, ...)
630 AbstractValue.__init__(self, map, ...)
631 self.template = "cbi/dvalue"
635 function DummyValue.parse(self)
639 function DummyValue.render(self, s)
640 luci.template.render(self.template, {self=self, section=s})
645 Flag - A flag being enabled or disabled
647 Flag = class(AbstractValue)
649 function Flag.__init__(self, ...)
650 AbstractValue.__init__(self, ...)
651 self.template = "cbi/fvalue"
657 -- A flag can only have two states: set or unset
658 function Flag.parse(self, section)
659 local fvalue = self:formvalue(section)
662 fvalue = self.enabled
664 fvalue = self.disabled
667 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
668 if not(fvalue == self:cfgvalue(section)) then
669 self:write(section, fvalue)
679 ListValue - A one-line value predefined in a list
680 widget: The widget that will be used (select, radio)
682 ListValue = class(AbstractValue)
684 function ListValue.__init__(self, ...)
685 AbstractValue.__init__(self, ...)
686 self.template = "cbi/lvalue"
691 self.widget = "select"
694 function ListValue.value(self, key, val)
696 table.insert(self.keylist, tostring(key))
697 table.insert(self.vallist, tostring(val))
700 function ListValue.validate(self, val)
701 if luci.util.contains(self.keylist, val) then
711 MultiValue - Multiple delimited values
712 widget: The widget that will be used (select, checkbox)
713 delimiter: The delimiter that will separate the values (default: " ")
715 MultiValue = class(AbstractValue)
717 function MultiValue.__init__(self, ...)
718 AbstractValue.__init__(self, ...)
719 self.template = "cbi/mvalue"
723 self.widget = "checkbox"
727 function MultiValue.render(self, ...)
728 if self.widget == "select" and not self.size then
729 self.size = #self.vallist
732 AbstractValue.render(self, ...)
735 function MultiValue.value(self, key, val)
737 table.insert(self.keylist, tostring(key))
738 table.insert(self.vallist, tostring(val))
741 function MultiValue.valuelist(self, section)
742 local val = self:cfgvalue(section)
744 if not(type(val) == "string") then
748 return luci.util.split(val, self.delimiter)
751 function MultiValue.validate(self, val)
752 val = (type(val) == "table") and val or {val}
756 for i, value in ipairs(val) do
757 if luci.util.contains(self.keylist, value) then
758 result = result and (result .. self.delimiter .. value) or value