0c2718ea8e37a8f3a673f783a0db68336721b38a
[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 = loadfile(cbidir..cbimap..".lua")
45
46 if not func then
47 error("Unable to load CBI map: " .. cbimap)
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 end
106
107 function Map.parse(self)
108 self.ucidata = ffluci.model.uci.show(self.config)
109 if not self.ucidata then
110 error("Unable to read UCI data: " .. self.config)
111 else
112 self.ucidata = self.ucidata[self.config]
113 end
114 Node.parse(self)
115 end
116
117 function Map.render(self)
118 self.ucidata = ffluci.model.uci.show(self.config)
119 if not self.ucidata then
120 error("Unable to read UCI data: " .. self.config)
121 else
122 self.ucidata = self.ucidata[self.config]
123 end
124 Node.render(self)
125 end
126
127 function Map.section(self, class, ...)
128 if instanceof(class, AbstractSection) then
129 local obj = class(...)
130 obj.map = self
131 obj.config = self.config
132 self:append(obj)
133 return obj
134 else
135 error("class must be a descendent of AbstractSection")
136 end
137 end
138
139 --[[
140 AbstractSection
141 ]]--
142 AbstractSection = class(Node)
143
144 function AbstractSection.__init__(self, sectiontype, ...)
145 Node.__init__(self, ...)
146 self.sectiontype = sectiontype
147 end
148
149 function AbstractSection.option(self, class, ...)
150 if instanceof(class, AbstractValue) then
151 local obj = class(...)
152 obj.map = self.map
153 obj.config = self.config
154 self:append(obj)
155 return obj
156 else
157 error("class must be a descendent of AbstractValue")
158 end
159 end
160
161
162
163 --[[
164 NamedSection - A fixed configuration section defined by its name
165 ]]--
166 NamedSection = class(AbstractSection)
167
168 function NamedSection.__init__(self, section, ...)
169 AbstractSection.__init__(self, ...)
170 self.template = "cbi/nsection"
171
172 self.section = section
173 end
174
175 function NamedSection.option(self, ...)
176 local obj = AbstractSection.option(self, ...)
177 obj.section = self.section
178 return obj
179 end
180
181
182 --[[
183 TypedSection - A (set of) configuration section(s) defined by the type
184 addremove: Defines whether the user can add/remove sections of this type
185 anonymous: Allow creating anonymous sections
186 valid: a table with valid names or a function returning nil if invalid
187 ]]--
188 TypedSection = class(AbstractSection)
189
190 function TypedSection.__init__(self, ...)
191 AbstractSection.__init__(self, ...)
192 self.template = "cbi/tsection"
193
194 self.addremove = true
195 self.anonymous = false
196 self.valid = nil
197 end
198
199 function TypedSection.parse(self)
200 for k, v in pairs(self:ucisections()) do
201 for i, node in ipairs(self.children) do
202 node.section = k
203 node:parse()
204 end
205 end
206 end
207
208 function TypedSection.render_children(self, section)
209 for k, node in ipairs(self.children) do
210 node.section = section
211 node:render()
212 end
213 end
214
215 function TypedSection.ucisections(self)
216 local sections = {}
217 for k, v in pairs(self.map.ucidata) do
218 if v[".type"] == self.sectiontype then
219 sections[k] = v
220 end
221 end
222 return sections
223 end
224
225
226 --[[
227 AbstractValue - An abstract Value Type
228 null: Value can be empty
229 valid: A function returning the value if it is valid otherwise nil
230 depends: A table of option => value pairs of which one must be true
231 default: The default value
232 ]]--
233 AbstractValue = class(Node)
234
235 function AbstractValue.__init__(self, option, ...)
236 Node.__init__(self, ...)
237 self.option = option
238
239 self.valid = nil
240 self.depends = nil
241 self.default = nil
242 end
243
244 function AbstractValue.formvalue(self)
245 local key = "cbid."..self.map.config.."."..self.section.."."..self.option
246 return ffluci.http.formvalue(key)
247 end
248
249 function AbstractValue.parse(self)
250 local fvalue = self:validate(self:formvalue())
251 if fvalue and not (fvalue == self:ucivalue()) then
252 self:write(fvalue)
253 end
254 end
255
256 function AbstractValue.ucivalue(self)
257 return self.map.ucidata[self.section][self.option]
258 end
259
260 function AbstractValue.validate(self, value)
261 return ffluci.util.validate(value, nil, nil, self.valid)
262 end
263
264 function AbstractValue.write(self, value)
265 return ffluci.model.uci.set(self.config, self.section, self.option, value)
266 end
267
268
269 --[[
270 Value - A one-line value
271 maxlength: The maximum length
272 isnumber: The value must be a valid (floating point) number
273 isinteger: The value must be a valid integer
274 ]]--
275 Value = class(AbstractValue)
276
277 function Value.__init__(self, ...)
278 AbstractValue.__init__(self, ...)
279 self.template = "cbi/value"
280
281 self.maxlength = nil
282 self.isnumber = false
283 self.isinteger = false
284 end
285
286
287 --[[
288 ListValue - A one-line value predefined in a list
289 ]]--
290 ListValue = class(AbstractValue)
291
292 function ListValue.__init__(self, ...)
293 AbstractValue.__init__(self, ...)
294 self.template = "cbi/lvalue"
295
296 self.list = {}
297 end
298
299 function ListValue.addValue(self, key, val)
300 val = val or key
301 self.list[key] = val
302 end