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