changeset 134:d681d2c3cd8d trunk

* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though). * Evaluation errors in expressions now include the original expression code in the traceback.
author cmlenz
date Sun, 06 Aug 2006 18:07:21 +0000
parents 79f445396cd7
children b86f496f6035
files markup/eval.py markup/input.py markup/template.py markup/tests/eval.py markup/tests/input.py markup/tests/template.py
diffstat 6 files changed, 97 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/markup/eval.py
+++ b/markup/eval.py
@@ -72,7 +72,7 @@
         @param source: the expression as string
         """
         self.source = source
-        self.code = _compile(source, filename, lineno)
+        self.code = _compile(self, filename, lineno)
 
     def __repr__(self):
         return '<Expression "%s">' % self.source
@@ -94,8 +94,8 @@
         return retval
 
 
-def _compile(source, filename=None, lineno=-1):
-    tree = parse(source, 'eval')
+def _compile(expr, filename=None, lineno=-1):
+    tree = parse(expr.source, 'eval')
     xform = ExpressionASTTransformer()
     tree = xform.visit(tree)
 
@@ -116,7 +116,7 @@
     # clone the code object while adjusting the line number
     return new.code(0, code.co_nlocals, code.co_stacksize,
                     code.co_flags | 0x0040, code.co_code, code.co_consts,
-                    code.co_names, code.co_varnames, filename, code.co_name,
+                    code.co_names, code.co_varnames, filename, repr(expr),
                     lineno, code.co_lnotab, (), ())
 
 def _lookup_name(data, name, locals_=None):
--- a/markup/input.py
+++ b/markup/input.py
@@ -109,13 +109,27 @@
     def _enqueue(self, kind, data, pos=None):
         if pos is None:
             pos = self._getpos()
+        if kind is Stream.TEXT:
+            # Expat reports the *end* of the text event as current position. We
+            # try to fix that up here as much as possible. Unfortunately, the
+            # offset is only valid for single-line text. For multi-line text,
+            # it is apparently not possible to determine at what offset it
+            # started
+            if '\n' in data:
+                lines = data.splitlines()
+                lineno = pos[1] - len(lines) + 1
+                offset = -1
+            else:
+                lineno = pos[1]
+                offset = pos[2] - len(data)
+            pos = (pos[0], lineno, offset)
         self._queue.append((kind, data, pos))
 
     def _getpos_unknown(self):
-        return (self.filename or '<string>', -1, -1)
+        return (self.filename, -1, -1)
 
     def _getpos(self):
-        return (self.filename or '<string>', self.expat.CurrentLineNumber,
+        return (self.filename, self.expat.CurrentLineNumber,
                 self.expat.CurrentColumnNumber)
 
     def _handle_start(self, tag, attrib):
--- a/markup/template.py
+++ b/markup/template.py
@@ -812,12 +812,13 @@
         @param offset: the column number at which the text starts in the source
             (optional)
         """
-        def _interpolate(text, patterns):
+        def _interpolate(text, patterns, filename=filename, lineno=lineno,
+                         offset=offset):
             for idx, group in enumerate(patterns.pop(0).split(text)):
                 if idx % 2:
                     try:
                         yield EXPR, Expression(group, filename, lineno), \
-                              (lineno, offset)
+                              (filename, lineno, offset)
                     except SyntaxError, err:
                         raise TemplateSyntaxError(err, filename, lineno,
                                                   offset + (err.offset or 0))
@@ -826,8 +827,14 @@
                         for result in _interpolate(group, patterns[:]):
                             yield result
                     else:
-                        yield TEXT, group.replace('$$', '$'), (filename, lineno,
-                                                               offset)
+                        yield TEXT, group.replace('$$', '$'), \
+                              (filename, lineno, offset)
+                if '\n' in group:
+                    lines = group.splitlines()
+                    lineno += len(lines) - 1
+                    offset += len(lines[-1])
+                else:
+                    offset += len(group)
         return _interpolate(text, [cls._FULL_EXPR_RE, cls._SHORT_EXPR_RE])
     _interpolate = classmethod(_interpolate)
 
