[Python-modules-commits] [python-kajiki] 01/01: New upstream version 0.5.5

Takaki Taniguchi takaki at moszumanska.debian.org
Tue Oct 11 04:29:43 UTC 2016


This is an automated email from the git hooks/post-receive script.

takaki pushed a commit to branch upstream
in repository python-kajiki.

commit 244143add180992a51df8fb0fa4e1f667cf9368a
Author: TANIGUCHI Takaki <takaki at asis.media-as.org>
Date:   Tue Oct 11 10:57:40 2016 +0900

    New upstream version 0.5.5
---
 CHANGES.rst              |  19 ++++++-
 Kajiki.egg-info/PKG-INFO |  21 +++++++-
 PKG-INFO                 |  21 +++++++-
 kajiki/ir.py             |   4 +-
 kajiki/template.py       |  85 +++++++++++++++++++++++++++++--
 kajiki/tests/test_xml.py |  78 ++++++++++++++++++++++++++++-
 kajiki/version.py        |   2 +-
 kajiki/xml_template.py   | 127 +++++++++++++++++++++++++++++++++++++++++++++--
 8 files changed, 340 insertions(+), 17 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 037331a..d356a3f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,23 @@
 CHANGES
 =======
 
+0.5.5 (2016-06-08)
+------------------
+
+* ``py:attrs`` will now emit the attribute name itself or will omit the attribute at all in case of
+  ``bool`` values for 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'nohref',
+  'ismap', 'declare' and 'defer',
+
+0.5.4 (2016-06-04)
+------------------
+
+* ``py:switch`` now correctly supports multiple ``py:case`` statements.
+* text inside ``<script>`` and ``<style>`` tags is no longer collected translation.
+* Syntax errors now report the line and the surrounding code when there is a markup or python syntax error.
+* As ``py:swtich`` discards all its content apart from ``py:case`` and ``py:else`` statements it will now correctly report an error when the statements has other content.
+* ``py:else`` will now correctly detect spurious content between itself and ``py:if`` as the two must be consequential.
+* Improved code documentation on core classes.
+
 0.5.3 (2016-01-25)
 ------------------
 
@@ -23,7 +40,7 @@ CHANGES
 ------------------
 
 * CDATA sections created by the user are now properly preserved
-* ``cdata_scripts=False`` option in ``XMLTemplate`` allows disabling automatic CDATA for script and style tags.
+* ``cdata_scripts=False`` option in ``XMLTemplate`` allows disabling automatic CDATA for script and style tags.
 * Autoblocks experimental feature automatically creates blocks from specified tag names.
 
 0.4.4 (2013-09-07)
diff --git a/Kajiki.egg-info/PKG-INFO b/Kajiki.egg-info/PKG-INFO
index bc5e38d..d0889df 100644
--- a/Kajiki.egg-info/PKG-INFO
+++ b/Kajiki.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Kajiki
-Version: 0.5.3
+Version: 0.5.5
 Summary: Fast XML-based template engine with Genshi syntax and Jinja blocks
 Home-page: https://github.com/nandoflorestan/kajiki
 Author: Nando Florestan
@@ -78,6 +78,23 @@ Description: Kajiki provides fast well-formed XML templates
         CHANGES
         =======
         
+        0.5.5 (2016-06-08)
+        ------------------
+        
+        * ``py:attrs`` will now emit the attribute name itself or will omit the attribute at all in case of
+          ``bool`` values for 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'nohref',
+          'ismap', 'declare' and 'defer',
+        
+        0.5.4 (2016-06-04)
+        ------------------
+        
+        * ``py:switch`` now correctly supports multiple ``py:case`` statements.
+        * text inside ``<script>`` and ``<style>`` tags is no longer collected translation.
+        * Syntax errors now report the line and the surrounding code when there is a markup or python syntax error.
+        * As ``py:swtich`` discards all its content apart from ``py:case`` and ``py:else`` statements it will now correctly report an error when the statements has other content.
+        * ``py:else`` will now correctly detect spurious content between itself and ``py:if`` as the two must be consequential.
+        * Improved code documentation on core classes.
+        
         0.5.3 (2016-01-25)
         ------------------
         
