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 if not instanceof(map, Map) then
63 error("CBI map returns no valid map object!")
70 -- Node pseudo abstract class
73 function Node.__init__(self, title, description)
75 self.title = title or ""
76 self.description = description or ""
77 self.template = "cbi/node"
81 function Node._i18n(self, config, section, option, title, description)
84 if type(luci.i18n) == "table" then
86 local key = config:gsub("[^%w]+", "")
88 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
89 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
91 self.title = title or luci.i18n.translate( key, option or section or config )
92 self.description = description or luci.i18n.translate( key .. "_desc", "" )
97 function Node.append(self, obj)
98 table.insert(self.children, obj)
101 -- Parse this node and its children
102 function Node.parse(self, ...)
103 for k, child in ipairs(self.children) do
109 function Node.render(self, scope)
113 luci.template.render(self.template, scope)
116 -- Render the children
117 function Node.render_children(self, ...)
118 for k, node in ipairs(self.children) do
125 A simple template element
127 Template = class(Node)
129 function Template.__init__(self, template)
131 self.template = template
136 Map - A map describing a configuration file
140 function Map.__init__(self, config, ...)
141 Node.__init__(self, ...)
142 Node._i18n(self, config, nil, nil, ...)
145 self.template = "cbi/map"
146 if not uci.load(self.config) then
147 error("Unable to read UCI data: " .. self.config)
151 -- Use optimized UCI writing
152 function Map.parse(self, ...)
153 Node.parse(self, ...)
154 uci.save(self.config)
155 uci.unload(self.config)
158 -- Creates a child section
159 function Map.section(self, class, ...)
160 if instanceof(class, AbstractSection) then
161 local obj = class(self, ...)
165 error("class must be a descendent of AbstractSection")
170 function Map.add(self, sectiontype)
171 return uci.add(self.config, sectiontype)
175 function Map.set(self, section, option, value)
176 return uci.set(self.config, section, option or value, option and value)
180 function Map.del(self, section, option)
182 return uci.delete(self.config, section, option)
184 return uci.delete(self.config, section)
189 function Map.get(self, section, option)
191 return uci.get_all(self.config)
193 return uci.get(self.config, section, option)
195 return uci.get_all(self.config, section)
203 AbstractSection = class(Node)
205 function AbstractSection.__init__(self, map, sectiontype, ...)
206 Node.__init__(self, ...)
207 self.sectiontype = sectiontype
209 self.config = map.config
213 self.addremove = false
217 -- Appends a new option
218 function AbstractSection.option(self, class, option, ...)
219 if instanceof(class, AbstractValue) then
220 local obj = class(self.map, option, ...)
222 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
227 error("class must be a descendent of AbstractValue")
231 -- Parse optional options
232 function AbstractSection.parse_optionals(self, section)
233 if not self.optional then
237 self.optionals[section] = {}
239 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
240 for k,v in ipairs(self.children) do
241 if v.optional and not v:cfgvalue(section) then
242 if field == v.option then
245 table.insert(self.optionals[section], v)
250 if field and #field > 0 and self.dynamic then
251 self:add_dynamic(field)
255 -- Add a dynamic option
256 function AbstractSection.add_dynamic(self, field, optional)
257 local o = self:option(Value, field, field)
258 o.optional = optional
261 -- Parse all dynamic options
262 function AbstractSection.parse_dynamic(self, section)
263 if not self.dynamic then
267 local arr = luci.util.clone(self:cfgvalue(section))
268 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
269 for k, v in pairs(form) do
273 for key,val in pairs(arr) do
276 for i,c in ipairs(self.children) do
277 if c.option == key then
282 if create and key:sub(1, 1) ~= "." then
283 self:add_dynamic(key, true)
288 -- Returns the section's UCI table
289 function AbstractSection.cfgvalue(self, section)
290 return self.map:get(section)
293 -- Removes the section
294 function AbstractSection.remove(self, section)
295 return self.map:del(section)
298 -- Creates the section
299 function AbstractSection.create(self, section)
300 return self.map:set(section, nil, self.sectiontype)
306 NamedSection - A fixed configuration section defined by its name
308 NamedSection = class(AbstractSection)
310 function NamedSection.__init__(self, map, section, type, ...)
311 AbstractSection.__init__(self, map, type, ...)
312 Node._i18n(self, map.config, section, nil, ...)
314 self.template = "cbi/nsection"
315 self.section = section
316 self.addremove = false
319 function NamedSection.parse(self)
320 local s = self.section
321 local active = self:cfgvalue(s)
324 if self.addremove then
325 local path = self.config.."."..s
326 if active then -- Remove the section
327 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
330 else -- Create and apply default values
331 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
332 for k,v in pairs(self.children) do
333 v:write(s, v.default)
340 AbstractSection.parse_dynamic(self, s)
341 if luci.http.formvalue("cbi.submit") then
344 AbstractSection.parse_optionals(self, s)
350 TypedSection - A (set of) configuration section(s) defined by the type
351 addremove: Defines whether the user can add/remove sections of this type
352 anonymous: Allow creating anonymous sections
353 validate: a validation function returning nil if the section is invalid
355 TypedSection = class(AbstractSection)
357 function TypedSection.__init__(self, map, type, ...)
358 AbstractSection.__init__(self, map, type, ...)
359 Node._i18n(self, map.config, type, nil, ...)
361 self.template = "cbi/tsection"
365 self.anonymous = false
368 -- Return all matching UCI sections for this TypedSection
369 function TypedSection.cfgsections(self)
371 uci.foreach(self.map.config, self.sectiontype,
373 if self:checkscope(section[".name"]) then
374 table.insert(sections, section[".name"])
381 -- Creates a new section of this type with the given name (or anonymous)
382 function TypedSection.create(self, name)
384 self.map:set(name, nil, self.sectiontype)
386 name = self.map:add(self.sectiontype)
389 for k,v in pairs(self.children) do
391 self.map:set(name, v.option, v.default)
396 -- Limits scope to sections that have certain option => value pairs
397 function TypedSection.depends(self, option, value)
398 table.insert(self.deps, {option=option, value=value})
401 -- Excludes several sections by name
402 function TypedSection.exclude(self, field)
403 self.excludes[field] = true
406 function TypedSection.parse(self)
407 if self.addremove then
409 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
410 local name = luci.http.formvalue(crval)
411 if self.anonymous then
417 -- Ignore if it already exists
418 if self:cfgvalue(name) then
422 name = self:checkscope(name)
425 self.err_invalid = true
428 if name and name:len() > 0 then
435 crval = "cbi.rts." .. self.config
436 name = luci.http.formvaluetable(crval)
437 for k,v in pairs(name) do
438 if self:cfgvalue(k) and self:checkscope(k) then
444 for i, k in ipairs(self:cfgsections()) do
445 AbstractSection.parse_dynamic(self, k)
446 if luci.http.formvalue("cbi.submit") then
449 AbstractSection.parse_optionals(self, k)
453 -- Verifies scope of sections
454 function TypedSection.checkscope(self, section)
455 -- Check if we are not excluded
456 if self.excludes[section] then
460 -- Check if at least one dependency is met
461 if #self.deps > 0 and self:cfgvalue(section) then
464 for k, v in ipairs(self.deps) do
465 if self:cfgvalue(section)[v.option] == v.value then
475 return self:validate(section)
479 -- Dummy validate function
480 function TypedSection.validate(self, section)
486 AbstractValue - An abstract Value Type
487 null: Value can be empty
488 valid: A function returning the value if it is valid otherwise nil
489 depends: A table of option => value pairs of which one must be true
490 default: The default value
491 size: The size of the input fields
492 rmempty: Unset value if empty
493 optional: This value is optional (see AbstractSection.optionals)
495 AbstractValue = class(Node)
497 function AbstractValue.__init__(self, map, option, ...)
498 Node.__init__(self, ...)
501 self.config = map.config
502 self.tag_invalid = {}
508 self.optional = false
511 -- Add a dependencie to another section field
512 function AbstractValue.depends(self, field, value)
513 table.insert(self.deps, {field=field, value=value})
516 -- Return whether this object should be created
517 function AbstractValue.formcreated(self, section)
518 local key = "cbi.opt."..self.config.."."..section
519 return (luci.http.formvalue(key) == self.option)
522 -- Returns the formvalue for this object
523 function AbstractValue.formvalue(self, section)
524 local key = "cbid."..self.map.config.."."..section.."."..self.option
525 return luci.http.formvalue(key)
528 function AbstractValue.parse(self, section)
529 local fvalue = self:formvalue(section)
531 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
532 fvalue = self:validate(fvalue)
534 self.tag_invalid[section] = true
536 if fvalue and not (fvalue == self:cfgvalue(section)) then
537 self:write(section, fvalue)
539 else -- Unset the UCI or error
540 if self.rmempty or self.optional then
546 -- Render if this value exists or if it is mandatory
547 function AbstractValue.render(self, s, scope)
548 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
552 Node.render(self, scope)
556 -- Return the UCI value of this object
557 function AbstractValue.cfgvalue(self, section)
558 return self.map:get(section, self.option)
561 -- Validate the form value
562 function AbstractValue.validate(self, value)
567 function AbstractValue.write(self, section, value)
568 return self.map:set(section, self.option, value)
572 function AbstractValue.remove(self, section)
573 return self.map:del(section, self.option)
580 Value - A one-line value
581 maxlength: The maximum length
583 Value = class(AbstractValue)
585 function Value.__init__(self, ...)
586 AbstractValue.__init__(self, ...)
587 self.template = "cbi/value"
592 -- This validation is a bit more complex
593 function Value.validate(self, val)
594 if self.maxlength and tostring(val):len() > self.maxlength then
602 -- DummyValue - This does nothing except being there
603 DummyValue = class(AbstractValue)
605 function DummyValue.__init__(self, map, ...)
606 AbstractValue.__init__(self, map, ...)
607 self.template = "cbi/dvalue"
611 function DummyValue.parse(self)
615 function DummyValue.render(self, s)
616 luci.template.render(self.template, {self=self, section=s})
621 Flag - A flag being enabled or disabled
623 Flag = class(AbstractValue)
625 function Flag.__init__(self, ...)
626 AbstractValue.__init__(self, ...)
627 self.template = "cbi/fvalue"
633 -- A flag can only have two states: set or unset
634 function Flag.parse(self, section)
635 local fvalue = self:formvalue(section)
638 fvalue = self.enabled
640 fvalue = self.disabled
643 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
644 if not(fvalue == self:cfgvalue(section)) then
645 self:write(section, fvalue)
655 ListValue - A one-line value predefined in a list
656 widget: The widget that will be used (select, radio)
658 ListValue = class(AbstractValue)
660 function ListValue.__init__(self, ...)
661 AbstractValue.__init__(self, ...)
662 self.template = "cbi/lvalue"
667 self.widget = "select"
670 function ListValue.value(self, key, val)
672 table.insert(self.keylist, tostring(key))
673 table.insert(self.vallist, tostring(val))
676 function ListValue.validate(self, val)
677 if luci.util.contains(self.keylist, val) then
687 MultiValue - Multiple delimited values
688 widget: The widget that will be used (select, checkbox)
689 delimiter: The delimiter that will separate the values (default: " ")
691 MultiValue = class(AbstractValue)
693 function MultiValue.__init__(self, ...)
694 AbstractValue.__init__(self, ...)
695 self.template = "cbi/mvalue"
699 self.widget = "checkbox"
703 function MultiValue.render(self, ...)
704 if self.widget == "select" and not self.size then
705 self.size = #self.vallist
708 AbstractValue.render(self, ...)
711 function MultiValue.value(self, key, val)
713 table.insert(self.keylist, tostring(key))
714 table.insert(self.vallist, tostring(val))
717 function MultiValue.valuelist(self, section)
718 local val = self:cfgvalue(section)
720 if not(type(val) == "string") then
724 return luci.util.split(val, self.delimiter)
727 function MultiValue.validate(self, val)
728 if not(type(val) == "string") then
734 for value in val:gmatch("[^\n]+") do
735 if luci.util.contains(self.keylist, value) then
736 result = result .. self.delimiter .. value
740 if result:len() > 0 then
741 return result:sub(self.delimiter:len() + 1)