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 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)
56 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
60 if not instanceof(map, Map) then
61 error("CBI map returns no valid map object!")
68 -- Node pseudo abstract class
71 function Node.__init__(self, title, description)
73 self.title = title or ""
74 self.description = description or ""
75 self.template = "cbi/node"
79 function Node._i18n(self, config, section, option, title, description)
82 if type(luci.i18n) == "table" then
84 local key = config:gsub("[^%w]+", "")
86 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
87 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
89 self.title = title or luci.i18n.translate( key, option or section or config )
90 self.description = description or luci.i18n.translate( key .. "_desc", "" )
95 function Node.append(self, obj)
96 table.insert(self.children, obj)
99 -- Parse this node and its children
100 function Node.parse(self, ...)
101 for k, child in ipairs(self.children) do
107 function Node.render(self, scope)
111 luci.template.render(self.template, scope)
114 -- Render the children
115 function Node.render_children(self, ...)
116 for k, node in ipairs(self.children) do
123 A simple template element
125 Template = class(Node)
127 function Template.__init__(self, template)
129 self.template = template
134 Map - A map describing a configuration file
138 function Map.__init__(self, config, ...)
139 Node.__init__(self, ...)
140 Node._i18n(self, config, nil, nil, ...)
143 self.template = "cbi/map"
144 self.uci = luci.model.uci.Session()
145 self.ucidata, self.uciorder = self.uci:sections(self.config)
146 if not self.ucidata or not self.uciorder then
147 error("Unable to read UCI data: " .. self.config)
151 -- Use optimized UCI writing
152 function Map.parse(self, ...)
153 self.uci:t_load(self.config)
154 Node.parse(self, ...)
155 self.uci:t_save(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 local name = self.uci:t_add(self.config, sectiontype)
173 self.ucidata[name] = {}
174 self.ucidata[name][".type"] = sectiontype
175 table.insert(self.uciorder, name)
181 function Map.set(self, section, option, value)
182 local stat = self.uci:t_set(self.config, section, option, value)
184 local val = self.uci:t_get(self.config, section, option)
186 self.ucidata[section][option] = val
188 if not self.ucidata[section] then
189 self.ucidata[section] = {}
191 self.ucidata[section][".type"] = val
192 table.insert(self.uciorder, section)
199 function Map.del(self, section, option)
200 local stat = self.uci:t_del(self.config, section, option)
203 self.ucidata[section][option] = nil
205 self.ucidata[section] = nil
206 for i, k in ipairs(self.uciorder) do
208 table.remove(self.uciorder, i)
217 function Map.get(self, section, option)
219 return self.ucidata, self.uciorder
220 elseif option and self.ucidata[section] then
221 return self.ucidata[section][option]
223 return self.ucidata[section]
231 AbstractSection = class(Node)
233 function AbstractSection.__init__(self, map, sectiontype, ...)
234 Node.__init__(self, ...)
235 self.sectiontype = sectiontype
237 self.config = map.config
241 self.addremove = false
245 -- Appends a new option
246 function AbstractSection.option(self, class, option, ...)
247 if instanceof(class, AbstractValue) then
248 local obj = class(self.map, option, ...)
250 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
255 error("class must be a descendent of AbstractValue")
259 -- Parse optional options
260 function AbstractSection.parse_optionals(self, section)
261 if not self.optional then
265 self.optionals[section] = {}
267 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
268 for k,v in ipairs(self.children) do
269 if v.optional and not v:cfgvalue(section) then
270 if field == v.option then
273 table.insert(self.optionals[section], v)
278 if field and #field > 0 and self.dynamic then
279 self:add_dynamic(field)
283 -- Add a dynamic option
284 function AbstractSection.add_dynamic(self, field, optional)
285 local o = self:option(Value, field, field)
286 o.optional = optional
289 -- Parse all dynamic options
290 function AbstractSection.parse_dynamic(self, section)
291 if not self.dynamic then
295 local arr = luci.util.clone(self:cfgvalue(section))
296 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
297 for k, v in pairs(form) do
301 for key,val in pairs(arr) do
304 for i,c in ipairs(self.children) do
305 if c.option == key then
310 if create and key:sub(1, 1) ~= "." then
311 self:add_dynamic(key, true)
316 -- Returns the section's UCI table
317 function AbstractSection.cfgvalue(self, section)
318 return self.map:get(section)
321 -- Removes the section
322 function AbstractSection.remove(self, section)
323 return self.map:del(section)
326 -- Creates the section
327 function AbstractSection.create(self, section)
328 return self.map:set(section, nil, self.sectiontype)
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) and self:create(s) then
360 for k,v in pairs(self.children) do
361 v:write(s, v.default)
368 AbstractSection.parse_dynamic(self, s)
369 if luci.http.formvalue("cbi.submit") then
372 AbstractSection.parse_optionals(self, s)
378 TypedSection - A (set of) configuration section(s) defined by the type
379 addremove: Defines whether the user can add/remove sections of this type
380 anonymous: Allow creating anonymous sections
381 validate: a validation function returning nil if the section is invalid
383 TypedSection = class(AbstractSection)
385 function TypedSection.__init__(self, map, type, ...)
386 AbstractSection.__init__(self, map, type, ...)
387 Node._i18n(self, map.config, type, nil, ...)
389 self.template = "cbi/tsection"
393 self.anonymous = false
396 -- Return all matching UCI sections for this TypedSection
397 function TypedSection.cfgsections(self)
399 local map, order = self.map:get()
401 for i, k in ipairs(order) do
402 if map[k][".type"] == self.sectiontype then
403 if self:checkscope(k) then
404 table.insert(sections, k)
412 -- Creates a new section of this type with the given name (or anonymous)
413 function TypedSection.create(self, name)
415 self.map:set(name, nil, self.sectiontype)
417 name = self.map:add(self.sectiontype)
420 for k,v in pairs(self.children) do
422 self.map:set(name, v.option, v.default)
427 -- Limits scope to sections that have certain option => value pairs
428 function TypedSection.depends(self, option, value)
429 table.insert(self.deps, {option=option, value=value})
432 -- Excludes several sections by name
433 function TypedSection.exclude(self, field)
434 self.excludes[field] = true
437 function TypedSection.parse(self)
438 if self.addremove then
440 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
441 local name = luci.http.formvalue(crval)
442 if self.anonymous then
448 -- Ignore if it already exists
449 if self:cfgvalue(name) then
453 name = self:checkscope(name)
456 self.err_invalid = true
459 if name and name:len() > 0 then
466 crval = "cbi.rts." .. self.config
467 name = luci.http.formvaluetable(crval)
468 for k,v in pairs(name) do
469 if self:cfgvalue(k) and self:checkscope(k) then
475 for i, k in ipairs(self:cfgsections()) do
476 AbstractSection.parse_dynamic(self, k)
477 if luci.http.formvalue("cbi.submit") then
480 AbstractSection.parse_optionals(self, k)
484 -- Verifies scope of sections
485 function TypedSection.checkscope(self, section)
486 -- Check if we are not excluded
487 if self.excludes[section] then
491 -- Check if at least one dependency is met
492 if #self.deps > 0 and self:cfgvalue(section) then
495 for k, v in ipairs(self.deps) do
496 if self:cfgvalue(section)[v.option] == v.value then
506 return self:validate(section)
510 -- Dummy validate function
511 function TypedSection.validate(self, section)
517 AbstractValue - An abstract Value Type
518 null: Value can be empty
519 valid: A function returning the value if it is valid otherwise nil
520 depends: A table of option => value pairs of which one must be true
521 default: The default value
522 size: The size of the input fields
523 rmempty: Unset value if empty
524 optional: This value is optional (see AbstractSection.optionals)
526 AbstractValue = class(Node)
528 function AbstractValue.__init__(self, map, option, ...)
529 Node.__init__(self, ...)
532 self.config = map.config
533 self.tag_invalid = {}
539 self.optional = false
542 -- Add a dependencie to another section field
543 function AbstractValue.depends(self, field, value)
544 table.insert(self.deps, {field=field, value=value})
547 -- Return whether this object should be created
548 function AbstractValue.formcreated(self, section)
549 local key = "cbi.opt."..self.config.."."..section
550 return (luci.http.formvalue(key) == self.option)
553 -- Returns the formvalue for this object
554 function AbstractValue.formvalue(self, section)
555 local key = "cbid."..self.map.config.."."..section.."."..self.option
556 return luci.http.formvalue(key)
559 function AbstractValue.parse(self, section)
560 local fvalue = self:formvalue(section)
562 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
563 fvalue = self:validate(fvalue)
565 self.tag_invalid[section] = true
567 if fvalue and not (fvalue == self:cfgvalue(section)) then
568 self:write(section, fvalue)
570 else -- Unset the UCI or error
571 if self.rmempty or self.optional then
577 -- Render if this value exists or if it is mandatory
578 function AbstractValue.render(self, s, scope)
579 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
583 Node.render(self, scope)
587 -- Return the UCI value of this object
588 function AbstractValue.cfgvalue(self, section)
589 return self.map:get(section, self.option)
592 -- Validate the form value
593 function AbstractValue.validate(self, value)
598 function AbstractValue.write(self, section, value)
599 return self.map:set(section, self.option, value)
603 function AbstractValue.remove(self, section)
604 return self.map:del(section, self.option)
611 Value - A one-line value
612 maxlength: The maximum length
613 isnumber: The value must be a valid (floating point) number
614 isinteger: The value must be a valid integer
615 ispositive: The value must be positive (and a number)
617 Value = class(AbstractValue)
619 function Value.__init__(self, ...)
620 AbstractValue.__init__(self, ...)
621 self.template = "cbi/value"
624 self.isnumber = false
625 self.isinteger = false
628 -- This validation is a bit more complex
629 function Value.validate(self, val)
630 if self.maxlength and tostring(val):len() > self.maxlength then
634 return luci.util.validate(val, self.isnumber, self.isinteger)
638 -- DummyValue - This does nothing except being there
639 DummyValue = class(AbstractValue)
641 function DummyValue.__init__(self, map, ...)
642 AbstractValue.__init__(self, map, ...)
643 self.template = "cbi/dvalue"
647 function DummyValue.parse(self)
651 function DummyValue.render(self, s)
652 luci.template.render(self.template, {self=self, section=s})
657 Flag - A flag being enabled or disabled
659 Flag = class(AbstractValue)
661 function Flag.__init__(self, ...)
662 AbstractValue.__init__(self, ...)
663 self.template = "cbi/fvalue"
669 -- A flag can only have two states: set or unset
670 function Flag.parse(self, section)
671 local fvalue = self:formvalue(section)
674 fvalue = self.enabled
676 fvalue = self.disabled
679 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
680 if not(fvalue == self:cfgvalue(section)) then
681 self:write(section, fvalue)
691 ListValue - A one-line value predefined in a list
692 widget: The widget that will be used (select, radio)
694 ListValue = class(AbstractValue)
696 function ListValue.__init__(self, ...)
697 AbstractValue.__init__(self, ...)
698 self.template = "cbi/lvalue"
703 self.widget = "select"
706 function ListValue.value(self, key, val)
708 table.insert(self.keylist, tostring(key))
709 table.insert(self.vallist, tostring(val))
712 function ListValue.validate(self, val)
713 if luci.util.contains(self.keylist, val) then
723 MultiValue - Multiple delimited values
724 widget: The widget that will be used (select, checkbox)
725 delimiter: The delimiter that will separate the values (default: " ")
727 MultiValue = class(AbstractValue)
729 function MultiValue.__init__(self, ...)
730 AbstractValue.__init__(self, ...)
731 self.template = "cbi/mvalue"
735 self.widget = "checkbox"
739 function MultiValue.render(self, ...)
740 if self.widget == "select" and not self.size then
741 self.size = #self.vallist
744 AbstractValue.render(self, ...)
747 function MultiValue.value(self, key, val)
749 table.insert(self.keylist, tostring(key))
750 table.insert(self.vallist, tostring(val))
753 function MultiValue.valuelist(self, section)
754 local val = self:cfgvalue(section)
756 if not(type(val) == "string") then
760 return luci.util.split(val, self.delimiter)
763 function MultiValue.validate(self, val)
764 if not(type(val) == "string") then
770 for value in val:gmatch("[^\n]+") do
771 if luci.util.contains(self.keylist, value) then
772 result = result .. self.delimiter .. value
776 if result:len() > 0 then
777 return result:sub(self.delimiter:len() + 1)