@@ -100,7 +117,7 @@ Description: Kajiki provides fast well-formed XML templates
         ------------------
         
         * CDATA sections created by the user are now properly preserved
-        * ``cdata_scripts=False`` option in ``XMLTemplate`` allows disabling automatic CDATA for script and style tags.
+        * ``cdata_scripts=False`` option in ``XMLTemplate`` allows disabling automatic CDATA for script and style tags.
         * Autoblocks experimental feature automatically creates blocks from specified tag names.
         
         0.4.4 (2013-09-07)
diff --git a/PKG-INFO b/PKG-INFO
index bc5e38d..d0889df 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Kajiki
-Version: 0.5.3
+Version: 0.5.5
 Summary: Fast XML-based template engine with Genshi syntax and Jinja blocks
 Home-page: https://github.com/nandoflorestan/kajiki
 Author: Nando Florestan
@@ -78,6 +78,23 @@ Description: Kajiki provides fast well-formed XML templates
         CHANGES
         =======
         
+        0.5.5 (2016-06-08)
+        ------------------
+        
+        * ``py:attrs`` will now emit the attribute name itself or will omit the attribute at all in case of
+          ``bool`` values for 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'nohref',
+          'ismap', 'declare' and 'defer',
+        
+        0.5.4 (2016-06-04)
+        ------------------
+        
+        * ``py:switch`` now correctly supports multiple ``py:case`` statements.
+        * text inside ``<script>`` and ``<style>`` tags is no longer collected translation.
+        * Syntax errors now report the line and the surrounding code when there is a markup or python syntax error.
+        * As ``py:swtich`` discards all its content apart from ``py:case`` and ``py:else`` statements it will now correctly report an error when the statements has other content.
+        * ``py:else`` will now correctly detect spurious content between itself and ``py:if`` as the two must be consequential.
+        * Improved code documentation on core classes.
+        
         0.5.3 (2016-01-25)
         ------------------
         
@@ -100,7 +117,7 @@ Description: Kajiki provides fast well-formed XML templates
         ------------------
         
         * CDATA sections created by the user are now properly preserved
-        * ``cdata_scripts=False`` option in ``XMLTemplate`` allows disabling automatic CDATA for script and style tags.
+        * ``cdata_scripts=False`` option in ``XMLTemplate`` allows disabling automatic CDATA for script and style tags.
         * Autoblocks experimental feature automatically creates blocks from specified tag names.
         
         0.4.4 (2013-09-07)
diff --git a/kajiki/ir.py b/kajiki/ir.py
index 7a81e80..425cc55 100644
--- a/kajiki/ir.py
+++ b/kajiki/ir.py
@@ -69,6 +69,7 @@ class DedentNode(Node):
 
 
 class TemplateNode(HierNode):
+    """Represents the root Intermediate Representation node of a template."""
     class TemplateTail(Node):
         def py(self):
             yield self.line('template = kajiki.Template(template)')
@@ -241,6 +242,7 @@ class SwitchNode(HierNode):
 
     def py(self):
         yield self.line('local.__kj__.push_switch(%s)' % self.decl)
+        yield self.line('if False: pass')
 
     def __iter__(self):
         yield self
@@ -255,7 +257,7 @@ class CaseNode(HierNode):
         self.decl = decl
 
     def py(self):
-        yield self.line('if local.__kj__.case(%s):' % self.decl)
+        yield self.line('elif local.__kj__.case(%s):' % self.decl)
 
 
 class IfNode(HierNode):
diff --git a/kajiki/template.py b/kajiki/template.py
index 84dd5ae..285ae1b 100644
--- a/kajiki/template.py
+++ b/kajiki/template.py
@@ -33,6 +33,27 @@ class _obj(object):
 
 
 class _Template(object):
