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
558 Node.render(self, scope)
562 -- Return the UCI value of this object
563 function AbstractValue.cfgvalue(self, section)
564 return self.map:get(section, self.option)
567 -- Validate the form value
568 function AbstractValue.validate(self, value)
573 function AbstractValue.write(self, section, value)
574 return self.map:set(section, self.option, value)
578 function AbstractValue.remove(self, section)
579 return self.map:del(section, self.option)
586 Value - A one-line value
587 maxlength: The maximum length
589 Value = class(AbstractValue)
591 function Value.__init__(self, ...)
592 AbstractValue.__init__(self, ...)
593 self.template = "cbi/value"
598 -- This validation is a bit more complex
599 function Value.validate(self, val)
600 if self.maxlength and tostring(val):len() > self.maxlength then
608 -- DummyValue - This does nothing except being there
609 DummyValue = class(AbstractValue)
611 function DummyValue.__init__(self, map, ...)
612 AbstractValue.__init__(self, map, ...)
613 self.template = "cbi/dvalue"
617 function DummyValue.parse(self)
621 function DummyValue.render(self, s)
622 luci.template.render(self.template, {self=self, section=s})
627 Flag - A flag being enabled or disabled
629 Flag = class(AbstractValue)
631 function Flag.__init__(self, ...)
632 AbstractValue.__init__(self, ...)
633 self.template = "cbi/fvalue"
639 -- A flag can only have two states: set or unset
640 function Flag.parse(self, section)
641 local fvalue = self:formvalue(section)
644 fvalue = self.enabled
646 fvalue = self.disabled
649 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
650 if not(fvalue == self:cfgvalue(section)) then
651 self:write(section, fvalue)
661 ListValue - A one-line value predefined in a list
662 widget: The widget that will be used (select, radio)
664 ListValue = class(AbstractValue)
666 function ListValue.__init__(self, ...)
667 AbstractValue.__init__(self, ...)
668 self.template = "cbi/lvalue"
673 self.widget = "select"
676 function ListValue.value(self, key, val)
678 table.insert(self.keylist, tostring(key))
679 table.insert(self.vallist, tostring(val))
682 function ListValue.validate(self, val)
683 if luci.util.contains(self.keylist, val) then
693 MultiValue - Multiple delimited values
694 widget: The widget that will be used (select, checkbox)
695 delimiter: The delimiter that will separate the values (default: " ")
697 MultiValue = class(AbstractValue)
699 function MultiValue.__init__(self, ...)
700 AbstractValue.__init__(self, ...)
701 self.template = "cbi/mvalue"
705 self.widget = "checkbox"
709 function MultiValue.render(self, ...)
710 if self.widget == "select" and not self.size then
711 self.size = #self.vallist
714 AbstractValue.render(self, ...)
717 function MultiValue.value(self, key, val)
719 table.insert(self.keylist, tostring(key))
720 table.insert(self.vallist, tostring(val))
723 function MultiValue.valuelist(self, section)
724 local val = self:cfgvalue(section)
726 if not(type(val) == "string") then
730 return luci.util.split(val, self.delimiter)
733 function MultiValue.validate(self, val)
734 val = (type(val) == "table") and val or {val}
738 for i, value in ipairs(val) do
739 if luci.util.contains(self.keylist, value) then
740 result = result and (result .. self.delimiter .. value) or value