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