-import types, itertools, re
-import logging as log
-
"""
-Lazy conditional string evaluation module for configuration variables.
+conf.py
+-------
+Lazy conditional string evaluation module for Moe configuration variables.
-* Each variable has ordered list of operations (definitions), each SETs or APPENDs an expression
-to the value. Each operation may be guarded by condition.
-NOTE: Variable is undefined even if some 'APPEND' apply but no 'SET' applies. This might change.
+* Each variable has ordered list of operations (definitions), each defining operation either
+assigns (SET) or appends (APPEND) value of an expression to the variable. Each operation may be guarded by condition(s).
-* Each condition is a formula (tree consisting of 'AND', 'OR', 'NOT' and '==', '!=' between
-two expressions.
+NOTE: If no 'SET' applies, a variable is still undefined even if some 'APPEND' applies. This might change.
+
+* Each condition is a formula (tree consisting of 'AND', 'OR', 'NOT' and '==', '!=' between two expressions.
* Expression is a list of strings and variables to be expanded.
NOTE: All expanded data should be (or is converted to) unicode
-TODO: Fixing variables.
+
+TODO: Fixing value of variables.
TODO: Cleanup of unused undefined variables.
TODO: Better variable name checking (no name '.'-structural prefix of another)
TODO: Implemet "subtree" listing.
TODO: Test conditions and unicode
"""
-"""
-The configuration syntax is the following (TODO: add whitespaces WSP)
-TODO: decide '()' around formulas
-TODO: check escaping in expressions
-TODO: should whitespace (incl. '\\n') be allowed (almost) everywhere?
- can comment be anywhere whitespace can?
-
-FILE = BLOCK
-BLOCK + '\\n' = () | STATEMENT ( STATEMENT-SEP STATEMENT )*
-
-STATEMENT-SEP = ( '\\n' | ';' )
-WSP = ( ' ' | '\\t' | '\\n' | COMMENT )*
-
-COMMENT = re'#[^\\n]*\\n'
-
-STATEMENT = CONDITION | OPERATION | SUBTREE
-
-OPERATION = VARNAME ( '=' | '+=' ) EXPRESSION
-SUBTREE = VARNAME '{' BLOCK '}'
-CONDITION = 'if' FORMULA '{' BLOCK '}'
-
-FORMULA = ( EXPRESSION ( '!=' | '==' ) EXPRESSION ) | '(' AND ')' | '(' OR ')' | NOT
-AND = FORMULA 'and' FORMULA
-OR = FORMULA 'or' FORMULA
-NOT = 'not' FORMULA
-
-EXPRESSION = '"' ( ECHAR | '{' VARNAME '}' )* '"' | re"'[^'\\n]*'"
-ECHAR = re'([^\\{}]|\\\\|\\{|\\}|\\n)*'
-"""
-
-
-
-
-c_tree_sep = u'.'
-c_comment = u'#'
-c_open = u'{'
-c_close = u'}'
-c_if = u'if'
-
-"Variable name regexp, dots (separators) must be separated from edges and each other."
-re_key = re.compile(r'\A([A-Za-z0-9_-]+\.)*[A-Za-z0-9_-]+\Z')
+import types, itertools, re
+import logging as log
+from confparser import VARNAME_re
-"Allowed depth of recursion -- includes ALL recursive calls, so should quite high."
+"Allowed depth of recursion - includes ALL recursive calls, so should quite high."
c_maxdepth = 256
-"Maximum attained depth of recursion"
+"Maximum attained depth of recursion - for debug/testing"
debug_maxdepth = 0
def check_depth(depth):
if not key in self.variables:
if not create:
raise ConfigError('Config variable %r undefined.', key)
- if not re_key.match(key):
+ if not VARNAME_re.match(key):
raise ConfigError('Invalid variable identifier %r in config', key)
self.variables[key] = ConfigVar(key)
return self.variables[key]
('AND', c1, c1), ('OR', c1, c2), ('NOT', c1),
('==', e1, e2), ('!=', e1, e2) where e1, e2 are `ConfigExpression`s.
"""
- def __init__(self, text, clause, parent=None):
+ def __init__(self, formula, text=None, parent=None):
"""
- Condition defined by `text` (informative), `clause` as in class definition,
+ Condition defined by `text` (informative), `formula` as in class definition,
`parent` is the parent condition (if any).
"""
+ if not text:
+ text = self.formula_string(formula)
super(ConfigVar, self).__init__(text)
- self.clause = clause
+ self.formula = formula
self.parent = parent
# Setup dependencies on used variables (not on the parent condition)
for v in self.variables():
if self.parent:
self.parent.dependants.add(self)
def variables(self, cl=None):
- "Return an iterator of variables used in clause `cl`"
+ "Return an iterator of variables used in formula `cl`"
if not cl:
- cl = self.clause
+ cl = self.formula
if cl[0] in ['==','!=']:
return itertools.chain(cl[1].variables(), cl[2].variables())
if cl[0] in ['AND','OR']:
if self.parent:
self.parent.dependants.discard(self)
def evaluate(self, cl=None, depth=0):
- """Evaluate clause `cl` (or the entire condition).
+ """Evaluate formula `cl` (or the entire condition).
Partial evaluation for AND and OR. Tests the parent condition first."""
check_depth(depth)
if not cl:
- cl = self.clause
+ cl = self.formula
if self.parent and not self.parent.value():
return False
if cl[0] in ['==','!=']:
if cl[0] == 'OR' and v1: return True
if cl[0] == 'AND' and not v1: return False
return self.evaluate(cl[2], depth+1)
+ def formula_string(self, formula):
+ "Create a string representation of a formula."
+ if formula[0] == 'AND':
+ return itertools.chain(['('], self.formula_string(formula[1]), [' and '], self.formula_string(formula[2]),[')'])
+ elif formula[0] == 'OR':
+ return itertools.chain(['('], self.formula_string(formula[1]), [' or '], self.formula_string(formula[2]),[')'])
+ elif formula[0] == 'NOT':
+ return itertools.chain(['(not '], self.formula_string(formula[1]),[')'])
+ elif formula[0] in ['==', '!=']:
+ return itertools.chain(formula[1], formula[0], formula[2])
+ return iter(['<invalid formula>'])
def str(self, parents=False):
"Retur the defining expression, if `parents` set, then prefixed with parent conditions."
if parents and self.parent: