Optimized uvl.dependencies
[project/luci.git] / libs / uvl / luasrc / uvl / dependencies.lua
index b8d7286..d19149e 100644 (file)
@@ -1,6 +1,6 @@
 --[[
 
-UCI Validation Layer - Main Library
+UCI Validation Layer - Dependency helper
 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
 (c) 2008 Steven Barth <steven@midlink.org>
 
@@ -14,15 +14,16 @@ $Id$
 
 ]]--
 
-module( "luci.uvl.dependencies", package.seeall )
+local uvl = require "luci.uvl"
+local ERR = require "luci.uvl.errors"
+local util = require "luci.util"
+local table = require "table"
+
+local type, unpack = type, unpack
+local ipairs, pairs = ipairs, pairs
+
+module "luci.uvl.dependencies"
 
-local function _assert( condition, fmt, ... )
-       if not condition then
-               return assert( nil, string.format( fmt, ... ) )
-       else
-               return condition
-       end
-end
 
 
 function _parse_reference( r, c, s, o )
@@ -33,98 +34,163 @@ function _parse_reference( r, c, s, o )
                option  = o
        }
 
-       for i, v in ipairs(luci.util.split(r,".")) do
-               table.insert( ref, (v:gsub( "%$(.+)", function(n) return vars[n] end )) )
+       for v in r:gmatch("[^.]+") do
+               ref[#ref+1] = (v:gsub( "%$(.+)", vars ))
        end
-
-       if #ref == 1 and c and s then
-               ref = { c, s, ref[1] }
-       elseif #ref == 2 and c then
-               ref = { c, unpack(ref) }
-       elseif #ref ~= 3 then
-               print("INVALID REFERENCE: "..#ref, c, s, o)
-               ref = nil
+       
+       if #ref < 2 then
+               table.insert(ref, 1, s or '$section')
+       end
+       if #ref < 3 then
+               table.insert(ref, 1, c or '$config')
        end
 
        return ref
 end
 
-function check_dependency( self, uci, conf, sect, optn, nodeps, section2 )
+function _serialize_dependency( dep, v )
+       local str
+
+       for k, v in util.spairs( dep,
+               function(a,b)
+                       a = ( type(dep[a]) ~= "boolean" and "_" or "" ) .. a
+                       b = ( type(dep[b]) ~= "boolean" and "_" or "" ) .. b
+                       return a < b
+               end
+       ) do
+               str = ( str and str .. " and " or "" ) .. k ..
+                       ( type(v) ~= "boolean" and "=" .. v or "" )
+       end
+
+       return str
+end
+
+function check( self, object, nodeps )
 
---     print( "Depency check:    ", conf .. '.' .. sect .. ( optn and '.' .. optn or '' ) )
+       local derr = ERR.DEPENDENCY(object)
 
-       local key = conf .. '.' .. sect .. ( optn and '.' .. optn or '' )
-       if not self.beenthere[key] then
-               self.beenthere[key] = true
+       if not self.depseen[object:cid()] then
+               self.depseen[object:cid()] = true
        else
-               print("CIRCULAR DEPENDENCY!")
-               return false, "Recursive depency detected"
+               return false, derr:child(ERR.DEP_RECURSIVE(object))
        end
 
-       -- check for config
-       if not self.packages[conf] then self:read_scheme(conf) end
-       local item = self.packages[conf]
-
-       -- check for section
-       if sect then
-               item = _assert( self:_scheme_section( uci, conf, sect ) or self:_scheme_section( uci, conf, section2 ),
-                       "Unknown section '%s' in scheme '%s' requested",
-                       sect or '<nil>', conf or '<nil>' )
-
-               -- check for option
-               if optn then
-                       item = _assert( self:_scheme_option( uci, conf, sect, optn ) or
-                                                       self:_scheme_option( uci, conf, section2, optn ),
-                               "Unknown variable '%s' in scheme '%s', section '%s' requested",
-                               optn or '<nil>', conf or '<nil>', sect or '<nil>' )
+       if object:scheme('depends') then
+               local ok    = true
+               local valid = false
+
+               for _, dep in ipairs(object:scheme('depends')) do
+                       local subcondition = true
+                       for k, v in pairs(dep) do
+                               -- XXX: better error
+                               local ref = _parse_reference( k, unpack(object.cref) )
+
+                               if not ref then
+                                       return false, derr:child(ERR.SME_BADDEP(object,k))
+                               end
+
+                               local option = uvl.option( self, object.c, unpack(ref) )
+
+                               valid, err = self:_validate_option( option, true )
+                               if valid then
+                                       if not (
+                                               ( type(v) == "boolean" and option:value() ) or
+                                               ( ref[3] and option:value() ) == v
+                                       ) then
+                                               subcondition = false
+
+                                               local depstr = _serialize_dependency( dep, v )
+                                               derr:child(
+                                                       type(v) == "boolean"
+                                                               and ERR.DEP_NOVALUE(option, depstr)
+                                                               or  ERR.DEP_NOTEQUAL(option, {depstr, v})
+                                               )
+
+                                               break
+                                       end
+                               else
+                                       subcondition = false
+
+                                       local depstr = _serialize_dependency( dep, v )
+                                       derr:child(ERR.DEP_NOTVALID(option, depstr):child(err))
+
+                                       break
+                               end
+                       end
+
+                       if subcondition then
+                               ok = true
+                               break
+                       else
+                               ok = false
+                       end
                end
-       end
 
-       if item.depends then
-               local ok = false
-               local valid, err
+               if not ok then
+                       return false, derr
+               end
+       else
+               return true
+       end
 
-               for _, dep in ipairs(item.depends) do
-                       --print("DEP:",luci.util.serialize_data(dep))
+       if object:scheme("type") == "enum" and
+          object:scheme("enum_depends")[object:value()]
+       then
+               local ok    = true
+               local valid = false
+               local enum  = object:enum()
+               local eerr  = ERR.DEP_BADENUM(enum)
 
+               for _, dep in ipairs(enum:scheme('enum_depends')[object:value()]) do
                        local subcondition = true
-
                        for k, v in pairs(dep) do
                                -- XXX: better error
-                               local ref = _assert( _parse_reference(k,conf,sect,optn),
-                                       "Ambiguous dependency reference '" .. k .. "' given" )
+                               local ref = _parse_reference( k, unpack(object.cref) )
+
+                               if not ref then
+                                       return false, derr:child(eerr:child(ERR.SME_BADDEP(enum,k)))
+                               end
+
+                               local option = luci.uvl.option( self, object.c, unpack(ref) )
 
-                               -- XXX: true -> nodeps
-                               valid, err = self:validate_option(ref[1], ref[2], ref[3], uci, true, section2)
+                               valid, err = self:_validate_option( option, true )
                                if valid then
-                                       --print("CHK:",uci[ref[2]][ref[3]],v,unpack(ref))
                                        if not (
-                                               ( type(v) == "boolean" and uci[ref[2]][ref[3]] ) or
-                                               ( ref[3] and uci[ref[2]][ref[3]] ) == v
+                                               ( type(v) == "boolean" and object.config[ref[2]][ref[3]] ) or
+                                               ( ref[3] and object:config() ) == v
                                        ) then
                                                subcondition = false
-                                               err = type(v) ~= "boolean"
-                                                       and "Option '" .. table.concat( ref, "." ) .. "' doesn't match requested type '" .. v .. '"'
-                                                       or  "Option '" .. table.concat( ref, "." ) .. "' has no value"
+
+                                               local depstr = _serialize_dependency( dep, v )
+                                               eerr:child(
+                                                       type(v) == "boolean"
+                                                               and ERR.DEP_NOVALUE(option, depstr)
+                                                               or  ERR.DEP_NOTEQUAL(option, {depstr, v})
+                                               )
 
                                                break
                                        end
                                else
                                        subcondition = false
+
+                                       local depstr = _serialize_dependency( dep, v )
+                                       eerr:child(ERR.DEP_NOTVALID(option, depstr):child(err))
+
                                        break
                                end
                        end
 
                        if subcondition then
---                             print( " -> Success (condition matched)\n" )
                                return true
+                       else
+                               ok = false
                        end
                end
 
---             print( " -> Failed\n" )
-               return false, err
+               if not ok then
+                       return false, derr:child(eerr)
+               end
        end
 
---     print( " -> Success (no depends)\n" )
        return true
 end