* luci/core: cbi.lua: automatic i18n capabilities; whitespace cleanup
[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, section, ...)
160 if instanceof(class, AbstractSection) then
161 local obj = class(self, section, ...)
162
163 Node._i18n(obj, self.config, section, nil, ...)
164
165 self:append(obj)
166 return obj
167 else
168 error("class must be a descendent of AbstractSection")
169 end
170 end
171
172 -- UCI add
173 function Map.add(self, sectiontype)
174 local name = self.uci:t_add(self.config, sectiontype)
175 if name then
176 self.ucidata[name] = {}
177 self.ucidata[name][".type"] = sectiontype
178 table.insert(self.uciorder, name)
179 end
180 return name
181 end
182
183 -- UCI set
184 function Map.set(self, section, option, value)
185 local stat = self.uci:t_set(self.config, section, option, value)
186 if stat then
187 local val = self.uci:t_get(self.config, section, option)
188 if option then
189 self.ucidata[section][option] = val
190 else
191 if not self.ucidata[section] then
192 self.ucidata[section] = {}
193 end
194 self.ucidata[section][".type"] = val
195 table.insert(self.uciorder, section)
196 end
197 end
198 return stat
199 end
200
201 -- UCI del
202 function Map.del(self, section, option)
203 local stat = self.uci:t_del(self.config, section, option)
204 if stat then
205 if option then
206 self.ucidata[section][option] = nil
207 else
208 self.ucidata[section] = nil
209 for i, k in ipairs(self.uciorder) do
210 if section == k then
211 table.remove(self.uciorder, i)
212 end
213 end
214 end
215 end
216 return stat
217 end
218
219 -- UCI get (cached)
220 function Map.get(self, section, option)
221 if not section then
222 return self.ucidata, self.uciorder
223 elseif option and self.ucidata[section] then
224 return self.ucidata[section][option]
225 else
226 return self.ucidata[section]
227 end
228 end
229
230
231 --[[
232 AbstractSection
233 ]]--
234 AbstractSection = class(Node)
235
236 function AbstractSection.__init__(self, map, sectiontype, ...)
237 Node.__init__(self, ...)
238 self.sectiontype = sectiontype
239 self.map = map
240 self.config = map.config
241 self.optionals = {}
242
243 self.optional = true
244 self.addremove = false
245 self.dynamic = false
246 end
247
248 -- Appends a new option
249 function AbstractSection.option(self, class, option, ...)
250 if instanceof(class, AbstractValue) then
251 local obj = class(self.map, option, ...)
252
253 Node._i18n(obj, self.config, self.section, option, ...)
254
255 self:append(obj)
256 return obj
257 else
258 error("class must be a descendent of AbstractValue")
259 end
260 end
261
262 -- Parse optional options
263 function AbstractSection.parse_optionals(self, section)
264 if not self.optional then
265 return
266 end
267
268 self.optionals[section] = {}
269
270 local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
271 for k,v in ipairs(self.children) do
272 if v.optional and not v:cfgvalue(section) then
273 if field == v.option then
274 field = nil
275 else
276 table.insert(self.optionals[section], v)
277 end
278 end
279 end
280
281 if field and #field > 0 and self.dynamic then
282 self:add_dynamic(field)
283 end
284 end
285
286 -- Add a dynamic option
287 function AbstractSection.add_dynamic(self, field, optional)
288 local o = self:option(Value, field, field)
289 o.optional = optional
290 end
291
292 -- Parse all dynamic options
293 function AbstractSection.parse_dynamic(self, section)
294 if not self.dynamic then
295 return
296 end
297
298 local arr = luci.util.clone(self:cfgvalue(section))
299 local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
300 for k, v in pairs(form) do
301 arr[k] = v
302 end
303
304 for key,val in pairs(arr) do
305 local create = true
306
307 for i,c in ipairs(self.children) do
308 if c.option == key then
309 create = false
310 end
311 end
312
313 if create and key:sub(1, 1) ~= "." then
314 self:add_dynamic(key, true)
315 end
316 end
317 end
318
319 -- Returns the section's UCI table
320 function AbstractSection.cfgvalue(self, section)
321 return self.map:get(section)
322 end
323
324 -- Removes the section
325 function AbstractSection.remove(self, section)
326 return self.map:del(section)
327 end
328
329 -- Creates the section
330 function AbstractSection.create(self, section)
331 return self.map:set(section, nil, self.sectiontype)
332 end
333
334
335
336 --[[
337 NamedSection - A fixed configuration section defined by its name
338 ]]--
339 NamedSection = class(AbstractSection)
340
341 function NamedSection.__init__(self, map, section, ...)
342 AbstractSection.__init__(self, map, ...)
343 self.template = "cbi/nsection"
344
345 self.section = section
346 self.addremove = false
347 end
348
349 function NamedSection.parse(self)
350 local s = self.section
351 local active = self:cfgvalue(s)
352
353
354 if self.addremove then
355 local path = self.config.."."..s
356 if active then -- Remove the section
357 if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
358 return
359 end
360 else -- Create and apply default values
361 if luci.http.formvalue("cbi.cns."..path) and self:create(s) then
362 for k,v in pairs(self.children) do
363 v:write(s, v.default)
364 end
365 end
366 end
367 end
368
369 if active then
370 AbstractSection.parse_dynamic(self, s)
371 if luci.http.formvalue("cbi.submit") then
372 Node.parse(self, s)
373 end
374 AbstractSection.parse_optionals(self, s)
375 end
376 end
377
378
379 --[[
380 TypedSection - A (set of) configuration section(s) defined by the type
381 addremove: Defines whether the user can add/remove sections of this type
382 anonymous: Allow creating anonymous sections
383 validate: a validation function returning nil if the section is invalid
384 ]]--
385 TypedSection = class(AbstractSection)
386
387 function TypedSection.__init__(self, ...)
388 AbstractSection.__init__(self, ...)
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