* luci/libs: uvl: implement "named" flag for schemes
[project/luci.git] / libs / uvl / luasrc / uvl.lua
1 --[[
2
3 UCI Validation Layer - Main Library
4 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
5 (c) 2008 Steven Barth <steven@midlink.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11         http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17
18 --- UVL - UCI Validation Layer
19 -- @class       module
20 -- @cstyle      instance
21
22 module( "luci.uvl", package.seeall )
23
24 require("luci.fs")
25 require("luci.util")
26 require("luci.model.uci")
27 require("luci.uvl.loghelper")
28 require("luci.uvl.datatypes")
29 require("luci.uvl.validation")
30 require("luci.uvl.dependencies")
31
32
33 TYPE_SECTION  = 0x01
34 TYPE_VARIABLE = 0x02
35 TYPE_ENUM     = 0x03
36
37 --- Boolean; default true;
38 -- treat sections found in config but not in scheme as error
39 STRICT_UNKNOWN_SECTIONS    = true
40
41 --- Boolean; default true;
42 -- treat options found in config but not in scheme as error
43 STRICT_UNKNOWN_OPTIONS     = true
44
45 --- Boolean; default true;
46 -- treat failed external validators as error
47 STRICT_EXTERNAL_VALIDATORS = true
48
49 --- Boolean; default true;
50 -- treat list values stored as options like errors
51 STRICT_LIST_TYPE           = true
52
53
54 local default_schemedir = "/lib/uci/schema"
55
56 local function _assert( condition, fmt, ... )
57         if not condition then
58                 return assert( nil, string.format( fmt, ... ) )
59         else
60                 return condition
61         end
62 end
63
64
65 --- Object constructor
66 -- @class                       function
67 -- @name                        UVL
68 -- @param schemedir     Path to the scheme directory (optional)
69 -- @return                      Instance object
70 UVL = luci.util.class()
71
72 function UVL.__init__( self, schemedir )
73         self.schemedir  = schemedir or default_schemedir
74         self.packages   = { }
75         self.beenthere  = { }
76         self.uci                = luci.model.uci
77         self.dep                = luci.uvl.dependencies
78         self.log        = luci.uvl.loghelper
79         self.datatypes  = luci.uvl.datatypes
80 end
81
82
83 --- Parse given scheme and return the scheme tree.
84 -- @param scheme        Name of the scheme to parse
85 -- @return                      Table containing the parsed scheme or nil on error
86 -- @return                      String containing the reason for errors (if any)
87 function UVL.get_scheme( self, scheme )
88         if not self.packages[scheme] then
89                 local ok, err = pcall( self.read_scheme, self, scheme )
90                 if not ok then
91                         return nil, self.log.scheme_error( scheme, err )
92                 end
93         end
94         return self.packages[scheme], nil
95 end
96
97 --- Return a table containing the dependencies of specified section or option.
98 -- @param config        Name of the configuration or parsed scheme object
99 -- @param section       Type of the section
100 -- @param option        Name of the option (optional)
101 -- @return                      Table containing the dependencies or nil on error
102 -- @return                      String containing the reason for errors (if any)
103 function UVL.get_dependencies( self, config, section, option )
104         config = ( type(config) == "string" and self:get_scheme(config) or config )
105
106         local deps = { }
107         local dt
108
109         if not config.sections[section] then return deps end
110
111         if option and config.variables[section][option] then
112                 dt = config.variables[section][option].depends
113         else
114                 dt = config.sections[section].depends
115         end
116
117         if dt then
118                 for _, d in ipairs(dt) do
119                         local sdeps = { }
120                         for k, v in pairs(d) do
121                                 local r = self.dep._parse_reference( k )
122                                 if r then
123                                         sdeps[r] = v
124                                 else
125                                         return nil, string.format(
126                                                 'Ambiguous dependency reference "%s" for object ' ..
127                                                 '"%s.%s%s" given',
128                                                         k, config.name, section,
129                                                         option and '.' .. option or ''
130                                         )
131                                 end
132                         end
133                         table.insert( deps, sdeps )
134                 end
135         end
136         return deps
137 end
138
139 --- Validate given configuration, section or option.
140 -- @param config        Name of the configuration to validate
141 -- @param section       Name of the section to validate (optional)
142 -- @param option        Name of the option to validate (optional)
143 -- @return                      Boolean indicating whether the given config validates
144 -- @return                      String containing the reason for errors (if any)
145 function UVL.validate( self, config, section, option )
146         if config and section and option then
147                 return self:validate_option( config, section, option )
148         elseif config and section then
149                 return self:validate_section( config, section )
150         elseif config then
151                 return self:validate_config( config )
152         end
153 end
154
155 --- Validate given configuration.
156 -- @param config        Name of the configuration to validate
157 -- @return                      Boolean indicating whether the given config validates
158 -- @return                      String containing the reason for errors (if any)
159 function UVL.validate_config( self, config )
160
161         if not self.packages[config] then
162                 local ok, err = pcall( self.read_scheme, self, config )
163                 if not ok then
164                         return false, self.log.scheme_error( config, err )
165                 end
166         end
167
168         self.uci.load_config( config )
169         self.beenthere = { }
170
171         local co = self.uci.get_all( config )
172         local sc = { }
173
174         if not co then
175                 return false, 'Unable to load configuration "' .. config .. '"'
176         end
177
178         local function _uci_foreach( type, func )
179                 local ok, err
180                 for k, v in pairs(co) do
181                         if co[k]['.type'] == type then
182                                 sc[type] = sc[type] + 1
183                                 ok, err = func( k, v )
184                                 if not ok then
185                                         err = self.log.config_error( config, err )
186                                         break
187                                 end
188                         end
189                 end
190                 return ok, err
191         end
192
193         for k, v in pairs( self.packages[config].sections ) do
194                 sc[k] = 0
195                 local ok, err = _uci_foreach( k,
196                         function(s)
197                                 local sect = luci.uvl.section( self, co, k, config, s )
198                                 return self:_validate_section( sect )
199                         end
200                 )
201                 if not ok then return false, err end
202         end
203
204         if STRICT_UNKNOWN_SECTIONS then
205                 for k, v in pairs(co) do
206                         if not self.beenthere[config..'.'..k] then
207                                 return false, self.log.config_error( config,
208                                         "Section '" .. config .. '.' .. co[k]['.type'] ..
209                                         "' not found in scheme" )
210                         end
211                 end
212         end
213
214         for _, k in ipairs(luci.util.keys(sc)) do
215                 local s = self.packages[config].sections[k]
216
217                 if s.required and sc[k] == 0 then
218                         return false, self.log.config_error( config,
219                                 'Required section "' .. k .. '" not found in config' )
220                 elseif s.unique and sc[k] > 1 then
221                         return false, self.log.config_error( config,
222                                 'Unique section "' .. k .. '" occurs multiple times in config' )
223                 end
224         end
225
226         return true, nil
227 end
228
229 --- Validate given config section.
230 -- @param config        Name of the configuration to validate
231 -- @param section       Name of the section to validate
232 -- @return                      Boolean indicating whether the given config validates
233 -- @return                      String containing the reason for errors (if any)
234 function UVL.validate_section( self, config, section )
235
236         if not self.packages[config] then
237                 local ok, err = pcall( self.read_scheme, self, config )
238                 if not ok then
239                         return false, self.log.scheme_error( config, err )
240                 end
241         end
242
243         self.uci.load_config( config )
244         self.beenthere = { }
245
246         local co = self.uci.get_all( config )
247
248         if not co then
249                 return false, 'Unable to load configuration "' .. config .. '"'
250         end
251
252         if co[section] then
253                 return self:_validate_section( luci.uvl.section(
254                         self, co, co[section]['.type'], config, section
255                 ) )
256         else
257                 return false, 'Section "' .. config .. '.' .. section ..
258                         '" not found in config. Nothing to do.'
259         end
260 end
261
262 --- Validate given config option.
263 -- @param config        Name of the configuration to validate
264 -- @param section       Name of the section to validate
265 -- @param option        Name of the option to validate
266 -- @return                      Boolean indicating whether the given config validates
267 -- @return                      String containing the reason for errors (if any)
268 function UVL.validate_option( self, config, section, option )
269
270         if not self.packages[config] then
271                 local ok, err = pcall( self.read_scheme, self, config )
272                 if not ok then
273                         return false, self.log.scheme_error( config, err )
274                 end
275         end
276
277         self.uci.load_config( config )
278         self.beenthere = { }
279
280         local co = self.uci.get_all( config )
281
282         if not co then
283                 return false, 'Unable to load configuration "' .. config .. '"'
284         end
285
286         if co[section] and co[section][option] then
287                 return self:_validate_option( luci.uvl.option(
288                         self, co, co[section]['.type'], config, section, option
289                 ) )
290         else
291                 return false, 'Option "' ..
292                         config .. '.' .. section .. '.' .. option ..
293                         '" not found in config. Nothing to do.'
294         end
295 end
296
297
298 function UVL._validate_section( self, section )
299
300         if section:values() then
301                 if section:section().named == true and
302                    section:values()['.anonymous'] == true
303                 then
304                         return false, self.log.section_error( section,
305                                 'The section of type "' .. section:sid() .. '" is stored ' ..
306                                 'anonymously in config but must be named' )
307                 end
308
309                 for _, v in ipairs(section:variables()) do
310                         local ok, err = self:_validate_option( v )
311
312                         if not ok then
313                                 return ok, self.log.section_error( section, err )
314                         end
315                 end
316
317                 local ok, err = luci.uvl.dependencies.check( self, section )
318
319                 if not ok then
320                         return false, err
321                 end
322         else
323                 return false, 'Option "' .. section:sid() .. '" not found in config'
324         end
325
326         if STRICT_UNKNOWN_OPTIONS and not section:section().dynamic then
327                 for k, v in pairs(section:values()) do
328                         if k:sub(1,1) ~= "." and not self.beenthere[
329                                 section:cid() .. '.' .. k
330                         ] then
331                                 return false, "Option '" .. section:sid() .. '.' .. k ..
332                                         "' not found in scheme"
333                         end
334                 end
335         end
336
337         return true, nil
338 end
339
340 function UVL._validate_option( self, option, nodeps )
341
342         local item = option:option()
343         local val  = option:value()
344
345         if not item and not ( option:section() and option:section().dynamic ) then
346                 return false, 'Option "' .. option:cid() ..
347                         '" not found in scheme'
348
349         elseif item then
350                 if item.required and not val then
351                         return false, 'Mandatory variable "' .. option:cid() ..
352                                 '" does not have a value'
353                 end
354
355                 if item.type == "enum" and val then
356                         if not item.values or not item.values[val] then
357                                 return false, 'Value "' .. ( val or '<nil>' ) ..
358                                         '" of given option "' .. option:cid() ..
359                                         '" is not defined in enum { ' ..
360                                                 table.concat( luci.util.keys(item.values), ", " ) ..
361                                         ' }'
362                         end
363                 elseif item.type == "list" and val then
364                         if type(val) ~= "table" and STRICT_LIST_TYPE then
365                                 return false, 'Option "' .. option:cid() ..
366                                         '" is defined as list but stored as plain value'
367                         end
368                 end
369
370                 if item.datatype and val then
371                         if self.datatypes[item.datatype] then
372                                 val = ( type(val) == "table" and val or { val } )
373                                 for i, v in ipairs(val) do
374                                         if not self.datatypes[item.datatype]( v ) then
375                                                 return false, 'Value' .. ( #val>1 and ' #'..i or '' ) ..
376                                                         ' "' .. ( v or '<nil>' ) ..
377                                                         '" of given option "' .. option:cid() ..
378                                                         '" does not validate as datatype "' ..
379                                                         item.datatype .. '"'
380                                         end
381                                 end
382                         else
383                                 return false, 'Unknown datatype "' ..
384                                         item.datatype .. '" encountered'
385                         end
386                 end
387
388                 if not nodeps then
389                         return luci.uvl.dependencies.check( self, option )
390                 end
391
392                 local ok, err = luci.uvl.validation.check( self, option )
393                 if not ok and STRICT_EXTERNAL_VALIDATORS then
394                         return false, self.log.validator_error( option, err )
395                 end
396         end
397
398         return true, nil
399 end
400
401 --- Find all parts of given scheme and construct validation tree.
402 -- This is normally done on demand, so you don't have to call this function
403 -- by yourself.
404 -- @param scheme        Name of the scheme to parse
405 function UVL.read_scheme( self, scheme )
406         local schemes = { }
407         local files = luci.fs.glob(self.schemedir .. '/*/' .. scheme)
408
409         if files then
410                 for i, file in ipairs( files ) do
411                         _assert( luci.fs.access(file), "Can't access file '%s'", file )
412
413                         self.uci.set_confdir( luci.fs.dirname(file) )
414                         self.uci.load( luci.fs.basename(file) )
415
416                         table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
417                 end
418
419                 return self:_read_scheme_parts( scheme, schemes )
420         else
421                 error(
422                         'Can not find scheme "' .. scheme ..
423                         '" in "' .. self.schemedir .. '"'
424                 )
425         end
426 end
427
428 -- Process all given parts and construct validation tree
429 function UVL._read_scheme_parts( self, scheme, schemes )
430
431         -- helper function to construct identifiers for given elements
432         local function _id( c, t )
433                 if c == TYPE_SECTION then
434                         return string.format(
435                                 'section "%s.%s"',
436                                         scheme, t.name or '?' )
437                 elseif c == TYPE_VARIABLE then
438                         return string.format(
439                                 'variable "%s.%s.%s"',
440                                         scheme, t.section or '?.?', t.name or '?' )
441                 elseif c == TYPE_ENUM then
442                         return string.format(
443                                 'enum "%s.%s.%s"',
444                                         scheme, t.variable or '?.?.?', t.value or '?' )
445                 end
446         end
447
448         -- helper function to check for required fields
449         local function _req( c, t, r )
450                 for i, v in ipairs(r) do
451                         _assert( t[v], 'Missing required field "%s" in %s', v, _id(c, t) )
452                 end
453         end
454
455         -- helper function to validate references
456         local function _ref( c, t )
457                 local k
458                 if c == TYPE_SECTION then
459                         k = "package"
460                 elseif c == TYPE_VARIABLE then
461                         k = "section"
462                 elseif c == TYPE_ENUM then
463                         k = "variable"
464                 end
465
466                 local r = luci.util.split( t[k], "." )
467                 r[1] = ( #r[1] > 0 and r[1] or scheme )
468
469                 _assert( #r == c, 'Malformed %s reference in %s', k, _id(c, t) )
470
471                 return r
472         end
473
474         -- helper function to read bools
475         local function _bool( v )
476                 return ( v == "true" or v == "yes" or v == "on" or v == "1" )
477         end
478
479         -- Step 1: get all sections
480         for i, conf in ipairs( schemes ) do
481                 for k, v in pairs( conf ) do
482                         if v['.type'] == 'section' then
483
484                                 _req( TYPE_SECTION, v, { "name", "package" } )
485
486                                 local r = _ref( TYPE_SECTION, v )
487
488                                 self.packages[r[1]] =
489                                         self.packages[r[1]] or {
490                                                 ["name"]      = r[1];
491                                                 ["sections"]  = { };
492                                                 ["variables"] = { };
493                                         }
494
495                                 local p = self.packages[r[1]]
496                                           p.sections[v.name]  = p.sections[v.name]  or { }
497                                           p.variables[v.name] = p.variables[v.name] or { }
498
499                                 local s = p.sections[v.name]
500
501                                 for k, v2 in pairs(v) do
502                                         if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
503                                                 if k == "depends" then
504                                                         s["depends"] = _assert(
505                                                                 self:_read_dependency( v2, s["depends"] ),
506                                                                 'Section "%s" in scheme "%s" has malformed ' ..
507                                                                 'dependency specification in "%s"',
508                                                                 v.name or '<nil>', scheme or '<nil>', k
509                                                         )
510                                                 elseif k == "dynamic" or k == "unique" or
511                                                        k == "required" or k == "named"
512                                                 then
513                                                         s[k] = _bool(v2)
514                                                 else
515                                                         s[k] = v2
516                                                 end
517                                         end
518                                 end
519
520                                 s.dynamic  = s.dynamic  or false
521                                 s.unique   = s.unique   or false
522                                 s.required = s.required or false
523                                 s.named    = s.named    or false
524                         end
525                 end
526         end
527
528         -- Step 2: get all variables
529         for i, conf in ipairs( schemes ) do
530                 for k, v in pairs( conf ) do
531                         if v['.type'] == "variable" then
532
533                                 _req( TYPE_VARIABLE, v, { "name", "section" } )
534
535                                 local r = _ref( TYPE_VARIABLE, v )
536
537                                 local p = _assert( self.packages[r[1]],
538                                         'Variable "%s" in scheme "%s" references unknown package "%s"',
539                                         v.name, scheme, r[1] )
540
541                                 local s = _assert( p.variables[r[2]],
542                                         'Variable "%s" in scheme "%s" references unknown section "%s"',
543                                         v.name, scheme, r[2] )
544
545                                 s[v.name] = s[v.name] or { }
546
547                                 local t = s[v.name]
548
549                                 for k, v2 in pairs(v) do
550                                         if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
551                                                 if k == "depends" then
552                                                         t["depends"] = _assert(
553                                                                 self:_read_dependency( v2, t["depends"] ),
554                                                                 'Invalid reference "%s" in "%s.%s.%s"',
555                                                                 v2, v.name, scheme, k
556                                                         )
557                                                 elseif k == "validator" then
558                                                         t["validators"] = _assert(
559                                                                 self:_read_validator( v2, t["validators"] ),
560                                                                 'Variable "%s" in scheme "%s" has malformed ' ..
561                                                                 'validator specification in "%s"',
562                                                                 v.name, scheme, k
563                                                         )
564                                                 elseif k == "required" then
565                                                         t[k] = _bool(v2)
566                                                 else
567                                                         t[k] = v2
568                                                 end
569                                         end
570                                 end
571
572                                 t.type     = t.type     or "variable"
573                                 t.datatype = t.datatype or "string"
574                                 t.required = t.required or false
575                         end
576                 end
577         end
578
579         -- Step 3: get all enums
580         for i, conf in ipairs( schemes ) do
581                 for k, v in pairs( conf ) do
582                         if v['.type'] == "enum" then
583
584                                 _req( TYPE_ENUM, v, { "value", "variable" } )
585
586                                 local r = _ref( TYPE_ENUM, v )
587                                 local p = _assert( self.packages[r[1]],
588                                         'Enum "%s" in scheme "%s" references unknown package "%s"',
589                                         v.value, scheme, r[1] )
590
591                                 local s = _assert( p.variables[r[2]],
592                                         'Enum "%s" in scheme "%s" references unknown section "%s"',
593                                         v.value, scheme, r[2] )
594
595                                 local t = _assert( s[r[3]],
596                                         'Enum "%s" in scheme "%s", section "%s" references ' ..
597                                         'unknown variable "%s"',
598                                         v.value, scheme, r[2], r[3] )
599
600                                 _assert( t.type == "enum",
601                                         'Enum "%s" in scheme "%s", section "%s" references ' ..
602                                         'variable "%s" with non enum type "%s"',
603                                         v.value, scheme, r[2], r[3], t.type )
604
605                                 if not t.values then
606                                         t.values = { [v.value] = v.title or v.value }
607                                 else
608                                         t.values[v.value] = v.title or v.value
609                                 end
610
611                                 if v.default then
612                                         _assert( not t.default,
613                                                 'Enum "%s" in scheme "%s", section "%s" redeclares ' ..
614                                                 'the default value of variable "%s"',
615                                                 v.value, scheme, r[2], v.variable )
616
617                                         t.default = v.value
618                                 end
619                         end
620                 end
621         end
622
623         return self
624 end
625
626 -- Read a dependency specification
627 function UVL._read_dependency( self, values, deps )
628         local expr = "%$?[a-zA-Z0-9_]+"
629         if values then
630                 values = ( type(values) == "table" and values or { values } )
631                 for _, value in ipairs(values) do
632                         local parts     = luci.util.split( value, "%s*,%s*", nil, true )
633                         local condition = { }
634                         for i, val in ipairs(parts) do
635                                 local k, v = unpack(luci.util.split(val, "%s*=%s*", nil, true))
636
637                                 if k and (
638                                         k:match("^"..expr.."%."..expr.."%."..expr.."$") or
639                                         k:match("^"..expr.."%."..expr.."$") or
640                                         k:match("^"..expr.."$")
641                                 ) then
642                                         condition[k] = v or true
643                                 else
644                                         return nil
645                                 end
646                         end
647
648                         if not deps then
649                                 deps = { condition }
650                         else
651                                 table.insert( deps, condition )
652                         end
653                 end
654         end
655
656         return deps
657 end
658
659 -- Read a validator specification
660 function UVL._read_validator( self, values, validators )
661         if values then
662                 values = ( type(values) == "table" and values or { values } )
663                 for _, value in ipairs(values) do
664                         local validator
665
666                         if value:match("^exec:") then
667                                 validator = value:gsub("^exec:","")
668                         elseif value:match("^lua:") then
669                                 validator = self:_resolve_function( (value:gsub("^lua:","") ) )
670                         end
671
672                         if validator then
673                                 if not validators then
674                                         validators = { validator }
675                                 else
676                                         table.insert( validators, validator )
677                                 end
678                         else
679                                 return nil
680                         end
681                 end
682
683                 return validators
684         end
685 end
686
687 -- Resolve given path
688 function UVL._resolve_function( self, value )
689         local path = luci.util.split(value, ".")
690
691         for i=1, #path-1 do
692                 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
693                 if stat and mod then
694                         for j=i+1, #path-1 do
695                                 if not type(mod) == "table" then
696                                         break
697                                 end
698                                 mod = mod[path[j]]
699                                 if not mod then
700                                         break
701                                 end
702                         end
703                         mod = type(mod) == "table" and mod[path[#path]] or nil
704                         if type(mod) == "function" then
705                                 return mod
706                         end
707                 end
708         end
709 end
710
711
712 --- Object representation of a scheme/config section.
713 -- @class       module
714 -- @cstyle      instance
715 -- @name        luci.uvl.section
716
717 --- Section instance constructor.
718 -- @class                       function
719 -- @name                        section
720 -- @param scheme        Scheme instance
721 -- @param co            Configuration data
722 -- @param st            Section type
723 -- @param c                     Configuration name
724 -- @param s                     Section name
725 -- @return                      Section instance
726 section = luci.util.class()
727
728 function section.__init__(self, scheme, co, st, c, s)
729         self.csection = co[s]
730         self.ssection = scheme.packages[c].sections[st]
731         self.cref     = { c, s }
732         self.sref     = { c, st }
733         self.scheme   = scheme
734         self.config   = co
735         self.type     = luci.uvl.TYPE_SECTION
736 end
737
738 --- Get the config path of this section.
739 -- @return      String containing the identifier
740 function section.cid(self)
741         return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' )
742 end
743
744 --- Get the scheme path of this section.
745 -- @return      String containing the identifier
746 function section.sid(self)
747         return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' )
748 end
749
750 --- Get all configuration values within this section.
751 -- @return      Table containing the values
752 function section.values(self)
753         return self.csection
754 end
755
756 --- Get the associated section information in scheme.
757 -- @return      Table containing the scheme properties
758 function section.section(self)
759         return self.ssection
760 end
761
762 --- Get all option objects associated with this section.
763 -- @return      Table containing all associated luci.uvl.option instances
764 function section.variables(self)
765         local v = { }
766         if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then
767                 for o, _ in pairs(
768                         self.scheme.packages[self.sref[1]].variables[self.sref[2]]
769                 ) do
770                         table.insert( v, luci.uvl.option(
771                                 self.scheme, self.config, self.sref[2],
772                                 self.cref[1], self.cref[2], o
773                         ) )
774                 end
775         end
776         return v
777 end
778
779
780 --- Object representation of a scheme/config option.
781 -- @class       module
782 -- @cstyle      instance
783 -- @name        luci.uvl.option
784
785 --- Section instance constructor.
786 -- @class                       function
787 -- @name                        option
788 -- @param scheme        Scheme instance
789 -- @param co            Configuration data
790 -- @param st            Section type
791 -- @param c                     Configuration name
792 -- @param s                     Section name
793 -- @param o                     Option name
794 -- @return                      Option instance
795 option = luci.util.class()
796
797 function option.__init__(self, scheme, co, st, c, s, o)
798         self.coption = co[s] and co[s][o] or nil
799         self.soption = scheme.packages[c].variables[st][o]
800         self.cref    = { c, s, o }
801         self.sref    = { c, st, o }
802         self.scheme  = scheme
803         self.config  = co
804         self.type    = luci.uvl.TYPE_OPTION
805 end
806
807 --- Get the config path of this option.
808 -- @return      String containing the identifier
809 function option.cid(self)
810         return ( self.cref[1] or '?' ) .. '.' ..
811                    ( self.cref[2] or '?' ) .. '.' ..
812                    ( self.cref[3] or '?' )
813 end
814
815 --- Get the scheme path of this option.
816 -- @return      String containing the identifier
817 function option.sid(self)
818         return ( self.sref[1] or '?' ) .. '.' ..
819                    ( self.sref[2] or '?' ) .. '.' ..
820                    ( self.sref[3] or '?' )
821 end
822
823 --- Get the value of this option.
824 -- @return      The associated configuration value
825 function option.value(self)
826         return self.coption
827 end
828
829 --- Get the associated option information in scheme.
830 -- @return      Table containing the scheme properties
831 function option.option(self)
832         return self.soption
833 end
834
835 --- Get the associated section information in scheme.
836 -- @return      Table containing the scheme properties
837 function option.section(self)
838         return self.scheme.packages[self.sref[1]].sections[self.sref[2]]
839 end