Added support for line-based comments.
authorArmin Ronacher <armin.ronacher@active-4.com>
Mon, 30 Mar 2009 19:00:16 +0000 (21:00 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Mon, 30 Mar 2009 19:00:16 +0000 (21:00 +0200)
--HG--
branch : trunk

CHANGES
docs/templates.rst
jinja2/defaults.py
jinja2/environment.py
jinja2/ext.py
jinja2/lexer.py
tests/test_old_bugs.py
tests/test_parser.py

diff --git a/CHANGES b/CHANGES
index 213cd725b6a34d0596263f4644d63f6936b77ccf..b218e87185c51fa23f3d787b611f6a2c7ed1cb90 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -19,6 +19,7 @@ Version 2.2
   variable and regular variable *after* the loop if that variable was unused
   *before* the loop.  (#331)
 - Added support for optional `scoped` modifier to blocks.
+- Added support for line-comments.
 
 Version 2.1.1
 -------------
index 21876388e55baf1641325a66677ed12698bc0562..7714416fb605bae14b33d17d1aa27f74597864a0 100644 (file)
@@ -252,6 +252,14 @@ precedes it.  For better readability statements that start a block (such as
         # endfor
         </ul>
 
+Since Jinja 2.2 line-based comments are available as well.  For example if
+the line-comment prefix is configured to be ``##`` everything from ``##`` to
+the end of the line is ignored (excluding the newline sign)::
+
+    # for item in seq:
+        <li>{{ item }}</li>     ## this comment is ignored
+    # endfor
+
 
 .. _template-inheritance:
 
index b1a2f6a249a906db7d10ceec15f1445407a08878..458485ea2c9b97a96d22b06b16fdae48cacc169e 100644 (file)
@@ -19,6 +19,7 @@ VARIABLE_END_STRING = '}}'
 COMMENT_START_STRING = '{#'
 COMMENT_END_STRING = '#}'
 LINE_STATEMENT_PREFIX = None
+LINE_COMMENT_PREFIX = None
 TRIM_BLOCKS = False
 NEWLINE_SEQUENCE = '\n'
 
index bfbb662095b99a85096ee5f074bad17db57bf0c6..23bf24b8987adeef09299b807fbb720bc2a8ba56 100644 (file)
@@ -118,6 +118,12 @@ class Environment(object):
             If given and a string, this will be used as prefix for line based
             statements.  See also :ref:`line-statements`.
 
+        `line_comment_prefix`
+            If given and a string, this will be used as prefix for line based
+            based comments.  See also :ref:`line-statements`.
+
+            .. versionadded:: 2.2
+
         `trim_blocks`
             If this is set to ``True`` the first newline after a block is
             removed (block, not variable tag!).  Defaults to `False`.
@@ -198,6 +204,7 @@ class Environment(object):
                  comment_start_string=COMMENT_START_STRING,
                  comment_end_string=COMMENT_END_STRING,
                  line_statement_prefix=LINE_STATEMENT_PREFIX,
+                 line_comment_prefix=LINE_COMMENT_PREFIX,
                  trim_blocks=TRIM_BLOCKS,
                  newline_sequence=NEWLINE_SEQUENCE,
                  extensions=(),
@@ -228,6 +235,7 @@ class Environment(object):
         self.comment_start_string = comment_start_string
         self.comment_end_string = comment_end_string
         self.line_statement_prefix = line_statement_prefix
+        self.line_comment_prefix = line_comment_prefix
         self.trim_blocks = trim_blocks
         self.newline_sequence = newline_sequence
 
@@ -266,10 +274,10 @@ class Environment(object):
     def overlay(self, block_start_string=missing, block_end_string=missing,
                 variable_start_string=missing, variable_end_string=missing,
                 comment_start_string=missing, comment_end_string=missing,
-                line_statement_prefix=missing, trim_blocks=missing,
-                extensions=missing, optimized=missing, undefined=missing,
-                finalize=missing, autoescape=missing, loader=missing,
-                cache_size=missing, auto_reload=missing,
+                line_statement_prefix=missing, line_comment_prefix=missing,
+                trim_blocks=missing, extensions=missing, optimized=missing,
+                undefined=missing, finalize=missing, autoescape=missing,
+                loader=missing, cache_size=missing, auto_reload=missing,
                 bytecode_cache=missing):
         """Create a new overlay environment that shares all the data with the
         current environment except of cache and the overriden attributes.
@@ -560,6 +568,7 @@ class Template(object):
                 comment_start_string=COMMENT_START_STRING,
                 comment_end_string=COMMENT_END_STRING,
                 line_statement_prefix=LINE_STATEMENT_PREFIX,
+                line_comment_prefix=LINE_COMMENT_PREFIX,
                 trim_blocks=TRIM_BLOCKS,
                 newline_sequence=NEWLINE_SEQUENCE,
                 extensions=(),
@@ -570,9 +579,9 @@ class Template(object):
         env = get_spontaneous_environment(
             block_start_string, block_end_string, variable_start_string,
             variable_end_string, comment_start_string, comment_end_string,
-            line_statement_prefix, trim_blocks, newline_sequence,
-            frozenset(extensions), optimized, undefined, finalize,
-            autoescape, None, 0, False, None)
+            line_statement_prefix, line_comment_prefix, trim_blocks,
+            newline_sequence, frozenset(extensions), optimized, undefined,
+            finalize, autoescape, None, 0, False, None)
         return env.from_string(source, template_class=cls)
 
     @classmethod
index ea3a894806e25393b92a319322bd61993a7fb3ca..bd61ebc3c442e7bb79e9926d8f787a15943da203 100644 (file)
@@ -429,6 +429,7 @@ def babel_extract(fileobj, keywords, comment_tags, options):
         options.get('comment_start_string', COMMENT_START_STRING),
         options.get('comment_end_string', COMMENT_END_STRING),
         options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
+        options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
         str(options.get('trim_blocks', TRIM_BLOCKS)).lower() in \
             ('1', 'on', 'yes', 'true'),
         NEWLINE_SEQUENCE, frozenset(extensions),
index 39aac9ff963b2803ad09d4d389555dd60356d59c..10cfd637a609962eddef13709da9a55e50aa82e8 100644 (file)
@@ -78,6 +78,9 @@ TOKEN_COMMENT_END = intern('comment_end')
 TOKEN_COMMENT = intern('comment')
 TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin')
 TOKEN_LINESTATEMENT_END = intern('linestatement_end')
+TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin')
+TOKEN_LINECOMMENT_END = intern('linecomment_end')
+TOKEN_LINECOMMENT = intern('linecomment')
 TOKEN_DATA = intern('data')
 TOKEN_INITIAL = intern('initial')
 TOKEN_EOF = intern('eof')
@@ -117,6 +120,11 @@ assert len(operators) == len(reverse_operators), 'operators dropped'
 operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
                          sorted(operators, key=lambda x: -len(x))))
 
+ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
+                            TOKEN_COMMENT_END, TOKEN_WHITESPACE,
+                            TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
+                            TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
+
 
 def count_newlines(value):
     """Count the number of newline characters in the string.  This is
@@ -125,6 +133,28 @@ def count_newlines(value):
     return len(newline_re.findall(value))
 
 
+def compile_rules(environment):
+    """Compiles all the rules from the environment into a list of rules."""
+    e = re.escape
+    rules = [
+        (len(environment.comment_start_string), 'comment',
+         e(environment.comment_start_string)),
+        (len(environment.block_start_string), 'block',
+         e(environment.block_start_string)),
+        (len(environment.variable_start_string), 'variable',
+         e(environment.variable_start_string)),
+    ]
+
+    if environment.line_statement_prefix is not None:
+        rules.append((len(environment.line_statement_prefix), 'linestatement',
+                      '^\\s*' + e(environment.line_statement_prefix)))
+    if environment.line_comment_prefix is not None:
+        rules.append((len(environment.line_comment_prefix), 'linecomment',
+                      '\\s*' + e(environment.line_comment_prefix)))
+
+    return [x[1:] for x in sorted(rules, reverse=True)]
+
+
 class Failure(object):
     """Class that raises a `TemplateSyntaxError` if called.
     Used by the `Lexer` to specify known errors.
@@ -302,6 +332,7 @@ def get_lexer(environment):
            environment.comment_start_string,
            environment.comment_end_string,
            environment.line_statement_prefix,
+           environment.line_comment_prefix,
            environment.trim_blocks,
            environment.newline_sequence)
     lexer = _lexer_cache.get(key)
@@ -340,22 +371,7 @@ class Lexer(object):
         # <%= for variables. (if someone wants asp like syntax)
         # variables are just part of the rules if variable processing
         # is required.
-        root_tag_rules = [
-            ('comment',     environment.comment_start_string),
-            ('block',       environment.block_start_string),
-            ('variable',    environment.variable_start_string)
-        ]
-        root_tag_rules.sort(key=lambda x: -len(x[1]))
-
-        # now escape the rules.  This is done here so that the escape
-        # signs don't count for the lengths of the tags.
-        root_tag_rules = [(a, e(b)) for a, b in root_tag_rules]
-
-        # if we have a line statement prefix we need an extra rule for
-        # that.  We add this rule *after* all the others.
-        if environment.line_statement_prefix is not None:
-            prefix = e(environment.line_statement_prefix)
-            root_tag_rules.insert(0, ('linestatement', '^\s*' + prefix))
+        root_tag_rules = compile_rules(environment)
 
         # block suffix if trimming is enabled
         block_suffix_re = environment.trim_blocks and '\\n?' or ''
@@ -416,7 +432,11 @@ class Lexer(object):
             # line statements
             TOKEN_LINESTATEMENT_BEGIN: [
                 (c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop')
-            ] + tag_rules
+            ] + tag_rules,
+            # line comments
+            TOKEN_LINECOMMENT_BEGIN: [
+                (c(r'.*?(?=\n|$)'), TOKEN_LINECOMMENT_END, '#pop')
+            ]
         }
 
     def _normalize_newlines(self, value):
@@ -434,8 +454,7 @@ class Lexer(object):
         every token in a :class:`Token` and converts the value.
         """
         for lineno, token, value in stream:
-            if token in ('comment_begin', 'comment', 'comment_end',
-                         'whitespace'):
+            if token in ignored_tokens:
                 continue
             elif token == 'linestatement_begin':
                 token = 'block_begin'
index f77e60cf36dcb7250cee6883eac38401f072b97f..59d6037e6c668842eccbd98bb973ef83f2aca6ce 100644 (file)
@@ -60,3 +60,9 @@ def test_weird_inline_comment():
     env = Environment(line_statement_prefix='%')
     raises(TemplateSyntaxError, env.from_string,
            '% for item in seq {# missing #}\n...% endfor')
+
+
+def test_old_macro_loop_scoping_bug(env):
+    tmpl = env.from_string('{% for i in (1, 2) %}{{ i }}{% endfor %}'
+                           '{% macro i() %}3{% endmacro %}{{ i() }}')
+    assert tmpl.render() == '123'
index fb338e1c20477c59ee0dff944997864da0fd524e..640394ebe9f5588e433454162c4d215ed152ffd9 100644 (file)
@@ -28,10 +28,17 @@ COMMENT_SYNTAX = '''\
 <!--- endfor -->'''
 
 MAKO_SYNTAX = '''\
+<%# regular comment %>
 % for item in seq:
     ${item}
 % endfor'''
 
+MAKO_SYNTAX_LINECOMMENTS = '''\
+<%# regular comment %>
+% for item in seq:
+    ${item} ## the rest of the stuff
+% endfor'''
+
 BALANCING = '''{{{'foo':'bar'}.foo}}'''
 
 STARTCOMMENT = '''{# foo comment
@@ -39,6 +46,14 @@ and bar comment #}
 {% macro blub() %}foo{% endmacro %}
 {{ blub() }}'''
 
+LINE_SYNTAX_PRIORITY = '''\
+/* ignore me.
+   I'm a multiline comment */
+# for item in seq:
+* ${item}          ## this is just extra stuff
+# endfor
+'''
+
 
 def test_php_syntax():
     env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
@@ -72,4 +87,15 @@ def test_line_syntax():
     env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
     tmpl = env.from_string(MAKO_SYNTAX)
     assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
-           range(5)
+            range(5)
+
+    env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
+    tmpl = env.from_string(MAKO_SYNTAX_LINECOMMENTS)
+    assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
+            range(5)
+
+
+def test_line_syntax_priority():
+    env = Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
+    tmpl = env.from_string(LINE_SYNTAX_PRIORITY)
+    assert tmpl.render(seq=[1, 2]).strip() == '* 1\n* 2'