2 LuCI - Configuration Bind Interface
5 Offers an interface for binding confiugration 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 class = luci.util.class
35 local instanceof = luci.util.instanceof
37 -- Loads a CBI map from given file, creating an environment and returns it
41 require("luci.config")
44 local cbidir = luci.sys.libpath() .. "/model/cbi/"
45 local func, err = loadfile(cbidir..cbimap..".lua")
51 luci.i18n.loadc("cbi")
53 luci.util.resfenv(func)
54 luci.util.updfenv(func, luci.cbi)
55 luci.util.extfenv(func, "translate", luci.i18n.translate)
59 if not instanceof(map, Map) then
60 error("CBI map returns no valid map object!")
67 -- Node pseudo abstract class
70 function Node.__init__(self, title, description)
72 self.title = title or ""
73 self.description = description or ""
74 self.template = "cbi/node"
78 function Node.append(self, obj)
79 table.insert(self.children, obj)
82 -- Parse this node and its children
83 function Node.parse(self, ...)
84 for k, child in ipairs(self.children) do
90 function Node.render(self, scope)
94 luci.template.render(self.template, scope)
97 -- Render the children
98 function Node.render_children(self, ...)
99 for k, node in ipairs(self.children) do
106 A simple template element
108 Template = class(Node)
110 function Template.__init__(self, template)
112 self.template = template
117 Map - A map describing a configuration file
121 function Map.__init__(self, config, ...)
122 Node.__init__(self, ...)
124 self.template = "cbi/map"
125 self.uci = luci.model.uci.Session()
126 self.ucidata, self.uciorder = self.uci:sections(self.config)
127 if not self.ucidata or not self.uciorder then
128 error("Unable to read UCI data: " .. self.config)
132 -- Use optimized UCI writing
133 function Map.parse(self, ...)
134 self.uci:t_load(self.config)
135 Node.parse(self, ...)
136 self.uci:t_save(self.config)
139 -- Creates a child section
140 function Map.section(self, class, ...)
141 if instanceof(class, AbstractSection) then
142 local obj = class(self, ...)
146 error("class must be a descendent of AbstractSection")
151 function Map.add(self, sectiontype)
152 local name = self.uci:t_add(self.config, sectiontype)
154 self.ucidata[name] = {}
155 self.ucidata[name][".type"] = sectiontype
156 table.insert(self.uciorder, name)
162 function Map.set(self, section, option, value)
163 local stat = self.uci:t_set(self.config, section, option, value)
165 local val = self.uci:t_get(self.config, section, option)
167 self.ucidata[section][option] = val
169 if not self.ucidata[section] then
170 self.ucidata[section] = {}
172 self.ucidata[section][".type"] = val
173 table.insert(self.uciorder, section)
180 function Map.del(self, section, option)
181 local stat = self.uci:t_del(self.config, section, option)
184 self.ucidata[section][option] = nil
186 self.ucidata[section] = nil
187 for i, k in ipairs(self.uciorder) do
189 table.remove(self.uciorder, i)
198 function Map.get(self, section, option)
200 return self.ucidata, self.uciorder
201 elseif option and self.ucidata[section] then
202 return self.ucidata[section][option]
204 return self.ucidata[section]
212 AbstractSection = class(Node)
214 function AbstractSection.__init__(self, map, sectiontype, ...)
215 Node.__init__(self, ...)
216 self.sectiontype = sectiontype
218 self.config = map.config
222 self.addremove = false
226 -- Appends a new option
227 function AbstractSection.option(self, class, ...)
228 if instanceof(class, AbstractValue) then
229 local obj = class(self.map, ...)
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, ...)
317 AbstractSection.__init__(self, map, ...)
318 self.template = "cbi/nsection"
320 self.section = section
321 self.addremove = false
324 function NamedSection.parse(self)
325 local s = self.section
326 local active = self:cfgvalue(s)
329 if self.addremove then
330 local path = self.config.."."..s
331 if active then -- Remove the section
332 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
335 else -- Create and apply default values
336 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
337 for k,v in pairs(self.children) do
338 v:write(s, v.default)
345 AbstractSection.parse_dynamic(self, s)
346 if luci.http.formvalue("cbi.submit") then
349 AbstractSection.parse_optionals(self, s)
355 TypedSection - A (set of) configuration section(s) defined by the type
356 addremove: Defines whether the user can add/remove sections of this type
357 anonymous: Allow creating anonymous sections
358 validate: a validation function returning nil if the section is invalid
360 TypedSection = class(AbstractSection)
362 function TypedSection.__init__(self, ...)
363 AbstractSection.__init__(self, ...)
364 self.template = "cbi/tsection"
368 self.anonymous = false
371 -- Return all matching UCI sections for this TypedSection
372 function TypedSection.cfgsections(self)
374 local map, order = self.map:get()
376 for i, k in ipairs(order) do
377 if map[k][".type"] == self.sectiontype then
378 if self:checkscope(k) then
379 table.insert(sections, k)
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 Node.render(self, scope)
561 -- Return the UCI value of this object
562 function AbstractValue.cfgvalue(self, section)
563 return self.map:get(section, self.option)
566 -- Validate the form value
567 function AbstractValue.validate(self, value)
572 function AbstractValue.write(self, section, value)
573 return self.map:set(section, self.option, value)
577 function AbstractValue.remove(self, section)
578 return self.map:del(section, self.option)
585 Value - A one-line value
586 maxlength: The maximum length
587 isnumber: The value must be a valid (floating point) number
588 isinteger: The value must be a valid integer
589 ispositive: The value must be positive (and a number)
591 Value = class(AbstractValue)
593 function Value.__init__(self, ...)
594 AbstractValue.__init__(self, ...)
595 self.template = "cbi/value"
598 self.isnumber = false
599 self.isinteger = false
602 -- This validation is a bit more complex
603 function Value.validate(self, val)
604 if self.maxlength and tostring(val):len() > self.maxlength then
608 return luci.util.validate(val, self.isnumber, self.isinteger)
612 -- DummyValue - This does nothing except being there
613 DummyValue = class(AbstractValue)
615 function DummyValue.__init__(self, map, ...)
616 AbstractValue.__init__(self, map, ...)
617 self.template = "cbi/dvalue"
621 function DummyValue.parse(self)
625 function DummyValue.render(self, s)
626 luci.template.render(self.template, {self=self, section=s})
631 Flag - A flag being enabled or disabled
633 Flag = class(AbstractValue)
635 function Flag.__init__(self, ...)
636 AbstractValue.__init__(self, ...)
637 self.template = "cbi/fvalue"
643 -- A flag can only have two states: set or unset
644 function Flag.parse(self, section)
645 local fvalue = self:formvalue(section)
648 fvalue = self.enabled
650 fvalue = self.disabled
653 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
654 if not(fvalue == self:cfgvalue(section)) then
655 self:write(section, fvalue)
665 ListValue - A one-line value predefined in a list
666 widget: The widget that will be used (select, radio)
668 ListValue = class(AbstractValue)
670 function ListValue.__init__(self, ...)
671 AbstractValue.__init__(self, ...)
672 self.template = "cbi/lvalue"
677 self.widget = "select"
680 function ListValue.value(self, key, val)
682 table.insert(self.keylist, tostring(key))
683 table.insert(self.vallist, tostring(val))
686 function ListValue.validate(self, val)
687 if luci.util.contains(self.keylist, val) then
697 MultiValue - Multiple delimited values
698 widget: The widget that will be used (select, checkbox)
699 delimiter: The delimiter that will separate the values (default: " ")
701 MultiValue = class(AbstractValue)
703 function MultiValue.__init__(self, ...)
704 AbstractValue.__init__(self, ...)
705 self.template = "cbi/mvalue"
709 self.widget = "checkbox"
713 function MultiValue.value(self, key, val)
715 table.insert(self.keylist, tostring(key))
716 table.insert(self.vallist, tostring(val))
719 function MultiValue.valuelist(self, section)
720 local val = self:cfgvalue(section)
722 if not(type(val) == "string") then
726 return luci.util.split(val, self.delimiter)
729 function MultiValue.validate(self, val)
730 if not(type(val) == "string") then
736 for value in val:gmatch("[^\n]+") do
737 if luci.util.contains(self.keylist, value) then
738 result = result .. self.delimiter .. value
742 if result:len() > 0 then
743 return result:sub(self.delimiter:len() + 1)