* CBI: updates
[project/luci.git] / src / ffluci / cbi.lua
1 --[[
2 FFLuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding confiugration values to certain
6 data types. Supports value and range validation and basic dependencies.
7
8 FileId:
9 $Id$
10
11 License:
12 Copyright 2008 Steven Barth <steven@midlink.org>
13
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
17
18 http://www.apache.org/licenses/LICENSE-2.0
19
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.
25
26 ]]--
27 module("ffluci.cbi", package.seeall)
28
29 require("ffluci.template")
30 require("ffluci.util")
31 require("ffluci.http")
32 require("ffluci.model.uci")
33
34 local Template = ffluci.template.Template
35 local class = ffluci.util.class
36 local instanceof = ffluci.util.instanceof
37
38
39 function load(cbimap)
40 require("ffluci.fs")
41 require("ffluci.i18n")
42
43 local cbidir = ffluci.fs.dirname(ffluci.util.__file__()) .. "model/cbi/"
44 local func, err = loadfile(cbidir..cbimap..".lua")
45
46 if not func then
47 error(err)
48 return nil
49 end
50
51 ffluci.util.resfenv(func)
52 ffluci.util.updfenv(func, ffluci.cbi)
53 ffluci.util.extfenv(func, "translate", ffluci.i18n.translate)
54
55 local map = func()
56
57 if not instanceof(map, Map) then
58 error("CBI map returns no valid map object!")
59 return nil
60 end
61
62 return map
63 end
64
65 -- Node pseudo abstract class
66 Node = class()
67
68 function Node.__init__(self, title, description)
69 self.children = {}
70 self.title = title or ""
71 self.description = description or ""
72 self.template = "cbi/node"
73 end
74
75 function Node.append(self, obj)
76 table.insert(self.children, obj)
77 end
78
79 function Node.parse(self)
80 for k, child in ipairs(self.children) do
81 child:parse()
82 end
83 end
84
85 function Node.render(self)
86 ffluci.template.render(self.template, {self=self})
87 end
88
89 function Node.render_children(self)
90 for k, node in ipairs(self.children) do
91 node:render()
92 end
93 end
94
95
96 --[[
97 Map - A map describing a configuration file
98 ]]--
99 Map = class(Node)
100
101 function Map.__init__(self, config, ...)
102 Node.__init__(self, ...)
103 self.config = config
104 self.template = "cbi/map"
105 self.uci = ffluci.model.uci.Session()
106 end
107
108 function Map.parse(self)
109 self.ucidata = self.uci:show(self.config)
110 if not self.ucidata then
111 error("Unable to read UCI data: " .. self.config)
112 else
113 self.ucidata = self.ucidata[self.config]
114 end
115 Node.parse(self)
116 end
117
118 function Map.render(self)
119 self.ucidata = self.uci:show(self.config)
120 if not self.ucidata then
121 error("Unable to read UCI data: " .. self.config)
122 else
123 self.ucidata = self.ucidata[self.config]
124 end
125 Node.render(self)
126 end
127
128 function Map.section(self, class, ...)
129 if instanceof(class, AbstractSection) then
130 local obj = class(...)
131 obj.map = self
132 obj.config = self.config
133 self:append(obj)
134 return obj
135 else
136 error("class must be a descendent of AbstractSection")
137 end
138 end
139
140 function Map.set(self, section, option, value)
141 return self.uci:set(self.config, section, option, value)
142 end
143
144 --[[
145 AbstractSection
146 ]]--
147 AbstractSection = class(Node)
148
149 function AbstractSection.__init__(self, sectiontype, ...)
150 Node.__init__(self, ...)
151 self.sectiontype = sectiontype
152 end
153
154 function AbstractSection.option(self, class, ...)
155 if instanceof(class, AbstractValue) then
156 local obj = class(...)
157 obj.map = self.map
158 obj.config = self.config
159 self:append(obj)
160 return obj
161 else
162 error("class must be a descendent of AbstractValue")
163 end
164 end
165
166
167
168 --[[
169 NamedSection - A fixed configuration section defined by its name
170 ]]--
171 NamedSection = class(AbstractSection)
172
173 function NamedSection.__init__(self, section, ...)
174 AbstractSection.__init__(self, ...)
175 self.template = "cbi/nsection"
176
177 self.section = section
178 end
179
180 function NamedSection.option(self, ...)
181 local obj = AbstractSection.option(self, ...)
182 obj.section = self.section
183 return obj
184 end
185
186
187 --[[
188 TypedSection - A (set of) configuration section(s) defined by the type
189 addremove: Defines whether the user can add/remove sections of this type
190 anonymous: Allow creating anonymous sections
191 valid: a table with valid names or a function returning nil if invalid
192 ]]--
193 TypedSection = class(AbstractSection)
194
195 function TypedSection.__init__(self, ...)
196 AbstractSection.__init__(self, ...)
197 self.template = "cbi/tsection"
198
199 self.addremove = true
200 self.anonymous = false
201 self.valid = nil
202 end
203
204 function TypedSection.parse(self)
205 for k, v in pairs(self:ucisections()) do
206 for i, node in ipairs(self.children) do
207 node.section = k
208 node:parse()
209 end
210 end
211 end
212
213 function TypedSection.render_children(self, section)
214 for k, node in ipairs(self.children) do
215 node.section = section
216 node:render()
217 end
218 end
219
220 function TypedSection.ucisections(self)
221 local sections = {}
222 for k, v in pairs(self.map.ucidata) do
223 if v[".type"] == self.sectiontype then
224 sections[k] = v
225 end
226 end
227 return sections
228 end
229
230
231 --[[
232 AbstractValue - An abstract Value Type
233 null: Value can be empty
234 valid: A function returning the value if it is valid otherwise nil
235 depends: A table of option => value pairs of which one must be true
236 default: The default value
237 ]]--
238 AbstractValue = class(Node)
239
240 function AbstractValue.__init__(self, option, ...)
241 Node.__init__(self, ...)
242 self.option = option
243
244 self.valid = nil
245 self.depends = nil
246 self.default = nil
247 end
248
249 function AbstractValue.formvalue(self)
250 local key = "cbid."..self.map.config.."."..self.section.."."..self.option
251 return ffluci.http.formvalue(key)
252 end
253
254 function AbstractValue.parse(self)
255 local fvalue = self:validate(self:formvalue())
256 if fvalue and not (fvalue == self:ucivalue()) then
257 self:write(fvalue)
258 end
259 end
260
261 function AbstractValue.ucivalue(self)
262 return self.map.ucidata[self.section][self.option]
263 end
264
265 function AbstractValue.validate(self, value)
266 return ffluci.util.validate(value, nil, nil, self.valid)
267 end
268
269 function AbstractValue.write(self, value)
270 return self.map:set(self.section, self.option, value)
271 end
272
273
274 --[[
275 Value - A one-line value
276 maxlength: The maximum length
277 isnumber: The value must be a valid (floating point) number
278 isinteger: The value must be a valid integer
279 ]]--
280 Value = class(AbstractValue)
281
282 function Value.__init__(self, ...)
283 AbstractValue.__init__(self, ...)
284 self.template = "cbi/value"
285
286 self.maxlength = nil
287 self.isnumber = false
288 self.isinteger = false
289 end
290
291
292 --[[
293 ListValue - A one-line value predefined in a list
294 ]]--
295 ListValue = class(AbstractValue)
296
297 function ListValue.__init__(self, ...)
298 AbstractValue.__init__(self, ...)
299 self.template = "cbi/lvalue"
300
301 self.list = {}
302 end
303
304 function ListValue.add_value(self, key, val)
305 val = val or key
306 self.list[key] = val
307 end