[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