+    """Base Class for all compiled Kajiki Templates.
+
+    All kajiki templates created from an ``ir.TemplateNode`` will
+    be subclasses of this class.
+
+    As the template body code runs inside ``__main__`` method of this
+    class, the instance of this class is always available as ``self``
+    inside the template code.
+
+    This class also makes available some global object inside the
+    template code itself:
+
+        - ``local`` which is the instance of the template
+        - ``defined`` which checks if the given variable is defined
+          inside the template scope.
+        - ``Markup`` which marks the passed object as markup code and
+          prevents escaping for its content.
+        - ``__kj__`` which is a special object used by generated code
+          providing features like keeping track of py:with stack or
+          or the gettext function used to translate text.
+    """
     __methods__ = ()
     loader = None
     base_globals = None
@@ -163,6 +184,8 @@ class _Template(object):
             attrs = attrs.items()
         if attrs is not None:
             for k, v in sorted(attrs):
+                if k in HTML_EMPTY_ATTRS and v in (True, False):
+                    v = k if v else None
                 if v is None:
                     continue
                 if mode.startswith('html') and k in HTML_EMPTY_ATTRS:
@@ -191,6 +214,25 @@ class _Template(object):
 
 
 def Template(ns):
+    """Creates a new ``_Template`` subclass from an entity with ``exposed`` functions.
+
+    Kajiki used classes as containers of the exposed functions for convenience,
+    but any object that can have the functions as attributes works.
+
+    To be a valid template the original entity must provide at least a ``__main__``
+    function::
+
+        class Example:
+            @kajiki.expose
+            def __main__():
+                yield 'Hi'
+
+        t = kajiki.Template(Example)
+        output = t().render()
+
+        print(output)
+        'Hi'
+    """
     dct = {}
     methods = dct['__methods__'] = []
     for name in dir(ns):
@@ -201,6 +243,12 @@ def Template(ns):
 
 
 def from_ir(ir_node):
+    """Creates a template class from Intermediate Representation TemplateNode.
+
+    This actually creates the class defined by the TemplateNode and returns
+    a subclass of it.
+    The returned class is a subclass of a `kajiki.template._Template`.
+    """
     py_lines = list(generate_python(ir_node))
     py_text = '\n'.join(map(str, py_lines))
     py_linenos = []
@@ -212,10 +260,8 @@ def from_ir(ir_node):
     dct = dict(kajiki=kajiki)
     try:
         exec(py_text, dct)
-    except (SyntaxError, IndentationError):  # pragma no cover
-        for i, line in enumerate(py_text.splitlines()):
-            print('%3d %s' % (i + 1, line))
-        raise
+    except (SyntaxError, IndentationError) as e:  # pragma no cover
+        raise KajikiSyntaxError(e.msg, py_text, e.filename, e.lineno, e.offset)
     tpl = dct['template']
     tpl.base_globals = dct
     tpl.py_text = py_text
@@ -316,3 +362,34 @@ else:
                             lnotab,
                             code.co_freevars,
                             code.co_cellvars)
+
+
+class KajikiSyntaxError(Exception):
+    def __init__(self, msg, source, filename, linen, coln):
+        super(KajikiSyntaxError, self).__init__(
+            '[%s:%s] %s\n%s' % (filename, linen, msg, self._get_source_snippet(source, linen))
+        )
+        self.filename = filename
+        self.linenum = linen
+        self.colnum = coln
+
+    def _get_source_snippet(self, source, linen):
+        SURROUNDING = 2
+        linen -= 1
+
+        parts = []
+        for i in range(SURROUNDING, 0, -1):
+            parts.append('\t     %s\n' % self._get_source_line(source, linen - i))
+        parts.append('\t --> %s\n' % self._get_source_line(source, linen))
+        for i in range(1, SURROUNDING+1):
+            parts.append('\t     %s\n' % self._get_source_line(source, linen + i))
+        return ''.join(parts)
+
+    def _get_source_line(self, source, linen):
+        if linen < 0:
+            return ''
+
+        try:
+            return source.splitlines()[linen]
+        except:
+            return ''
\ No newline at end of file
diff --git a/kajiki/tests/test_xml.py b/kajiki/tests/test_xml.py
index aa620b8..015ed2b 100755
--- a/kajiki/tests/test_xml.py
+++ b/kajiki/tests/test_xml.py
@@ -11,7 +11,8 @@ from unittest import TestCase, main
 from nine import chr, str
 import kajiki
 from kajiki import MockLoader, XMLTemplate, FileLoader, PackageLoader
