* 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