lib/cbi: Added support for multiple CBI maps per model
[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                 
558                 Node.render(self, scope)
559         end
560 end
561
562 -- Return the UCI value of this object
563 function AbstractValue.cfgvalue(self, section)
564         return self.map:get(section, self.option)
565 end
566
567 -- Validate the form value
568 function AbstractValue.validate(self, value)
569         return value
570 end
571
572 -- Write to UCI
573 function AbstractValue.write(self, section, value)
574         return self.map:set(section, self.option, value)
575 end
576
577 -- Remove from UCI
578 function AbstractValue.remove(self, section)
579         return self.map:del(section, self.option)
580 end
581
582
583
584
585 --[[
586 Value - A one-line value
587         maxlength:      The maximum length
588 ]]--
589 Value = class(AbstractValue)
590
591 function Value.__init__(self, ...)
592         AbstractValue.__init__(self, ...)
593         self.template  = "cbi/value"
594
595         self.maxlength  = nil
596 end
597
598 -- This validation is a bit more complex
599 function Value.validate(self, val)
600         if self.maxlength and tostring(val):len() > self.maxlength then
601                 val = nil
602         end
603
604         return val
605 end
606
607
608 -- DummyValue - This does nothing except being there
609 DummyValue = class(AbstractValue)
610
611 function DummyValue.__init__(self, map, ...)
612         AbstractValue.__init__(self, map, ...)
613         self.template = "cbi/dvalue"
614         self.value = nil
615 end
616
617 function DummyValue.parse(self)
618
619 end
620
621 function DummyValue.render(self, s)
622         luci.template.render(self.template, {self=self, section=s})
623 end
624
625
626 --[[
627 Flag - A flag being enabled or disabled
628 ]]--
629 Flag = class(AbstractValue)
630
631 function Flag.__init__(self, ...)
632         AbstractValue.__init__(self, ...)
633         self.template  = "cbi/fvalue"
634
635         self.enabled = "1"
636         self.disabled = "0"
637 end
638
639 -- A flag can only have two states: set or unset
640 function Flag.parse(self, section)
641         local fvalue = self:formvalue(section)
642
643         if fvalue then
644                 fvalue = self.enabled
645         else
646                 fvalue = self.disabled
647         end
648
649         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
650                 if not(fvalue == self:cfgvalue(section)) then
651                         self:write(section, fvalue)
652                 end
653         else
654                 self:remove(section)
655         end
656 end
657
658
659
660 --[[
661 ListValue - A one-line value predefined in a list
662         widget: The widget that will be used (select, radio)
663 ]]--
664 ListValue = class(AbstractValue)
665
666 function ListValue.__init__(self, ...)
667         AbstractValue.__init__(self, ...)
668         self.template  = "cbi/lvalue"
669         self.keylist = {}
670         self.vallist = {}
671
672         self.size   = 1
673         self.widget = "select"
674 end
675
676 function ListValue.value(self, key, val)
677         val = val or key
678         table.insert(self.keylist, tostring(key))
679         table.insert(self.vallist, tostring(val))
680 end
681
682 function ListValue.validate(self, val)
683         if luci.util.contains(self.keylist, val) then
684                 return val
685         else
686                 return nil
687         end
688 end
689
690
691
692 --[[
693 MultiValue - Multiple delimited values
694         widget: The widget that will be used (select, checkbox)
695         delimiter: The delimiter that will separate the values (default: " ")
696 ]]--
697 MultiValue = class(AbstractValue)
698
699 function MultiValue.__init__(self, ...)
700         AbstractValue.__init__(self, ...)
701         self.template = "cbi/mvalue"
702         self.keylist = {}
703         self.vallist = {}
704
705         self.widget = "checkbox"
706         self.delimiter = " "
707 end
708
709 function MultiValue.render(self, ...)
710         if self.widget == "select" and not self.size then
711                 self.size = #self.vallist
712         end
713
714         AbstractValue.render(self, ...)
715 end
716
717 function MultiValue.value(self, key, val)
718         val = val or key
719         table.insert(self.keylist, tostring(key))
720         table.insert(self.vallist, tostring(val))
721 end
722
723 function MultiValue.valuelist(self, section)
724         local val = self:cfgvalue(section)
725
726         if not(type(val) == "string") then
727                 return {}
728         end
729
730         return luci.util.split(val, self.delimiter)
731 end
732
733 function MultiValue.validate(self, val)
734         val = (type(val) == "table") and val or {val} 
735
736         local result
737
738         for i, value in ipairs(val) do
739                 if luci.util.contains(self.keylist, value) then
740                         result = result and (result .. self.delimiter .. value) or value
741                 end
742         end
743
744         return result
745 end