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