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