-
+from kajiki.ir import TranslatableTextNode
+from kajiki.xml_template import _Compiler, _Parser, XMLTemplateCompileError
 
 DATA = os.path.join(os.path.dirname(__file__), 'data')
 
@@ -151,6 +152,20 @@ class TestSimple(TestCase):
         perform(src, '<script>/*<![CDATA[*//**/\n{0}/**//*]]>*/</script>'.format(
                 script), mode='xml')
 
+    def test_scripts_non_translatable(self):
+        src = '<xml><div>Hi</div><script>hello world</script><style>hello style</style></xml>'
+        doc = _Parser('<string>', src).parse()
+
+        for n in _Compiler('<string>', doc).compile():
+            text = getattr(n, 'text', '')
+            if text in ('hello world', 'hello style'):
+                self.assertFalse(isinstance(n, TranslatableTextNode))
+
+        for n in _Compiler('<string>', doc, cdata_scripts=False).compile():
+            text = getattr(n, 'text', '')
+            if text in ('hello world', 'hello style'):
+                self.assertFalse(isinstance(n, TranslatableTextNode))
+
     def test_escape_dollar(self):
         perform('<div>$$</div>', '<div>$</div>')
 
@@ -219,6 +234,64 @@ $i is <py:switch test="i % 2">
 0 is even</div><div>
 1 is odd</div>''')
 
+    def test_switch_multi(self):
+        perform('''<div py:for="i in range(8)">
+$i is <py:switch test="i % 4">
+<py:case value="0">ok</py:case>
+<py:case value="1">nearly</py:case>
+<py:else>nope</py:else>
+</py:switch></div>''',   '''<div>
+0 is ok</div><div>
+1 is nearly</div><div>
+2 is nope</div><div>
+3 is nope</div><div>
+4 is ok</div><div>
+5 is nearly</div><div>
+6 is nope</div><div>
+7 is nope</div>''')
+
+    def test_switch_div(self):
+        try:
+            tpl = perform('''
+        <div class="test" py:switch="5 == 3">
+            <p py:case="True">True</p>
+            <p py:else="">False</p>
+        </div>''', '<div><div>False</div></div>')
+        except XMLTemplateCompileError as e:
+            self.assertTrue(
+                'py:with directive can only contain py:case and py:else nodes' in str(e)
+            )
+        else:
+            self.assertTrue(False, msg='Should have raised XMLTemplateParseError')
+
+
+class TestElse(TestCase):
+    def test_pyif_pyelse(self):
+        try:
+            tpl = perform('''
+            <div>
+                <div py:if="False">True</div>
+                <py:else>False</py:else>
+            </div>''', '''<div>False</div>''')
+        except XMLTemplateCompileError as e:
+            self.assertTrue(
+                'py:else directive must be inside a py:switch or directly after py:if' in str(e)
+            )
+        else:
+            self.assertTrue(False, msg='Should have raised XMLTemplateParseError')
+
+    def test_pyiftag_pyelse_continuation(self):
+        tpl = perform(
+            '''<div><div py:if="False">True</div><py:else>False</py:else></div>''',
+            '''<div>False</div>'''
+        )
+
+    def test_pyif_pyelse_continuation(self):
+        tpl = perform(
+            '''<div><py:if test="False">True</py:if><py:else>False</py:else></div>''',
+            '''<div>False</div>'''
+        )
+
 
 class TestWith(TestCase):
     def test_with(self):
@@ -619,6 +692,9 @@ class TestAttributes(TestCase):
         perform('''<div py:attrs="[('a', 5), ('b', 6)]"/>''',
                 '''<div a="5" b="6"/>''')
         perform('<div py:attrs="None"/>',   '<div/>')
+        perform('<div py:attrs="dict(checked=True)"/>', '<div checked="checked"/>')
+        perform('<div py:attrs="dict(checked=False)"/>', '<div/>')
+        perform('<div py:attrs="dict(checked=None)"/>', '<div/>')
 
     def test_strip(self):
         TPL = '<div><h1 py:strip="header">Header</h1></div>'
diff --git a/kajiki/version.py b/kajiki/version.py
index 68fce99..1606760 100644
--- a/kajiki/version.py
+++ b/kajiki/version.py
@@ -3,4 +3,4 @@ from __future__ import (absolute_import, division, print_function,
                         unicode_literals)
 
 __version__ = '0.5'
-__release__ = '0.5.3'
+__release__ = '0.5.5'
diff --git a/kajiki/xml_template.py b/kajiki/xml_template.py
index fcec6c1..befb26d 100644
--- a/kajiki/xml_template.py
+++ b/kajiki/xml_template.py
@@ -6,6 +6,8 @@ import re
 from codecs import open
 from xml import sax
 from xml.dom import minidom as dom
+from xml.sax import SAXParseException
+
 from nine import IS_PYTHON2, basestring, str, iteritems, native_str
 
 if IS_PYTHON2:
@@ -27,6 +29,14 @@ impl = dom.getDOMImplementation(' ')
 
 def XMLTemplate(source=None, filename=None, mode=None, is_fragment=False,
                 encoding='utf-8', autoblocks=None, cdata_scripts=True):
+    """Given XML source code of a Kajiki Templates parses returns a Template class.
+
+    The source code is parsed to its DOM representation, which is then
+    expanded to separate directives from tags and then compiled to the
+    Intermediate Representation tree. The Intermediate Representation
+    is then processed to create the Python code which defined the template
+    class of which a new instance is returned.
+    """
     if source is None:
         with open(filename, encoding=encoding) as f:
             source = f.read()  # source is a unicode string
@@ -49,6 +59,11 @@ def annotate(gen):
 
 
 class _Compiler(object):
+    """Compiles a DOM tree into Intermediate Representation TemplateNode.
+
+    Intermediate Representation is a tree of nodes that represent
+    Python Code that should be generated to execute the template.
+    """
     def __init__(self, filename, doc, mode=None, is_fragment=False,
                  autoblocks=None, cdata_scripts=True):
         self.filename = filename
@@ -281,7 +296,12 @@ class _Compiler(object):
 
     @annotate
     def _compile_text(self, node):
-        tc = _TextCompiler(self.filename, node.data, node.lineno)
+        kwargs = {}
+        if node.parentNode and node.parentNode.tagName in HTML_CDATA_TAGS:
+            # script and style should always be untranslatable.
+            kwargs['node_type'] = ir.TextNode
+
+        tc = _TextCompiler(self.filename, node.data, node.lineno, **kwargs)
         for x in tc:
             yield x
 
@@ -302,9 +322,20 @@ class _Compiler(object):
 
     @annotate
     def _compile_switch(self, node):
-        # Filter out text nodes
-        body = [x for x in self._compile_nop(node)
-                if not isinstance(x, ir.TextNode)]
+        body = []
+
+        # Filter out empty text nodes and report unsupported nodes
+        for n in self._compile_nop(node):
+            if isinstance(n, ir.TextNode) and not n.text.strip():
+                continue
+            elif not isinstance(n, (ir.CaseNode, ir.ElseNode)):
+                raise XMLTemplateCompileError(
+                    'py:with directive can only contain py:case and py:else nodes '
+                    'and cannot be placed on a tag.',
+                    doc=self.doc, filename=self.filename, linen=node.lineno
+                )
+            body.append(n)
+
         yield ir.SwitchNode(node.getAttribute('test'), *body)
 
     @annotate
@@ -319,6 +350,15 @@ class _Compiler(object):
 
     @annotate
     def _compile_else(self, node):
+        if (getattr(node.parentNode, 'tagName', '') != 'py:nop' and
+                not node.parentNode.hasAttribute('py:switch') and
+                getattr(node.previousSibling, 'tagName', '') != 'py:if'):
+            raise XMLTemplateCompileError(
+                'py:else directive must be inside a py:switch or directly after py:if '
+                'without text or spaces in between',
+                doc=self.doc, filename=self.filename, linen=node.lineno
+            )
+
         yield ir.ElseNode(
             *list(self._compile_nop(node)))
 
@@ -332,6 +372,8 @@ class _Compiler(object):
 def make_text_node(text, guard=None):
     '''Return a TranslatableTextNode if the text is not empty,
     otherwise a regular TextNode.
