OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 """ezt.py -- EaZy Templating | 2 """ezt.py -- EaZy Templating |
3 | 3 |
4 For documentation, please see: http://code.google.com/p/ezt/wiki/Syntax | 4 For documentation, please see: http://code.google.com/p/ezt/wiki/Syntax |
5 """ | 5 """ |
6 # | 6 # |
7 # Copyright (C) 2001-2011 Greg Stein. All Rights Reserved. | 7 # Copyright (C) 2001-2011 Greg Stein. All Rights Reserved. |
8 # | 8 # |
9 # Redistribution and use in source and binary forms, with or without | 9 # Redistribution and use in source and binary forms, with or without |
10 # modification, are permitted provided that the following conditions are | 10 # modification, are permitted provided that the following conditions are |
(...skipping 21 matching lines...) Expand all Loading... |
32 # | 32 # |
33 # This software is maintained by Greg and is available at: | 33 # This software is maintained by Greg and is available at: |
34 # http://code.google.com/p/ezt/ | 34 # http://code.google.com/p/ezt/ |
35 # | 35 # |
36 | 36 |
37 __author__ = 'Greg Stein' | 37 __author__ = 'Greg Stein' |
38 __version__ = '1.0' | 38 __version__ = '1.0' |
39 __license__ = 'BSD' | 39 __license__ = 'BSD' |
40 | 40 |
41 import re | 41 import re |
42 from types import StringType, IntType, FloatType, LongType | 42 from types import IntType, FloatType, LongType |
43 import os | 43 import os |
44 import urllib | 44 import urllib |
45 try: | 45 import StringIO |
46 import cStringIO | |
47 except ImportError: | |
48 import StringIO | |
49 cStringIO = StringIO | |
50 | 46 |
51 # | 47 # |
52 # Formatting types | 48 # Formatting types |
53 # | 49 # |
54 FORMAT_RAW = 'raw' | 50 FORMAT_RAW = 'raw' |
55 FORMAT_HTML = 'html' | 51 FORMAT_HTML = 'html' |
56 FORMAT_XML = 'xml' | 52 FORMAT_XML = 'xml' |
57 FORMAT_JS = 'js' | 53 FORMAT_JS = 'js' |
58 FORMAT_URL = 'url' | 54 FORMAT_URL = 'url' |
59 | 55 |
60 # | 56 # |
61 # This regular expression matches three alternatives: | 57 # This regular expression matches four alternatives: |
62 # expr: NEWLINE | DIRECTIVE | BRACKET | COMMENT | 58 # expr: NEWLINE | DIRECTIVE | BRACKET | COMMENT |
63 # DIRECTIVE: '[' ITEM (whitespace ITEM)* '] | 59 # DIRECTIVE: '[' ITEM (whitespace ARG)* '] |
64 # ITEM: STRING | NAME | 60 # ITEM: STRING | NAME |
| 61 # ARG: STRING | NAME | NUMBER |
65 # STRING: '"' (not-slash-or-dquote | '\' anychar)* '"' | 62 # STRING: '"' (not-slash-or-dquote | '\' anychar)* '"' |
66 # NAME: (alphanum | '_' | '-' | '.')+ | 63 # NAME: (alpha | '_') (alphanum | '_' | '-' | '.')* |
| 64 # NUMBER: digit+ |
67 # BRACKET: '[[]' | 65 # BRACKET: '[[]' |
68 # COMMENT: '[#' not-rbracket* ']' | 66 # COMMENT: '[#' not-rbracket* ']' |
69 # | 67 # |
| 68 # Note: the above BNR is a bit loose around ITEM/ARG/NAME/NUMBER. The |
| 69 # important point is that the first value in a directive must |
| 70 # start with '_' or an alpha character (no digits). This greatly |
| 71 # helps to avoid simple errors like '[0]' in templates. |
| 72 # |
70 # When used with the split() method, the return value will be composed of | 73 # When used with the split() method, the return value will be composed of |
71 # non-matching text and the three paren groups (NEWLINE, DIRECTIVE and | 74 # non-matching text and the three paren groups (NEWLINE, DIRECTIVE and |
72 # BRACKET). Since the COMMENT matches are not placed into a group, they are | 75 # BRACKET). Since the COMMENT matches are not placed into a group, they are |
73 # considered a "splitting" value and simply dropped. | 76 # considered a "splitting" value and simply dropped. |
74 # | 77 # |
75 _item = r'(?:"(?:[^\\"]|\\.)*"|[-\w.]+)' | 78 _item = r'(?:"(?:[^\\"]|\\.)*"|[A-Za-z_][-\w.]*)' |
| 79 _arg = r'(?:"(?:[^\\"]|\\.)*"|[-\w.]+)' |
76 _re_parse = re.compile(r'(\r?\n)|\[(%s(?: +%s)*)\]|(\[\[\])|\[#[^\]]*\]' % | 80 _re_parse = re.compile(r'(\r?\n)|\[(%s(?: +%s)*)\]|(\[\[\])|\[#[^\]]*\]' % |
77 (_item, _item)) | 81 (_item, _arg)) |
78 | 82 |
79 _re_args = re.compile(r'"(?:[^\\"]|\\.)*"|[-\w.]+') | 83 _re_args = re.compile(r'"(?:[^\\"]|\\.)*"|[-\w.]+') |
80 | 84 |
81 # block commands and their argument counts | 85 # block commands and their argument counts |
82 _block_cmd_specs = { 'if-index':2, 'for':1, 'is':2, 'define':1, 'format':1 } | 86 _block_cmd_specs = { 'if-index':2, 'for':1, 'is':2, 'define':1, 'format':1 } |
83 _block_cmds = _block_cmd_specs.keys() | 87 _block_cmds = _block_cmd_specs.keys() |
84 | 88 |
85 # two regular expressions for compressing whitespace. the first is used to | 89 # two regular expressions for compressing whitespace. the first is used to |
86 # compress any whitespace including a newline into a single newline. the | 90 # compress any whitespace including a newline into a single newline. the |
87 # second regex is used to compress runs of whitespace into a single space. | 91 # second regex is used to compress runs of whitespace into a single space. |
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
283 raise UnclosedBlocksError('Block opened at line %s' % stack[-1][4], | 287 raise UnclosedBlocksError('Block opened at line %s' % stack[-1][4], |
284 filename=filename) | 288 filename=filename) |
285 return program | 289 return program |
286 | 290 |
287 def _execute(self, program, fp, ctx): | 291 def _execute(self, program, fp, ctx): |
288 """This private helper function takes a 'program' sequence as created | 292 """This private helper function takes a 'program' sequence as created |
289 by the method '_parse' and executes it step by step. strings are written | 293 by the method '_parse' and executes it step by step. strings are written |
290 to the file object 'fp' and functions are called. | 294 to the file object 'fp' and functions are called. |
291 """ | 295 """ |
292 for step in program: | 296 for step in program: |
293 if isinstance(step, StringType): | 297 if isinstance(step, basestring): |
294 fp.write(step) | 298 fp.write(step) |
295 else: | 299 else: |
296 method, method_args, filename, line_number = step | 300 method, method_args, filename, line_number = step |
297 method(method_args, fp, ctx, filename, line_number) | 301 method(method_args, fp, ctx, filename, line_number) |
298 | 302 |
299 def _cmd_print(self, (transforms, valref), fp, ctx, filename, line_number): | 303 def _cmd_print(self, (transforms, valref), fp, ctx, filename, line_number): |
300 value = _get_value(valref, ctx, filename, line_number) | 304 value = _get_value(valref, ctx, filename, line_number) |
301 # if the value has a 'read' attribute, then it is a stream: copy it | 305 # if the value has a 'read' attribute, then it is a stream: copy it |
302 if hasattr(value, 'read'): | 306 if hasattr(value, 'read'): |
303 while 1: | 307 while 1: |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
381 section = t_section | 385 section = t_section |
382 else: | 386 else: |
383 section = f_section | 387 section = f_section |
384 if section is not None: | 388 if section is not None: |
385 self._execute(section, fp, ctx) | 389 self._execute(section, fp, ctx) |
386 | 390 |
387 def _cmd_for(self, args, fp, ctx, filename, line_number): | 391 def _cmd_for(self, args, fp, ctx, filename, line_number): |
388 ((valref,), unused, section) = args | 392 ((valref,), unused, section) = args |
389 list = _get_value(valref, ctx, filename, line_number) | 393 list = _get_value(valref, ctx, filename, line_number) |
390 refname = valref[0] | 394 refname = valref[0] |
391 if isinstance(list, StringType): | 395 if isinstance(list, basestring): |
392 raise NeedSequenceError(refname, filename, line_number) | 396 raise NeedSequenceError(refname, filename, line_number) |
393 ctx.for_index[refname] = idx = [ list, 0 ] | 397 ctx.for_index[refname] = idx = [ list, 0 ] |
394 for item in list: | 398 for item in list: |
395 self._execute(section, fp, ctx) | 399 self._execute(section, fp, ctx) |
396 idx[1] = idx[1] + 1 | 400 idx[1] = idx[1] + 1 |
397 del ctx.for_index[refname] | 401 del ctx.for_index[refname] |
398 | 402 |
399 def _cmd_define(self, args, fp, ctx, filename, line_number): | 403 def _cmd_define(self, args, fp, ctx, filename, line_number): |
400 ((name,), unused, section) = args | 404 ((name,), unused, section) = args |
401 valfp = cStringIO.StringIO() | 405 valfp = StringIO.StringIO() |
402 if section is not None: | 406 if section is not None: |
403 self._execute(section, valfp, ctx) | 407 self._execute(section, valfp, ctx) |
404 ctx.defines[name] = valfp.getvalue() | 408 ctx.defines[name] = valfp.getvalue() |
405 | 409 |
406 def boolean(value): | 410 def boolean(value): |
407 "Return a value suitable for [if-any bool_var] usage in a template." | 411 "Return a value suitable for [if-any bool_var] usage in a template." |
408 if value: | 412 if value: |
409 return 'yes' | 413 return 'yes' |
410 return None | 414 return None |
411 | 415 |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
477 elif ctx.defines.has_key(start): | 481 elif ctx.defines.has_key(start): |
478 ob = ctx.defines[start] | 482 ob = ctx.defines[start] |
479 elif hasattr(ctx.data, start): | 483 elif hasattr(ctx.data, start): |
480 ob = getattr(ctx.data, start) | 484 ob = getattr(ctx.data, start) |
481 else: | 485 else: |
482 raise UnknownReference(refname, filename, line_number) | 486 raise UnknownReference(refname, filename, line_number) |
483 | 487 |
484 # walk the rest of the dotted reference | 488 # walk the rest of the dotted reference |
485 for attr in rest: | 489 for attr in rest: |
486 try: | 490 try: |
487 ob = getattr(ob, attr) | 491 if isinstance(ob, dict): |
488 except AttributeError: | 492 ob = ob[attr] |
| 493 else: |
| 494 ob = getattr(ob, attr) |
| 495 except AttributeError, KeyError: |
489 raise UnknownReference(refname, filename, line_number) | 496 raise UnknownReference(refname, filename, line_number) |
490 | 497 |
491 # make sure we return a string instead of some various Python types | 498 # make sure we return a string instead of some various Python types |
492 if isinstance(ob, (IntType, FloatType, LongType)): | 499 if isinstance(ob, (IntType, FloatType, LongType)): |
493 return str(ob) | 500 return str(ob) |
494 if ob is None: | 501 if ob is None: |
495 return '' | 502 return '' |
496 | 503 |
497 # string or a sequence | 504 # string or a sequence |
498 return ob | 505 return ob |
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
644 | 651 |
645 def _test(argv): | 652 def _test(argv): |
646 import doctest, ezt | 653 import doctest, ezt |
647 verbose = "-v" in argv | 654 verbose = "-v" in argv |
648 return doctest.testmod(ezt, verbose=verbose) | 655 return doctest.testmod(ezt, verbose=verbose) |
649 | 656 |
650 if __name__ == "__main__": | 657 if __name__ == "__main__": |
651 # invoke unit test for this module: | 658 # invoke unit test for this module: |
652 import sys | 659 import sys |
653 sys.exit(_test(sys.argv)[0]) | 660 sys.exit(_test(sys.argv)[0]) |
OLD | NEW |