* luci/core: cbi.lua: automatically set size of MultiValue fields
[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
583 -- fixup size for MultiValue fields
584 if instanceof(self, MultiValue) and self.widget == "select" and not self.size then
585 self.size = #self.vallist
586 end
587
588 Node.render(self, scope)
589 end
590 end
591
592 -- Return the UCI value of this object
593 function AbstractValue.cfgvalue(self, section)
594 return self.map:get(section, self.option)
595 end
596
597 -- Validate the form value
598 function AbstractValue.validate(self, value)
599 return value
600 end
601
602 -- Write to UCI
603 function AbstractValue.write(self, section, value)
604 return self.map:set(section, self.option, value)
605 end
606
607 -- Remove from UCI
608 function AbstractValue.remove(self, section)
609 return self.map:del(section, self.option)
610 end
611
612
613
614
615 --[[
616 Value - A one-line value
617 maxlength: The maximum length
618 isnumber: The value must be a valid (floating point) number
619 isinteger: The value must be a valid integer
620 ispositive: The value must be positive (and a number)
621 ]]--
622 Value = class(AbstractValue)
623
624 function Value.__init__(self, ...)
625 AbstractValue.__init__(self, ...)
626 self.template = "cbi/value"
627
628 self.maxlength = nil
629 self.isnumber = false
630 self.isinteger = false
631 end
632
633 -- This validation is a bit more complex
634 function Value.validate(self, val)
635 if self.maxlength and tostring(val):len() > self.maxlength then
636 val = nil
637 end
638
639 return luci.util.validate(val, self.isnumber, self.isinteger)
640 end
641
642
643 -- DummyValue - This does nothing except being there
644 DummyValue = class(AbstractValue)
645
646 function DummyValue.__init__(self, map, ...)
647 AbstractValue.__init__(self, map, ...)
648 self.template = "cbi/dvalue"
649 self.value = nil
650 end
651
652 function DummyValue.parse(self)
653
654 end
655
656 function DummyValue.render(self, s)
657 luci.template.render(self.template, {self=self, section=s})
658 end
659
660
661 --[[
662 Flag - A flag being enabled or disabled
663 ]]--
664 Flag = class(AbstractValue)
665
666 function Flag.__init__(self, ...)
667 AbstractValue.__init__(self, ...)
668 self.template = "cbi/fvalue"
669
670 self.enabled = "1"
671 self.disabled = "0"
672 end
673
674 -- A flag can only have two states: set or unset
675 function Flag.parse(self, section)
676 local fvalue = self:formvalue(section)
677
678 if fvalue then
679 fvalue = self.enabled
680 else
681 fvalue = self.disabled
682 end
683
684 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
685 if not(fvalue == self:cfgvalue(section)) then
686 self:write(section, fvalue)
687 end
688 else
689 self:remove(section)
690 end
691 end
692
693
694
695 --[[
696 ListValue - A one-line value predefined in a list
697 widget: The widget that will be used (select, radio)
698 ]]--
699 ListValue = class(AbstractValue)
700
701 function ListValue.__init__(self, ...)
702 AbstractValue.__init__(self, ...)
703 self.template = "cbi/lvalue"
704 self.keylist = {}
705 self.vallist = {}
706
707 self.size = 1
708 self.widget = "select"
709 end
710
711 function ListValue.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 ListValue.validate(self, val)
718 if luci.util.contains(self.keylist, val) then
719 return val
720 else
721 return nil
722 end
723 end
724
725
726
727 --[[
728 MultiValue - Multiple delimited values
729 widget: The widget that will be used (select, checkbox)
730 delimiter: The delimiter that will separate the values (default: " ")
731 ]]--
732 MultiValue = class(AbstractValue)
733
734 function MultiValue.__init__(self, ...)
735 AbstractValue.__init__(self, ...)
736 self.template = "cbi/mvalue"
737 self.keylist = {}
738 self.vallist = {}
739
740 self.widget = "checkbox"
741 self.delimiter = " "
742 end
743
744 function MultiValue.value(self, key, val)
745 val = val or key
746 table.insert(self.keylist, tostring(key))
747 table.insert(self.vallist, tostring(val))
748 end
749
750 function MultiValue.valuelist(self, section)
751 local val = self:cfgvalue(section)
752
753 if not(type(val) == "string") then
754 return {}
755 end
756
757 return luci.util.split(val, self.delimiter)
758 end
759
760 function MultiValue.validate(self, val)
761 if not(type(val) == "string") then
762 return nil
763 end
764
765 local result = ""
766
767 for value in val:gmatch("[^\n]+") do
768 if luci.util.contains(self.keylist, value) then
769 result = result .. self.delimiter .. value
770 end
771 end
772
773 if result:len() > 0 then
774 return result:sub(self.delimiter:len() + 1)
775 else
776 return nil
777 end
778 end