2 FFLuCI - 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("ffluci.cbi", package.seeall)
29 require("ffluci.template")
30 require("ffluci.util")
31 require("ffluci.http")
32 require("ffluci.model.uci")
34 local Template = ffluci.template.Template
35 local class = ffluci.util.class
36 local instanceof = ffluci.util.instanceof
41 require("ffluci.i18n")
43 local cbidir = ffluci.fs.dirname(ffluci.util.__file__()) .. "model/cbi/"
44 local func, err = loadfile(cbidir..cbimap..".lua")
51 ffluci.util.resfenv(func)
52 ffluci.util.updfenv(func, ffluci.cbi)
53 ffluci.util.extfenv(func, "translate", ffluci.i18n.translate)
57 if not instanceof(map, Map) then
58 error("CBI map returns no valid map object!")
62 ffluci.i18n.loadc("cbi")
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"
77 function Node.append(self, obj)
78 table.insert(self.children, obj)
81 function Node.parse(self)
82 for k, child in ipairs(self.children) do
87 function Node.render(self)
88 ffluci.template.render(self.template, {self=self})
91 function Node.render_children(self)
92 for k, node in ipairs(self.children) do
99 Map - A map describing a configuration file
103 function Map.__init__(self, config, ...)
104 Node.__init__(self, ...)
106 self.template = "cbi/map"
107 self.uci = ffluci.model.uci.Session()
110 function Map.parse(self)
111 self.ucidata = self.uci:show(self.config)
112 if not self.ucidata then
113 error("Unable to read UCI data: " .. self.config)
115 self.ucidata = self.ucidata[self.config]
120 function Map.render(self)
121 self.ucidata = self.uci:show(self.config)
122 if not self.ucidata then
123 error("Unable to read UCI data: " .. self.config)
125 self.ucidata = self.ucidata[self.config]
130 function Map.section(self, class, ...)
131 if instanceof(class, AbstractSection) then
132 local obj = class(...)
134 obj.config = self.config
138 error("class must be a descendent of AbstractSection")
142 function Map.add(self, sectiontype)
143 return self.uci:add(self.config, sectiontype)
146 function Map.set(self, section, option, value)
147 return self.uci:set(self.config, section, option, value)
150 function Map.remove(self, section, option)
151 return self.uci:del(self.config, section, option)
157 AbstractSection = class(Node)
159 function AbstractSection.__init__(self, sectiontype, ...)
160 Node.__init__(self, ...)
161 self.sectiontype = sectiontype
164 function AbstractSection.option(self, class, ...)
165 if instanceof(class, AbstractValue) then
166 local obj = class(...)
168 obj.config = self.config
172 error("class must be a descendent of AbstractValue")
179 NamedSection - A fixed configuration section defined by its name
181 NamedSection = class(AbstractSection)
183 function NamedSection.__init__(self, section, ...)
184 AbstractSection.__init__(self, ...)
185 self.template = "cbi/nsection"
187 self.section = section
190 function NamedSection.option(self, ...)
191 local obj = AbstractSection.option(self, ...)
192 obj.section = self.section
198 TypedSection - A (set of) configuration section(s) defined by the type
199 addremove: Defines whether the user can add/remove sections of this type
200 anonymous: Allow creating anonymous sections
201 valid: a list of names or a validation function for creating sections
202 scope: a list of names or a validation function for editing sections
204 TypedSection = class(AbstractSection)
206 function TypedSection.__init__(self, ...)
207 AbstractSection.__init__(self, ...)
208 self.template = "cbi/tsection"
210 self.addremove = true
211 self.anonymous = false
216 function TypedSection.create(self, name)
218 self.map:set(name, nil, self.sectiontype)
220 name = self.map:add(self.sectiontype)
223 for k,v in pairs(self.children) do
225 self.map:set(name, v.option, v.default)
230 function TypedSection.parse(self)
231 if self.addremove then
232 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
233 local name = ffluci.http.formvalue(crval)
234 if self.anonymous then
240 name = ffluci.util.validate(name, self.valid)
242 self.err_invalid = true
244 if name and name:len() > 0 then
251 crval = "cbi.rts." .. self.config
252 name = ffluci.http.formvalue(crval)
253 if type(name) == "table" then
254 for k,v in pairs(name) do
255 if ffluci.util.validate(k, self.valid) then
262 for k, v in pairs(self:ucisections()) do
263 for i, node in ipairs(self.children) do
270 function TypedSection.remove(self, name)
271 return self.map:remove(name)
274 function TypedSection.render_children(self, section)
275 for k, node in ipairs(self.children) do
276 node.section = section
281 function TypedSection.ucisections(self)
283 for k, v in pairs(self.map.ucidata) do
284 if v[".type"] == self.sectiontype then
285 if ffluci.util.validate(k, self.scope) then
295 AbstractValue - An abstract Value Type
296 null: Value can be empty
297 valid: A function returning the value if it is valid otherwise nil
298 depends: A table of option => value pairs of which one must be true
299 default: The default value
300 size: The size of the input fields
302 AbstractValue = class(Node)
304 function AbstractValue.__init__(self, option, ...)
305 Node.__init__(self, ...)
314 function AbstractValue.formvalue(self)
315 local key = "cbid."..self.map.config.."."..self.section.."."..self.option
316 return ffluci.http.formvalue(key)
319 function AbstractValue.parse(self)
320 local fvalue = self:formvalue()
322 fvalue = self:validate(fvalue)
324 self.err_invalid = true
326 if fvalue and not (fvalue == self:ucivalue()) then
332 function AbstractValue.ucivalue(self)
333 return self.map.ucidata[self.section][self.option]
336 function AbstractValue.validate(self, val)
337 return ffluci.util.validate(val, self.valid)
340 function AbstractValue.write(self, value)
341 return self.map:set(self.section, self.option, value)
348 Value - A one-line value
349 maxlength: The maximum length
350 isnumber: The value must be a valid (floating point) number
351 isinteger: The value must be a valid integer
352 ispositive: The value must be positive (and a number)
354 Value = class(AbstractValue)
356 function Value.__init__(self, ...)
357 AbstractValue.__init__(self, ...)
358 self.template = "cbi/value"
361 self.isnumber = false
362 self.isinteger = false
365 function Value.validate(self, val)
366 if self.maxlength and tostring(val):len() > self.maxlength then
370 return ffluci.util.validate(val, self.valid, self.isnumber, self.isinteger)
375 ListValue - A one-line value predefined in a list
377 ListValue = class(AbstractValue)
379 function ListValue.__init__(self, ...)
380 AbstractValue.__init__(self, ...)
381 self.template = "cbi/lvalue"
386 function ListValue.add_value(self, key, val)