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