summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--HISTORY.md7
-rw-r--r--README.md19
-rw-r--r--TODO.md13
-rw-r--r--gh/images/logo_phillips.pngbin0 -> 173595 bytes
-rw-r--r--pystache/loader.py2
-rw-r--r--pystache/parsed.py24
-rw-r--r--pystache/parser.py4
-rw-r--r--pystache/renderengine.py16
-rw-r--r--pystache/renderer.py46
-rw-r--r--pystache/specloader.py3
-rw-r--r--pystache/template_spec.py30
-rw-r--r--pystache/tests/common.py8
-rw-r--r--pystache/tests/test_renderengine.py45
-rw-r--r--pystache/tests/test_renderer.py40
-rw-r--r--pystache/tests/test_specloader.py32
-rw-r--r--setup.py163
-rw-r--r--setup_description.rst105
18 files changed, 411 insertions, 149 deletions
diff --git a/.gitignore b/.gitignore
index 9761831..758d62d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,8 @@
# Our tox runs convert the doctests in *.rst files to Python 3 prior to
# running tests. Ignore these temporary files.
*.temp2to3.rst
-# The setup.py "prep" command converts *.md to *.temp.rst.
+# The setup.py "prep" command converts *.md to *.temp.rst (via *.temp.md).
+*.temp.md
*.temp.rst
# TextMate project file
*.tmproj
diff --git a/HISTORY.md b/HISTORY.md
index 0a09b43..30d3c34 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -4,6 +4,11 @@ History
0.5.3 (TBD)
-----------
+- Added ability to customize string coercion (e.g. to have None render as
+ `''`) (issue \#130).
+- Added Renderer.render_name() to render a template by name (issue \#122).
+- Added TemplateSpec.template_path to specify an absolute path to a
+ template (issue \#41).
- Added option of raising errors on missing tags/partials:
`Renderer(missing_tags='strict')` (issue \#110).
- Added support for finding and loading templates by file name in
@@ -19,6 +24,7 @@ History
strings (issue \#118).
- Convert HISTORY and README files from reST to Markdown.
- More robust handling of byte strings in Python 3.
+- Added Creative Commons license for David Phillips's logo.
0.5.2 (2012-05-03)
------------------
@@ -148,4 +154,3 @@ Bug fixes:
------------------
- First release
-
diff --git a/README.md b/README.md
index 5644b7b..8a4bf18 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,13 @@
Pystache
========
-<!-- We strip out 1-line HTML comments prior to passing to pandoc because -->
-<!-- PyPI rejects reST long descriptions that contain HTML. -->
-
+<!-- Since PyPI rejects reST long descriptions that contain HTML, -->
+<!-- HTML comments must be removed when converting this file to reST. -->
+<!-- For more information on PyPI's behavior in this regard, see: -->
+<!-- http://docs.python.org/distutils/uploading.html#pypi-package-display -->
+<!-- The Pystache setup script strips 1-line HTML comments prior -->
+<!-- to converting to reST, so all HTML comments should be one line. -->
+<!-- -->
<!-- We leave the leading brackets empty here. Otherwise, unwanted -->
<!-- caption text shows up in the reST version converted by pandoc. -->
![](https://s3.amazonaws.com/webdev_bucket/pystache.png "mustachioed, monocled snake by David Phillips")
@@ -28,7 +32,6 @@ on [PyPI](http://pypi.python.org/pypi/pystache). This version of
Pystache passes all tests in [version
1.1.2](https://github.com/mustache/spec/tree/v1.1.2) of the spec.
-Logo: [David Phillips](http://davidphillips.us/)
Requirements
------------
@@ -255,10 +258,16 @@ There is a [mailing list](http://librelist.com/browser/pystache/). Note
that there is a bit of a delay between posting a message and seeing it
appear in the mailing list archive.
-Authors
+Credits
-------
>>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek' }
>>> print pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}", context)
Author: Chris Wanstrath
Maintainer: Chris Jerdonek
+
+Pystache logo by [David Phillips](http://davidphillips.us/) and licensed
+under a [Creative Commons Attribution-ShareAlike 3.0 Unported
+License](http://creativecommons.org/licenses/by-sa/3.0/deed.en_US).
+![](http://i.creativecommons.org/l/by-sa/3.0/88x31.png "Creative
+Commons Attribution-ShareAlike 3.0 Unported License")
diff --git a/TODO.md b/TODO.md
index 59f8e77..00c675a 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,12 +1,15 @@
TODO
====
-* Strip HTML from Markdown files prior to passing to pandoc.
-* Make sure PyPI can parse the new README. See also:
- * http://bugs.python.org/issue15231
- * https://github.com/cjerdonek/molt/commit/91a7f158fdd5b93b90201ae0c128d2bbbaf7f913#README.md
+In master branch, after merging to master:
+
* Enable web page after merging.
-* Add a Renderer.render_name() method to render by template name.
+* Change README to link to the repo version of the logo.
+
+In development branch:
+
+* End support for Python 2.4.
+* Add Python 3.3 to tox file (after deprecating 2.4).
* Turn the benchmarking script at pystache/tests/benchmark.py into a command in pystache/commands, or
make it a subcommand of one of the existing commands (i.e. using a command argument).
* Provide support for logging in at least one of the commands.
diff --git a/gh/images/logo_phillips.png b/gh/images/logo_phillips.png
new file mode 100644
index 0000000..7491901
--- /dev/null
+++ b/gh/images/logo_phillips.png
Binary files differ
diff --git a/pystache/loader.py b/pystache/loader.py
index 5855392..d4a7e53 100644
--- a/pystache/loader.py
+++ b/pystache/loader.py
@@ -33,6 +33,8 @@ class Loader(object):
"""
Loads the template associated to a name or user-defined object.
+ All load_*() methods return the template as a unicode string.
+
"""
def __init__(self, file_encoding=None, extension=None, to_unicode=None,
diff --git a/pystache/parsed.py b/pystache/parsed.py
index e94c644..372d96c 100644
--- a/pystache/parsed.py
+++ b/pystache/parsed.py
@@ -8,6 +8,16 @@ Exposes a class that represents a parsed (or compiled) template.
class ParsedTemplate(object):
+ """
+ Represents a parsed or compiled template.
+
+ An instance wraps a list of unicode strings and node objects. A node
+ object must have a `render(engine, stack)` method that accepts a
+ RenderEngine instance and a ContextStack instance and returns a unicode
+ string.
+
+ """
+
def __init__(self):
self._parse_tree = []
@@ -18,10 +28,8 @@ class ParsedTemplate(object):
"""
Arguments:
- node: a unicode string or node object instance. A node object
- instance must have a `render(engine, stack)` method that
- accepts a RenderEngine instance and a ContextStack instance and
- returns a unicode string.
+ node: a unicode string or node object instance. See the class
+ docstring for information.
"""
self._parse_tree.append(node)
@@ -32,10 +40,10 @@ class ParsedTemplate(object):
"""
# We avoid use of the ternary operator for Python 2.4 support.
- def get_unicode(val):
- if type(val) is unicode:
- return val
- return val.render(engine, context)
+ def get_unicode(node):
+ if type(node) is unicode:
+ return node
+ return node.render(engine, context)
parts = map(get_unicode, self._parse_tree)
s = ''.join(parts)
diff --git a/pystache/parser.py b/pystache/parser.py
index 8b82776..c6a171f 100644
--- a/pystache/parser.py
+++ b/pystache/parser.py
@@ -189,10 +189,10 @@ class _SectionNode(object):
return _format(self, exclude=['delimiters', 'template'])
def render(self, engine, context):
- data = engine.fetch_section_data(context, self.key)
+ values = engine.fetch_section_data(context, self.key)
parts = []
- for val in data:
+ for val in values:
if callable(val):
# Lambdas special case section rendering and bypass pushing
# the data value onto the context stack. From the spec--
diff --git a/pystache/renderengine.py b/pystache/renderengine.py
index ef2c145..c797b17 100644
--- a/pystache/renderengine.py
+++ b/pystache/renderengine.py
@@ -43,7 +43,7 @@ class RenderEngine(object):
# that encapsulates the customizable aspects of converting
# strings and resolving partials and names from context.
def __init__(self, literal=None, escape=None, resolve_context=None,
- resolve_partial=None):
+ resolve_partial=None, to_str=None):
"""
Arguments:
@@ -76,11 +76,17 @@ class RenderEngine(object):
The function should accept a template name string and return a
template string of type unicode (not a subclass).
+ to_str: a function that accepts an object and returns a string (e.g.
+ the built-in function str). This function is used for string
+ coercion whenever a string is required (e.g. for converting None
+ or 0 to a string).
+
"""
self.escape = escape
self.literal = literal
self.resolve_context = resolve_context
self.resolve_partial = resolve_partial
+ self.to_str = to_str
# TODO: Rename context to stack throughout this module.
@@ -103,11 +109,15 @@ class RenderEngine(object):
return self._render_value(val(), context)
if not is_string(val):
- return str(val)
+ return self.to_str(val)
return val
def fetch_section_data(self, context, name):
+ """
+ Fetch the value of a section as a list.
+
+ """
data = self.resolve_context(context, name)
# From the spec:
@@ -149,7 +159,7 @@ class RenderEngine(object):
"""
if not is_string(val):
# In case the template is an integer, for example.
- val = str(val)
+ val = self.to_str(val)
if type(val) is not unicode:
val = self.literal(val)
return self.render(val, context, delimiters)
diff --git a/pystache/renderer.py b/pystache/renderer.py
index 20e4d48..ff6a90c 100644
--- a/pystache/renderer.py
+++ b/pystache/renderer.py
@@ -23,11 +23,11 @@ class Renderer(object):
A class for rendering mustache templates.
This class supports several rendering options which are described in
- the constructor's docstring. Among these, the constructor supports
- passing a custom partial loader.
+ the constructor's docstring. Other behavior can be customized by
+ subclassing this class.
- Here is an example of rendering a template using a custom partial loader
- that loads partials from a string-string dictionary.
+ For example, one can pass a string-string dictionary to the constructor
+ to bypass loading partials from the file system:
>>> partials = {'partial': 'Hello, {{thing}}!'}
>>> renderer = Renderer(partials=partials)
@@ -35,6 +35,16 @@ class Renderer(object):
>>> print renderer.render('{{>partial}}', {'thing': 'world'})
Hello, world!
+ To customize string coercion (e.g. to render False values as ''), one can
+ subclass this class. For example:
+
+ class MyRenderer(Renderer):
+ def str_coerce(self, val):
+ if not val:
+ return ''
+ else:
+ return str(val)
+
"""
def __init__(self, file_encoding=None, string_encoding=None,
@@ -146,6 +156,20 @@ class Renderer(object):
"""
return self._context
+ # We could not choose str() as the name because 2to3 renames the unicode()
+ # method of this class to str().
+ def str_coerce(self, val):
+ """
+ Coerce a non-string value to a string.
+
+ This method is called whenever a non-string is encountered during the
+ rendering process when a string is needed (e.g. if a context value
+ for string interpolation is not a string). To customize string
+ coercion, you can override this method.
+
+ """
+ return str(val)
+
def _to_unicode_soft(self, s):
"""
Convert a basestring to unicode, preserving any unicode subclass.
@@ -307,7 +331,8 @@ class Renderer(object):
engine = RenderEngine(literal=self._to_unicode_hard,
escape=self._escape_to_unicode,
resolve_context=resolve_context,
- resolve_partial=resolve_partial)
+ resolve_partial=resolve_partial,
+ to_str=self.str_coerce)
return engine
# TODO: add unit tests for this method.
@@ -341,6 +366,17 @@ class Renderer(object):
return self._render_string(template, *context, **kwargs)
+ def render_name(self, template_name, *context, **kwargs):
+ """
+ Render the template with the given name using the given context.
+
+ See the render() docstring for more information.
+
+ """
+ loader = self._make_loader()
+ template = loader.load_name(template_name)
+ return self._render_string(template, *context, **kwargs)
+
def render_path(self, template_path, *context, **kwargs):
"""
Render the template at the given path using the given context.
diff --git a/pystache/specloader.py b/pystache/specloader.py
index 3cb0f1a..3a77d4c 100644
--- a/pystache/specloader.py
+++ b/pystache/specloader.py
@@ -55,6 +55,9 @@ class SpecLoader(object):
Find and return the path to the template associated to the instance.
"""
+ if spec.template_path is not None:
+ return spec.template_path
+
dir_path, file_name = self._find_relative(spec)
locator = self.loader._make_locator()
diff --git a/pystache/template_spec.py b/pystache/template_spec.py
index 76ce784..9e9f454 100644
--- a/pystache/template_spec.py
+++ b/pystache/template_spec.py
@@ -4,12 +4,11 @@
Provides a class to customize template information on a per-view basis.
To customize template properties for a particular view, create that view
-from a class that subclasses TemplateSpec. The "Spec" in TemplateSpec
-stands for template information that is "special" or "specified".
+from a class that subclasses TemplateSpec. The "spec" in TemplateSpec
+stands for "special" or "specified" template information.
"""
-# TODO: finish the class docstring.
class TemplateSpec(object):
"""
@@ -28,20 +27,27 @@ class TemplateSpec(object):
template: the template as a string.
- template_rel_path: the path to the template file, relative to the
- directory containing the module defining the class.
-
- template_rel_directory: the directory containing the template file, relative
- to the directory containing the module defining the class.
+ template_encoding: the encoding used by the template.
template_extension: the template file extension. Defaults to "mustache".
Pass False for no extension (i.e. extensionless template files).
+ template_name: the name of the template.
+
+ template_path: absolute path to the template.
+
+ template_rel_directory: the directory containing the template file,
+ relative to the directory containing the module defining the class.
+
+ template_rel_path: the path to the template file, relative to the
+ directory containing the module defining the class.
+
"""
template = None
- template_rel_path = None
- template_rel_directory = None
- template_name = None
- template_extension = None
template_encoding = None
+ template_extension = None
+ template_name = None
+ template_path = None
+ template_rel_directory = None
+ template_rel_path = None
diff --git a/pystache/tests/common.py b/pystache/tests/common.py
index 307a2be..99be4c8 100644
--- a/pystache/tests/common.py
+++ b/pystache/tests/common.py
@@ -43,7 +43,10 @@ def html_escape(u):
return u.replace("'", '&#x27;')
-def get_data_path(file_name):
+def get_data_path(file_name=None):
+ """Return the path to a file in the test data directory."""
+ if file_name is None:
+ file_name = ""
return os.path.join(DATA_DIR, file_name)
@@ -139,8 +142,7 @@ class AssertStringMixin:
format = "%s"
# Show both friendly and literal versions.
- details = """String mismatch: %%s\
-
+ details = """String mismatch: %%s
Expected: \"""%s\"""
Actual: \"""%s\"""
diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py
index 4c40c47..db916f7 100644
--- a/pystache/tests/test_renderengine.py
+++ b/pystache/tests/test_renderengine.py
@@ -55,11 +55,13 @@ class RenderEngineTestCase(unittest.TestCase):
"""
# In real-life, these arguments would be functions
- engine = RenderEngine(resolve_partial="foo", literal="literal", escape="escape")
+ engine = RenderEngine(resolve_partial="foo", literal="literal",
+ escape="escape", to_str="str")
self.assertEqual(engine.escape, "escape")
self.assertEqual(engine.literal, "literal")
self.assertEqual(engine.resolve_partial, "foo")
+ self.assertEqual(engine.to_str, "str")
class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
@@ -182,6 +184,47 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
self._assert_render(u'**bar bar**', template, context, engine=engine)
+ # Custom to_str for testing purposes.
+ def _to_str(self, val):
+ if not val:
+ return ''
+ else:
+ return str(val)
+
+ def test_to_str(self):
+ """Test the to_str attribute."""
+ engine = self._engine()
+ template = '{{value}}'
+ context = {'value': None}
+
+ self._assert_render(u'None', template, context, engine=engine)
+ engine.to_str = self._to_str
+ self._assert_render(u'', template, context, engine=engine)
+
+ def test_to_str__lambda(self):
+ """Test the to_str attribute for a lambda."""
+ engine = self._engine()
+ template = '{{value}}'
+ context = {'value': lambda: None}
+
+ self._assert_render(u'None', template, context, engine=engine)
+ engine.to_str = self._to_str
+ self._assert_render(u'', template, context, engine=engine)
+
+ def test_to_str__section_list(self):
+ """Test the to_str attribute for a section list."""
+ engine = self._engine()
+ template = '{{#list}}{{.}}{{/list}}'
+ context = {'list': [None, None]}
+
+ self._assert_render(u'NoneNone', template, context, engine=engine)
+ engine.to_str = self._to_str
+ self._assert_render(u'', template, context, engine=engine)
+
+ def test_to_str__section_lambda(self):
+ # TODO: add a test for a "method with an arity of 1".
+ pass
+
def test__non_basestring__literal_and_escaped(self):
"""
Test a context value that is not a basestring instance.
diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py
index 69cc64d..0dbe0d9 100644
--- a/pystache/tests/test_renderer.py
+++ b/pystache/tests/test_renderer.py
@@ -368,6 +368,13 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
# TypeError: decoding Unicode is not supported
self.assertEqual(resolve_partial("partial"), "foo")
+ def test_render_name(self):
+ """Test the render_name() method."""
+ data_dir = get_data_path()
+ renderer = Renderer(search_dirs=data_dir)
+ actual = renderer.render_name("say_hello", to='foo')
+ self.assertString(actual, u"Hello, foo")
+
def test_render_path(self):
"""
Test the render_path() method.
@@ -418,6 +425,39 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
actual = renderer.render(view)
self.assertEqual('Hi pizza!', actual)
+ def test_custom_string_coercion_via_assignment(self):
+ """
+ Test that string coercion can be customized via attribute assignment.
+
+ """
+ renderer = self._renderer()
+ def to_str(val):
+ if not val:
+ return ''
+ else:
+ return str(val)
+
+ self.assertEqual(renderer.render('{{value}}', value=None), 'None')
+ renderer.str_coerce = to_str
+ self.assertEqual(renderer.render('{{value}}', value=None), '')
+
+ def test_custom_string_coercion_via_subclassing(self):
+ """
+ Test that string coercion can be customized via subclassing.
+
+ """
+ class MyRenderer(Renderer):
+ def str_coerce(self, val):
+ if not val:
+ return ''
+ else:
+ return str(val)
+ renderer1 = Renderer()
+ renderer2 = MyRenderer()
+
+ self.assertEqual(renderer1.render('{{value}}', value=None), 'None')
+ self.assertEqual(renderer2.render('{{value}}', value=None), '')
+
# By testing that Renderer.render() constructs the right RenderEngine,
# we no longer need to exercise all rendering code paths through
diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py
index 24fb34d..d934987 100644
--- a/pystache/tests/test_specloader.py
+++ b/pystache/tests/test_specloader.py
@@ -30,6 +30,14 @@ class Thing(object):
pass
+class AssertPathsMixin:
+
+ """A unittest.TestCase mixin to check path equality."""
+
+ def assertPaths(self, actual, expected):
+ self.assertEqual(actual, expected)
+
+
class ViewTestCase(unittest.TestCase, AssertStringMixin):
def test_template_rel_directory(self):
@@ -174,7 +182,8 @@ def _make_specloader():
return SpecLoader(loader=loader)
-class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
+class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
+ AssertPathsMixin):
"""
Tests template_spec.SpecLoader.
@@ -288,13 +297,21 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
self.assertEqual(loader.s, "template-foo")
self.assertEqual(loader.encoding, "encoding-foo")
+ def test_find__template_path(self):
+ """Test _find() with TemplateSpec.template_path."""
+ loader = self._make_specloader()
+ custom = TemplateSpec()
+ custom.template_path = "path/foo"
+ actual = loader._find(custom)
+ self.assertPaths(actual, "path/foo")
+
# TODO: migrate these tests into the SpecLoaderTests class.
# TODO: rename the get_template() tests to test load().
# TODO: condense, reorganize, and rename the tests so that it is
# clear whether we have full test coverage (e.g. organized by
# TemplateSpec attributes or something).
-class TemplateSpecTests(unittest.TestCase):
+class TemplateSpecTests(unittest.TestCase, AssertPathsMixin):
def _make_loader(self):
return _make_specloader()
@@ -358,13 +375,6 @@ class TemplateSpecTests(unittest.TestCase):
view.template_extension = 'txt'
self._assert_template_location(view, (None, 'sample_view.txt'))
- def _assert_paths(self, actual, expected):
- """
- Assert that two paths are the same.
-
- """
- self.assertEqual(actual, expected)
-
def test_find__with_directory(self):
"""
Test _find() with a view that has a directory specified.
@@ -379,7 +389,7 @@ class TemplateSpecTests(unittest.TestCase):
actual = loader._find(view)
expected = os.path.join(DATA_DIR, 'foo/bar.txt')
- self._assert_paths(actual, expected)
+ self.assertPaths(actual, expected)
def test_find__without_directory(self):
"""
@@ -394,7 +404,7 @@ class TemplateSpecTests(unittest.TestCase):
actual = loader._find(view)
expected = os.path.join(DATA_DIR, 'sample_view.mustache')
- self._assert_paths(actual, expected)
+ self.assertPaths(actual, expected)
def _assert_get_template(self, custom, expected):
loader = self._make_loader()
diff --git a/setup.py b/setup.py
index a50a0aa..8e334d1 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,9 @@ to do this step:
It helps to review this auto-generated file on GitHub prior to uploading
because the long description will be sent to PyPI and appear there after
-publishing.
+publishing. PyPI attempts to convert this string to HTML before displaying
+it on the PyPI project page. If PyPI finds any issues, it will render it
+instead as plain-text, which we do not want.
To check in advance that PyPI will accept and parse the reST file as HTML,
you can use the rst2html program installed by the docutils package
@@ -31,8 +33,8 @@ you can use the rst2html program installed by the docutils package
$ pip install docutils
-You will want to issue a command like the following and check that no
-warnings are issued:
+To check the file, run the following command and confirm that it reports
+no warnings:
$ python setup.py --long-description | rst2html.py -v --no-raw > out.html
@@ -40,7 +42,6 @@ See here for more information:
http://docs.python.org/distutils/uploading.html#pypi-package-display
-
(2) Push to PyPI. To release a new version of Pystache to PyPI--
http://pypi.python.org/pypi/pystache
@@ -119,9 +120,9 @@ README_PATH = 'README.md'
HISTORY_PATH = 'HISTORY.md'
LICENSE_PATH = 'LICENSE'
-DESCRIPTION_PATH = 'setup_description.rst'
+RST_DESCRIPTION_PATH = 'setup_description.rst'
-TEMP_REST_EXTENSION = '.temp.rst'
+TEMP_EXTENSION = '.temp'
PREP_COMMAND = 'prep'
@@ -140,6 +141,12 @@ CLASSIFIERS = (
'Programming Language :: Python :: Implementation :: PyPy',
)
+# Comments in reST begin with two dots.
+RST_LONG_DESCRIPTION_INTRO = """\
+.. Do not edit this file. This file is auto-generated for PyPI by setup.py
+.. using pandoc, so edits should go in the source files rather than here.
+"""
+
def read(path):
"""
@@ -162,7 +169,7 @@ def write(u, path):
Write a unicode string to a file (as utf-8).
"""
- print("Writing to: %s" % path)
+ print("writing to: %s" % path)
# This function implementation was chosen to be compatible across Python 2/3.
f = open(path, "wb")
try:
@@ -172,64 +179,125 @@ def write(u, path):
f.close()
-def make_temp_path(path):
+def make_temp_path(path, new_ext=None):
+ """
+ Arguments:
+
+ new_ext: the new file extension, including the leading dot.
+ Defaults to preserving the existing file extension.
+
+ """
root, ext = os.path.splitext(path)
- temp_path = root + TEMP_REST_EXTENSION
+ if new_ext is None:
+ new_ext = ext
+ temp_path = root + TEMP_EXTENSION + new_ext
return temp_path
-def make_description_file(target_path):
+def strip_html_comments(text):
+ """Strip HTML comments from a unicode string."""
+ lines = text.splitlines(True) # preserve line endings.
+
+ # Remove HTML comments (which we only allow to take a special form).
+ new_lines = filter(lambda line: not line.startswith("<!--"), lines)
+
+ return "".join(new_lines)
+
+
+# We write the converted file to a temp file to simplify debugging and
+# to avoid removing a valid pre-existing file on failure.
+def convert_md_to_rst(md_path, rst_temp_path):
"""
- Generate the long_description needed for setup.py.
+ Convert the contents of a file from Markdown to reStructuredText.
+
+ Returns the converted text as a Unicode string.
+
+ Arguments:
- The long description needs to be formatted as reStructuredText:
+ md_path: a path to a UTF-8 encoded Markdown file to convert.
- http://docs.python.org/distutils/setupscript.html#additional-meta-data
+ rst_temp_path: a temporary path to which to write the converted contents.
"""
- # Comments in reST begin with two dots.
- intro = """\
-.. This file is auto-generated by setup.py for PyPI using pandoc, so this
-.. file should not be edited. Edits should go in the source files.
-"""
+ # Pandoc uses the UTF-8 character encoding for both input and output.
+ command = "pandoc --write=rst --output=%s %s" % (rst_temp_path, md_path)
+ print("converting with pandoc: %s to %s\n-->%s" % (md_path, rst_temp_path,
+ command))
+
+ if os.path.exists(rst_temp_path):
+ os.remove(rst_temp_path)
+
+ os.system(command)
+
+ if not os.path.exists(rst_temp_path):
+ s = ("Error running: %s\n"
+ " Did you install pandoc per the %s docstring?" % (command,
+ __file__))
+ sys.exit(s)
+
+ return read(rst_temp_path)
- readme_path = convert_md_to_rst(README_PATH)
- history_path = convert_md_to_rst(HISTORY_PATH)
- license = """\
+# The long_description needs to be formatted as reStructuredText.
+# See the following for more information:
+#
+# http://docs.python.org/distutils/setupscript.html#additional-meta-data
+# http://docs.python.org/distutils/uploading.html#pypi-package-display
+#
+def make_long_description():
+ """
+ Generate the reST long_description for setup() from source files.
+
+ Returns the generated long_description as a unicode string.
+
+ """
+ readme_path = README_PATH
+
+ # Remove our HTML comments because PyPI does not allow it.
+ # See the setup.py docstring for more info on this.
+ readme_md = strip_html_comments(read(readme_path))
+ history_md = strip_html_comments(read(HISTORY_PATH))
+ license_md = """\
License
=======
""" + read(LICENSE_PATH)
- sections = [intro, read(readme_path), read(history_path), license]
+ sections = [readme_md, history_md, license_md]
+ md_description = '\n\n'.join(sections)
+
+ # Write the combined Markdown file to a temp path.
+ md_ext = os.path.splitext(readme_path)[1]
+ md_description_path = make_temp_path(RST_DESCRIPTION_PATH, new_ext=md_ext)
+ write(md_description, md_description_path)
- description = '\n'.join(sections)
+ rst_temp_path = make_temp_path(RST_DESCRIPTION_PATH)
+ long_description = convert_md_to_rst(md_path=md_description_path,
+ rst_temp_path=rst_temp_path)
- write(description, target_path)
+ return "\n".join([RST_LONG_DESCRIPTION_INTRO, long_description])
def prep():
- make_description_file(DESCRIPTION_PATH)
+ """Update the reST long_description file."""
+ long_description = make_long_description()
+ write(long_description, RST_DESCRIPTION_PATH)
-def publish():
- """
- Publish this package to PyPI (aka "the Cheeseshop").
- """
- temp_path = make_temp_path(DESCRIPTION_PATH)
- make_description_file(temp_path)
+def publish():
+ """Publish this package to PyPI (aka "the Cheeseshop")."""
+ long_description = make_long_description()
- if read(temp_path) != read(DESCRIPTION_PATH):
+ if long_description != read(RST_DESCRIPTION_PATH):
print("""\
Description file not up-to-date: %s
Run the following command and commit the changes--
python setup.py %s
-""" % (DESCRIPTION_PATH, PREP_COMMAND))
+""" % (RST_DESCRIPTION_PATH, PREP_COMMAND))
sys.exit()
- print("Description up-to-date: %s" % DESCRIPTION_PATH)
+ print("Description up-to-date: %s" % RST_DESCRIPTION_PATH)
answer = raw_input("Are you sure you want to publish to PyPI (yes/no)?")
@@ -239,31 +307,6 @@ Run the following command and commit the changes--
os.system('python setup.py sdist upload')
-def convert_md_to_rst(path):
- """
- Convert the given file from markdown to reStructuredText.
-
- Returns the new path.
-
- """
- temp_path = make_temp_path(path)
- print("Converting: %s to %s" % (path, temp_path))
-
- if os.path.exists(temp_path):
- os.remove(temp_path)
-
- # Pandoc uses the UTF-8 character encoding for both input and output.
- command = "pandoc --write=rst --output=%s %s" % (temp_path, path)
- os.system(command)
-
- if not os.path.exists(temp_path):
- s = ("Error running: %s\n"
- " Did you install pandoc per the %s docstring?" % (command, __file__))
- sys.exit(s)
-
- return temp_path
-
-
# We use the package simplejson for older Python versions since Python
# does not contain the module json before 2.6:
#
@@ -332,7 +375,7 @@ def main(sys_argv):
prep()
sys.exit()
- long_description = read(DESCRIPTION_PATH)
+ long_description = read(RST_DESCRIPTION_PATH)
template_files = ['*.mustache', '*.txt']
extra_args = get_extra_args()
diff --git a/setup_description.rst b/setup_description.rst
index 4e97663..1c32b0c 100644
--- a/setup_description.rst
+++ b/setup_description.rst
@@ -1,18 +1,17 @@
-.. This file is auto-generated by setup.py for PyPI using pandoc, so this
-.. file should not be edited. Edits should go in the source files.
+.. Do not edit this file. This file is auto-generated for PyPI by setup.py
+.. using pandoc, so edits should go in the source files rather than here.
Pystache
========
-.. raw:: html
-
- <!-- We leave the brackets empty. Otherwise, text shows up in the reST
- version converted by pandoc. -->
-
.. figure:: https://s3.amazonaws.com/webdev_bucket/pystache.png
:align: center
:alt: mustachioed, monocled snake by David Phillips
+.. figure:: https://secure.travis-ci.org/defunkt/pystache.png?branch=master,development
+ :align: center
+ :alt:
+
`Pystache <https://github.com/defunkt/pystache>`_ is a Python
implementation of `Mustache <http://mustache.github.com/>`_. Mustache is
a framework-agnostic, logic-free templating system inspired by
@@ -46,6 +45,14 @@ Pystache is tested with--
- Python 2.7
- Python 3.1
- Python 3.2
+- `PyPy <http://pypy.org/>`_
+
+`Distribute <http://packages.python.org/distribute/>`_ (the setuptools
+fork) is recommended over
+`setuptools <http://pypi.python.org/pypi/setuptools>`_, and is required
+in some cases (e.g. for Python 3 support). If you use
+`pip <http://www.pip-installer.org/>`_, you probably already satisfy
+this requirement.
JSON support is needed only for the command-line interface and to run
the spec tests. We require simplejson for earlier versions of Python
@@ -67,6 +74,11 @@ Install It
::
pip install pystache
+
+And test it--
+
+::
+
pystache-test
To install and test from source (e.g. from GitHub), see the Develop
@@ -147,8 +159,8 @@ Pystache has supported Python 3 since version 0.5.1. Pystache behaves
slightly differently between Python 2 and 3, as follows:
- In Python 2, the default html-escape function ``cgi.escape()`` does
- not escape single quotes; whereas in Python 3, the default escape
- function ``html.escape()`` does escape single quotes.
+ not escape single quotes. In Python 3, the default escape function
+ ``html.escape()`` does escape single quotes.
- In both Python 2 and 3, the string and file encodings default to
``sys.getdefaultencoding()``. However, this function can return
different values under Python 2 and 3, even when run from the same
@@ -207,7 +219,8 @@ command!), you can use `tox <http://pypi.python.org/pypi/tox>`_:
::
- pip install tox
+ pip install 'virtualenv<1.8' # Version 1.8 dropped support for Python 2.4.
+ pip install 'tox<1.4' # Version 1.4 dropped support for Python 2.4.
tox
If you do not have all Python versions listed in ``tox.ini``--
@@ -241,18 +254,42 @@ To run a subset of the tests, you can use
pip install nose
nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present
-**Running Pystache from source with Python 3.** Pystache is written in
-Python 2 and must be converted with
-`2to3 <http://docs.python.org/library/2to3.html>`_ prior to running
-under Python 3. The installation process (and tox) do this conversion
+Using Python 3 with Pystache from source
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Pystache is written in Python 2 and must be converted to Python 3 prior
+to using it with Python 3. The installation process (and tox) do this
automatically.
+To convert the code to Python 3 manually (while using Python 3)--
+
+::
+
+ python setup.py build
+
+This writes the converted code to a subdirectory called ``build``. By
+design, Python 3 builds
+`cannot <https://bitbucket.org/tarek/distribute/issue/292/allow-use_2to3-with-python-2>`_
+be created from Python 2.
+
+To convert the code without using setup.py, you can use
+`2to3 <http://docs.python.org/library/2to3.html>`_ as follows (two
+steps)--
+
+::
+
+ 2to3 --write --nobackups --no-diffs --doctests_only pystache
+ 2to3 --write --nobackups --no-diffs pystache
+
+This converts the code (and doctests) in place.
+
To ``import pystache`` from a source distribution while using Python 3,
be sure that you are importing from a directory containing a converted
-version (e.g. from your site-packages directory after manually
-installing) and not from the original source directory. Otherwise, you
-will get a syntax error. You can help ensure this by not running the
-Python IDE from the project directory when importing Pystache.
+version of the code (e.g. from the ``build`` directory after
+converting), and not from the original (unconverted) source directory.
+Otherwise, you will get a syntax error. You can help prevent this by not
+running the Python IDE from the project directory when importing
+Pystache while using Python 3.
Mailing List
------------
@@ -271,7 +308,6 @@ Authors
Author: Chris Wanstrath
Maintainer: Chris Jerdonek
-
History
=======
@@ -280,13 +316,19 @@ History
- Added option of raising errors on missing tags/partials:
``Renderer(missing_tags='strict')`` (issue #110).
+- Added support for finding and loading templates by file name in
+ addition to by template name (issue #127). [xgecko]
- Added a ``parse()`` function that yields a printable, pre-compiled
parse tree.
- Added support for rendering pre-compiled templates.
+- Added support for `PyPy <http://pypy.org/>`_ (issue #125).
+- Added support for `Travis CI <http://travis-ci.org>`_ (issue #124).
+ [msabramo]
- Bugfix: exceptions raised from a property are no longer swallowed
when getting a key from a context stack (issue #110).
- Bugfix: lambda section values can now return non-ascii, non-unicode
strings (issue #118).
+- Convert HISTORY and README files from reST to Markdown.
- More robust handling of byte strings in Python 3.
0.5.2 (2012-05-03)
@@ -418,29 +460,28 @@ Bug fixes:
- First release
-
License
=======
-Copyright (C) 2012 Chris Jerdonek. All rights reserved.
+Copyright (C) 2012 Chris Jerdonek. All rights reserved.
Copyright (c) 2009 Chris Wanstrath
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.