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