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