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, section, ...)
160 if instanceof(class, AbstractSection) then
161 local obj = class(self, section, ...)
163 Node._i18n(obj, self.config, section, nil, ...)
168 error("class must be a descendent of AbstractSection")
173 function Map.add(self, sectiontype)
174 local name = self.uci:t_add(self.config, sectiontype)
176 self.ucidata[name] = {}
177 self.ucidata[name][".type"] = sectiontype
178 table.insert(self.uciorder, name)
184 function Map.set(self, section, option, value)
185 local stat = self.uci:t_set(self.config, section, option, value)
187 local val = self.uci:t_get(self.config, section, option)
189 self.ucidata[section][option] = val
191 if not self.ucidata[section] then
192 self.ucidata[section] = {}
194 self.ucidata[section][".type"] = val
195 table.insert(self.uciorder, section)
202 function Map.del(self, section, option)
203 local stat = self.uci:t_del(self.config, section, option)
206 self.ucidata[section][option] = nil
208 self.ucidata[section] = nil
209 for i, k in ipairs(self.uciorder) do
211 table.remove(self.uciorder, i)
220 function Map.get(self, section, option)
222 return self.ucidata, self.uciorder
223 elseif option and self.ucidata[section] then
224 return self.ucidata[section][option]
226 return self.ucidata[section]
234 AbstractSection = class(Node)
236 function AbstractSection.__init__(self, map, sectiontype, ...)
237 Node.__init__(self, ...)
238 self.sectiontype = sectiontype
240 self.config = map.config
244 self.addremove = false
248 -- Appends a new option
249 function AbstractSection.option(self, class, option, ...)
250 if instanceof(class, AbstractValue) then
251 local obj = class(self.map, option, ...)
253 Node._i18n(obj, self.config, self.section, option, ...)
258 error("class must be a descendent of AbstractValue")
262 -- Parse optional options
263 function AbstractSection.parse_optionals(self, section)
264 if not self.optional then
268 self.optionals[section] = {}
270 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
271 for k,v in ipairs(self.children) do
272 if v.optional and not v:cfgvalue(section) then
273 if field == v.option then
276 table.insert(self.optionals[section], v)
281 if field and #field > 0 and self.dynamic then
282 self:add_dynamic(field)
286 -- Add a dynamic option
287 function AbstractSection.add_dynamic(self, field, optional)
288 local o = self:option(Value, field, field)
289 o.optional = optional
292 -- Parse all dynamic options
293 function AbstractSection.parse_dynamic(self, section)
294 if not self.dynamic then
298 local arr = luci.util.clone(self:cfgvalue(section))
299 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
300 for k, v in pairs(form) do
304 for key,val in pairs(arr) do
307 for i,c in ipairs(self.children) do
308 if c.option == key then
313 if create and key:sub(1, 1) ~= "." then
314 self:add_dynamic(key, true)
319 -- Returns the section's UCI table
320 function AbstractSection.cfgvalue(self, section)
321 return self.map:get(section)
324 -- Removes the section
325 function AbstractSection.remove(self, section)
326 return self.map:del(section)
329 -- Creates the section
330 function AbstractSection.create(self, section)
331 return self.map:set(section, nil, self.sectiontype)
337 NamedSection - A fixed configuration section defined by its name
339 NamedSection = class(AbstractSection)
341 function NamedSection.__init__(self, map, section, ...)
342 AbstractSection.__init__(self, map, ...)
343 self.template = "cbi/nsection"
345 self.section = section
346 self.addremove = false
349 function NamedSection.parse(self)
350 local s = self.section
351 local active = self:cfgvalue(s)
354 if self.addremove then
355 local path = self.config.."."..s
356 if active then -- Remove the section
357 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
360 else -- Create and apply default values
361 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
362 for k,v in pairs(self.children) do
363 v:write(s, v.default)
370 AbstractSection.parse_dynamic(self, s)
371 if luci.http.formvalue("cbi.submit") then
374 AbstractSection.parse_optionals(self, s)
380 TypedSection - A (set of) configuration section(s) defined by the type
381 addremove: Defines whether the user can add/remove sections of this type
382 anonymous: Allow creating anonymous sections
383 validate: a validation function returning nil if the section is invalid
385 TypedSection = class(AbstractSection)
387 function TypedSection.__init__(self, ...)
388 AbstractSection.__init__(self, ...)
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
582 Node.render(self, scope)
586 -- Return the UCI value of this object
587 function AbstractValue.cfgvalue(self, section)
588 return self.map:get(section, self.option)
591 -- Validate the form value
592 function AbstractValue.validate(self, value)
597 function AbstractValue.write(self, section, value)
598 return self.map:set(section, self.option, value)
602 function AbstractValue.remove(self, section)
603 return self.map:del(section, self.option)
610 Value - A one-line value
611 maxlength: The maximum length
612 isnumber: The value must be a valid (floating point) number
613 isinteger: The value must be a valid integer
614 ispositive: The value must be positive (and a number)
616 Value = class(AbstractValue)
618 function Value.__init__(self, ...)
619 AbstractValue.__init__(self, ...)
620 self.template = "cbi/value"
623 self.isnumber = false
624 self.isinteger = false
627 -- This validation is a bit more complex
628 function Value.validate(self, val)
629 if self.maxlength and tostring(val):len() > self.maxlength then
633 return luci.util.validate(val, self.isnumber, self.isinteger)
637 -- DummyValue - This does nothing except being there
638 DummyValue = class(AbstractValue)
640 function DummyValue.__init__(self, map, ...)
641 AbstractValue.__init__(self, map, ...)
642 self.template = "cbi/dvalue"
646 function DummyValue.parse(self)
650 function DummyValue.render(self, s)
651 luci.template.render(self.template, {self=self, section=s})
656 Flag - A flag being enabled or disabled
658 Flag = class(AbstractValue)
660 function Flag.__init__(self, ...)
661 AbstractValue.__init__(self, ...)
662 self.template = "cbi/fvalue"
668 -- A flag can only have two states: set or unset
669 function Flag.parse(self, section)
670 local fvalue = self:formvalue(section)
673 fvalue = self.enabled
675 fvalue = self.disabled
678 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
679 if not(fvalue == self:cfgvalue(section)) then
680 self:write(section, fvalue)
690 ListValue - A one-line value predefined in a list
691 widget: The widget that will be used (select, radio)
693 ListValue = class(AbstractValue)
695 function ListValue.__init__(self, ...)
696 AbstractValue.__init__(self, ...)
697 self.template = "cbi/lvalue"
702 self.widget = "select"
705 function ListValue.value(self, key, val)
707 table.insert(self.keylist, tostring(key))
708 table.insert(self.vallist, tostring(val))
711 function ListValue.validate(self, val)
712 if luci.util.contains(self.keylist, val) then
722 MultiValue - Multiple delimited values
723 widget: The widget that will be used (select, checkbox)
724 delimiter: The delimiter that will separate the values (default: " ")
726 MultiValue = class(AbstractValue)
728 function MultiValue.__init__(self, ...)
729 AbstractValue.__init__(self, ...)
730 self.template = "cbi/mvalue"
734 self.widget = "checkbox"
738 function MultiValue.value(self, key, val)
740 table.insert(self.keylist, tostring(key))
741 table.insert(self.vallist, tostring(val))
744 function MultiValue.valuelist(self, section)
745 local val = self:cfgvalue(section)
747 if not(type(val) == "string") then
751 return luci.util.split(val, self.delimiter)
754 function MultiValue.validate(self, val)
755 if not(type(val) == "string") then
761 for value in val:gmatch("[^\n]+") do
762 if luci.util.contains(self.keylist, value) then
763 result = result .. self.delimiter .. value
767 if result:len() > 0 then
768 return result:sub(self.delimiter:len() + 1)