7e2f53c80c06a79a710358ed63fea17aae327398
[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 FORM_NODATA  =  0
39 FORM_VALID   =  1
40 FORM_INVALID = -1
41
42 CREATE_PREFIX = "cbi.cts."
43 REMOVE_PREFIX = "cbi.rts."
44
45 -- Loads a CBI map from given file, creating an environment and returns it
46 function load(cbimap, ...)
47         require("luci.fs")
48         require("luci.i18n")
49         require("luci.config")
50         require("luci.util")
51
52         local cbidir = luci.util.libpath() .. "/model/cbi/"
53         local func, err = loadfile(cbidir..cbimap..".lua")
54
55         if not func then
56                 return nil
57         end
58
59         luci.i18n.loadc("cbi")
60
61         luci.util.resfenv(func)
62         luci.util.updfenv(func, luci.cbi)
63         luci.util.extfenv(func, "translate", luci.i18n.translate)
64         luci.util.extfenv(func, "translatef", luci.i18n.translatef)
65         luci.util.extfenv(func, "arg", {...})
66
67         local maps = {func()}
68
69         for i, map in ipairs(maps) do
70                 if not instanceof(map, Node) then
71                         error("CBI map returns no valid map object!")
72                         return nil
73                 end
74         end
75
76         return maps
77 end
78
79 -- Node pseudo abstract class
80 Node = class()
81
82 function Node.__init__(self, title, description)
83         self.children = {}
84         self.title = title or ""
85         self.description = description or ""
86         self.template = "cbi/node"
87 end
88
89 -- i18n helper
90 function Node._i18n(self, config, section, option, title, description)
91
92         -- i18n loaded?
93         if type(luci.i18n) == "table" then
94
95                 local key = config and config:gsub("[^%w]+", "") or ""
96
97                 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
98                 if option  then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "")  end
99
100                 self.title = title or luci.i18n.translate( key, option or section or config )
101                 self.description = description or luci.i18n.translate( key .. "_desc", "" )
102         end
103 end
104
105 -- Append child nodes
106 function Node.append(self, obj)
107         table.insert(self.children, obj)
108 end
109
110 -- Parse this node and its children
111 function Node.parse(self, ...)
112         for k, child in ipairs(self.children) do
113                 child:parse(...)
114         end
115 end
116
117 -- Render this node
118 function Node.render(self, scope)
119         scope = scope or {}
120         scope.self = self
121
122         luci.template.render(self.template, scope)
123 end
124
125 -- Render the children
126 function Node.render_children(self, ...)
127         for k, node in ipairs(self.children) do
128                 node:render(...)
129         end
130 end
131
132
133 --[[
134 A simple template element
135 ]]--
136 Template = class(Node)
137
138 function Template.__init__(self, template)
139         Node.__init__(self)
140         self.template = template
141 end
142
143 function Template.render(self)
144         luci.template.render(self.template, {self=self})
145 end
146
147
148 --[[
149 Map - A map describing a configuration file
150 ]]--
151 Map = class(Node)
152
153 function Map.__init__(self, config, ...)
154         Node.__init__(self, ...)
155         Node._i18n(self, config, nil, nil, ...)
156
157         self.config = config
158         self.parsechain = {self.config}
159         self.template = "cbi/map"
160         if not uci.load(self.config) then
161                 error("Unable to read UCI data: " .. self.config)
162         end
163 end
164
165
166 -- Chain foreign config
167 function Map.chain(self, config)
168         table.insert(self.parsechain, config)
169 end
170
171 -- Use optimized UCI writing
172 function Map.parse(self, ...)
173         Node.parse(self, ...)
174         for i, config in ipairs(self.parsechain) do
175                 uci.save(config)
176         end
177         if luci.http.formvalue("cbi.apply") then
178                 for i, config in ipairs(self.parsechain) do
179                         uci.commit(config)
180                         if luci.config.uci_oncommit and luci.config.uci_oncommit[config] then
181                                 luci.util.exec(luci.config.uci_oncommit[config])
182                         end
183
184                         -- Refresh data because commit changes section names
185                         uci.unload(config)
186                         uci.load(config)
187                 end
188
189                 -- Reparse sections
190                 Node.parse(self, ...)
191
192         end
193         for i, config in ipairs(self.parsechain) do
194                 uci.unload(config)
195         end
196 end
197
198 -- Creates a child section
199 function Map.section(self, class, ...)
200         if instanceof(class, AbstractSection) then
201                 local obj  = class(self, ...)
202                 self:append(obj)
203                 return obj
204         else
205                 error("class must be a descendent of AbstractSection")
206         end
207 end
208
209 -- UCI add
210 function Map.add(self, sectiontype)
211         return uci.add(self.config, sectiontype)
212 end
213
214 -- UCI set
215 function Map.set(self, section, option, value)
216         if option then
217                 return uci.set(self.config, section, option, value)
218         else
219                 return uci.set(self.config, section, value)
220         end
221 end
222
223 -- UCI del
224 function Map.del(self, section, option)
225         if option then
226                 return uci.delete(self.config, section, option)
227         else
228                 return uci.delete(self.config, section)
229         end
230 end
231
232 -- UCI get
233 function Map.get(self, section, option)
234         if not section then
235                 return uci.get_all(self.config)
236         elseif option then
237                 return uci.get(self.config, section, option)
238         else
239                 return uci.get_all(self.config, section)
240         end
241 end
242
243 -- UCI stateget
244 function Map.stateget(self, section, option)
245         return uci.get_statevalue(self.config, section, option)
246 end
247
248
249 --[[
250 Page - A simple node
251 ]]--
252
253 Page = class(Node)
254 Page.__init__ = Node.__init__
255 Page.parse    = function() end
256
257
258 --[[
259 SimpleForm - A Simple non-UCI form
260 ]]--
261 SimpleForm = class(Node)
262
263 function SimpleForm.__init__(self, config, title, description, data)
264         Node.__init__(self, title, description)
265         self.config = config
266         self.data = data or {}
267         self.template = "cbi/simpleform"
268         self.dorender = true
269 end
270
271 function SimpleForm.parse(self, ...)
272         if luci.http.formvalue("cbi.submit") then
273                 Node.parse(self, 1, ...)
274         end
275                 
276         local valid = true
277         for k, j in ipairs(self.children) do 
278                 for i, v in ipairs(j.children) do
279                         valid = valid 
280                          and (not v.tag_missing or not v.tag_missing[1])
281                          and (not v.tag_invalid or not v.tag_invalid[1])
282                 end
283         end
284         
285         local state = 
286                 not luci.http.formvalue("cbi.submit") and 0
287                 or valid and 1
288                 or -1
289
290         self.dorender = self:handle(state, self.data) ~= false
291 end
292
293 function SimpleForm.render(self, ...)
294         if self.dorender then
295                 Node.render(self, ...)
296         end
297 end
298
299 function SimpleForm.section(self, class, ...)
300         if instanceof(class, AbstractSection) then
301                 local obj  = class(self, ...)
302                 self:append(obj)
303                 return obj
304         else
305                 error("class must be a descendent of AbstractSection")
306         end
307 end
308
309 -- Creates a child field
310 function SimpleForm.field(self, class, ...)
311         local section
312         for k, v in ipairs(self.children) do
313                 if instanceof(v, SimpleSection) then
314                         section = v
315                         break
316                 end
317         end
318         if not section then
319                 section = self:section(SimpleSection)
320         end
321         
322         if instanceof(class, AbstractValue) then
323                 local obj  = class(self, ...)
324                 obj.track_missing = true
325                 section:append(obj)
326                 return obj
327         else
328                 error("class must be a descendent of AbstractValue")
329         end
330 end
331
332 function SimpleForm.set(self, section, option, value)
333         self.data[option] = value
334 end
335
336
337 function SimpleForm.del(self, section, option)
338         self.data[option] = nil
339 end
340
341
342 function SimpleForm.get(self, section, option)
343         return self.data[option]
344 end
345
346
347
348 --[[
349 AbstractSection
350 ]]--
351 AbstractSection = class(Node)
352
353 function AbstractSection.__init__(self, map, sectiontype, ...)
354         Node.__init__(self, ...)
355         self.sectiontype = sectiontype
356         self.map = map
357         self.config = map.config
358         self.optionals = {}
359         self.defaults = {}
360
361         self.optional = true
362         self.addremove = false
363         self.dynamic = false
364 end
365
366 -- Appends a new option
367 function AbstractSection.option(self, class, option, ...)
368         if instanceof(class, AbstractValue) then
369                 local obj  = class(self.map, option, ...)
370
371                 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
372
373                 self:append(obj)
374                 return obj
375         else
376                 error("class must be a descendent of AbstractValue")
377         end
378 end
379
380 -- Parse optional options
381 function AbstractSection.parse_optionals(self, section)
382         if not self.optional then
383                 return
384         end
385
386         self.optionals[section] = {}
387
388         local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
389         for k,v in ipairs(self.children) do
390                 if v.optional and not v:cfgvalue(section) then
391                         if field == v.option then
392                                 field = nil
393                         else
394                                 table.insert(self.optionals[section], v)
395                         end
396                 end
397         end
398
399         if field and #field > 0 and self.dynamic then
400                 self:add_dynamic(field)
401         end
402 end
403
404 -- Add a dynamic option
405 function AbstractSection.add_dynamic(self, field, optional)
406         local o = self:option(Value, field, field)
407         o.optional = optional
408 end
409
410 -- Parse all dynamic options
411 function AbstractSection.parse_dynamic(self, section)
412         if not self.dynamic then
413                 return
414         end
415
416         local arr  = luci.util.clone(self:cfgvalue(section))
417         local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
418         for k, v in pairs(form) do
419                 arr[k] = v
420         end
421
422         for key,val in pairs(arr) do
423                 local create = true
424
425                 for i,c in ipairs(self.children) do
426                         if c.option == key then
427                                 create = false
428                         end
429                 end
430
431                 if create and key:sub(1, 1) ~= "." then
432                         self:add_dynamic(key, true)
433                 end
434         end
435 end
436
437 -- Returns the section's UCI table
438 function AbstractSection.cfgvalue(self, section)
439         return self.map:get(section)
440 end
441
442 -- Removes the section
443 function AbstractSection.remove(self, section)
444         return self.map:del(section)
445 end
446
447 -- Creates the section
448 function AbstractSection.create(self, section)
449         local stat
450         
451         if section then
452                 stat = self.map:set(section, nil, self.sectiontype)
453         else
454                 section = self.map:add(self.sectiontype)
455                 stat = section
456         end
457
458         if stat then
459                 for k,v in pairs(self.children) do
460                         if v.default then
461                                 self.map:set(section, v.option, v.default)
462                         end
463                 end
464
465                 for k,v in pairs(self.defaults) do
466                         self.map:set(section, k, v)
467                 end
468         end
469
470         return stat
471 end
472
473
474 SimpleSection = class(AbstractSection)
475
476 function SimpleSection.__init__(self, form, ...)
477         AbstractSection.__init__(self, form, nil, ...)
478         self.template = "cbi/nullsection"
479 end
480
481
482 Table = class(AbstractSection)
483
484 function Table.__init__(self, form, data, ...)
485         local datasource = {}
486         datasource.config = "table"
487         self.data = data
488         
489         function datasource.get(self, section, option)
490                 return data[section][option]
491         end
492         
493         AbstractSection.__init__(self, datasource, "table", ...)
494         self.template = "cbi/tblsection"
495         self.rowcolors = true
496         self.anonymous = true
497 end
498
499 function Table.cfgsections(self)
500         local sections = {}
501         
502         for i, v in pairs(self.data) do
503                 table.insert(sections, i)
504         end
505         
506         return sections
507 end
508
509
510
511 --[[
512 NamedSection - A fixed configuration section defined by its name
513 ]]--
514 NamedSection = class(AbstractSection)
515
516 function NamedSection.__init__(self, map, section, type, ...)
517         AbstractSection.__init__(self, map, type, ...)
518         Node._i18n(self, map.config, section, nil, ...)
519
520         self.template = "cbi/nsection"
521         self.section = section
522         self.addremove = false
523 end
524
525 function NamedSection.parse(self)
526         local s = self.section
527         local active = self:cfgvalue(s)
528
529
530         if self.addremove then
531                 local path = self.config.."."..s
532                 if active then -- Remove the section
533                         if luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
534                                 return
535                         end
536                 else           -- Create and apply default values
537                         if luci.http.formvalue("cbi.cns."..path) then
538                                 self:create(s)
539                                 return
540                         end
541                 end
542         end
543
544         if active then
545                 AbstractSection.parse_dynamic(self, s)
546                 if luci.http.formvalue("cbi.submit") then
547                         Node.parse(self, s)
548                 end
549                 AbstractSection.parse_optionals(self, s)
550         end
551 end
552
553
554 --[[
555 TypedSection - A (set of) configuration section(s) defined by the type
556         addremove:      Defines whether the user can add/remove sections of this type
557         anonymous:  Allow creating anonymous sections
558         validate:       a validation function returning nil if the section is invalid
559 ]]--
560 TypedSection = class(AbstractSection)
561
562 function TypedSection.__init__(self, map, type, ...)
563         AbstractSection.__init__(self, map, type, ...)
564         Node._i18n(self, map.config, type, nil, ...)
565
566         self.template  = "cbi/tsection"
567         self.deps = {}
568
569         self.anonymous = false
570 end
571
572 -- Return all matching UCI sections for this TypedSection
573 function TypedSection.cfgsections(self)
574         local sections = {}
575         uci.foreach(self.map.config, self.sectiontype,
576                 function (section)
577                         if self:checkscope(section[".name"]) then
578                                 table.insert(sections, section[".name"])
579                         end
580                 end)
581
582         return sections
583 end
584
585 -- Limits scope to sections that have certain option => value pairs
586 function TypedSection.depends(self, option, value)
587         table.insert(self.deps, {option=option, value=value})
588 end
589
590 function TypedSection.parse(self)
591         if self.addremove then
592                 -- Create
593                 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
594                 local name  = luci.http.formvalue(crval)
595                 if self.anonymous then
596                         if name then
597                                 self:create()
598                         end
599                 else
600                         if name then
601                                 -- Ignore if it already exists
602                                 if self:cfgvalue(name) then
603                                         name = nil;
604                                 end
605
606                                 name = self:checkscope(name)
607
608                                 if not name then
609                                         self.err_invalid = true
610                                 end
611
612                                 if name and name:len() > 0 then
613                                         self:create(name)
614                                 end
615                         end
616                 end
617
618                 -- Remove
619                 crval = REMOVE_PREFIX .. self.config
620                 name = luci.http.formvaluetable(crval)
621                 for k,v in pairs(name) do
622                         if self:cfgvalue(k) and self:checkscope(k) then
623                                 self:remove(k)
624                         end
625                 end
626         end
627
628         for i, k in ipairs(self:cfgsections()) do
629                 AbstractSection.parse_dynamic(self, k)
630                 if luci.http.formvalue("cbi.submit") then
631                         Node.parse(self, k)
632                 end
633                 AbstractSection.parse_optionals(self, k)
634         end
635 end
636
637 -- Verifies scope of sections
638 function TypedSection.checkscope(self, section)
639         -- Check if we are not excluded
640         if self.filter and not self:filter(section) then
641                 return nil
642         end
643
644         -- Check if at least one dependency is met
645         if #self.deps > 0 and self:cfgvalue(section) then
646                 local stat = false
647
648                 for k, v in ipairs(self.deps) do
649                         if self:cfgvalue(section)[v.option] == v.value then
650                                 stat = true
651                         end
652                 end
653
654                 if not stat then
655                         return nil
656                 end
657         end
658
659         return self:validate(section)
660 end
661
662
663 -- Dummy validate function
664 function TypedSection.validate(self, section)
665         return section
666 end
667
668
669 --[[
670 AbstractValue - An abstract Value Type
671         null:           Value can be empty
672         valid:          A function returning the value if it is valid otherwise nil
673         depends:        A table of option => value pairs of which one must be true
674         default:        The default value
675         size:           The size of the input fields
676         rmempty:        Unset value if empty
677         optional:       This value is optional (see AbstractSection.optionals)
678 ]]--
679 AbstractValue = class(Node)
680
681 function AbstractValue.__init__(self, map, option, ...)
682         Node.__init__(self, ...)
683         self.option = option
684         self.map    = map
685         self.config = map.config
686         self.tag_invalid = {}
687         self.tag_missing = {}
688         self.deps = {}
689
690         self.track_missing = false
691         self.rmempty   = false
692         self.default   = nil
693         self.size      = nil
694         self.optional  = false
695         self.stateful  = false
696 end
697
698 -- Add a dependencie to another section field
699 function AbstractValue.depends(self, field, value)
700         table.insert(self.deps, {field=field, value=value})
701 end
702
703 -- Return whether this object should be created
704 function AbstractValue.formcreated(self, section)
705         local key = "cbi.opt."..self.config.."."..section
706         return (luci.http.formvalue(key) == self.option)
707 end
708
709 -- Returns the formvalue for this object
710 function AbstractValue.formvalue(self, section)
711         local key = "cbid."..self.map.config.."."..section.."."..self.option
712         return luci.http.formvalue(key)
713 end
714
715 function AbstractValue.additional(self, value)
716         self.optional = value
717 end
718
719 function AbstractValue.mandatory(self, value)
720         self.rmempty = not value
721 end
722
723 function AbstractValue.parse(self, section)
724         local fvalue = self:formvalue(section)
725         local cvalue = self:cfgvalue(section)
726
727         if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
728                 fvalue = self:transform(self:validate(fvalue, section))
729                 if not fvalue then
730                         self.tag_invalid[section] = true
731                 end
732                 if fvalue and not (fvalue == cvalue) then
733                         self:write(section, fvalue)
734                 end
735         else                                                    -- Unset the UCI or error
736                 if self.rmempty or self.optional then
737                         self:remove(section)
738                 elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
739                         self.tag_missing[section] = true
740                 end
741         end
742 end
743
744 -- Render if this value exists or if it is mandatory
745 function AbstractValue.render(self, s, scope)
746         if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
747                 scope = scope or {}
748                 scope.section = s
749                 scope.cbid    = "cbid." .. self.config ..
750                                 "."     .. s           ..
751                                                 "."     .. self.option
752
753                 scope.ifattr = function(cond,key,val)
754                         if cond then
755                                 return string.format(
756                                         ' %s="%s"', tostring(key),
757                                         luci.util.pcdata(tostring( val
758                                          or scope[key]
759                                          or (type(self[key]) ~= "function" and self[key])
760                                          or "" ))
761                                 )
762                         else
763                                 return ''
764                         end
765                 end
766
767                 scope.attr = function(...)
768                         return scope.ifattr( true, ... )
769                 end
770
771                 Node.render(self, scope)
772         end
773 end
774
775 -- Return the UCI value of this object
776 function AbstractValue.cfgvalue(self, section)
777         return self.stateful
778          and self.map:stateget(section, self.option)
779          or  self.map:get(section, self.option)
780 end
781
782 -- Validate the form value
783 function AbstractValue.validate(self, value)
784         return value
785 end
786
787 AbstractValue.transform = AbstractValue.validate
788
789
790 -- Write to UCI
791 function AbstractValue.write(self, section, value)
792         return self.map:set(section, self.option, value)
793 end
794
795 -- Remove from UCI
796 function AbstractValue.remove(self, section)
797         return self.map:del(section, self.option)
798 end
799
800
801
802
803 --[[
804 Value - A one-line value
805         maxlength:      The maximum length
806 ]]--
807 Value = class(AbstractValue)
808
809 function Value.__init__(self, ...)
810         AbstractValue.__init__(self, ...)
811         self.template  = "cbi/value"
812         self.keylist = {}
813         self.vallist = {}
814 end
815
816 function Value.value(self, key, val)
817         val = val or key
818         table.insert(self.keylist, tostring(key))
819         table.insert(self.vallist, tostring(val))
820 end
821
822
823 -- DummyValue - This does nothing except being there
824 DummyValue = class(AbstractValue)
825
826 function DummyValue.__init__(self, map, ...)
827         AbstractValue.__init__(self, map, ...)
828         self.template = "cbi/dvalue"
829         self.value = nil
830 end
831
832 function DummyValue.parse(self)
833
834 end
835
836
837 --[[
838 Flag - A flag being enabled or disabled
839 ]]--
840 Flag = class(AbstractValue)
841
842 function Flag.__init__(self, ...)
843         AbstractValue.__init__(self, ...)
844         self.template  = "cbi/fvalue"
845
846         self.enabled = "1"
847         self.disabled = "0"
848 end
849
850 -- A flag can only have two states: set or unset
851 function Flag.parse(self, section)
852         local fvalue = self:formvalue(section)
853
854         if fvalue then
855                 fvalue = self.enabled
856         else
857                 fvalue = self.disabled
858         end
859
860         if fvalue == self.enabled or (not self.optional and not self.rmempty) then
861                 if not(fvalue == self:cfgvalue(section)) then
862                         self:write(section, fvalue)
863                 end
864         else
865                 self:remove(section)
866         end
867 end
868
869
870
871 --[[
872 ListValue - A one-line value predefined in a list
873         widget: The widget that will be used (select, radio)
874 ]]--
875 ListValue = class(AbstractValue)
876
877 function ListValue.__init__(self, ...)
878         AbstractValue.__init__(self, ...)
879         self.template  = "cbi/lvalue"
880         self.keylist = {}
881         self.vallist = {}
882
883         self.size   = 1
884         self.widget = "select"
885 end
886
887 function ListValue.value(self, key, val)
888         val = val or key
889         table.insert(self.keylist, tostring(key))
890         table.insert(self.vallist, tostring(val))
891 end
892
893 function ListValue.validate(self, val)
894         if luci.util.contains(self.keylist, val) then
895                 return val
896         else
897                 return nil
898         end
899 end
900
901
902
903 --[[
904 MultiValue - Multiple delimited values
905         widget: The widget that will be used (select, checkbox)
906         delimiter: The delimiter that will separate the values (default: " ")
907 ]]--
908 MultiValue = class(AbstractValue)
909
910 function MultiValue.__init__(self, ...)
911         AbstractValue.__init__(self, ...)
912         self.template = "cbi/mvalue"
913         self.keylist = {}
914         self.vallist = {}
915
916         self.widget = "checkbox"
917         self.delimiter = " "
918 end
919
920 function MultiValue.render(self, ...)
921         if self.widget == "select" and not self.size then
922                 self.size = #self.vallist
923         end
924
925         AbstractValue.render(self, ...)
926 end
927
928 function MultiValue.value(self, key, val)
929         val = val or key
930         table.insert(self.keylist, tostring(key))
931         table.insert(self.vallist, tostring(val))
932 end
933
934 function MultiValue.valuelist(self, section)
935         local val = self:cfgvalue(section)
936
937         if not(type(val) == "string") then
938                 return {}
939         end
940
941         return luci.util.split(val, self.delimiter)
942 end
943
944 function MultiValue.validate(self, val)
945         val = (type(val) == "table") and val or {val}
946
947         local result
948
949         for i, value in ipairs(val) do
950                 if luci.util.contains(self.keylist, value) then
951                         result = result and (result .. self.delimiter .. value) or value
952                 end
953         end
954
955         return result
956 end
957
958 --[[
959 TextValue - A multi-line value
960         rows:   Rows
961 ]]--
962 TextValue = class(AbstractValue)
963
964 function TextValue.__init__(self, ...)
965         AbstractValue.__init__(self, ...)
966         self.template  = "cbi/tvalue"
967 end