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