1 import types, itertools
14 class ConfigError(Exception):
17 # WARNING: Currently, only appending to a variable results in undefined
19 class ConfigTree(object):
21 Configuration subtree (namespace). Contains subtrees and variables.
23 def __init__(self, name, tree):
25 Create configuration subtree `name` under `tree`.
26 Use `name=None` and `tree=None` for the root tree.
32 # Elements of this tree (variables and subtrees)
34 # Full name with separators (None for root tree)
35 if self.tree and self.tree.fullname:
36 self.fullname = self.tree.fullname + c_tree_sep + self.name
38 self.fullname = self.name
40 self.tree.add(self.name, self)
41 def add(self, name, element):
42 "Add an element (subtree or a variable)."
43 if self.elements.has_key(name):
44 raise ConfigError('Element %s already present in tree %s'%(name, self.fullname))
45 self.elements[name] = element
46 def remove(self, name):
47 "Remove an element (subtree or a variable)."
48 self.elements.pop(name)
49 def pprint(self, indent=0):
51 Pretty printing of the tree.
52 Returns list of lines (strings).
55 s = [u'%*sSubtree %s'%(indent, u'', self.name)]
57 s = [u'%*sRoot tree'%(indent, u'')]
58 for e in sorted(self.elements.values()):
59 s.extend(e.pprint(indent + c_pprint_indent))
62 class ConfigElem(object):
64 Base class for cahed config elements - variables and conditions
66 def __init__(self, name, tree):
67 # Name in the subtree, for conditions the defining text
71 # Full name with separators (only informative for conditions)
72 if self.tree.fullname:
73 self.fullname = self.tree.fullname + c_tree_sep + self.name
75 self.fullname = self.name
76 # Vars and conditions depending on value of this one
77 self.dependants = set([])
78 # Cached value (may be None in case of evaluation error)
80 self.cached_val = None
81 def depth_check(self, depth):
83 Helper to check for recursion depth.
85 if depth > c_maxdepth:
86 raise ConfigError('Too deep recursion in config evaluation (cyclic substitution?)')
87 def invalidate(self, depth=0):
89 Invalidate cached data and invalidate all dependants.
90 Does nothing if not cached.
92 self.depth_check(depth)
94 log.debug('invalidating %r', self.fullname)
96 for d in self.dependants:
97 d.invalidate(depth + 1)
98 def evaluate(self, depth=0):
100 Cached interface for get_value(), returns a value or throws an exception.
102 self.depth_check(depth)
104 self.cached_val = self.get_value(depth+1)
106 if self.cached_val == None:
107 raise ConfigError("Unable to evaluate %r."%(self.fullname,))
108 return self.cached_val
109 def get_value(self, depth=0):
110 raise ConfigError('get_value() not implemented')
112 class ConfigEqCondition(ConfigElem):
113 "Simple (in)equality condition"
114 def __init__(self, name, tree, parent_cond, ex1, ex2, equality=True):
115 super(ConfigVar, self).__init__(name, tree)
116 # Tuple of two compared expressions
117 self.expressions = (ex1, ex2)
118 # If true, test for equality, otherwise inequality
119 self.equality = equality
120 # Parent condition in the configuration tree (or None)
121 self.parent_cond = parent_cond
122 # Setup dependencies on used variables (not on the parent condition)
123 for e in self.expressions:
124 for v in e.variables():
125 v.dependants.add(self)
126 def remove_dependencies(self):
127 "Remove self as dependant from all dependencies"
128 for e in self.expressions:
129 for v in e.variables():
130 v.dependants.remove(self)
131 def get_value(self, depth=0):
132 "Evaluate the condition (not cached)"
133 v = (ex1.evaluate(depth+1) == ex2.evaluate(depth+1))
140 class ConfigVar(ConfigElem):
141 def __init__(self, name, tree):
142 super(ConfigVar, self).__init__(name, tree)
143 # Ordered list of operations
144 # (operation, [condition], expression )
145 # operation is currently restricted to 'SET' and 'APPEND'
146 self.expressions = []
147 # Fixed to value (may bi None)
149 self.fixed_val = None
150 # Register in a subtree
152 self.tree.add(name, self)
154 "Return a set of variables used in the expressions"
155 return set(sum([ list(e[2].variables()) for e in self.expressions ], []))
156 def conditions(self):
157 "Return a set of conditions used in the expressions"
158 return set(sum([ e[1] for e in self.expressions ], []))
159 def add_operation(self, operation, conditions, expression, index=None):
161 Adds a new operation to the given index (None for appending).
162 Adds the variable as a dependant to the conditions and variables in the expressions.
164 # First invalidate cached value
167 expr = (operation, conditions, expression)
169 self.expressions.insert(index, expr)
171 self.expressions.append(expr)
172 # Create dependencies
173 for v in expression.variables():
174 v.dependants.add(self)
176 c.dependants.add(self)
177 def remove_operation(self, index):
179 Remove the operation at given index.
180 Also removes the variable as dependant from all conditions and variables used in this
181 operation that are no longer used. NOTE: this may be slow.
183 # First invalidate cached value
185 # Remove the operation
186 operation, conditions, expression = self.expressions[index]
187 self.expressions.pop(index)
188 # Remove obsolete dependencies on variables
189 vs = self.variables()
190 for v in expression.variables():
192 v.dependants.remove(self)
193 # Remove obsolete dependencies on variables
194 cs = self.conditions()
197 c.dependants.remove(self)
198 def get_value(self, depth=0):
199 log.debug('evaluating var %r', self.fullname)
200 # List of strings to be concatenated
202 # Scan for last applicable expression - try each starting from the end, concatenate extensions
203 for i in range(len(self.expressions)-1, -1, -1):
204 operation, conditions, expr = self.expressions[i]
205 # Check all conditions guarding an expression
206 if all([c.evaluate(depth+1) for c in conditions]):
207 val.insert(0, expr.evaluate(depth+1))
208 if operation == 'SET':
211 def pprint(self, indent=0):
213 Pretty printing of the variable. Includes all operations.
214 Returns iterator of lines (unicode strings).
216 yield u'%*s%s = \'%s\'' % (indent, u'', self.name, self.evaluate(0))
217 for operation, conditions, expr in self.expressions:
218 yield u'%*s%s %s %s' % (indent + c_pprint_indent, '', operation, conditions, expr)
220 class ConfigExpression(object):
222 String expression with some unexpanded config variables. Used in variable operations and conditions.
223 Expression is given as a list of unicode strings and ConfigVar variables to be expanded.
225 def __init__(self, exprlist, original = ''):
226 self.exprlist = exprlist
227 # Original defining string
228 self.original = original
229 # Replace strings with
230 for i in range(len(self.exprlist)):
232 if isinstance(e, types.StringTypes):
233 if not isinstance(e, unicode):
234 self.exprlist[i] = unicode(e, 'ascii')
236 "Return an iterator of variables user in the expression"
237 return itertools.ifilter(lambda e: isinstance(e, ConfigVar), self.exprlist)
240 def evaluate(self, depth):
241 "Return a unicode result of expansion of the variables."
243 for e in self.exprlist:
244 if isinstance(e, ConfigVar):
245 s.append(e.evaluate(depth+1))
246 elif isinstance(e, unicode):
249 raise ConfigError('Unsupported type %s in expression \'%s\'.'%(type(e), self))