+
+    This avoid spending the cost of translating empty nodes.
     '''
     if text.strip():
         return ir.TranslatableTextNode(text, guard)
@@ -413,6 +455,12 @@ class _TextCompiler(object):
 
 
 class _Parser(sax.ContentHandler):
+    """Parse an XML template into a Tree of DOM Nodes.
+
+    Nodes should then be passed to a `_Compiler` to be
+    converted into the intermediate representation and
+    then to Python Code.
+    """
     DTD = '<!DOCTYPE kajiki SYSTEM "kajiki.dtd">'
 
     def __init__(self, filename, source):
@@ -456,7 +504,16 @@ class _Parser(sax.ContentHandler):
         source.setEncoding(native_str('utf-8'))
         source.setByteStream(BytesIO(byts))
         source.setSystemId(self._filename)
-        parser.parse(source)
+
+        try:
+            parser.parse(source)
+        except SAXParseException as e:
+            exc = XMLTemplateParseError(e.getMessage(), self._source, self._filename,
+                                        e.getLineNumber(), e.getColumnNumber())
+            exc.__cause__ = None
+            raise exc
+
+        self._doc._source = self._source
         return self._doc
 
     # ContentHandler implementation
@@ -539,6 +596,24 @@ class _Parser(sax.ContentHandler):
 
 
 def expand(tree, parent=None):
+    """Expands directives attached to nodes into separate nodes.
+
+    This will convert all instances of::
+
+        <div py:if="check">
+        </div>
+
+    into::
+
+        <py:if test="check">
+            <div>
+            </div>
+        </py:if>
+
+    This ensures that whenever a template is processed there is no
+    different between the two formats as the Compiler will always
+    receive the latter.
+    """
     if isinstance(tree, dom.Document):
         expand(tree.firstChild, tree)
         return tree
@@ -574,3 +649,45 @@ def expand(tree, parent=None):
     for child in tree.childNodes:
         expand(child, tree)
     return tree
+
+
+class XMLTemplateError(Exception):
+    def __init__(self, msg, source, filename, linen, coln):
+        super(XMLTemplateError, self).__init__(
+            '[%s:%s] %s\n%s' % (filename, linen, msg, self._get_source_snippet(source, linen))
+        )
+        self.filename = filename
+        self.linenum = linen
+        self.colnum = coln
+
+    def _get_source_snippet(self, source, linen):
+        SURROUNDING = 2
+        linen -= 1
+
+        parts = []
+        for i in range(SURROUNDING, 0, -1):
+            parts.append('\t     %s\n' % self._get_source_line(source, linen - i))
+        parts.append('\t --> %s\n' % self._get_source_line(source, linen))
+        for i in range(1, SURROUNDING + 1):
+            parts.append('\t     %s\n' % self._get_source_line(source, linen + i))
+        return ''.join(parts)
+
+    def _get_source_line(self, source, linen):
+        if linen < 0:
+            return ''
+
+        try:
+            return source.splitlines()[linen]
+        except:
+            return ''
+
+
+class XMLTemplateCompileError(XMLTemplateError):
+    def __init__(self, msg, doc, filename, linen):
+        super(XMLTemplateCompileError, self).__init__(
+            msg, getattr(doc, '_source', ''), filename, linen, 0
+        )
+
+
+class XMLTemplateParseError(XMLTemplateError):
+    pass

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-kajiki.git



More information about the Python-modules-commits mailing list