[Python-modules-commits] [python-kajiki] 02/06: Imported Upstream version 0.5.2
Takaki Taniguchi
takaki at moszumanska.debian.org
Wed Nov 25 13:59:09 UTC 2015
This is an automated email from the git hooks/post-receive script.
takaki pushed a commit to branch master
in repository python-kajiki.
commit bafb48759269b58e9c3ba682ccb9e4fc4b418621
Author: TANIGUCHI Takaki <takaki at asis.media-as.org>
Date: Wed Nov 25 22:49:28 2015 +0900
Imported Upstream version 0.5.2
---
CHANGES.rst | 19 +++++++
Kajiki.egg-info/PKG-INFO | 23 ++++++++-
Kajiki.egg-info/SOURCES.txt | 1 +
Kajiki.egg-info/pbr.json | 1 +
PKG-INFO | 23 ++++++++-
README.rst | 2 +
kajiki/i18n.py | 7 ++-
kajiki/ir.py | 30 +++++------
kajiki/loader.py | 8 +--
kajiki/template.py | 2 +-
kajiki/tests/test_xml.py | 118 ++++++++++++++++++++++++++++++++++++++++++--
kajiki/version.py | 4 +-
kajiki/xml_template.py | 75 +++++++++++++++++++++++-----
13 files changed, 272 insertions(+), 41 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 93fec72..6a0ea50 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,25 @@
CHANGES
=======
+0.5.2 (2015-10-13)
+------------------
+
+* TranslatableTextNodes are now only generated for non-empty strings
+* ``py:with`` statement now accepts multiple variables separated by semicolon
+* Babel message extractor fixed on Python2
+
+0.5.1 (2015-07-26)
+------------------
+
+* Fix crash on PyPy
+
+0.5.0 (2015-07-25)
+------------------
+
+* 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.
+* 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 1c8efcf..71bcba3 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.4.4
+Version: 0.5.2
Summary: Fast XML-based template engine with Genshi syntax and Jinja blocks
Home-page: https://github.com/nandoflorestan/kajiki
Author: Nando Florestan
@@ -27,6 +27,8 @@ Description: Kajiki provides fast well-formed XML templates
Example
=======
+ .. code:: python
+
>>> import kajiki
>>> Template = kajiki.XMLTemplate('''<html>
... <head><title>$title</title></head>
@@ -76,6 +78,25 @@ Description: Kajiki provides fast well-formed XML templates
CHANGES
=======
+ 0.5.2 (2015-10-13)
+ ------------------
+
+ * TranslatableTextNodes are now only generated for non-empty strings
+ * ``py:with`` statement now accepts multiple variables separated by semicolon
+ * Babel message extractor fixed on Python2
+
+ 0.5.1 (2015-07-26)
+ ------------------
+
+ * Fix crash on PyPy
+
+ 0.5.0 (2015-07-25)
+ ------------------
+
+ * 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.
+ * Autoblocks experimental feature automatically creates blocks from specified tag names.
+
0.4.4 (2013-09-07)
------------------
diff --git a/Kajiki.egg-info/SOURCES.txt b/Kajiki.egg-info/SOURCES.txt
index 839d31e..06cd081 100644
--- a/Kajiki.egg-info/SOURCES.txt
+++ b/Kajiki.egg-info/SOURCES.txt
@@ -8,6 +8,7 @@ Kajiki.egg-info/SOURCES.txt
Kajiki.egg-info/dependency_links.txt
Kajiki.egg-info/entry_points.txt
Kajiki.egg-info/not-zip-safe
+Kajiki.egg-info/pbr.json
Kajiki.egg-info/requires.txt
Kajiki.egg-info/top_level.txt
docs/Makefile
diff --git a/Kajiki.egg-info/pbr.json b/Kajiki.egg-info/pbr.json
new file mode 100644
index 0000000..fbb37c4
--- /dev/null
+++ b/Kajiki.egg-info/pbr.json
@@ -0,0 +1 @@
+{"is_release": true, "git_version": "592a738"}
\ No newline at end of file
diff --git a/PKG-INFO b/PKG-INFO
index 1c8efcf..71bcba3 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: Kajiki
-Version: 0.4.4
+Version: 0.5.2
Summary: Fast XML-based template engine with Genshi syntax and Jinja blocks
Home-page: https://github.com/nandoflorestan/kajiki
Author: Nando Florestan
@@ -27,6 +27,8 @@ Description: Kajiki provides fast well-formed XML templates
Example
=======
+ .. code:: python
+
>>> import kajiki
>>> Template = kajiki.XMLTemplate('''<html>
... <head><title>$title</title></head>
@@ -76,6 +78,25 @@ Description: Kajiki provides fast well-formed XML templates
CHANGES
=======
+ 0.5.2 (2015-10-13)
+ ------------------
+
+ * TranslatableTextNodes are now only generated for non-empty strings
+ * ``py:with`` statement now accepts multiple variables separated by semicolon
+ * Babel message extractor fixed on Python2
+
+ 0.5.1 (2015-07-26)
+ ------------------
+
+ * Fix crash on PyPy
+
+ 0.5.0 (2015-07-25)
+ ------------------
+
+ * 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.
+ * Autoblocks experimental feature automatically creates blocks from specified tag names.
+
0.4.4 (2013-09-07)
------------------
diff --git a/README.rst b/README.rst
index b96da43..cbb76b4 100644
--- a/README.rst
+++ b/README.rst
@@ -19,6 +19,8 @@ And more features are coming soon; stay tuned!
Example
=======
+.. code:: python
+
>>> import kajiki
>>> Template = kajiki.XMLTemplate('''<html>
... <head><title>$title</title></head>
diff --git a/kajiki/i18n.py b/kajiki/i18n.py
index 0702730..e7d54cc 100644
--- a/kajiki/i18n.py
+++ b/kajiki/i18n.py
@@ -13,7 +13,10 @@ def extract(fileobj, keywords, comment_tags, options):
'''Babel entry point that extracts translation strings from XML templates.
'''
from .xml_template import _Parser, _Compiler, expand
- doc = _Parser(filename='<string>', source=fileobj.read()).parse()
+ source = fileobj.read()
+ if isinstance(source, bytes):
+ source = source.decode('utf-8')
+ doc = _Parser(filename='<string>', source=source).parse()
expand(doc)
compiler = _Compiler(filename='<string>', doc=doc,
mode=options.get('mode', 'xml'),
@@ -23,4 +26,4 @@ def extract(fileobj, keywords, comment_tags, options):
if isinstance(node, TranslatableTextNode):
if node.text.strip():
for line in node.text.split('\n'):
- yield (node.lineno, '_', line, [])
+ yield (node.lineno, '_', line, [])
diff --git a/kajiki/ir.py b/kajiki/ir.py
index a2785dd..dc0ba42 100644
--- a/kajiki/ir.py
+++ b/kajiki/ir.py
@@ -202,13 +202,13 @@ class WithNode(HierNode):
def __init__(self, vars, *body):
super(WithNode, self).__init__(body)
- self.vars_text = vars
- self.vars = dict(var.split('=', 1)
- for var in vars.split(';'))
+ self.vars = dict(var.split('=', 1) for var in vars.split(';'))
+ self.vars_args = ','.join('%s=%s' % v for v in self.vars.items())
def py(self):
yield self.line(
- 'local.__kj__.push_with(locals(), %s)' % self.vars_text)
+ 'local.__kj__.push_with(locals(), %s)' % self.vars_args
+ )
for k, v in iteritems(self.vars):
yield self.line('%s = %s' % (k, v))
@@ -257,7 +257,7 @@ class IfNode(HierNode):
class ElseNode(HierNode):
- def __init__(self, *body):
+ def __init__(self, *body):
super(ElseNode, self).__init__(body)
def py(self):
@@ -410,18 +410,14 @@ class PythonNode(Node):
def optimize(iter_node):
last_node = None
for node in iter_node:
- if type(node) == TextNode:
- if (type(last_node) == TextNode
- and last_node.guard == node.guard):
- last_node.text += node.text
- else:
- if last_node is not None:
- yield last_node
- last_node = node
- else:
- if last_node is not None:
- yield last_node
- last_node = node
+ if (type(node) == TextNode and type(last_node) == TextNode
+ and last_node.guard == node.guard):
+ last_node.text += node.text
+ # Erase this node by not yielding it.
+ continue
+ if last_node is not None:
+ yield last_node
+ last_node = node
if last_node is not None:
yield last_node
diff --git a/kajiki/loader.py b/kajiki/loader.py
index fdee048..935f73c 100644
--- a/kajiki/loader.py
+++ b/kajiki/loader.py
@@ -3,7 +3,6 @@
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from nine import basestring, itervalues
-import codecs
import os
import pkg_resources
@@ -42,7 +41,7 @@ class MockLoader(Loader):
class FileLoader(Loader):
def __init__(self, path, reload=True, force_mode=None,
- autoescape_text=False):
+ autoescape_text=False, xml_autoblocks=None):
super(FileLoader, self).__init__()
from kajiki import XMLTemplate, TextTemplate
if isinstance(path, basestring):
@@ -53,6 +52,7 @@ class FileLoader(Loader):
self._reload = reload
self._force_mode = force_mode
self._autoescape_text = autoescape_text
+ self._xml_autoblocks = xml_autoblocks
self.extension_map = dict(
txt=lambda *a, **kw: TextTemplate(
autoescape=self._autoescape_text, *a, **kw),
@@ -88,7 +88,9 @@ class FileLoader(Loader):
autoescape=self._autoescape_text, *args, **kwargs)
elif self._force_mode:
return XMLTemplate(filename=filename,
- mode=self._force_mode, *args, **kwargs)
+ mode=self._force_mode,
+ autoblocks=self._xml_autoblocks,
+ *args, **kwargs)
else:
ext = os.path.splitext(filename)[1][1:]
return self.extension_map[ext](
diff --git a/kajiki/template.py b/kajiki/template.py
index 33d39ab..d3d6ca1 100644
--- a/kajiki/template.py
+++ b/kajiki/template.py
@@ -153,7 +153,7 @@ class _Template(object):
# Preserve our tests and Kajiki behaviour across Python versions:
return uval.replace('&', '&').replace('<', '<') \
.replace('>', '>').replace('"', '"')
- # .replace("'", '''))
+ # .replace("'", '''))
# Above we do NOT escape the single quote; we don't need it because
# all HTML attributes are double-quoted in our output.
else:
diff --git a/kajiki/tests/test_xml.py b/kajiki/tests/test_xml.py
index 9c703bb..bc286a7 100755
--- a/kajiki/tests/test_xml.py
+++ b/kajiki/tests/test_xml.py
@@ -66,12 +66,13 @@ class TestExpand(TestCase):
def perform(source, expected_output, context=dict(name='Rick'),
- mode='xml', is_fragment=True):
- tpl = XMLTemplate(source, mode=mode, is_fragment=is_fragment)
+ mode='xml', is_fragment=True, cdata_scripts=True):
+ tpl = XMLTemplate(source, mode=mode, is_fragment=is_fragment,
+ cdata_scripts=cdata_scripts)
try:
rsp = tpl(context).render()
assert isinstance(rsp, str), 'render() must return a unicode string.'
- assert rsp == expected_output, rsp
+ assert rsp == expected_output, (rsp, expected_output)
except:
print('\n' + tpl.py_text)
raise
@@ -127,6 +128,29 @@ class TestSimple(TestCase):
perform(src, '<script>/*<![CDATA[*/ Rick /*]]>*/</script>', mode='xml')
perform(src, '<script> Rick </script>', mode='html')
+ def test_CDATA_disabled(self):
+ src = '<script> $name </script>'
+ perform(src, '<script> Rick </script>', mode='xml', cdata_scripts=False)
+ perform(src, '<script> Rick </script>', mode='html', cdata_scripts=False)
+
+ def test_CDATA_escaping(self):
+ src = '''<myxml><data><![CDATA[>ð $name]]></data></myxml>'''
+ perform(src, '<myxml><data><![CDATA[>ð Rick]]></data></myxml>', mode='xml')
+ perform(src, '<myxml><data><![CDATA[>ð Rick]]></data></myxml>', mode='html')
+
+ def test_CDATA_escaping_mixed(self):
+ src = '''<myxml><data><![CDATA[>ð $name]]> ></data></myxml>'''
+ perform(src, '<myxml><data><![CDATA[>ð Rick]]> ></data></myxml>', mode='xml')
+ perform(src, '<myxml><data><![CDATA[>ð Rick]]> ></data></myxml>', mode='html')
+
+ def test_script_commented_CDATA(self):
+ script = 'if (1 < 2) { doc.write("<p>Offen bach</p>"); }\n'
+ src = '<script>/*<![CDATA[*/\n{0}/*]]>*/</script>'.format(script)
+ perform(src, mode='html',
+ expected_output='<script>/**/\n{0}/**/</script>'.format(script))
+ perform(src, '<script>/*<![CDATA[*//**/\n{0}/**//*]]>*/</script>'.format(
+ script), mode='xml')
+
def test_escape_dollar(self):
perform('<div>$$</div>', '<div>$</div>')
@@ -208,6 +232,18 @@ class TestWith(TestCase):
<div>foo</div>
</div>''')
+ def test_with_multiple(self):
+ perform('''<div py:with="a='foo';b=3">
+<div>$a - $b</div>
+<div py:with="a=5;b=1">$a - $b</div>
+<div>$a - $b</div>
+</div>''', '''<div>
+<div>foo - 3</div>
+<div>5 - 1</div>
+<div>foo - 3</div>
+</div>''')
+
+
class TestFunction(TestCase):
def test_function(self):
@@ -430,6 +466,82 @@ Thanks for the gift!</p>
Sincerely,<br/><em>Rick</em>
</div>''', rsp
+ def test_autoblocks(self):
+ loader = MockLoader({
+ 'parent.html': XMLTemplate('''
+<html py:strip="">
+<head></head>
+<body>
+ <p py:block="body">It was good seeing you last Friday.
+ Thanks for the gift!</p>
+</body>
+</html>'''),
+ 'child.html': XMLTemplate('''
+<html>
+<py:extends href="parent.html"/>
+<body><em>Great conference this weekend!</em></body>
+</html>''', autoblocks=['body'])})
+
+ parent = loader.import_('parent.html')
+ rsp = parent().render()
+ assert rsp == '''
+<head/>
+<body>
+ <p>It was good seeing you last Friday.
+ Thanks for the gift!</p>
+</body>
+''', rsp
+
+ child = loader.import_('child.html')
+ rsp = child().render()
+ assert rsp == '''<html>
+
+<head/>
+<body>
+ <em>Great conference this weekend!</em>
+</body>
+
+
+</html>''', rsp
+
+ def test_autoblocks_disabling(self):
+ loader = MockLoader({
+ 'parent.html': XMLTemplate('''
+<html py:strip="">
+<head></head>
+<body py:autoblock="False">
+ <p py:block="body">It was good seeing you last Friday.
+ Thanks for the gift!</p>
+</body>
+</html>''', autoblocks=['body']),
+ 'child.html': XMLTemplate('''
+<html>
+<py:extends href="parent.html"/>
+<body><em>Great conference this weekend!</em></body>
+</html>''', autoblocks=['body'])})
+
+ parent = loader.import_('parent.html')
+ rsp = parent().render()
+ assert rsp == '''
+<head/>
+<body>
+ <p>It was good seeing you last Friday.
+ Thanks for the gift!</p>
+</body>
+''', rsp
+
+ child = loader.import_('child.html')
+ rsp = child().render()
+ assert rsp == '''<html>
+
+<head/>
+<body>
+ <em>Great conference this weekend!</em>
+</body>
+
+
+</html>''', rsp
+
class TestClosure(TestCase):
def test(self):
diff --git a/kajiki/version.py b/kajiki/version.py
index d0745a6..5665fa9 100644
--- a/kajiki/version.py
+++ b/kajiki/version.py
@@ -2,5 +2,5 @@
from __future__ import (absolute_import, division, print_function,
unicode_literals)
-__version__ = '0.4'
-__release__ = '0.4.4'
+__version__ = '0.5'
+__release__ = '0.5.2'
diff --git a/kajiki/xml_template.py b/kajiki/xml_template.py
index 40bcddc..fcec6c1 100644
--- a/kajiki/xml_template.py
+++ b/kajiki/xml_template.py
@@ -6,7 +6,7 @@ import re
from codecs import open
from xml import sax
from xml.dom import minidom as dom
-from nine import IS_PYTHON2, basestring, str, iteritems
+from nine import IS_PYTHON2, basestring, str, iteritems, native_str
if IS_PYTHON2:
from cStringIO import StringIO as BytesIO
@@ -26,7 +26,7 @@ impl = dom.getDOMImplementation(' ')
def XMLTemplate(source=None, filename=None, mode=None, is_fragment=False,
- encoding='utf-8'):
+ encoding='utf-8', autoblocks=None, cdata_scripts=True):
if source is None:
with open(filename, encoding=encoding) as f:
source = f.read() # source is a unicode string
@@ -34,7 +34,8 @@ def XMLTemplate(source=None, filename=None, mode=None, is_fragment=False,
filename = '<string>'
doc = _Parser(filename, source).parse()
expand(doc)
- compiler = _Compiler(filename, doc, mode=mode, is_fragment=is_fragment)
+ compiler = _Compiler(filename, doc, mode=mode, is_fragment=is_fragment,
+ autoblocks=autoblocks, cdata_scripts=cdata_scripts)
ir_ = compiler.compile()
return template.from_ir(ir_)
@@ -48,7 +49,8 @@ def annotate(gen):
class _Compiler(object):
- def __init__(self, filename, doc, mode=None, is_fragment=False):
+ def __init__(self, filename, doc, mode=None, is_fragment=False,
+ autoblocks=None, cdata_scripts=True):
self.filename = filename
self.doc = doc
self.is_fragment = is_fragment
@@ -56,6 +58,8 @@ class _Compiler(object):
self.functions['__main__()'] = []
self.function_lnos = {}
self.mod_py = []
+ self.autoblocks = autoblocks or []
+ self.cdata_scripts = cdata_scripts
self.in_def = False
self.is_child = False
# The rendering mode is either specified in the *mode* argument,
@@ -100,6 +104,22 @@ class _Compiler(object):
ir_node.filename = self.filename
ir_node.lineno = dom_node.lineno
+ def _is_autoblock(self, node):
+ if node.tagName not in self.autoblocks:
+ return False
+
+ if node.hasAttribute('py:autoblock'):
+ guard = node.getAttribute('py:autoblock').lower()
+ if guard not in ('false', 'true'):
+ raise ValueError('py:autoblock is evaluated at compile time '
+ 'and only accepts True/False constants')
+ if guard == 'false':
+ # We throw away the attribute so it doesn't remain in rendered nodes.
+ node.removeAttribute('py:autoblock')
+ return False
+
+ return True
+
def _compile_node(self, node):
if isinstance(node, dom.Comment):
return self._compile_comment(node)
@@ -107,6 +127,10 @@ class _Compiler(object):
return self._compile_text(node)
elif isinstance(node, dom.ProcessingInstruction):
return self._compile_pi(node)
+ elif self._is_autoblock(node):
+ # Set the name of the block equal to the tag itself.
+ node.setAttribute('name', node.tagName)
+ return self._compile_block(node)
elif node.tagName.startswith('py:'):
# Handle directives
compiler = getattr(
@@ -147,20 +171,28 @@ class _Compiler(object):
else:
if node.childNodes:
yield ir.TextNode('>', guard)
- if node.tagName in HTML_CDATA_TAGS:
+ if self.cdata_scripts and node.tagName in HTML_CDATA_TAGS:
# Special behaviour for <script>, <style> tags:
if self.mode == 'xml': # Start escaping
yield ir.TextNode('/*<![CDATA[*/')
# Need to unescape the contents of these tags
for child in node.childNodes:
+ # CDATA for scripts and styles are automatically managed.
+ if getattr(child, '_cdata', False):
+ continue
assert isinstance(child, dom.Text)
for x in self._compile_text(child):
- x.text = unescape(x.text)
+ if child.escaped: # If user declared CDATA no escaping happened.
+ x.text = unescape(x.text)
yield x
if self.mode == 'xml': # Finish escaping
yield ir.TextNode('/*]]>*/')
else:
for cn in node.childNodes:
+ # Keep CDATA sections around if declared by user
+ if getattr(cn, '_cdata', False):
+ yield ir.TextNode(cn.data)
+ continue
for x in self._compile_node(cn):
yield x
if not (self.mode.startswith('html')
@@ -297,13 +329,22 @@ class _Compiler(object):
yield x
+def make_text_node(text, guard=None):
+ '''Return a TranslatableTextNode if the text is not empty,
+ otherwise a regular TextNode.
+ '''
+ if text.strip():
+ return ir.TranslatableTextNode(text, guard)
+ return ir.TextNode(text, guard)
+
+
class _TextCompiler(object):
'''Separates expressions such as ${some_var} from the ordinary text
around them in the template source and generates ExprNode instances and
TextNode instances accordingly.
'''
def __init__(self, filename, source, lineno,
- node_type=ir.TranslatableTextNode, in_html_attr=False):
+ node_type=make_text_node, in_html_attr=False):
self.filename = filename
self.source = source
self.orig_lineno = lineno
@@ -396,6 +437,7 @@ class _Parser(sax.ContentHandler):
self._doc._dtd, position, source = extract_dtd(source)
# Use our own DTD just for XML parsing
self._source = source[:position] + self.DTD + source[position:]
+ self._cdata_stack = []
def parse(self):
self._parser = parser = sax.make_parser()
@@ -411,7 +453,7 @@ class _Parser(sax.ContentHandler):
# So if source is unicode, we pre-encode it:
# TODO Is this dance really necessary? Can't I just call a function?
byts = self._source.encode('utf-8')
- source.setEncoding('utf-8')
+ source.setEncoding(native_str('utf-8'))
source.setByteStream(BytesIO(byts))
source.setSystemId(self._filename)
parser.parse(source)
@@ -434,9 +476,12 @@ class _Parser(sax.ContentHandler):
assert name == popped.tagName
def characters(self, content):
- content = sax.saxutils.escape(content)
+ should_escape = not self._cdata_stack
+ if should_escape:
+ content = sax.saxutils.escape(content)
node = self._doc.createTextNode(content)
node.lineno = self._parser.getLineNumber()
+ node.escaped = should_escape
self._els[-1].appendChild(node)
def processingInstruction(self, target, data):
@@ -473,10 +518,18 @@ class _Parser(sax.ContentHandler):
self._els[-1].appendChild(node)
def startCDATA(self):
- pass
+ node = self._doc.createTextNode('<![CDATA[')
+ node._cdata = True
+ node.lineno = self._parser.getLineNumber()
+ self._els[-1].appendChild(node)
+ self._cdata_stack.append(self._els[-1])
def endCDATA(self):
- pass
+ node = self._doc.createTextNode(']]>')
+ node._cdata = True
+ node.lineno = self._parser.getLineNumber()
+ self._els[-1].appendChild(node)
+ self._cdata_stack.pop()
def startDTD(self, name, pubid, sysid):
self._doc.doctype = impl.createDocumentType(name, pubid, sysid)
--
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