--- a/markup/tests/eval.py
+++ b/markup/tests/eval.py
@@ -235,6 +235,8 @@
             frame = exc_traceback.tb_next
             while frame.tb_next:
                 frame = frame.tb_next
+            self.assertEqual('<Expression "nothing()">',
+                             frame.tb_frame.f_code.co_name)
             self.assertEqual('index.html', frame.tb_frame.f_code.co_filename)
             self.assertEqual(50, frame.tb_lineno)
 
--- a/markup/tests/input.py
+++ b/markup/tests/input.py
@@ -12,15 +12,63 @@
 # history and logs, available at http://markup.edgewall.org/log/.
 
 import doctest
+from StringIO import StringIO
+import sys
 import unittest
 
 from markup.core import Stream
-from markup.input import XMLParser
+from markup.input import XMLParser, HTMLParser
+
+
+class XMLParserTestCase(unittest.TestCase):
+
+    def test_text_node_pos_single_line(self):
+        text = '<elem>foo bar</elem>'
+        events = list(XMLParser(StringIO(text)))
+        kind, data, pos = events[1]
+        self.assertEqual(Stream.TEXT, kind)
+        self.assertEqual(u'foo bar', data)
+        if sys.version_info[:2] >= (2, 4):
+            self.assertEqual((None, 1, 6), pos)
+
+    def test_text_node_pos_multi_line(self):
+        text = '''<elem>foo
+bar</elem>'''
+        events = list(XMLParser(StringIO(text)))
+        kind, data, pos = events[1]
+        self.assertEqual(Stream.TEXT, kind)
+        self.assertEqual(u'foo\nbar', data)
+        if sys.version_info[:2] >= (2, 4):
+            self.assertEqual((None, 1, -1), pos)
+
+
+class HTMLParserTestCase(unittest.TestCase):
+
+    def test_text_node_pos_single_line(self):
+        text = '<elem>foo bar</elem>'
+        events = list(HTMLParser(StringIO(text)))
+        kind, data, pos = events[1]
+        self.assertEqual(Stream.TEXT, kind)
+        self.assertEqual(u'foo bar', data)
+        if sys.version_info[:2] >= (2, 4):
+            self.assertEqual((None, 1, 6), pos)
+
+    def test_text_node_pos_multi_line(self):
+        text = '''<elem>foo
+bar</elem>'''
+        events = list(HTMLParser(StringIO(text)))
+        kind, data, pos = events[1]
+        self.assertEqual(Stream.TEXT, kind)
+        self.assertEqual(u'foo\nbar', data)
+        if sys.version_info[:2] >= (2, 4):
+            self.assertEqual((None, 1, 6), pos)
 
 
 def suite():
     suite = unittest.TestSuite()
-    suite.addTest(doctest.DocTestSuite(XMLParser.__module__))
+    #suite.addTest(doctest.DocTestSuite(XMLParser.__module__))
+    suite.addTest(unittest.makeSuite(XMLParserTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(HTMLParserTestCase, 'test'))
     return suite
 
 if __name__ == '__main__':
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -518,7 +518,6 @@
             self.assertEqual('test.html', e.filename)
             if sys.version_info[:2] >= (2, 4):
                 self.assertEqual(1, e.lineno)
-                # We don't really care about the offset here, do we?
 
     def test_expression_syntax_error(self):
         xml = """<p>
@@ -532,6 +531,20 @@
             if sys.version_info[:2] >= (2, 4):
                 self.assertEqual(2, e.lineno)
 
+    def test_expression_syntax_error_multi_line(self):
+        xml = """<p><em></em>
+
+ ${bar"}
+
+        </p>"""
+        try:
+            tmpl = Template(xml, filename='test.html')
+            self.fail('Expected SyntaxError')
+        except TemplateSyntaxError, e:
+            self.assertEqual('test.html', e.filename)
+            if sys.version_info[:2] >= (2, 4):
+                self.assertEqual(3, e.lineno)
+
     def test_markup_noescape(self):
         """
         Verify that outputting context data that is a `Markup` instance is not
Copyright (C) 2012-2017 Edgewall Software