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