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