* libs/cbi: Fixed a bug which prevented creation of non-anonymous sections
[project/luci.git] / libs / cbi / luasrc / cbi.lua
1 --[[
2 LuCI - Configuration Bind Interface
3
4 Description:
5 Offers an interface for binding configuration 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("luci.cbi", package.seeall)
28
29 require("luci.template")
30 require("luci.util")
31 require("luci.http")
32 require("luci.model.uci")
33
34 local uci = luci.model.uci
35 local class = luci.util.class
36 local instanceof = luci.util.instanceof
37
38
39 -- Loads a CBI map from given file, creating an environment and returns it
40 function load(cbimap)
41 require("luci.fs")
42 require("luci.i18n")
43 require("luci.config")
44 require("luci.sys")
45
46 local cbidir = luci.sys.libpath() .. "/model/cbi/"
47 local func, err = loadfile(cbidir..cbimap..".lua")
48
49 if not func then
50 return nil
51 end
52
53 luci.i18n.loadc("cbi")
54
55 luci.util.resfenv(func)
56 luci.util.updfenv(func, luci.cbi)
57 luci.util.extfenv(func, "translate", luci.i18n.translate)
58 luci.util.extfenv(func, "translatef", luci.i18n.translatef)
59
60 local map = func()
61
62 if not instanceof(map, Map) then
63 error("CBI map returns no valid map object!")
64 return nil
65 end
66
67 return map
68 end
69
70 -- Node pseudo abstract class
71 Node = class()
72
73 function Node.__init__(self, title, description)
74 self.children = {}
75 self.title = title or ""
76 self.description = description or ""
77 self.template = "cbi/node"
78 end
79
80 -- i18n helper
81 function Node._i18n(self, config, section, option, title, description)
82
83 -- i18n loaded?
84 if type(luci.i18n) == "table" then
85
86 local key = config:gsub("[^%w]+", "")
87
88 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
89 if option then key = key .. "_" .. option:lower():gsub("[^%w]+", "") end
90
91 self.title = title or luci.i18n.translate( key, option or section or config )
92 self.description = description or luci.i18n.translate( key .. "_desc", "" )
93 end
94 end
95
96 -- Append child nodes
97 function Node.append(self, obj)
98 table.insert(self.children, obj)
99 end
100
101 -- Parse this node and its children
102 function Node.parse(self, ...)
103 for k, child in ipairs(self.children) do
104 child:parse(...)
105 end
106 end
107
108 -- Render this node
109 function Node.render(self, scope)
110 scope = scope or {}
111 scope.self = self
112
113 luci.template.render(self.template, scope)
114 end
115
116 -- Render the children
117 function Node.render_children(self, ...)
118 for k, node in ipairs(self.children) do
119 node:render(...)
120 end
121 end
122
123
124 --[[
125 A simple template element
126 ]]--
127 Template = class(Node)
128
129 function Template.__init__(self, template)
130 Node.__init__(self)
131 self.template = template
132 end
133
134
135 --[[
136 Map - A map describing a configuration file
137 ]]--
138 Map = class(Node)
139
140 function Map.__init__(self, config, ...)
141 Node.__init__(self, ...)
142 Node._i18n(self, config, nil, nil, ...)
143
144 self.config = config
145 self.template = "cbi/map"
146 if not uci.load(self.config) then
147 error("Unable to read UCI data: " .. self.config)
148 end
149 end
150
151 -- Use optimized UCI writing
152 function Map.parse(self, ...)
153 Node.parse(self, ...)
154 uci.save(self.config)
155 uci.unload(self.config)
156 end
157
158 -- Creates a child section
159 function Map.section(self, class, ...)
160 if instanceof(class, AbstractSection) then
161 local obj = class(self, ...)
162 self:append(obj)
163 return obj
164 else
165 error("class must be a descendent of AbstractSection")
166 end
167 end
168
169 -- UCI add
170 function Map.add(self, sectiontype)
171 return uci.add(self.config, sectiontype)
172 end
173
174 -- UCI set
175 function Map.set(self, section, option, value)
176 if option then
177 return uci.set(self.config, section, option, value)
178 else
179 return uci.set(self.config, section, value)
180 end
181 end
182
183 -- UCI del
184 function Map.del(self, section, option)
185 if option then
186 return uci.delete(self.config, section, option)
187 else
188 return uci.delete(self.config, section)
189 end
190 end
191
192 -- UCI get
193 function Map.get(self, section, option)
194 if not section then
195 return uci.get_all(self.config)
196 elseif option then
197 return uci.get(self.config, section, option)
198 else
199 return uci.get_all(self.config, section)
200 end
201 end
202
203
204 --[[
205 AbstractSection
206 ]]--
207 AbstractSection = class(Node)
208
209 function AbstractSection.__init__(self, map, sectiontype, ...)
210 Node.__init__(self, ...)
211 self.sectiontype = sectiontype
212 self.map = map
213 self.config = map.config
214 self.optionals = {}
215
216 self.optional = true
217 self.addremove = false
218 self.dynamic = false
219 end
220
221 -- Appends a new option
222 function AbstractSection.option(self, class, option, ...)
223 if instanceof(class, AbstractValue) then
224 local obj = class(self.map, option, ...)
225
226 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
227
228 self:append(obj)
229 return obj
230 else
231 error("class must be a descendent of AbstractValue")
232 end
233 end
234
235 -- Parse optional options
236 function AbstractSection.parse_optionals(self, section)
237 if not self.optional then
238 return
239 end
240
241 self.optionals[section] = {}
242
243 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
244 for k,v in ipairs(self.children) do
245 if v.optional and not v:cfgvalue(section) then
246 if field == v.option then
247 field = nil
248 else
249 table.insert(self.optionals[section], v)
250 end
251 end
252 end
253
254 if field and #field > 0 and self.dynamic then
255 self:add_dynamic(field)
256 end
257 end
258
259 -- Add a dynamic option
260 function AbstractSection.add_dynamic(self, field, optional)
261 local o = self:option(Value, field, field)
262 o.optional = optional
263 end
264
265 -- Parse all dynamic options
266 function AbstractSection.parse_dynamic(self, section)
267 if not self.dynamic then
268 return
269 end
270
271 local arr = luci.util.clone(self:cfgvalue(section))
272 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
273 for k, v in pairs(form) do
274 arr[k] = v
275 end
276
277 for key,val in pairs(arr) do
278 local create = true
279
280 for i,c in ipairs(self.children) do
281 if c.option == key then
282 create = false
283 end
284 end
285
286 if create and key:sub(1, 1) ~= "." then
287 self:add_dynamic(key, true)
288 end
289 end
290 end
291
292 -- Returns the section's UCI table
293 function AbstractSection.cfgvalue(self, section)
294 return self.map:get(section)
295 end
296
297 -- Removes the section
298 function AbstractSection.remove(self, section)
299 return self.map:del(section)
300 end
301
302 -- Creates the section
303 function AbstractSection.create(self, section)
304 return self.map:set(section, nil, self.sectiontype)
305 end
306
307
308
309 --[[
310 NamedSection - A fixed configuration section defined by its name
311 ]]--
312 NamedSection = class(AbstractSection)
313
314 function NamedSection.__init__(self, map, section, type, ...)
315 AbstractSection.__init__(self, map, type, ...)
316 Node._i18n(self, map.config, section, nil, ...)
317
318 self.template = "cbi/nsection"
319 self.section = section
320 self.addremove = false
321 end
322
323 function NamedSection.parse(self)
324 local s = self.section
325 local active = self:cfgvalue(s)
326
327
328 if self.addremove then
329 local path = self.config.."."..s
330 if active then -- Remove the section
331 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
332 return
333 end
334 else -- Create and apply default values
335 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
336 for k,v in pairs(self.children) do
337 v:write(s, v.default)
338 end
339 end
340 end
341 end
342
343 if active then
344 AbstractSection.parse_dynamic(self, s)
345 if luci.http.formvalue("cbi.submit") then
346 Node.parse(self, s)
347 end
348 AbstractSection.parse_optionals(self, s)
349 end
350 end
351
352
353 --[[
354 TypedSection - A (set of) configuration section(s) defined by the type
355 addremove: Defines whether the user can add/remove sections of this type
356 anonymous: Allow creating anonymous sections
357 validate: a validation function returning nil if the section is invalid
358 ]]--
359 TypedSection = class(AbstractSection)
360
361 function TypedSection.__init__(self, map, type, ...)
362 AbstractSection.__init__(self, map, type, ...)
363 Node._i18n(self, map.config, type, nil, ...)
364
365 self.template = "cbi/tsection"
366 self.deps = {}
367 self.excludes = {}
368
369 self.anonymous = false
370 end
371
372 -- Return all matching UCI sections for this TypedSection
373 function TypedSection.cfgsections(self)
374 local sections = {}
375 uci.foreach(self.map.config, self.sectiontype,
376 function (section)
377 if self:checkscope(section[".name"]) then
378 table.insert(sections, section[".name"])
379 end
380 end)
381
382 return sections
383 end
384
385 -- Creates a new section of this type with the given name (or anonymous)
386 function TypedSection.create(self, name)
387 if name then
388 self.map:set(name, nil, self.sectiontype)
389 else
390 name = self.map:add(self.sectiontype)
391 end
392
393 for k,v in pairs(self.children) do
394 if v.default then
395 self.map:set(name, v.option, v.default)
396 end
397 end
398 end
399
400 -- Limits scope to sections that have certain option => value pairs
401 function TypedSection.depends(self, option, value)
402 table.insert(self.deps, {option=option, value=value})
403 end
404
405 -- Excludes several sections by name
406 function TypedSection.exclude(self, field)
407 self.excludes[field] = true
408 end
409
410 function TypedSection.parse(self)
411 if self.addremove then
412 -- Create
413 local crval = "cbi.cts." .. self.config .. "." .. self.sectiontype
414 local name = luci.http.formvalue(crval)
415 if self.anonymous then
416 if name then
417 self:create()
418 end
419 else
420 if name then
421 -- Ignore if it already exists
422 if self:cfgvalue(name) then
423 name = nil;
424 end
425
426 name = self:checkscope(name)
427
428 if not name then
429 self.err_invalid = true
430 end
431
432 if name and name:len() > 0 then
433 self:create(name)
434 end
435 end
436 end
437
438 -- Remove
439 crval = "cbi.rts." .. self.config
440 name = luci.http.formvaluetable(crval)
441 for k,v in pairs(name) do
442 if self:cfgvalue(k) and self:checkscope(k) then
443 self:remove(k)
444 end
445 end
446 end
447
448 for i, k in ipairs(self:cfgsections()) do
449 AbstractSection.parse_dynamic(self, k)
450 if luci.http.formvalue("cbi.submit") then
451 Node.parse(self, k)
452 end
453 AbstractSection.parse_optionals(self, k)
454 end
455 end
456
457 -- Verifies scope of sections
458 function TypedSection.checkscope(self, section)
459 -- Check if we are not excluded
460 if self.excludes[section] then
461 return nil
462 end
463
464 -- Check if at least one dependency is met
465 if #self.deps > 0 and self:cfgvalue(section) then
466 local stat = false
467
468 for k, v in ipairs(self.deps) do
469 if self:cfgvalue(section)[v.option] == v.value then
470 stat = true
471 end
472 end
473
474 if not stat then
475 return nil
476 end
477 end
478
479 return self:validate(section)
480 end
481
482
483 -- Dummy validate function
484 function TypedSection.validate(self, section)
485 return section
486 end
487
488
489 --[[
490 AbstractValue - An abstract Value Type
491 null: Value can be empty
492 valid: A function returning the value if it is valid otherwise nil
493 depends: A table of option => value pairs of which one must be true
494 default: The default value
495 size: The size of the input fields
496 rmempty: Unset value if empty
497 optional: This value is optional (see AbstractSection.optionals)
498 ]]--
499 AbstractValue = class(Node)
500
501 function AbstractValue.__init__(self, map, option, ...)
502 Node.__init__(self, ...)
503 self.option = option
504 self.map = map
505 self.config = map.config
506 self.tag_invalid = {}
507 self.deps = {}
508
509 self.rmempty = false
510 self.default = nil
511 self.size = nil
512 self.optional = false
513 end
514
515 -- Add a dependencie to another section field
516 function AbstractValue.depends(self, field, value)
517 table.insert(self.deps, {field=field, value=value})
518 end
519
520 -- Return whether this object should be created
521 function AbstractValue.formcreated(self, section)
522 local key = "cbi.opt."..self.config.."."..section
523 return (luci.http.formvalue(key) == self.option)
524 end
525
526 -- Returns the formvalue for this object
527 function AbstractValue.formvalue(self, section)
528 local key = "cbid."..self.map.config.."."..section.."."..self.option
529 return luci.http.formvalue(key)
530 end
531
532 function AbstractValue.parse(self, section)
533 local fvalue = self:formvalue(section)
534
535 if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
536 fvalue = self:validate(fvalue)
537 if not fvalue then
538 self.tag_invalid[section] = true
539 end
540 if fvalue and not (fvalue == self:cfgvalue(section)) then
541 self:write(section, fvalue)
542 end
543 else -- Unset the UCI or error
544 if self.rmempty or self.optional then
545 self:remove(section)
546 end
547 end
548 end
549
550 -- Render if this value exists or if it is mandatory
551 function AbstractValue.render(self, s, scope)
552 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
553 scope = scope or {}
554 scope.section = s
555
556 Node.render(self, scope)
557 end
558 end
559
560 -- Return the UCI value of this object
561 function AbstractValue.cfgvalue(self, section)
562 return self.map:get(section, self.option)
563 end
564
565 -- Validate the form value
566 function AbstractValue.validate(self, value)
567 return value
568 end
569
570 -- Write to UCI
571 function AbstractValue.write(self, section, value)
572 return self.map:set(section, self.option, value)
573 end
574
575 -- Remove from UCI
576 function AbstractValue.remove(self, section)
577 return self.map:del(section, self.option)
578 end
579
580
581
582
583 --[[
584 Value - A one-line value
585 maxlength: The maximum length
586 ]]--
587 Value = class(AbstractValue)
588
589 function Value.__init__(self, ...)
590 AbstractValue.__init__(self, ...)
591 self.template = "cbi/value"
592
593 self.maxlength = nil
594 end
595
596 -- This validation is a bit more complex
597 function Value.validate(self, val)
598 if self.maxlength and tostring(val):len() > self.maxlength then
599 val = nil
600 end
601
602 return val
603 end
604
605
606 -- DummyValue - This does nothing except being there
607 DummyValue = class(AbstractValue)
608
609 function DummyValue.__init__(self, map, ...)
610 AbstractValue.__init__(self, map, ...)
611 self.template = "cbi/dvalue"
612 self.value = nil
613 end
614
615 function DummyValue.parse(self)
616
617 end
618
619 function DummyValue.render(self, s)
620 luci.template.render(self.template, {self=self, section=s})
621 end
622
623
624 --[[
625 Flag - A flag being enabled or disabled
626 ]]--
627 Flag = class(AbstractValue)
628
629 function Flag.__init__(self, ...)
630 AbstractValue.__init__(self, ...)
631 self.template = "cbi/fvalue"
632
633 self.enabled = "1"
634 self.disabled = "0"
635 end
636
637 -- A flag can only have two states: set or unset
638 function Flag.parse(self, section)
639 local fvalue = self:formvalue(section)
640
641 if fvalue then
642 fvalue = self.enabled
643 else
644 fvalue = self.disabled
645 end
646
647 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
648 if not(fvalue == self:cfgvalue(section)) then
649 self:write(section, fvalue)
650 end
651 else
652 self:remove(section)
653 end
654 end
655
656
657
658 --[[
659 ListValue - A one-line value predefined in a list
660 widget: The widget that will be used (select, radio)
661 ]]--
662 ListValue = class(AbstractValue)
663
664 function ListValue.__init__(self, ...)
665 AbstractValue.__init__(self, ...)
666 self.template = "cbi/lvalue"
667 self.keylist = {}
668 self.vallist = {}
669
670 self.size = 1
671 self.widget = "select"
672 end
673
674 function ListValue.value(self, key, val)
675 val = val or key
676 table.insert(self.keylist, tostring(key))
677 table.insert(self.vallist, tostring(val))
678 end
679
680 function ListValue.validate(self, val)
681 if luci.util.contains(self.keylist, val) then
682 return val
683 else
684 return nil
685 end
686 end
687
688
689
690 --[[
691 MultiValue - Multiple delimited values
692 widget: The widget that will be used (select, checkbox)
693 delimiter: The delimiter that will separate the values (default: " ")
694 ]]--
695 MultiValue = class(AbstractValue)
696
697 function MultiValue.__init__(self, ...)
698 AbstractValue.__init__(self, ...)
699 self.template = "cbi/mvalue"
700 self.keylist = {}
701 self.vallist = {}
702
703 self.widget = "checkbox"
704 self.delimiter = " "
705 end
706
707 function MultiValue.render(self, ...)
708 if self.widget == "select" and not self.size then
709 self.size = #self.vallist
710 end
711
712 AbstractValue.render(self, ...)
713 end
714
715 function MultiValue.value(self, key, val)
716 val = val or key
717 table.insert(self.keylist, tostring(key))
718 table.insert(self.vallist, tostring(val))
719 end
720
721 function MultiValue.valuelist(self, section)
722 local val = self:cfgvalue(section)
723
724 if not(type(val) == "string") then
725 return {}
726 end
727
728 return luci.util.split(val, self.delimiter)
729 end
730
731 function MultiValue.validate(self, val)
732 if not(type(val) == "string") then
733 return nil
734 end
735
736 local result = ""
737
738 for value in val:gmatch("[^\n]+") do
739 if luci.util.contains(self.keylist, value) then
740 result = result .. self.delimiter .. value
741 end
742 end
743
744 if result:len() > 0 then
745 return result:sub(self.delimiter:len() + 1)
746 else
747 return nil
748 end
749 end