Changeset 24
- Timestamp:
- 19/08/04 12:05:48 (4 years ago)
- Files:
-
- trunk/airspeed.py (modified) (8 diffs)
- trunk/airspeed_test.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/airspeed.py
r23 r24 1 1 #!/usr/bin/env python 2 2 3 import re 4 import cStringIO as StringIO 5 import operator 3 import re, operator 4 5 try: import cStringIO as StringIO 6 except ImportError: import StringIO 6 7 7 8 __all__ = ['TemplateSyntaxError', 'Template'] … … 91 92 m = pattern.match(self._full_text, self.end) 92 93 if not m: raise NoMatch() 94 self.end = m.start(pattern.groups) 95 return m.groups()[:-1] 96 97 def next_match(self, pattern): 98 m = pattern.match(self._full_text, self.end) 99 if not m: return False 93 100 self.end = m.start(pattern.groups) 94 101 return m.groups()[:-1] … … 212 219 def parse(self): 213 220 self.identity_match(self.DOT) 214 self.expression = self.next_element( Expression)221 self.expression = self.next_element(VariableExpression) 215 222 216 223 def calculate(self, current_object, global_namespace): … … 218 225 219 226 220 class Expression(_Element):227 class VariableExpression(_Element): 221 228 subexpression = None 222 229 … … 260 267 def parse(self): 261 268 self.silent, self.braces = self.identity_match(self.START) 262 self.expression = self.require_next_element( Expression, 'expression')269 self.expression = self.require_next_element(VariableExpression, 'expression') 263 270 if self.braces: self.require_match(self.CLOSING_BRACE, '}') 264 271 … … 276 283 def parse(self): 277 284 self.identity_match(self.LEADING_DOLLAR) 278 self.expression = self.require_next_element( Expression, 'name')285 self.expression = self.require_next_element(VariableExpression, 'name') 279 286 self.calculate = self.expression.calculate 280 287 … … 392 399 393 400 401 class MacroDefinition(_Element): 402 START = re.compile(r'#macro\b(.*)', re.S + re.I) 403 OPEN_PAREN = re.compile(r'[ \t]*\(\s*(.*)$', re.S) 404 NAME = re.compile(r'\s*([a-z][a-z_0-9]*)\b(.*)', re.S + re.I) 405 CLOSE_PAREN = re.compile(r'[ \t]*\)(.*)$', re.S) 406 ARG_NAME = re.compile(r'[ \t]+\$([a-z][a-z_0-9]*)(.*)$', re.S + re.I) 407 RESERVED_NAMES = ('if', 'else', 'elseif', 'set', 'macro', 'foreach', 'parse', 'include', 'stop', 'end') 408 def parse(self): 409 self.identity_match(self.START) 410 self.require_match(self.OPEN_PAREN, '(') 411 self.macro_name, = self.require_match(self.NAME, 'macro name') 412 if self.macro_name.lower() in self.RESERVED_NAMES: 413 raise self.syntax_error('non-reserved name') 414 self.arg_names = [] 415 while True: 416 m = self.next_match(self.ARG_NAME) 417 if not m: break 418 self.arg_names.append(m[0]) 419 self.require_match(self.CLOSE_PAREN, ') or arg name') 420 self.block = self.require_next_element(Block, 'block') 421 self.require_next_element(End, 'block') 422 423 def evaluate(self, namespace, stream): 424 macro_key = '#' + self.macro_name.lower() 425 if macro_key in namespace: 426 raise Exception("cannot redefine macro") 427 namespace[macro_key] = self 428 429 def execute_macro(self, namespace, stream, arg_value_elements): 430 if len(arg_value_elements) != len(self.arg_names): 431 raise Exception("expected %d arguments, got %d" % (len(self.arg_names), len(arg_value_elements))) 432 macro_namespace = LocalNamespace(namespace) 433 for arg_name, arg_value in zip(self.arg_names, arg_value_elements): 434 macro_namespace[arg_name] = arg_value.calculate(namespace) 435 self.block.evaluate(macro_namespace, stream) 436 437 438 class MacroCall(_Element): 439 START = re.compile(r'#([a-z][a-z_0-9]*)\b(.*)', re.S + re.I) 440 OPEN_PAREN = re.compile(r'[ \t]*\(\s*(.*)$', re.S) 441 CLOSE_PAREN = re.compile(r'[ \t]*\)(.*)$', re.S) 442 SPACE = re.compile(r'[ \t]+(.*)$', re.S) 443 444 def parse(self): 445 self.macro_name, = self.identity_match(self.START) 446 self.macro_name = self.macro_name.lower() 447 self.args = [] 448 if self.macro_name in MacroDefinition.RESERVED_NAMES or self.macro_name.startswith('end'): 449 raise NoMatch() 450 self.require_match(self.OPEN_PAREN, '(') 451 while True: 452 try: self.args.append(self.next_element(Value)) 453 except NoMatch: break 454 if not self.optional_match(self.SPACE): break 455 self.require_match(self.CLOSE_PAREN, 'argument value or )') 456 457 def evaluate(self, namespace, stream): 458 try: macro = namespace['#' + self.macro_name] 459 except KeyError: raise Exception('no such macro: ' + self.macro_name) 460 macro.execute_macro(namespace, stream, self.args) 461 462 394 463 class SetDirective(_Element): 395 464 START = re.compile(r'#set\b(.*)', re.S + re.I) … … 441 510 self.children = [] 442 511 while True: 443 try: 444 self.children.append(self.next_element((Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective))) 445 except NoMatch: 446 break 512 try: self.children.append(self.next_element((Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective, MacroDefinition, MacroCall))) 513 except NoMatch: break 447 514 448 515 def evaluate(self, namespace, stream): trunk/airspeed_test.py
r22 r24 295 295 self.assertEquals('yes', template.merge({'value': 1})) 296 296 self.assertEquals('', template.merge({'value': 2})) 297 298 def test_cannot_define_macro_to_override_reserved_statements(self): 299 for reserved in ('if', 'else', 'elseif', 'set', 'macro', 'foreach', 'parse', 'include', 'stop', 'end'): 300 template = airspeed.Template('#macro ( %s $value) $value #end' % reserved) 301 self.assertRaises(airspeed.TemplateSyntaxError, template.merge, {}) 302 303 def test_cannot_call_undefined_macro(self): 304 template = airspeed.Template('#undefined()') 305 self.assertRaises(Exception, template.merge, {}) 306 307 def test_define_and_use_macro_with_no_parameters(self): 308 template = airspeed.Template('#macro ( hello)hi#end#hello ()#hello()') 309 self.assertEquals('hihi', template.merge({'text': 'hello'})) 310 311 def test_define_and_use_macro_with_one_parameter(self): 312 template = airspeed.Template('#macro ( bold $value)<strong>$value</strong>#end#bold ($text)') 313 self.assertEquals('<strong>hello</strong>', template.merge({'text': 'hello'})) 314 315 def test_use_of_macro_name_is_case_insensitive(self): 316 template = airspeed.Template('#macro ( bold $value)<strong>$value</strong>#end#BoLd ($text)') 317 self.assertEquals('<strong>hello</strong>', template.merge({'text': 'hello'})) 318 319 def test_define_and_use_macro_with_two_parameter(self): 320 template = airspeed.Template('#macro (addition $value1 $value2 )$value1+$value2#end#addition (1 2)') 321 self.assertEquals('1+2', template.merge({})) 322 template = airspeed.Template('#macro (addition $value1 $value2 )$value1+$value2#end#addition( $one $two )') 323 self.assertEquals('ONE+TWO', template.merge({'one': 'ONE', 'two': 'TWO'})) 324 325 def test_cannot_redefine_macro(self): 326 template = airspeed.Template('#macro ( hello)hi#end#macro(hello)again#end') 327 self.assertRaises(Exception, template.merge, {}) ## Should this be TemplateSyntaxError? 328 297 329 # 298 330 # TODO: 299 331 # 300 # Comparative operators >=, ==, !=, <=, !301 332 # Math expressions 302 333 # Gobbling up whitespace (tricky!) … … 304 335 # list literals 305 336 # #parse, #include 306 # #macro 337 # Bind #macro calls at compile time? 338 # Interpolated strings 307 339 # Directives inside string literals 308 340 # map literals
