* luci/libs: uvl: implement get_scheme() and get_dependencies()
[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
302                 for _, v in ipairs(section:variables()) do
303                         local ok, err = self:_validate_option( v )
304
305                         if not ok then
306                                 return ok, self.log.section_error( section, err )
307                         end
308                 end
309
310                 local ok, err = luci.uvl.dependencies.check( self, section )
311
312                 if not ok then
313                         return false, err
314                 end
315         else
316                 return false, 'Option "' .. section:sid() .. '" not found in config'
317         end
318
319         if STRICT_UNKNOWN_OPTIONS and not section:section().dynamic then
320                 for k, v in pairs(section:values()) do
321                         if k:sub(1,1) ~= "." and not self.beenthere[
322                                 section:cid() .. '.' .. k
323                         ] then
324                                 return false, "Option '" .. section:sid() .. '.' .. k ..
325                                         "' not found in scheme"
326                         end
327                 end
328         end
329
330         return true, nil
331 end
332
333 function UVL._validate_option( self, option, nodeps )
334
335         local item = option:option()
336         local val  = option:value()
337
338         if not item and not ( option:section() and option:section().dynamic ) then
339                 return false, 'Option "' .. option:cid() ..
340                         '" not found in scheme'
341
342         elseif item then
343                 if item.required and not val then
344                         return false, 'Mandatory variable "' .. option:cid() ..
345                                 '" does not have a value'
346                 end
347
348                 if item.type == "enum" and val then
349                         if not item.values or not item.values[val] then
350                                 return false, 'Value "' .. ( val or '<nil>' ) ..
351                                         '" of given option "' .. option:cid() ..
352                                         '" is not defined in enum { ' ..
353                                                 table.concat( luci.util.keys(item.values), ", " ) ..
354                                         ' }'
355                         end
356                 elseif item.type == "list" and val then
357                         if type(val) ~= "table" and STRICT_LIST_TYPE then
358                                 return false, 'Option "' .. option:cid() ..
359                                         '" is defined as list but stored as plain value'
360                         end
361                 end
362
363                 if item.datatype and val then
364                         if self.datatypes[item.datatype] then
365                                 val = ( type(val) == "table" and val or { val } )
366                                 for i, v in ipairs(val) do
367                                         if not self.datatypes[item.datatype]( v ) then
368                                                 return false, 'Value' .. ( #val>1 and ' #'..i or '' ) ..
369                                                         ' "' .. ( v or '<nil>' ) ..
370                                                         '" of given option "' .. option:cid() ..
371                                                         '" does not validate as datatype "' ..
372                                                         item.datatype .. '"'
373                                         end
374                                 end
375                         else
376                                 return false, 'Unknown datatype "' ..
377                                         item.datatype .. '" encountered'
378                         end
379                 end
380
381                 if not nodeps then
382                         return luci.uvl.dependencies.check( self, option )
383                 end
384
385                 local ok, err = luci.uvl.validation.check( self, option )
386                 if not ok and STRICT_EXTERNAL_VALIDATORS then
387                         return false, self.log.validator_error( option, err )
388                 end
389         end
390
391         return true, nil
392 end
393
394 --- Find all parts of given scheme and construct validation tree.
395 -- This is normally done on demand, so you don't have to call this function
396 -- by yourself.
397 -- @param scheme        Name of the scheme to parse
398 function UVL.read_scheme( self, scheme )
399         local schemes = { }
400         local files = luci.fs.glob(self.schemedir .. '/*/' .. scheme)
401
402         if files then
403                 for i, file in ipairs( files ) do
404                         _assert( luci.fs.access(file), "Can't access file '%s'", file )
405
406                         self.uci.set_confdir( luci.fs.dirname(file) )
407                         self.uci.load( luci.fs.basename(file) )
408
409                         table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
410                 end
411
412                 return self:_read_scheme_parts( scheme, schemes )
413         else
414                 error(
415                         'Can not find scheme "' .. scheme ..
416                         '" in "' .. self.schemedir .. '"'
417                 )
418         end
419 end
420
421 -- Process all given parts and construct validation tree
422 function UVL._read_scheme_parts( self, scheme, schemes )
423
424         -- helper function to construct identifiers for given elements
425         local function _id( c, t )
426                 if c == TYPE_SECTION then
427                         return string.format(
428                                 'section "%s.%s"',
429                                         scheme, t.name or '?' )
430                 elseif c == TYPE_VARIABLE then
431                         return string.format(
432                                 'variable "%s.%s.%s"',
433                                         scheme, t.section or '?.?', t.name or '?' )
434                 elseif c == TYPE_ENUM then
435                         return string.format(
436                                 'enum "%s.%s.%s"',
437                                         scheme, t.variable or '?.?.?', t.value or '?' )
438                 end
439         end
440
441         -- helper function to check for required fields
442         local function _req( c, t, r )
443                 for i, v in ipairs(r) do
444                         _assert( t[v], 'Missing required field "%s" in %s', v, _id(c, t) )
445                 end
446         end
447
448         -- helper function to validate references
449         local function _ref( c, t )
450                 local k
451                 if c == TYPE_SECTION then
452                         k = "package"
453                 elseif c == TYPE_VARIABLE then
454                         k = "section"
455                 elseif c == TYPE_ENUM then
456                         k = "variable"
457                 end
458
459                 local r = luci.util.split( t[k], "." )
460                 r[1] = ( #r[1] > 0 and r[1] or scheme )
461
462                 _assert( #r == c, 'Malformed %s reference in %s', k, _id(c, t) )
463
464                 return r
465         end
466
467         -- helper function to read bools
468         local function _bool( v )
469                 return ( v == "true" or v == "yes" or v == "on" or v == "1" )
470         end
471
472         -- Step 1: get all sections
473         for i, conf in ipairs( schemes ) do
474                 for k, v in pairs( conf ) do
475                         if v['.type'] == 'section' then
476
477                                 _req( TYPE_SECTION, v, { "name", "package" } )
478
479                                 local r = _ref( TYPE_SECTION, v )
480
481                                 self.packages[r[1]] =
482                                         self.packages[r[1]] or {
483                                                 ["name"]      = r[1];
484                                                 ["sections"]  = { };
485                                                 ["variables"] = { };
486                                         }
487
488                                 local p = self.packages[r[1]]
489                                           p.sections[v.name]  = p.sections[v.name]  or { }
490                                           p.variables[v.name] = p.variables[v.name] or { }
491
492                                 local s = p.sections[v.name]
493
494                                 for k, v2 in pairs(v) do
495                                         if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
496                                                 if k == "depends" then
497                                                         s["depends"] = _assert(
498                                                                 self:_read_dependency( v2, s["depends"] ),
499                                                                 'Section "%s" in scheme "%s" has malformed ' ..
500                                                                 'dependency specification in "%s"',
501                                                                 v.name or '<nil>', scheme or '<nil>', k
502                                                         )
503                                                 elseif k == "dynamic" or k == "unique" or k == "required" then
504                                                         s[k] = _bool(v2)
505                                                 else
506                                                         s[k] = v2
507                                                 end
508                                         end
509                                 end
510
511                                 s.dynamic  = s.dynamic  or false
512                                 s.unique   = s.unique   or false
513                                 s.required = s.required or false
514                         end
515                 end
516         end
517
518         -- Step 2: get all variables
519         for i, conf in ipairs( schemes ) do
520                 for k, v in pairs( conf ) do
521                         if v['.type'] == "variable" then
522
523                                 _req( TYPE_VARIABLE, v, { "name", "section" } )
524
525                                 local r = _ref( TYPE_VARIABLE, v )
526
527                                 local p = _assert( self.packages[r[1]],
528                                         'Variable "%s" in scheme "%s" references unknown package "%s"',
529                                         v.name, scheme, r[1] )
530
531                                 local s = _assert( p.variables[r[2]],
532                                         'Variable "%s" in scheme "%s" references unknown section "%s"',
533                                         v.name, scheme, r[2] )
534
535                                 s[v.name] = s[v.name] or { }
536
537                                 local t = s[v.name]
538
539                                 for k, v2 in pairs(v) do
540                                         if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
541                                                 if k == "depends" then
542                                                         t["depends"] = _assert(
543                                                                 self:_read_dependency( v2, t["depends"] ),
544                                                                 'Invalid reference "%s" in "%s.%s.%s"',
545                                                                 v2, v.name, scheme, k
546                                                         )
547                                                 elseif k == "validator" then
548                                                         t["validators"] = _assert(
549                                                                 self:_read_validator( v2, t["validators"] ),
550                                                                 'Variable "%s" in scheme "%s" has malformed ' ..
551                                                                 'validator specification in "%s"',
552                                                                 v.name, scheme, k
553                                                         )
554                                                 elseif k == "required" then
555                                                         t[k] = _bool(v2)
556                                                 else
557                                                         t[k] = v2
558                                                 end
559                                         end
560                                 end
561
562                                 t.type     = t.type     or "variable"
563                                 t.datatype = t.datatype or "string"
564                                 t.required = t.required or false
565                         end
566                 end
567         end
568
569         -- Step 3: get all enums
570         for i, conf in ipairs( schemes ) do
571                 for k, v in pairs( conf ) do
572                         if v['.type'] == "enum" then
573
574                                 _req( TYPE_ENUM, v, { "value", "variable" } )
575
576                                 local r = _ref( TYPE_ENUM, v )
577                                 local p = _assert( self.packages[r[1]],
578                                         'Enum "%s" in scheme "%s" references unknown package "%s"',
579                                         v.value, scheme, r[1] )
580
581                                 local s = _assert( p.variables[r[2]],
582                                         'Enum "%s" in scheme "%s" references unknown section "%s"',
583                                         v.value, scheme, r[2] )
584
585                                 local t = _assert( s[r[3]],
586                                         'Enum "%s" in scheme "%s", section "%s" references ' ..
587                                         'unknown variable "%s"',
588                                         v.value, scheme, r[2], r[3] )
589
590                                 _assert( t.type == "enum",
591                                         'Enum "%s" in scheme "%s", section "%s" references ' ..
592                                         'variable "%s" with non enum type "%s"',
593                                         v.value, scheme, r[2], r[3], t.type )
594
595                                 if not t.values then
596                                         t.values = { [v.value] = v.title or v.value }
597                                 else
598                                         t.values[v.value] = v.title or v.value
599                                 end
600
601                                 if v.default then
602                                         _assert( not t.default,
603                                                 'Enum "%s" in scheme "%s", section "%s" redeclares ' ..
604                                                 'the default value of variable "%s"',
605                                                 v.value, scheme, r[2], v.variable )
606
607                                         t.default = v.value
608                                 end
609                         end
610                 end
611         end
612
613         return self
614 end
615
616 -- Read a dependency specification
617 function UVL._read_dependency( self, values, deps )
618         local expr = "%$?[a-zA-Z0-9_]+"
619         if values then
620                 values = ( type(values) == "table" and values or { values } )
621                 for _, value in ipairs(values) do
622                         local parts     = luci.util.split( value, "%s*,%s*", nil, true )
623                         local condition = { }
624                         for i, val in ipairs(parts) do
625                                 local k, v = unpack(luci.util.split(val, "%s*=%s*", nil, true))
626
627                                 if k and (
628                                         k:match("^"..expr.."%."..expr.."%."..expr.."$") or
629                                         k:match("^"..expr.."%."..expr.."$") or
630                                         k:match("^"..expr.."$")
631                                 ) then
632                                         condition[k] = v or true
633                                 else
634                                         return nil
635                                 end
636                         end
637
638                         if not deps then
639                                 deps = { condition }
640                         else
641                                 table.insert( deps, condition )
642                         end
643                 end
644         end
645
646         return deps
647 end
648
649 -- Read a validator specification
650 function UVL._read_validator( self, values, validators )
651         if values then
652                 values = ( type(values) == "table" and values or { values } )
653                 for _, value in ipairs(values) do
654                         local validator
655
656                         if value:match("^exec:") then
657                                 validator = value:gsub("^exec:","")
658                         elseif value:match("^lua:") then
659                                 validator = self:_resolve_function( (value:gsub("^lua:","") ) )
660                         end
661
662                         if validator then
663                                 if not validators then
664                                         validators = { validator }
665                                 else
666                                         table.insert( validators, validator )
667                                 end
668                         else
669                                 return nil
670                         end
671                 end
672
673                 return validators
674         end
675 end
676
677 -- Resolve given path
678 function UVL._resolve_function( self, value )
679         local path = luci.util.split(value, ".")
680
681         for i=1, #path-1 do
682                 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
683                 if stat and mod then
684                         for j=i+1, #path-1 do
685                                 if not type(mod) == "table" then
686                                         break
687                                 end
688                                 mod = mod[path[j]]
689                                 if not mod then
690                                         break
691                                 end
692                         end
693                         mod = type(mod) == "table" and mod[path[#path]] or nil
694                         if type(mod) == "function" then
695                                 return mod
696                         end
697                 end
698         end
699 end
700
701
702 --- Object representation of a scheme/config section.
703 -- @class       module
704 -- @cstyle      instance
705 -- @name        luci.uvl.section
706
707 --- Section instance constructor.
708 -- @class                       function
709 -- @name                        section
710 -- @param scheme        Scheme instance
711 -- @param co            Configuration data
712 -- @param st            Section type
713 -- @param c                     Configuration name
714 -- @param s                     Section name
715 -- @return                      Section instance
716 section = luci.util.class()
717
718 function section.__init__(self, scheme, co, st, c, s)
719         self.csection = co[s]
720         self.ssection = scheme.packages[c].sections[st]
721         self.cref     = { c, s }
722         self.sref     = { c, st }
723         self.scheme   = scheme
724         self.config   = co
725         self.type     = luci.uvl.TYPE_SECTION
726 end
727
728 --- Get the config path of this section.
729 -- @return      String containing the identifier
730 function section.cid(self)
731         return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' )
732 end
733
734 --- Get the scheme path of this section.
735 -- @return      String containing the identifier
736 function section.sid(self)
737         return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' )
738 end
739
740 --- Get all configuration values within this section.
741 -- @return      Table containing the values
742 function section.values(self)
743         return self.csection
744 end
745
746 --- Get the associated section information in scheme.
747 -- @return      Table containing the scheme properties
748 function section.section(self)
749         return self.ssection
750 end
751
752 --- Get all option objects associated with this section.
753 -- @return      Table containing all associated luci.uvl.option instances
754 function section.variables(self)
755         local v = { }
756         if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then
757                 for o, _ in pairs(
758                         self.scheme.packages[self.sref[1]].variables[self.sref[2]]
759                 ) do
760                         table.insert( v, luci.uvl.option(
761                                 self.scheme, self.config, self.sref[2],
762                                 self.cref[1], self.cref[2], o
763                         ) )
764                 end
765         end
766         return v
767 end
768
769
770 --- Object representation of a scheme/config option.
771 -- @class       module
772 -- @cstyle      instance
773 -- @name        luci.uvl.option
774
775 --- Section instance constructor.
776 -- @class                       function
777 -- @name                        option
778 -- @param scheme        Scheme instance
779 -- @param co            Configuration data
780 -- @param st            Section type
781 -- @param c                     Configuration name
782 -- @param s                     Section name
783 -- @param o                     Option name
784 -- @return                      Option instance
785 option = luci.util.class()
786
787 function option.__init__(self, scheme, co, st, c, s, o)
788         self.coption = co[s] and co[s][o] or nil
789         self.soption = scheme.packages[c].variables[st][o]
790         self.cref    = { c, s, o }
791         self.sref    = { c, st, o }
792         self.scheme  = scheme
793         self.config  = co
794         self.type    = luci.uvl.TYPE_OPTION
795 end
796
797 --- Get the config path of this option.
798 -- @return      String containing the identifier
799 function option.cid(self)
800         return ( self.cref[1] or '?' ) .. '.' ..
801                    ( self.cref[2] or '?' ) .. '.' ..
802                    ( self.cref[3] or '?' )
803 end
804
805 --- Get the scheme path of this option.
806 -- @return      String containing the identifier
807 function option.sid(self)
808         return ( self.sref[1] or '?' ) .. '.' ..
809                    ( self.sref[2] or '?' ) .. '.' ..
810                    ( self.sref[3] or '?' )
811 end
812
813 --- Get the value of this option.
814 -- @return      The associated configuration value
815 function option.value(self)
816         return self.coption
817 end
818
819 --- Get the associated option information in scheme.
820 -- @return      Table containing the scheme properties
821 function option.option(self)
822         return self.soption
823 end
824
825 --- Get the associated section information in scheme.
826 -- @return      Table containing the scheme properties
827 function option.section(self)
828         return self.scheme.packages[self.sref[1]].sections[self.sref[2]]
829 end