Changeset 20
- Timestamp:
- 18/08/04 14:22:13 (4 years ago)
- Files:
-
- trunk/airspeed.py (modified) (20 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/airspeed.py
r19 r20 21 21 22 22 def merge_to(self, namespace, fileobj): 23 output = []24 23 if not self.evaluator: 25 24 self.evaluator = TemplateBody(self.content) … … 50 49 except KeyError: return self.parent[key] 51 50 51 def __repr__(self): 52 return dict.__repr__(self) + '->' + repr(self.parent) 53 52 54 53 55 class _Element: 54 def match_or_reject(self, pattern, text):56 def identity_match(self, pattern, text): 55 57 m = pattern.match(text) 56 58 if not m: raise NoMatch() … … 62 64 return m.groups() 63 65 64 def next_element(self, element_class, text): 65 element = element_class(text) 66 return element, element.remaining_text 67 68 def require_next_element(self, element_class, text, expected): 69 try: element = element_class(text) 70 except NoMatch: raise TemplateSyntaxError(self, expected, text) 71 else: return element, element.remaining_text 66 def next_element(self, element_spec, text): 67 if callable(element_spec): 68 element = element_spec(text) 69 return element, element.remaining_text 70 else: 71 for element_class in element_spec: 72 try: element = element_class(text) 73 except NoMatch: pass 74 else: return element, element.remaining_text 75 raise NoMatch() 76 77 def require_next_element(self, element_spec, text, expected): 78 if callable(element_spec): 79 try: element = element_spec(text) 80 except NoMatch: raise TemplateSyntaxError(self, expected, text) 81 else: return element, element.remaining_text 82 else: 83 for element_class in element_spec: 84 try: element = element_class(text) 85 except NoMatch: pass 86 else: return element, element.remaining_text 87 expected = ', '.join([cls.__name__ for cls in element_spec]) 88 raise TemplateSyntaxError(self, 'one of: ' + expected, text) 72 89 73 90 … … 76 93 ESCAPED_CHAR = re.compile(r'\\([\\\$#])') 77 94 def __init__(self, text): 78 text, self.remaining_text = self. match_or_reject(self.MY_PATTERN, text)95 text, self.remaining_text = self.identity_match(self.MY_PATTERN, text) 79 96 def unescape(match): 80 97 return match.group(1) … … 85 102 86 103 87 88 104 class IntegerLiteral(_Element): 89 105 MY_PATTERN = re.compile(r'^(\d+)(.*)', re.S) 90 106 def __init__(self, text): 91 self.value, self.remaining_text = self. match_or_reject(self.MY_PATTERN, text)107 self.value, self.remaining_text = self.identity_match(self.MY_PATTERN, text) 92 108 self.value = int(self.value) 93 109 … … 100 116 ESCAPED_CHAR = re.compile(r'\\([nrbt"\\])') 101 117 def __init__(self, text): 102 value, self.remaining_text = self. match_or_reject(self.MY_PATTERN, text)118 value, self.remaining_text = self.identity_match(self.MY_PATTERN, text) 103 119 def unescape(match): 104 120 return {'n': '\n', 'r': '\r', 'b': '\b', 't': '\t', '"': '"', '\\': '\\'}[match.group(1)] … … 111 127 class Value(_Element): 112 128 def __init__(self, text): 113 if text.startswith('$'): 114 self.expression = Expression(text[1:]) 115 else: 116 try: 117 self.expression = IntegerLiteral(text) 118 except NoMatch: 119 self.expression = StringLiteral(text) 120 self.remaining_text = self.expression.remaining_text 129 self.expression, self.remaining_text = self.next_element((PlainReference, IntegerLiteral, StringLiteral), text) 121 130 122 131 def calculate(self, namespace): … … 124 133 125 134 135 class NameOrCall(_Element): 136 NAME_PATTERN = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)(.*)$', re.S) 137 parameters = None 138 def __init__(self, text): 139 self.name, text = self.identity_match(self.NAME_PATTERN, text) 140 try: self.parameters, text = self.next_element(ParameterList, text) 141 except NoMatch: pass 142 self.remaining_text = text 143 144 def calculate(self, namespace, top_namespace): 145 try: result = getattr(namespace, self.name) 146 except AttributeError: 147 try: result = namespace[self.name] 148 except KeyError: result = None 149 if result is None: 150 return None ## TODO: an explicit 'not found' exception? 151 if self.parameters is not None: 152 values = [value.calculate(top_namespace) for value in self.parameters.values] 153 result = result(*values) 154 return result 155 156 126 157 class Expression(_Element): 127 NAME_PATTERN = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)(.*)$', re.S)128 158 def __init__(self, text): 129 159 self.names_and_calls = [] 130 try: text = self.read_name_or_call(text)131 except NoMatch: raise TemplateSyntaxError(self, 'name or call', text)160 part, text = self.require_next_element(NameOrCall, text, 'name') 161 self.names_and_calls.append(part) 132 162 while text.startswith('.'): 133 163 try: 134 text = self.read_name_or_call(text[1:])135 except NoMatch: # for the '$name. blah' case136 break164 part, text = self.next_element(NameOrCall, text[1:]) 165 self.names_and_calls.append(part) 166 except NoMatch: break # for the '$name. blah' case 137 167 self.remaining_text = text 138 168 139 def read_name_or_call(self, text): 140 name, text = self.match_or_reject(self.NAME_PATTERN, text) 141 parameter_list = None 142 try: 143 parameter_list, text = self.next_element(ParameterList, text) 144 except NoMatch: 145 pass 146 self.names_and_calls.append((name, parameter_list)) 147 return text 148 149 def calculate(self, namespace): 150 result = namespace 151 for name, parameters in self.names_and_calls: 152 try: result = getattr(result, name) 153 except AttributeError: 154 try: result = result[name] 155 except KeyError: pass 156 if result in (None, namespace): return None ## TODO: an explicit 'not found' exception? 157 if parameters is not None: 158 values = [value.calculate(namespace) for value in parameters.values] 159 result = result(*values) 160 return result 169 def calculate(self, namespace): 170 value = namespace 171 for part in self.names_and_calls: 172 value = part.calculate(value, namespace) 173 if value is None: return None 174 return value 161 175 162 176 … … 168 182 def __init__(self, text): 169 183 self.values = [] 170 text, = self. match_or_reject(self.OPENING_PATTERN, text)184 text, = self.identity_match(self.OPENING_PATTERN, text) 171 185 try: value, text = self.next_element(Value, text) 172 186 except NoMatch: … … 186 200 CLOSING_BRACE_PATTERN = re.compile(r'^\}(.*)$', re.S) 187 201 def __init__(self, text): 188 self.silent, self.braces, text = self. match_or_reject(self.MY_PATTERN, text)202 self.silent, self.braces, text = self.identity_match(self.MY_PATTERN, text) 189 203 self.expression, text = self.require_next_element(Expression, text, 'expression') 190 204 if self.braces: … … 197 211 if self.silent: value = '' 198 212 else: 199 value_as_str = '.'.join([name for name, paramsin self.expression.names_and_calls])213 value_as_str = '.'.join([name.name for name in self.expression.names_and_calls]) 200 214 if self.braces: value = '${%s}' % value_as_str 201 215 else: value = '$%s' % value_as_str … … 203 217 204 218 219 class PlainReference(_Element): 220 def __init__(self, text): 221 if not text.startswith('$'): raise NoMatch() 222 self.expression, self.remaining_text = self.require_next_element(Expression, text[1:], 'name') 223 self.calculate = self.expression.calculate 224 225 205 226 class Null: 206 227 def evaluate(self, namespace, stream): pass … … 210 231 COMMENT_PATTERN = re.compile('^#(?:#.*?(?:\n|$)|\*.*?\*#(?:[ \t]*\n)?)(.*)$', re.M + re.S) 211 232 def __init__(self, text): 212 self.remaining_text, = self. match_or_reject(self.COMMENT_PATTERN, text)233 self.remaining_text, = self.identity_match(self.COMMENT_PATTERN, text) 213 234 214 235 … … 220 241 self.expression, text = self.next_element(Value, text) 221 242 self.remaining_text, = self.require_match(self.CLOSING_PATTERN, text, ')') 222 223 def calculate(self, namespace): 224 return self.expression.calculate(namespace) 243 self.calculate = self.expression.calculate 225 244 226 245 … … 228 247 END = re.compile(r'^#end(.*)', re.I + re.S) 229 248 def __init__(self, text): 230 self.remaining_text, = self.match_or_reject(self.END, text) 249 self.remaining_text, = self.identity_match(self.END, text) 250 251 252 class ElseBlock(_Element): 253 START = re.compile(r'^#else(.*)$', re.S + re.I) 254 def __init__(self, text): 255 text, = self.identity_match(self.START, text) 256 self.block, self.remaining_text = self.require_next_element(Block, text, 'block') 257 self.evaluate = self.block.evaluate 258 259 260 class ElseifBlock(_Element): 261 START = re.compile(r'^#elseif\b\s*(.*)$', re.S + re.I) 262 def __init__(self, text): 263 text, = self.identity_match(self.START, text) 264 self.condition, text = self.require_next_element(Condition, text, 'condition') 265 self.block, self.remaining_text = self.require_next_element(Block, text, 'block') 266 self.calculate = self.condition.calculate 267 self.evaluate = self.block.evaluate 231 268 232 269 … … 234 271 START = re.compile(r'^#if\b\s*(.*)$', re.S + re.I) 235 272 START_ELSEIF = re.compile(r'^#elseif\b\s*(.*)$', re.S + re.I) 236 START_ELSE = re.compile(r'^#else(.*)$', re.S + re.I)237 273 else_block = Null() 238 274 239 275 def __init__(self, text): 240 text, = self. match_or_reject(self.START, text)276 text, = self.identity_match(self.START, text) 241 277 self.condition, text = self.next_element(Condition, text) 242 278 self.block, text = self.next_element(Block, text) 243 self.elseif _conditions = []279 self.elseifs = [] 244 280 while True: 245 m = self.START_ELSEIF.match(text) 246 if not m: break 247 text = m.group(1) 248 elseif_condition, text = self.require_next_element(Condition, text, 'condition') 249 elseif_block, text = self.require_next_element(Block, text, 'block') 250 self.elseif_conditions.append((elseif_condition, elseif_block)) 251 m = self.START_ELSE.match(text) 252 if m: 253 self.else_block, text = self.require_next_element(Block, m.group(1), 'block') 281 try: 282 elseif_block, text = self.next_element(ElseifBlock, text) 283 self.elseifs.append(elseif_block) 284 except NoMatch: 285 break 286 try: self.else_block, text = self.next_element(ElseBlock, text) 287 except NoMatch: pass 254 288 end, self.remaining_text = self.require_next_element(End, text, '#else, #elseif or #end') 255 289 … … 258 292 self.block.evaluate(namespace, stream) 259 293 else: 260 for elseif , block in self.elseif_conditions:294 for elseif in self.elseifs: 261 295 if elseif.calculate(namespace): 262 block.evaluate(namespace, stream)296 elseif.evaluate(namespace, stream) 263 297 return 264 298 self.else_block.evaluate(namespace, stream) 265 299 266 300 267 class SetDirective(_Element):268 START = re.compile(r'^ #set\s*\(\s*\$([a-z_][a-z0-9_]*)\s*=\s*(.*)$', re.S + re.I)301 class Assignment(_Element): 302 START = re.compile(r'^\s*\(\s*\$([a-z_][a-z0-9_]*)\s*=\s*(.*)$', re.S) 269 303 CLOSING_PATTERN = re.compile(r'^\s*\)(?:[ \t]*\r?\n)?(.*)$', re.S + re.M) 270 304 def __init__(self, text): 271 ## Could be cleaner b/c syntax error if no '(' 272 self.var_name, text = self.match_or_reject(self.START, text) 305 self.var_name, text = self.identity_match(self.START, text) 273 306 self.value, text = self.next_element(Value, text) 274 307 self.remaining_text, = self.require_match(self.CLOSING_PATTERN, text, ')') 275 308 276 def evaluate(self, namespace, stream):309 def calculate(self, namespace): 277 310 namespace[self.var_name] = self.value.calculate(namespace) 311 312 313 class SetDirective(_Element): 314 START = re.compile(r'^#set\b(.*)', re.S + re.I) 315 def __init__(self, text): 316 text, = self.identity_match(self.START, text) 317 self.assignment, self.remaining_text = self.require_next_element(Assignment, text, 'assignment') 318 319 def evaluate(self, namespace, stream): 320 self.assignment.calculate(namespace) 278 321 279 322 … … 283 326 def __init__(self, text): 284 327 ## Could be cleaner b/c syntax error if no '(' 285 self.loop_var_name, text = self. match_or_reject(self.START, text)328 self.loop_var_name, text = self.identity_match(self.START, text) 286 329 self.value, text = self.next_element(Value, text) 287 330 text, = self.require_match(self.CLOSING_PATTERN, text, ')') … … 315 358 self.children = [] 316 359 while text: 317 child = None 318 for child_type in (Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective): 319 try: 320 child, text = self.next_element(child_type, text) 321 self.children.append(child) 322 break 323 except NoMatch: 324 continue 325 if child is None: 360 try: 361 child, text = self.next_element((Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective), text) 362 self.children.append(child) 363 except NoMatch: 326 364 break 327 365 self.remaining_text = text … … 330 368 for child in self.children: 331 369 child.evaluate(namespace, stream) 332 333
