зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1712151: Vendor pystache automatically r=ahal
Add pystache to vendor `requirements.in` so that it's vendored according to `./mach vendor python` "ignore" rules. This ensures that sufficient files are vendored such that installing the package from it's `setup.py` file is possible. Differential Revision: https://phabricator.services.mozilla.com/D122898
This commit is contained in:
Родитель
a16f941f30
Коммит
648d810e4b
|
@ -1,17 +0,0 @@
|
|||
*.pyc
|
||||
.DS_Store
|
||||
# Tox support. See: http://pypi.python.org/pypi/tox
|
||||
.tox
|
||||
# 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 (via *.temp.md).
|
||||
*.temp.md
|
||||
*.temp.rst
|
||||
# TextMate project file
|
||||
*.tmproj
|
||||
# Distribution-related folders and files.
|
||||
build
|
||||
dist
|
||||
MANIFEST
|
||||
pystache.egg-info
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "ext/spec"]
|
||||
path = ext/spec
|
||||
url = http://github.com/mustache/spec.git
|
|
@ -1,14 +0,0 @@
|
|||
language: python
|
||||
|
||||
# Travis CI has no plans to support Jython and no longer supports Python 2.5.
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.2
|
||||
- pypy
|
||||
|
||||
script:
|
||||
- python setup.py install
|
||||
# Include the spec tests directory for Mustache spec tests and the
|
||||
# project directory for doctests.
|
||||
- pystache-test . ext/spec/specs
|
|
@ -0,0 +1,536 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: pystache
|
||||
Version: 0.5.4
|
||||
Summary: Mustache for Python
|
||||
Home-page: http://github.com/defunkt/pystache
|
||||
Author: Chris Jerdonek
|
||||
Author-email: chris.jerdonek@gmail.com
|
||||
License: MIT
|
||||
Description: .. 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
|
||||
========
|
||||
|
||||
.. figure:: http://defunkt.github.com/pystache/images/logo_phillips.png
|
||||
:alt: mustachioed, monocled snake by David Phillips
|
||||
|
||||
.. figure:: https://secure.travis-ci.org/defunkt/pystache.png
|
||||
:alt: Travis CI current build status
|
||||
|
||||
`Pystache <http://defunkt.github.com/pystache>`__ is a Python
|
||||
implementation of `Mustache <http://mustache.github.com/>`__. Mustache
|
||||
is a framework-agnostic, logic-free templating system inspired by
|
||||
`ctemplate <http://code.google.com/p/google-ctemplate/>`__ and
|
||||
`et <http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html>`__.
|
||||
Like ctemplate, Mustache "emphasizes separating logic from presentation:
|
||||
it is impossible to embed application logic in this template language."
|
||||
|
||||
The `mustache(5) <http://mustache.github.com/mustache.5.html>`__ man
|
||||
page provides a good introduction to Mustache's syntax. For a more
|
||||
complete (and more current) description of Mustache's behavior, see the
|
||||
official `Mustache spec <https://github.com/mustache/spec>`__.
|
||||
|
||||
Pystache is `semantically versioned <http://semver.org>`__ and can be
|
||||
found 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.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Pystache is tested with--
|
||||
|
||||
- Python 2.4 (requires simplejson `version
|
||||
2.0.9 <http://pypi.python.org/pypi/simplejson/2.0.9>`__ or earlier)
|
||||
- Python 2.5 (requires
|
||||
`simplejson <http://pypi.python.org/pypi/simplejson/>`__)
|
||||
- Python 2.6
|
||||
- Python 2.7
|
||||
- Python 3.1
|
||||
- Python 3.2
|
||||
- Python 3.3
|
||||
- `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
|
||||
since Python's `json <http://docs.python.org/library/json.html>`__
|
||||
module was added in Python 2.6.
|
||||
|
||||
For Python 2.4 we require an earlier version of simplejson since
|
||||
simplejson stopped officially supporting Python 2.4 in simplejson
|
||||
version 2.1.0. Earlier versions of simplejson can be installed manually,
|
||||
as follows:
|
||||
|
||||
::
|
||||
|
||||
pip install 'simplejson<2.1.0'
|
||||
|
||||
Official support for Python 2.4 will end with Pystache version 0.6.0.
|
||||
|
||||
Install It
|
||||
----------
|
||||
|
||||
::
|
||||
|
||||
pip install pystache
|
||||
|
||||
And test it--
|
||||
|
||||
::
|
||||
|
||||
pystache-test
|
||||
|
||||
To install and test from source (e.g. from GitHub), see the Develop
|
||||
section.
|
||||
|
||||
Use It
|
||||
------
|
||||
|
||||
::
|
||||
|
||||
>>> import pystache
|
||||
>>> print pystache.render('Hi {{person}}!', {'person': 'Mom'})
|
||||
Hi Mom!
|
||||
|
||||
You can also create dedicated view classes to hold your view logic.
|
||||
|
||||
Here's your view class (in .../examples/readme.py):
|
||||
|
||||
::
|
||||
|
||||
class SayHello(object):
|
||||
def to(self):
|
||||
return "Pizza"
|
||||
|
||||
Instantiating like so:
|
||||
|
||||
::
|
||||
|
||||
>>> from pystache.tests.examples.readme import SayHello
|
||||
>>> hello = SayHello()
|
||||
|
||||
Then your template, say\_hello.mustache (by default in the same
|
||||
directory as your class definition):
|
||||
|
||||
::
|
||||
|
||||
Hello, {{to}}!
|
||||
|
||||
Pull it together:
|
||||
|
||||
::
|
||||
|
||||
>>> renderer = pystache.Renderer()
|
||||
>>> print renderer.render(hello)
|
||||
Hello, Pizza!
|
||||
|
||||
For greater control over rendering (e.g. to specify a custom template
|
||||
directory), use the ``Renderer`` class like above. One can pass
|
||||
attributes to the Renderer class constructor or set them on a Renderer
|
||||
instance. To customize template loading on a per-view basis, subclass
|
||||
``TemplateSpec``. See the docstrings of the
|
||||
`Renderer <https://github.com/defunkt/pystache/blob/master/pystache/renderer.py>`__
|
||||
class and
|
||||
`TemplateSpec <https://github.com/defunkt/pystache/blob/master/pystache/template_spec.py>`__
|
||||
class for more information.
|
||||
|
||||
You can also pre-parse a template:
|
||||
|
||||
::
|
||||
|
||||
>>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}")
|
||||
>>> print parsed
|
||||
[u'Hey ', _SectionNode(key=u'who', index_begin=12, index_end=18, parsed=[_EscapeNode(key=u'.'), u'!'])]
|
||||
|
||||
And then:
|
||||
|
||||
::
|
||||
|
||||
>>> print renderer.render(parsed, {'who': 'Pops'})
|
||||
Hey Pops!
|
||||
>>> print renderer.render(parsed, {'who': 'you'})
|
||||
Hey you!
|
||||
|
||||
Python 3
|
||||
--------
|
||||
|
||||
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. 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
|
||||
system. Check your own system for the behavior on your system, or do
|
||||
not rely on the defaults by passing in the encodings explicitly (e.g.
|
||||
to the ``Renderer`` class).
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
This section describes how Pystache handles unicode, strings, and
|
||||
encodings.
|
||||
|
||||
Internally, Pystache uses `only unicode
|
||||
strings <http://docs.python.org/howto/unicode.html#tips-for-writing-unicode-aware-programs>`__
|
||||
(``str`` in Python 3 and ``unicode`` in Python 2). For input, Pystache
|
||||
accepts both unicode strings and byte strings (``bytes`` in Python 3 and
|
||||
``str`` in Python 2). For output, Pystache's template rendering methods
|
||||
return only unicode.
|
||||
|
||||
Pystache's ``Renderer`` class supports a number of attributes to control
|
||||
how Pystache converts byte strings to unicode on input. These include
|
||||
the ``file_encoding``, ``string_encoding``, and ``decode_errors``
|
||||
attributes.
|
||||
|
||||
The ``file_encoding`` attribute is the encoding the renderer uses to
|
||||
convert to unicode any files read from the file system. Similarly,
|
||||
``string_encoding`` is the encoding the renderer uses to convert any
|
||||
other byte strings encountered during the rendering process into unicode
|
||||
(e.g. context values that are encoded byte strings).
|
||||
|
||||
The ``decode_errors`` attribute is what the renderer passes as the
|
||||
``errors`` argument to Python's built-in unicode-decoding function
|
||||
(``str()`` in Python 3 and ``unicode()`` in Python 2). The valid values
|
||||
for this argument are ``strict``, ``ignore``, and ``replace``.
|
||||
|
||||
Each of these attributes can be set via the ``Renderer`` class's
|
||||
constructor using a keyword argument of the same name. See the Renderer
|
||||
class's docstrings for further details. In addition, the
|
||||
``file_encoding`` attribute can be controlled on a per-view basis by
|
||||
subclassing the ``TemplateSpec`` class. When not specified explicitly,
|
||||
these attributes default to values set in Pystache's ``defaults``
|
||||
module.
|
||||
|
||||
Develop
|
||||
-------
|
||||
|
||||
To test from a source distribution (without installing)--
|
||||
|
||||
::
|
||||
|
||||
python test_pystache.py
|
||||
|
||||
To test Pystache with multiple versions of Python (with a single
|
||||
command!), you can use `tox <http://pypi.python.org/pypi/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``--
|
||||
|
||||
::
|
||||
|
||||
tox -e py26,py32 # for example
|
||||
|
||||
The source distribution tests also include doctests and tests from the
|
||||
Mustache spec. To include tests from the Mustache spec in your test
|
||||
runs:
|
||||
|
||||
::
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
The test harness parses the spec's (more human-readable) yaml files if
|
||||
`PyYAML <http://pypi.python.org/pypi/PyYAML>`__ is present. Otherwise,
|
||||
it parses the json files. To install PyYAML--
|
||||
|
||||
::
|
||||
|
||||
pip install pyyaml
|
||||
|
||||
To run a subset of the tests, you can use
|
||||
`nose <http://somethingaboutorange.com/mrl/projects/nose/0.11.1/testing.html>`__:
|
||||
|
||||
::
|
||||
|
||||
pip install nose
|
||||
nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present
|
||||
|
||||
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 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
|
||||
------------
|
||||
|
||||
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.
|
||||
|
||||
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/>`__ is
|
||||
licensed under a `Creative Commons Attribution-ShareAlike 3.0 Unported
|
||||
License <http://creativecommons.org/licenses/by-sa/3.0/deed.en_US>`__.
|
||||
|image0|
|
||||
|
||||
History
|
||||
=======
|
||||
|
||||
**Note:** Official support for Python 2.4 will end with Pystache version
|
||||
0.6.0.
|
||||
|
||||
0.5.4 (2014-07-11)
|
||||
------------------
|
||||
|
||||
- Bugfix: made test with filenames OS agnostic (issue #162).
|
||||
|
||||
0.5.3 (2012-11-03)
|
||||
------------------
|
||||
|
||||
- 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
|
||||
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 Python 3.3 to the list of supported versions.
|
||||
- Added support for `PyPy <http://pypy.org/>`__ (issue #125).
|
||||
- Added support for `Travis CI <http://travis-ci.org>`__ (issue #124).
|
||||
[msabramo]
|
||||
- Bugfix: ``defaults.DELIMITERS`` can now be changed at runtime (issue
|
||||
#135). [bennoleslie]
|
||||
- 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).
|
||||
- Bugfix: allow ``test_pystache.py`` and ``tox`` to pass when run from
|
||||
a downloaded sdist (i.e. without the spec test directory).
|
||||
- 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)
|
||||
------------------
|
||||
|
||||
- Added support for dot notation and version 1.1.2 of the spec (issue
|
||||
#99). [rbp]
|
||||
- Missing partials now render as empty string per latest version of
|
||||
spec (issue #115).
|
||||
- Bugfix: falsey values now coerced to strings using str().
|
||||
- Bugfix: lambda return values for sections no longer pushed onto
|
||||
context stack (issue #113).
|
||||
- Bugfix: lists of lambdas for sections were not rendered (issue #114).
|
||||
|
||||
0.5.1 (2012-04-24)
|
||||
------------------
|
||||
|
||||
- Added support for Python 3.1 and 3.2.
|
||||
- Added tox support to test multiple Python versions.
|
||||
- Added test script entry point: pystache-test.
|
||||
- Added \_\_version\_\_ package attribute.
|
||||
- Test harness now supports both YAML and JSON forms of Mustache spec.
|
||||
- Test harness no longer requires nose.
|
||||
|
||||
0.5.0 (2012-04-03)
|
||||
------------------
|
||||
|
||||
This version represents a major rewrite and refactoring of the code base
|
||||
that also adds features and fixes many bugs. All functionality and
|
||||
nearly all unit tests have been preserved. However, some backwards
|
||||
incompatible changes to the API have been made.
|
||||
|
||||
Below is a selection of some of the changes (not exhaustive).
|
||||
|
||||
Highlights:
|
||||
|
||||
- Pystache now passes all tests in version 1.0.3 of the `Mustache
|
||||
spec <https://github.com/mustache/spec>`__. [pvande]
|
||||
- Removed View class: it is no longer necessary to subclass from View
|
||||
or from any other class to create a view.
|
||||
- Replaced Template with Renderer class: template rendering behavior
|
||||
can be modified via the Renderer constructor or by setting attributes
|
||||
on a Renderer instance.
|
||||
- Added TemplateSpec class: template rendering can be specified on a
|
||||
per-view basis by subclassing from TemplateSpec.
|
||||
- Introduced separation of concerns and removed circular dependencies
|
||||
(e.g. between Template and View classes, cf. `issue
|
||||
#13 <https://github.com/defunkt/pystache/issues/13>`__).
|
||||
- Unicode now used consistently throughout the rendering process.
|
||||
- Expanded test coverage: nosetests now runs doctests and ~105 test
|
||||
cases from the Mustache spec (increasing the number of tests from 56
|
||||
to ~315).
|
||||
- Added a rudimentary benchmarking script to gauge performance while
|
||||
refactoring.
|
||||
- Extensive documentation added (e.g. docstrings).
|
||||
|
||||
Other changes:
|
||||
|
||||
- Added a command-line interface. [vrde]
|
||||
- The main rendering class now accepts a custom partial loader (e.g. a
|
||||
dictionary) and a custom escape function.
|
||||
- Non-ascii characters in str strings are now supported while
|
||||
rendering.
|
||||
- Added string encoding, file encoding, and errors options for decoding
|
||||
to unicode.
|
||||
- Removed the output encoding option.
|
||||
- Removed the use of markupsafe.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Context values no longer processed as template strings.
|
||||
[jakearchibald]
|
||||
- Whitespace surrounding sections is no longer altered, per the spec.
|
||||
[heliodor]
|
||||
- Zeroes now render correctly when using PyPy. [alex]
|
||||
- Multline comments now permitted. [fczuardi]
|
||||
- Extensionless template files are now supported.
|
||||
- Passing ``**kwargs`` to ``Template()`` no longer modifies the
|
||||
context.
|
||||
- Passing ``**kwargs`` to ``Template()`` with no context no longer
|
||||
raises an exception.
|
||||
|
||||
0.4.1 (2012-03-25)
|
||||
------------------
|
||||
|
||||
- Added support for Python 2.4. [wangtz, jvantuyl]
|
||||
|
||||
0.4.0 (2011-01-12)
|
||||
------------------
|
||||
|
||||
- Add support for nested contexts (within template and view)
|
||||
- Add support for inverted lists
|
||||
- Decoupled template loading
|
||||
|
||||
0.3.1 (2010-05-07)
|
||||
------------------
|
||||
|
||||
- Fix package
|
||||
|
||||
0.3.0 (2010-05-03)
|
||||
------------------
|
||||
|
||||
- View.template\_path can now hold a list of path
|
||||
- Add {{& blah}} as an alias for {{{ blah }}}
|
||||
- Higher Order Sections
|
||||
- Inverted sections
|
||||
|
||||
0.2.0 (2010-02-15)
|
||||
------------------
|
||||
|
||||
- Bugfix: Methods returning False or None are not rendered
|
||||
- Bugfix: Don't render an empty string when a tag's value is 0.
|
||||
[enaeseth]
|
||||
- Add support for using non-callables as View attributes.
|
||||
[joshthecoder]
|
||||
- Allow using View instances as attributes. [joshthecoder]
|
||||
- Support for Unicode and non-ASCII-encoded bytestring output.
|
||||
[enaeseth]
|
||||
- Template file encoding awareness. [enaeseth]
|
||||
|
||||
0.1.1 (2009-11-13)
|
||||
------------------
|
||||
|
||||
- Ensure we're dealing with strings, always
|
||||
- Tests can be run by executing the test file directly
|
||||
|
||||
0.1.0 (2009-11-12)
|
||||
------------------
|
||||
|
||||
- First release
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
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
|
||||
"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 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.
|
||||
|
||||
.. |image0| image:: http://i.creativecommons.org/l/by-sa/3.0/88x31.png
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.4
|
||||
Classifier: Programming Language :: Python :: 2.5
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.1
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 170 KiB |
|
@ -43,7 +43,7 @@ def parse(template, delimiters=None):
|
|||
|
||||
def _compile_template_re(delimiters):
|
||||
"""
|
||||
Return a regular expression object (re.RegexObject) instance.
|
||||
Return a regular expresssion object (re.RegexObject) instance.
|
||||
|
||||
"""
|
||||
# The possible tag type characters following the opening tag,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
A rudimentary backward- and forward-compatible script to benchmark pystache.
|
||||
|
||||
Usage:
|
||||
|
||||
tests/benchmark.py 10000
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
from timeit import Timer
|
||||
|
||||
import pystache
|
||||
|
||||
# TODO: make the example realistic.
|
||||
|
||||
examples = [
|
||||
# Test case: 1
|
||||
("""{{#person}}Hi {{name}}{{/person}}""",
|
||||
{"person": {"name": "Jon"}},
|
||||
"Hi Jon"),
|
||||
|
||||
# Test case: 2
|
||||
("""\
|
||||
<div class="comments">
|
||||
<h3>{{header}}</h3>
|
||||
<ul>
|
||||
{{#comments}}<li class="comment">
|
||||
<h5>{{name}}</h5><p>{{body}}</p>
|
||||
</li>{{/comments}}
|
||||
</ul>
|
||||
</div>""",
|
||||
{'header': "My Post Comments",
|
||||
'comments': [
|
||||
{'name': "Joe", 'body': "Thanks for this post!"},
|
||||
{'name': "Sam", 'body': "Thanks for this post!"},
|
||||
{'name': "Heather", 'body': "Thanks for this post!"},
|
||||
{'name': "Kathy", 'body': "Thanks for this post!"},
|
||||
{'name': "George", 'body': "Thanks for this post!"}]},
|
||||
"""\
|
||||
<div class="comments">
|
||||
<h3>My Post Comments</h3>
|
||||
<ul>
|
||||
<li class="comment">
|
||||
<h5>Joe</h5><p>Thanks for this post!</p>
|
||||
</li><li class="comment">
|
||||
<h5>Sam</h5><p>Thanks for this post!</p>
|
||||
</li><li class="comment">
|
||||
<h5>Heather</h5><p>Thanks for this post!</p>
|
||||
</li><li class="comment">
|
||||
<h5>Kathy</h5><p>Thanks for this post!</p>
|
||||
</li><li class="comment">
|
||||
<h5>George</h5><p>Thanks for this post!</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>"""),
|
||||
]
|
||||
|
||||
|
||||
def make_test_function(example):
|
||||
|
||||
template, context, expected = example
|
||||
|
||||
def test():
|
||||
actual = pystache.render(template, context)
|
||||
if actual != expected:
|
||||
raise Exception("Benchmark mismatch: \n%s\n*** != ***\n%s" % (expected, actual))
|
||||
|
||||
return test
|
||||
|
||||
|
||||
def main(sys_argv):
|
||||
args = sys_argv[1:]
|
||||
count = int(args[0])
|
||||
|
||||
print "Benchmarking: %sx" % count
|
||||
print
|
||||
|
||||
for example in examples:
|
||||
|
||||
test = make_test_function(example)
|
||||
|
||||
t = Timer(test,)
|
||||
print min(t.repeat(repeat=3, number=count))
|
||||
|
||||
print "Done"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Provides test-related code that can be used by all tests.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pystache
|
||||
from pystache import defaults
|
||||
from pystache.tests import examples
|
||||
|
||||
# Save a reference to the original function to avoid recursion.
|
||||
_DEFAULT_TAG_ESCAPE = defaults.TAG_ESCAPE
|
||||
_TESTS_DIR = os.path.dirname(pystache.tests.__file__)
|
||||
|
||||
DATA_DIR = os.path.join(_TESTS_DIR, 'data') # i.e. 'pystache/tests/data'.
|
||||
EXAMPLES_DIR = os.path.dirname(examples.__file__)
|
||||
PACKAGE_DIR = os.path.dirname(pystache.__file__)
|
||||
PROJECT_DIR = os.path.join(PACKAGE_DIR, '..')
|
||||
# TEXT_DOCTEST_PATHS: the paths to text files (i.e. non-module files)
|
||||
# containing doctests. The paths should be relative to the project directory.
|
||||
TEXT_DOCTEST_PATHS = ['README.md']
|
||||
|
||||
UNITTEST_FILE_PREFIX = "test_"
|
||||
|
||||
|
||||
def get_spec_test_dir(project_dir):
|
||||
return os.path.join(project_dir, 'ext', 'spec', 'specs')
|
||||
|
||||
|
||||
def html_escape(u):
|
||||
"""
|
||||
An html escape function that behaves the same in both Python 2 and 3.
|
||||
|
||||
This function is needed because single quotes are escaped in Python 3
|
||||
(to '''), but not in Python 2.
|
||||
|
||||
The global defaults.TAG_ESCAPE can be set to this function in the
|
||||
setUp() and tearDown() of unittest test cases, for example, for
|
||||
consistent test results.
|
||||
|
||||
"""
|
||||
u = _DEFAULT_TAG_ESCAPE(u)
|
||||
return u.replace("'", ''')
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# Functions related to get_module_names().
|
||||
|
||||
def _find_files(root_dir, should_include):
|
||||
"""
|
||||
Return a list of paths to all modules below the given directory.
|
||||
|
||||
Arguments:
|
||||
|
||||
should_include: a function that accepts a file path and returns True or False.
|
||||
|
||||
"""
|
||||
paths = [] # Return value.
|
||||
|
||||
is_module = lambda path: path.endswith(".py")
|
||||
|
||||
# os.walk() is new in Python 2.3
|
||||
# http://docs.python.org/library/os.html#os.walk
|
||||
for dir_path, dir_names, file_names in os.walk(root_dir):
|
||||
new_paths = [os.path.join(dir_path, file_name) for file_name in file_names]
|
||||
new_paths = filter(is_module, new_paths)
|
||||
new_paths = filter(should_include, new_paths)
|
||||
paths.extend(new_paths)
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
def _make_module_names(package_dir, paths):
|
||||
"""
|
||||
Return a list of fully-qualified module names given a list of module paths.
|
||||
|
||||
"""
|
||||
package_dir = os.path.abspath(package_dir)
|
||||
package_name = os.path.split(package_dir)[1]
|
||||
|
||||
prefix_length = len(package_dir)
|
||||
|
||||
module_names = []
|
||||
for path in paths:
|
||||
path = os.path.abspath(path) # for example <path_to_package>/subpackage/module.py
|
||||
rel_path = path[prefix_length:] # for example /subpackage/module.py
|
||||
rel_path = os.path.splitext(rel_path)[0] # for example /subpackage/module
|
||||
|
||||
parts = []
|
||||
while True:
|
||||
(rel_path, tail) = os.path.split(rel_path)
|
||||
if not tail:
|
||||
break
|
||||
parts.insert(0, tail)
|
||||
# We now have, for example, ['subpackage', 'module'].
|
||||
parts.insert(0, package_name)
|
||||
module = ".".join(parts)
|
||||
module_names.append(module)
|
||||
|
||||
return module_names
|
||||
|
||||
|
||||
def get_module_names(package_dir=None, should_include=None):
|
||||
"""
|
||||
Return a list of fully-qualified module names in the given package.
|
||||
|
||||
"""
|
||||
if package_dir is None:
|
||||
package_dir = PACKAGE_DIR
|
||||
|
||||
if should_include is None:
|
||||
should_include = lambda path: True
|
||||
|
||||
paths = _find_files(package_dir, should_include)
|
||||
names = _make_module_names(package_dir, paths)
|
||||
names.sort()
|
||||
|
||||
return names
|
||||
|
||||
|
||||
class AssertStringMixin:
|
||||
|
||||
"""A unittest.TestCase mixin to check string equality."""
|
||||
|
||||
def assertString(self, actual, expected, format=None):
|
||||
"""
|
||||
Assert that the given strings are equal and have the same type.
|
||||
|
||||
Arguments:
|
||||
|
||||
format: a format string containing a single conversion specifier %s.
|
||||
Defaults to "%s".
|
||||
|
||||
"""
|
||||
if format is None:
|
||||
format = "%s"
|
||||
|
||||
# Show both friendly and literal versions.
|
||||
details = """String mismatch: %%s
|
||||
|
||||
Expected: \"""%s\"""
|
||||
Actual: \"""%s\"""
|
||||
|
||||
Expected: %s
|
||||
Actual: %s""" % (expected, actual, repr(expected), repr(actual))
|
||||
|
||||
def make_message(reason):
|
||||
description = details % reason
|
||||
return format % description
|
||||
|
||||
self.assertEqual(actual, expected, make_message("different characters"))
|
||||
|
||||
reason = "types different: %s != %s (actual)" % (repr(type(expected)), repr(type(actual)))
|
||||
self.assertEqual(type(expected), type(actual), make_message(reason))
|
||||
|
||||
|
||||
class AssertIsMixin:
|
||||
|
||||
"""A unittest.TestCase mixin adding assertIs()."""
|
||||
|
||||
# unittest.assertIs() is not available until Python 2.7:
|
||||
# http://docs.python.org/library/unittest.html#unittest.TestCase.assertIsNone
|
||||
def assertIs(self, first, second):
|
||||
self.assertTrue(first is second, msg="%s is not %s" % (repr(first), repr(second)))
|
||||
|
||||
|
||||
class AssertExceptionMixin:
|
||||
|
||||
"""A unittest.TestCase mixin adding assertException()."""
|
||||
|
||||
# unittest.assertRaisesRegexp() is not available until Python 2.7:
|
||||
# http://docs.python.org/library/unittest.html#unittest.TestCase.assertRaisesRegexp
|
||||
def assertException(self, exception_type, msg, callable, *args, **kwds):
|
||||
try:
|
||||
callable(*args, **kwds)
|
||||
raise Exception("Expected exception: %s: %s" % (exception_type, repr(msg)))
|
||||
except exception_type, err:
|
||||
self.assertEqual(str(err), msg)
|
||||
|
||||
|
||||
class SetupDefaults(object):
|
||||
|
||||
"""
|
||||
Mix this class in to a unittest.TestCase for standard defaults.
|
||||
|
||||
This class allows for consistent test results across Python 2/3.
|
||||
|
||||
"""
|
||||
|
||||
def setup_defaults(self):
|
||||
self.original_decode_errors = defaults.DECODE_ERRORS
|
||||
self.original_file_encoding = defaults.FILE_ENCODING
|
||||
self.original_string_encoding = defaults.STRING_ENCODING
|
||||
|
||||
defaults.DECODE_ERRORS = 'strict'
|
||||
defaults.FILE_ENCODING = 'ascii'
|
||||
defaults.STRING_ENCODING = 'ascii'
|
||||
|
||||
def teardown_defaults(self):
|
||||
defaults.DECODE_ERRORS = self.original_decode_errors
|
||||
defaults.FILE_ENCODING = self.original_file_encoding
|
||||
defaults.STRING_ENCODING = self.original_string_encoding
|
||||
|
||||
|
||||
class Attachable(object):
|
||||
"""
|
||||
A class that attaches all constructor named parameters as attributes.
|
||||
|
||||
For example--
|
||||
|
||||
>>> obj = Attachable(foo=42, size="of the universe")
|
||||
>>> repr(obj)
|
||||
"Attachable(foo=42, size='of the universe')"
|
||||
>>> obj.foo
|
||||
42
|
||||
>>> obj.size
|
||||
'of the universe'
|
||||
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
self.__args__ = kwargs
|
||||
for arg, value in kwargs.iteritems():
|
||||
setattr(self, arg, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__,
|
||||
", ".join("%s=%s" % (k, repr(v))
|
||||
for k, v in self.__args__.iteritems()))
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -0,0 +1 @@
|
|||
ascii: abc
|
|
@ -0,0 +1 @@
|
|||
This file is used to test locate_path()'s search order.
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
1
third_party/python/pystache/pystache/tests/data/locator/duplicate.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/data/locator/duplicate.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
This file is used to test locate_path()'s search order.
|
|
@ -0,0 +1 @@
|
|||
Test template file
|
|
@ -0,0 +1 @@
|
|||
non-ascii: é
|
|
@ -0,0 +1 @@
|
|||
ascii: abc
|
|
@ -0,0 +1 @@
|
|||
Hello, {{to}}
|
|
@ -0,0 +1,21 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class SayHello(object):
|
||||
|
||||
def to(self):
|
||||
return "World"
|
||||
|
||||
|
||||
class SampleView(TemplateSpec):
|
||||
pass
|
||||
|
||||
|
||||
class NonAscii(TemplateSpec):
|
||||
pass
|
|
@ -0,0 +1,94 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Exposes a get_doctests() function for the project's test harness.
|
||||
|
||||
"""
|
||||
|
||||
import doctest
|
||||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
# Then pull in modules needed for 2to3 conversion. The modules
|
||||
# below are not necessarily available in older versions of Python.
|
||||
from lib2to3.main import main as lib2to3main # new in Python 2.6?
|
||||
from shutil import copyfile
|
||||
|
||||
from pystache.tests.common import TEXT_DOCTEST_PATHS
|
||||
from pystache.tests.common import get_module_names
|
||||
|
||||
|
||||
# This module follows the guidance documented here:
|
||||
#
|
||||
# http://docs.python.org/library/doctest.html#unittest-api
|
||||
#
|
||||
|
||||
def get_doctests(text_file_dir):
|
||||
"""
|
||||
Return a list of TestSuite instances for all doctests in the project.
|
||||
|
||||
Arguments:
|
||||
|
||||
text_file_dir: the directory in which to search for all text files
|
||||
(i.e. non-module files) containing doctests.
|
||||
|
||||
"""
|
||||
# Since module_relative is False in our calls to DocFileSuite below,
|
||||
# paths should be OS-specific. See the following for more info--
|
||||
#
|
||||
# http://docs.python.org/library/doctest.html#doctest.DocFileSuite
|
||||
#
|
||||
paths = [os.path.normpath(os.path.join(text_file_dir, path)) for path in TEXT_DOCTEST_PATHS]
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
# Skip the README doctests in Python 3 for now because examples
|
||||
# rendering to unicode do not give consistent results
|
||||
# (e.g. 'foo' vs u'foo').
|
||||
# paths = _convert_paths(paths)
|
||||
paths = []
|
||||
|
||||
suites = []
|
||||
|
||||
for path in paths:
|
||||
suite = doctest.DocFileSuite(path, module_relative=False)
|
||||
suites.append(suite)
|
||||
|
||||
modules = get_module_names()
|
||||
for module in modules:
|
||||
suite = doctest.DocTestSuite(module)
|
||||
suites.append(suite)
|
||||
|
||||
return suites
|
||||
|
||||
|
||||
def _convert_2to3(path):
|
||||
"""
|
||||
Convert the given file, and return the path to the converted files.
|
||||
|
||||
"""
|
||||
base, ext = os.path.splitext(path)
|
||||
# For example, "README.temp2to3.rst".
|
||||
new_path = "%s.temp2to3%s" % (base, ext)
|
||||
|
||||
copyfile(path, new_path)
|
||||
|
||||
args = ['--doctests_only', '--no-diffs', '--write', '--nobackups', new_path]
|
||||
lib2to3main("lib2to3.fixes", args=args)
|
||||
|
||||
return new_path
|
||||
|
||||
|
||||
def _convert_paths(paths):
|
||||
"""
|
||||
Convert the given files, and return the paths to the converted files.
|
||||
|
||||
"""
|
||||
new_paths = []
|
||||
for path in paths:
|
||||
new_path = _convert_2to3(path)
|
||||
new_paths.append(new_path)
|
||||
|
||||
return new_paths
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -0,0 +1 @@
|
|||
<h1>{{title}}{{! just something interesting... #or not... }}</h1>
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Comments(object):
|
||||
|
||||
def title(self):
|
||||
return "A Comedy of Errors"
|
|
@ -0,0 +1,6 @@
|
|||
<h1>{{ header }}</h1>
|
||||
{{#list}}
|
||||
<ul>
|
||||
{{#item}}{{# current }}<li><strong>{{name}}</strong></li>
|
||||
{{/ current }}{{#link}}<li><a href="{{url}}">{{name}}</a></li>
|
||||
{{/link}}{{/item}}</ul>{{/list}}{{#empty}}<p>The list is empty.</p>{{/empty}}
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Complex(object):
|
||||
|
||||
def header(self):
|
||||
return "Colors"
|
||||
|
||||
def item(self):
|
||||
items = []
|
||||
items.append({ 'name': 'red', 'current': True, 'url': '#Red' })
|
||||
items.append({ 'name': 'green', 'link': True, 'url': '#Green' })
|
||||
items.append({ 'name': 'blue', 'link': True, 'url': '#Blue' })
|
||||
return items
|
||||
|
||||
def list(self):
|
||||
return not self.empty()
|
||||
|
||||
def empty(self):
|
||||
return len(self.item()) == 0
|
||||
|
||||
def empty_list(self):
|
||||
return [];
|
|
@ -0,0 +1,6 @@
|
|||
{{=<% %>=}}
|
||||
* <% first %>
|
||||
<%=| |=%>
|
||||
* | second |
|
||||
|={{ }}=|
|
||||
* {{ third }}
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Delimiters(object):
|
||||
|
||||
def first(self):
|
||||
return "It worked the first time."
|
||||
|
||||
def second(self):
|
||||
return "And it worked the second time."
|
||||
|
||||
def third(self):
|
||||
return "Then, surprisingly, it worked the third time."
|
3
third_party/python/pystache/pystache/tests/examples/double_section.mustache
поставляемый
Normal file
3
third_party/python/pystache/pystache/tests/examples/double_section.mustache
поставляемый
Normal file
|
@ -0,0 +1,3 @@
|
|||
{{#t}}* first{{/t}}
|
||||
* {{two}}
|
||||
{{#t}}* third{{/t}}
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class DoubleSection(object):
|
||||
|
||||
def t(self):
|
||||
return True
|
||||
|
||||
def two(self):
|
||||
return "second"
|
|
@ -0,0 +1 @@
|
|||
<h1>{{title}}</h1>
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Escaped(object):
|
||||
|
||||
def title(self):
|
||||
return "Bear > Shark"
|
1
third_party/python/pystache/pystache/tests/examples/inner_partial.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/inner_partial.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
Again, {{title}}!
|
|
@ -0,0 +1 @@
|
|||
## Again, {{title}}! ##
|
|
@ -0,0 +1 @@
|
|||
{{^f}}one{{/f}}, {{ two }}, {{^f}}three{{/f}}{{^t}}, four!{{/t}}{{^empty_list}}, empty list{{/empty_list}}{{^populated_list}}, shouldn't see me{{/populated_list}}
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class Inverted(object):
|
||||
|
||||
def t(self):
|
||||
return True
|
||||
|
||||
def f(self):
|
||||
return False
|
||||
|
||||
def two(self):
|
||||
return 'two'
|
||||
|
||||
def empty_list(self):
|
||||
return []
|
||||
|
||||
def populated_list(self):
|
||||
return ['some_value']
|
||||
|
||||
class InvertedLists(Inverted, TemplateSpec):
|
||||
template_name = 'inverted'
|
||||
|
||||
def t(self):
|
||||
return [0, 1, 2]
|
||||
|
||||
def f(self):
|
||||
return []
|
|
@ -0,0 +1 @@
|
|||
{{#replace_foo_with_bar}}foo != bar. oh, it does!{{/replace_foo_with_bar}}
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
def rot(s, n=13):
|
||||
r = ""
|
||||
for c in s:
|
||||
cc = c
|
||||
if cc.isalpha():
|
||||
cc = cc.lower()
|
||||
o = ord(cc)
|
||||
ro = (o+n) % 122
|
||||
if ro == 0: ro = 122
|
||||
if ro < 97: ro += 96
|
||||
cc = chr(ro)
|
||||
r = ''.join((r,cc))
|
||||
return r
|
||||
|
||||
def replace(subject, this='foo', with_this='bar'):
|
||||
return subject.replace(this, with_this)
|
||||
|
||||
|
||||
# This class subclasses TemplateSpec because at least one unit test
|
||||
# sets the template attribute.
|
||||
class Lambdas(TemplateSpec):
|
||||
|
||||
def replace_foo_with_bar(self, text=None):
|
||||
return replace
|
||||
|
||||
def rot13(self, text=None):
|
||||
return rot
|
||||
|
||||
def sort(self, text=None):
|
||||
return lambda text: ''.join(sorted(text))
|
1
third_party/python/pystache/pystache/tests/examples/looping_partial.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/looping_partial.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
Looping partial {{item}}!
|
1
third_party/python/pystache/pystache/tests/examples/nested_context.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/nested_context.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class NestedContext(TemplateSpec):
|
||||
|
||||
def __init__(self, renderer):
|
||||
self.renderer = renderer
|
||||
|
||||
def _context_get(self, key):
|
||||
return self.renderer.context.get(key)
|
||||
|
||||
def outer_thing(self):
|
||||
return "two"
|
||||
|
||||
def foo(self):
|
||||
return {'thing1': 'one', 'thing2': 'foo'}
|
||||
|
||||
def derp(self):
|
||||
return [{'inner': 'car'}]
|
||||
|
||||
def herp(self):
|
||||
return [{'outer': 'car'}]
|
||||
|
||||
def nested_context_in_view(self):
|
||||
if self._context_get('outer') == self._context_get('inner'):
|
||||
return 'it works!'
|
||||
return ''
|
1
third_party/python/pystache/pystache/tests/examples/partial_in_partial.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/partial_in_partial.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
{{>simple}}
|
1
third_party/python/pystache/pystache/tests/examples/partial_with_lambda.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/partial_with_lambda.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
{{#rot13}}abcdefghijklm{{/rot13}}
|
1
third_party/python/pystache/pystache/tests/examples/partial_with_partial_and_lambda.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/partial_with_partial_and_lambda.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
{{>partial_with_lambda}}{{#rot13}}abcdefghijklm{{/rot13}}
|
12
third_party/python/pystache/pystache/tests/examples/partials_with_lambdas.py
поставляемый
Normal file
12
third_party/python/pystache/pystache/tests/examples/partials_with_lambdas.py
поставляемый
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache.tests.examples.lambdas import rot
|
||||
|
||||
class PartialsWithLambdas(object):
|
||||
|
||||
def rot(self):
|
||||
return rot
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class SayHello(object):
|
||||
def to(self):
|
||||
return "Pizza"
|
|
@ -0,0 +1 @@
|
|||
Hello, {{to}}!
|
|
@ -0,0 +1 @@
|
|||
Hi {{thing}}!{{blank}}
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class Simple(TemplateSpec):
|
||||
|
||||
def thing(self):
|
||||
return "pizza"
|
||||
|
||||
def blank(self):
|
||||
return ''
|
|
@ -0,0 +1 @@
|
|||
No tags...
|
2
third_party/python/pystache/pystache/tests/examples/template_partial.mustache
поставляемый
Normal file
2
third_party/python/pystache/pystache/tests/examples/template_partial.mustache
поставляемый
Normal file
|
@ -0,0 +1,2 @@
|
|||
<h1>{{title}}</h1>
|
||||
{{>inner_partial}}
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class TemplatePartial(TemplateSpec):
|
||||
|
||||
def __init__(self, renderer):
|
||||
self.renderer = renderer
|
||||
|
||||
def _context_get(self, key):
|
||||
return self.renderer.context.get(key)
|
||||
|
||||
def title(self):
|
||||
return "Welcome"
|
||||
|
||||
def title_bars(self):
|
||||
return '-' * len(self.title())
|
||||
|
||||
def looping(self):
|
||||
return [{'item': 'one'}, {'item': 'two'}, {'item': 'three'}]
|
||||
|
||||
def thing(self):
|
||||
return self._context_get('prop')
|
|
@ -0,0 +1,4 @@
|
|||
{{title}}
|
||||
{{title_bars}}
|
||||
|
||||
{{>inner_partial}}
|
|
@ -0,0 +1 @@
|
|||
<h1>{{{title}}}</h1>
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Unescaped(object):
|
||||
|
||||
def title(self):
|
||||
return "Bear > Shark"
|
1
third_party/python/pystache/pystache/tests/examples/unicode_input.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/unicode_input.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
abcdé
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class UnicodeInput(TemplateSpec):
|
||||
|
||||
template_encoding = 'utf8'
|
||||
|
||||
def age(self):
|
||||
return 156
|
1
third_party/python/pystache/pystache/tests/examples/unicode_output.mustache
поставляемый
Normal file
1
third_party/python/pystache/pystache/tests/examples/unicode_output.mustache
поставляемый
Normal file
|
@ -0,0 +1 @@
|
|||
<p>Name: {{name}}</p>
|
|
@ -0,0 +1,11 @@
|
|||
# encoding: utf-8
|
||||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class UnicodeOutput(object):
|
||||
|
||||
def name(self):
|
||||
return u'Henri Poincaré'
|
|
@ -0,0 +1,190 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Exposes a main() function that runs all tests in the project.
|
||||
|
||||
This module is for our test console script.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import TestCase, TestProgram
|
||||
|
||||
import pystache
|
||||
from pystache.tests.common import PACKAGE_DIR, PROJECT_DIR, UNITTEST_FILE_PREFIX
|
||||
from pystache.tests.common import get_module_names, get_spec_test_dir
|
||||
from pystache.tests.doctesting import get_doctests
|
||||
from pystache.tests.spectesting import get_spec_tests
|
||||
|
||||
|
||||
# If this command option is present, then the spec test and doctest directories
|
||||
# will be inserted if not provided.
|
||||
FROM_SOURCE_OPTION = "--from-source"
|
||||
|
||||
|
||||
def make_extra_tests(text_doctest_dir, spec_test_dir):
|
||||
tests = []
|
||||
|
||||
if text_doctest_dir is not None:
|
||||
doctest_suites = get_doctests(text_doctest_dir)
|
||||
tests.extend(doctest_suites)
|
||||
|
||||
if spec_test_dir is not None:
|
||||
spec_testcases = get_spec_tests(spec_test_dir)
|
||||
tests.extend(spec_testcases)
|
||||
|
||||
return unittest.TestSuite(tests)
|
||||
|
||||
|
||||
def make_test_program_class(extra_tests):
|
||||
"""
|
||||
Return a subclass of unittest.TestProgram.
|
||||
|
||||
"""
|
||||
# The function unittest.main() is an alias for unittest.TestProgram's
|
||||
# constructor. TestProgram's constructor does the following:
|
||||
#
|
||||
# 1. calls self.parseArgs(argv),
|
||||
# 2. which in turn calls self.createTests().
|
||||
# 3. then the constructor calls self.runTests().
|
||||
#
|
||||
# The createTests() method sets the self.test attribute by calling one
|
||||
# of self.testLoader's "loadTests" methods. Each loadTest method returns
|
||||
# a unittest.TestSuite instance. Thus, self.test is set to a TestSuite
|
||||
# instance prior to calling runTests().
|
||||
class PystacheTestProgram(TestProgram):
|
||||
|
||||
"""
|
||||
Instantiating an instance of this class runs all tests.
|
||||
|
||||
"""
|
||||
|
||||
def createTests(self):
|
||||
"""
|
||||
Load tests and set self.test to a unittest.TestSuite instance
|
||||
|
||||
Compare--
|
||||
|
||||
http://docs.python.org/library/unittest.html#unittest.TestSuite
|
||||
|
||||
"""
|
||||
super(PystacheTestProgram, self).createTests()
|
||||
self.test.addTests(extra_tests)
|
||||
|
||||
return PystacheTestProgram
|
||||
|
||||
|
||||
# Do not include "test" in this function's name to avoid it getting
|
||||
# picked up by nosetests.
|
||||
def main(sys_argv):
|
||||
"""
|
||||
Run all tests in the project.
|
||||
|
||||
Arguments:
|
||||
|
||||
sys_argv: a reference to sys.argv.
|
||||
|
||||
"""
|
||||
# TODO: use logging module
|
||||
print "pystache: running tests: argv: %s" % repr(sys_argv)
|
||||
|
||||
should_source_exist = False
|
||||
spec_test_dir = None
|
||||
project_dir = None
|
||||
|
||||
if len(sys_argv) > 1 and sys_argv[1] == FROM_SOURCE_OPTION:
|
||||
# This usually means the test_pystache.py convenience script
|
||||
# in the source directory was run.
|
||||
should_source_exist = True
|
||||
sys_argv.pop(1)
|
||||
|
||||
try:
|
||||
# TODO: use optparse command options instead.
|
||||
project_dir = sys_argv[1]
|
||||
sys_argv.pop(1)
|
||||
except IndexError:
|
||||
if should_source_exist:
|
||||
project_dir = PROJECT_DIR
|
||||
|
||||
try:
|
||||
# TODO: use optparse command options instead.
|
||||
spec_test_dir = sys_argv[1]
|
||||
sys_argv.pop(1)
|
||||
except IndexError:
|
||||
if project_dir is not None:
|
||||
# Then auto-detect the spec test directory.
|
||||
_spec_test_dir = get_spec_test_dir(project_dir)
|
||||
if not os.path.exists(_spec_test_dir):
|
||||
# Then the user is probably using a downloaded sdist rather
|
||||
# than a repository clone (since the sdist does not include
|
||||
# the spec test directory).
|
||||
print("pystache: skipping spec tests: spec test directory "
|
||||
"not found")
|
||||
else:
|
||||
spec_test_dir = _spec_test_dir
|
||||
|
||||
if len(sys_argv) <= 1 or sys_argv[-1].startswith("-"):
|
||||
# Then no explicit module or test names were provided, so
|
||||
# auto-detect all unit tests.
|
||||
module_names = _discover_test_modules(PACKAGE_DIR)
|
||||
sys_argv.extend(module_names)
|
||||
if project_dir is not None:
|
||||
# Add the current module for unit tests contained here (e.g.
|
||||
# to include SetupTests).
|
||||
sys_argv.append(__name__)
|
||||
|
||||
SetupTests.project_dir = project_dir
|
||||
|
||||
extra_tests = make_extra_tests(project_dir, spec_test_dir)
|
||||
test_program_class = make_test_program_class(extra_tests)
|
||||
|
||||
# We pass None for the module because we do not want the unittest
|
||||
# module to resolve module names relative to a given module.
|
||||
# (This would require importing all of the unittest modules from
|
||||
# this module.) See the loadTestsFromName() method of the
|
||||
# unittest.TestLoader class for more details on this parameter.
|
||||
test_program_class(argv=sys_argv, module=None)
|
||||
# No need to return since unitttest.main() exits.
|
||||
|
||||
|
||||
def _discover_test_modules(package_dir):
|
||||
"""
|
||||
Discover and return a sorted list of the names of unit-test modules.
|
||||
|
||||
"""
|
||||
def is_unittest_module(path):
|
||||
file_name = os.path.basename(path)
|
||||
return file_name.startswith(UNITTEST_FILE_PREFIX)
|
||||
|
||||
names = get_module_names(package_dir=package_dir, should_include=is_unittest_module)
|
||||
|
||||
# This is a sanity check to ensure that the unit-test discovery
|
||||
# methods are working.
|
||||
if len(names) < 1:
|
||||
raise Exception("No unit-test modules found--\n in %s" % package_dir)
|
||||
|
||||
return names
|
||||
|
||||
|
||||
class SetupTests(TestCase):
|
||||
|
||||
"""Tests about setup.py."""
|
||||
|
||||
project_dir = None
|
||||
|
||||
def test_version(self):
|
||||
"""
|
||||
Test that setup.py's version matches the package's version.
|
||||
|
||||
"""
|
||||
original_path = list(sys.path)
|
||||
|
||||
sys.path.insert(0, self.project_dir)
|
||||
|
||||
try:
|
||||
from setup import VERSION
|
||||
self.assertEqual(VERSION, pystache.__version__)
|
||||
finally:
|
||||
sys.path = original_path
|
|
@ -0,0 +1,285 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Exposes a get_spec_tests() function for the project's test harness.
|
||||
|
||||
Creates a unittest.TestCase for the tests defined in the mustache spec.
|
||||
|
||||
"""
|
||||
|
||||
# TODO: this module can be cleaned up somewhat.
|
||||
# TODO: move all of this code to pystache/tests/spectesting.py and
|
||||
# have it expose a get_spec_tests(spec_test_dir) function.
|
||||
|
||||
FILE_ENCODING = 'utf-8' # the encoding of the spec test files.
|
||||
|
||||
yaml = None
|
||||
|
||||
try:
|
||||
# We try yaml first since it is more convenient when adding and modifying
|
||||
# test cases by hand (since the YAML is human-readable and is the master
|
||||
# from which the JSON format is generated).
|
||||
import yaml
|
||||
except ImportError:
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
# The module json is not available prior to Python 2.6, whereas
|
||||
# simplejson is. The simplejson package dropped support for Python 2.4
|
||||
# in simplejson v2.1.0, so Python 2.4 requires a simplejson install
|
||||
# older than the most recent version.
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
# Raise an error with a type different from ImportError as a hack around
|
||||
# this issue:
|
||||
# http://bugs.python.org/issue7559
|
||||
from sys import exc_info
|
||||
ex_type, ex_value, tb = exc_info()
|
||||
new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value))
|
||||
raise new_ex.__class__, new_ex, tb
|
||||
file_extension = 'json'
|
||||
parser = json
|
||||
else:
|
||||
file_extension = 'yml'
|
||||
parser = yaml
|
||||
|
||||
|
||||
import codecs
|
||||
import glob
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
import pystache
|
||||
from pystache import common
|
||||
from pystache.renderer import Renderer
|
||||
from pystache.tests.common import AssertStringMixin
|
||||
|
||||
|
||||
def get_spec_tests(spec_test_dir):
|
||||
"""
|
||||
Return a list of unittest.TestCase instances.
|
||||
|
||||
"""
|
||||
# TODO: use logging module instead.
|
||||
print "pystache: spec tests: using %s" % _get_parser_info()
|
||||
|
||||
cases = []
|
||||
|
||||
# Make this absolute for easier diagnosis in case of error.
|
||||
spec_test_dir = os.path.abspath(spec_test_dir)
|
||||
spec_paths = glob.glob(os.path.join(spec_test_dir, '*.%s' % file_extension))
|
||||
|
||||
for path in spec_paths:
|
||||
new_cases = _read_spec_tests(path)
|
||||
cases.extend(new_cases)
|
||||
|
||||
# Store this as a value so that CheckSpecTestsFound is not checking
|
||||
# a reference to cases that contains itself.
|
||||
spec_test_count = len(cases)
|
||||
|
||||
# This test case lets us alert the user that spec tests are missing.
|
||||
class CheckSpecTestsFound(unittest.TestCase):
|
||||
|
||||
def runTest(self):
|
||||
if spec_test_count > 0:
|
||||
return
|
||||
raise Exception("Spec tests not found--\n in %s\n"
|
||||
" Consult the README file on how to add the Mustache spec tests." % repr(spec_test_dir))
|
||||
|
||||
case = CheckSpecTestsFound()
|
||||
cases.append(case)
|
||||
|
||||
return cases
|
||||
|
||||
|
||||
def _get_parser_info():
|
||||
return "%s (version %s)" % (parser.__name__, parser.__version__)
|
||||
|
||||
|
||||
def _read_spec_tests(path):
|
||||
"""
|
||||
Return a list of unittest.TestCase instances.
|
||||
|
||||
"""
|
||||
b = common.read(path)
|
||||
u = unicode(b, encoding=FILE_ENCODING)
|
||||
spec_data = parse(u)
|
||||
tests = spec_data['tests']
|
||||
|
||||
cases = []
|
||||
for data in tests:
|
||||
case = _deserialize_spec_test(data, path)
|
||||
cases.append(case)
|
||||
|
||||
return cases
|
||||
|
||||
|
||||
# TODO: simplify the implementation of this function.
|
||||
def _convert_children(node):
|
||||
"""
|
||||
Recursively convert to functions all "code strings" below the node.
|
||||
|
||||
This function is needed only for the json format.
|
||||
|
||||
"""
|
||||
if not isinstance(node, (list, dict)):
|
||||
# Then there is nothing to iterate over and recurse.
|
||||
return
|
||||
|
||||
if isinstance(node, list):
|
||||
for child in node:
|
||||
_convert_children(child)
|
||||
return
|
||||
# Otherwise, node is a dict, so attempt the conversion.
|
||||
|
||||
for key in node.keys():
|
||||
val = node[key]
|
||||
|
||||
if not isinstance(val, dict) or val.get('__tag__') != 'code':
|
||||
_convert_children(val)
|
||||
continue
|
||||
# Otherwise, we are at a "leaf" node.
|
||||
|
||||
val = eval(val['python'])
|
||||
node[key] = val
|
||||
continue
|
||||
|
||||
|
||||
def _deserialize_spec_test(data, file_path):
|
||||
"""
|
||||
Return a unittest.TestCase instance representing a spec test.
|
||||
|
||||
Arguments:
|
||||
|
||||
data: the dictionary of attributes for a single test.
|
||||
|
||||
"""
|
||||
context = data['data']
|
||||
description = data['desc']
|
||||
# PyYAML seems to leave ASCII strings as byte strings.
|
||||
expected = unicode(data['expected'])
|
||||
# TODO: switch to using dict.get().
|
||||
partials = data.has_key('partials') and data['partials'] or {}
|
||||
template = data['template']
|
||||
test_name = data['name']
|
||||
|
||||
_convert_children(context)
|
||||
|
||||
test_case = _make_spec_test(expected, template, context, partials, description, test_name, file_path)
|
||||
|
||||
return test_case
|
||||
|
||||
|
||||
def _make_spec_test(expected, template, context, partials, description, test_name, file_path):
|
||||
"""
|
||||
Return a unittest.TestCase instance representing a spec test.
|
||||
|
||||
"""
|
||||
file_name = os.path.basename(file_path)
|
||||
test_method_name = "Mustache spec (%s): %s" % (file_name, repr(test_name))
|
||||
|
||||
# We subclass SpecTestBase in order to control the test method name (for
|
||||
# the purposes of improved reporting).
|
||||
class SpecTest(SpecTestBase):
|
||||
pass
|
||||
|
||||
def run_test(self):
|
||||
self._runTest()
|
||||
|
||||
# TODO: should we restore this logic somewhere?
|
||||
# If we don't convert unicode to str, we get the following error:
|
||||
# "TypeError: __name__ must be set to a string object"
|
||||
# test.__name__ = str(name)
|
||||
setattr(SpecTest, test_method_name, run_test)
|
||||
case = SpecTest(test_method_name)
|
||||
|
||||
case._context = context
|
||||
case._description = description
|
||||
case._expected = expected
|
||||
case._file_path = file_path
|
||||
case._partials = partials
|
||||
case._template = template
|
||||
case._test_name = test_name
|
||||
|
||||
return case
|
||||
|
||||
|
||||
def parse(u):
|
||||
"""
|
||||
Parse the contents of a spec test file, and return a dict.
|
||||
|
||||
Arguments:
|
||||
|
||||
u: a unicode string.
|
||||
|
||||
"""
|
||||
# TODO: find a cleaner mechanism for choosing between the two.
|
||||
if yaml is None:
|
||||
# Then use json.
|
||||
|
||||
# The only way to get the simplejson module to return unicode strings
|
||||
# is to pass it unicode. See, for example--
|
||||
#
|
||||
# http://code.google.com/p/simplejson/issues/detail?id=40
|
||||
#
|
||||
# and the documentation of simplejson.loads():
|
||||
#
|
||||
# "If s is a str then decoded JSON strings that contain only ASCII
|
||||
# characters may be parsed as str for performance and memory reasons.
|
||||
# If your code expects only unicode the appropriate solution is
|
||||
# decode s to unicode prior to calling loads."
|
||||
#
|
||||
return json.loads(u)
|
||||
# Otherwise, yaml.
|
||||
|
||||
def code_constructor(loader, node):
|
||||
value = loader.construct_mapping(node)
|
||||
return eval(value['python'], {})
|
||||
|
||||
yaml.add_constructor(u'!code', code_constructor)
|
||||
return yaml.load(u)
|
||||
|
||||
|
||||
class SpecTestBase(unittest.TestCase, AssertStringMixin):
|
||||
|
||||
def _runTest(self):
|
||||
context = self._context
|
||||
description = self._description
|
||||
expected = self._expected
|
||||
file_path = self._file_path
|
||||
partials = self._partials
|
||||
template = self._template
|
||||
test_name = self._test_name
|
||||
|
||||
renderer = Renderer(partials=partials)
|
||||
actual = renderer.render(template, context)
|
||||
|
||||
# We need to escape the strings that occur in our format string because
|
||||
# they can contain % symbols, for example (in delimiters.yml)--
|
||||
#
|
||||
# "template: '{{=<% %>=}}(<%text%>)'"
|
||||
#
|
||||
def escape(s):
|
||||
return s.replace("%", "%%")
|
||||
|
||||
parser_info = _get_parser_info()
|
||||
subs = [repr(test_name), description, os.path.abspath(file_path),
|
||||
template, repr(context), parser_info]
|
||||
subs = tuple([escape(sub) for sub in subs])
|
||||
# We include the parsing module version info to help with troubleshooting
|
||||
# yaml/json/simplejson issues.
|
||||
message = """%s: %s
|
||||
|
||||
File: %s
|
||||
|
||||
Template: \"""%s\"""
|
||||
|
||||
Context: %s
|
||||
|
||||
%%s
|
||||
|
||||
[using %s]
|
||||
""" % subs
|
||||
|
||||
self.assertString(actual, expected, format=message)
|
|
@ -0,0 +1,36 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Tests of __init__.py.
|
||||
|
||||
"""
|
||||
|
||||
# Calling "import *" is allowed only at the module level.
|
||||
GLOBALS_INITIAL = globals().keys()
|
||||
from pystache import *
|
||||
GLOBALS_PYSTACHE_IMPORTED = globals().keys()
|
||||
|
||||
import unittest
|
||||
|
||||
import pystache
|
||||
|
||||
|
||||
class InitTests(unittest.TestCase):
|
||||
|
||||
def test___all__(self):
|
||||
"""
|
||||
Test that "from pystache import *" works as expected.
|
||||
|
||||
"""
|
||||
actual = set(GLOBALS_PYSTACHE_IMPORTED) - set(GLOBALS_INITIAL)
|
||||
expected = set(['parse', 'render', 'Renderer', 'TemplateSpec', 'GLOBALS_INITIAL'])
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_version_defined(self):
|
||||
"""
|
||||
Test that pystache.__version__ is set.
|
||||
|
||||
"""
|
||||
actual_version = pystache.__version__
|
||||
self.assertTrue(actual_version)
|
|
@ -0,0 +1,45 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests of commands.py.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from pystache.commands.render import main
|
||||
|
||||
|
||||
ORIGINAL_STDOUT = sys.stdout
|
||||
|
||||
|
||||
class MockStdout(object):
|
||||
|
||||
def __init__(self):
|
||||
self.output = ""
|
||||
|
||||
def write(self, str):
|
||||
self.output += str
|
||||
|
||||
|
||||
class CommandsTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
sys.stdout = MockStdout()
|
||||
|
||||
def callScript(self, template, context):
|
||||
argv = ['pystache', template, context]
|
||||
main(argv)
|
||||
return sys.stdout.output
|
||||
|
||||
def testMainSimple(self):
|
||||
"""
|
||||
Test a simple command-line case.
|
||||
|
||||
"""
|
||||
actual = self.callScript("Hi {{thing}}", '{"thing": "world"}')
|
||||
self.assertEqual(actual, u"Hi world\n")
|
||||
|
||||
def tearDown(self):
|
||||
sys.stdout = ORIGINAL_STDOUT
|
|
@ -0,0 +1,499 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests of context.py.
|
||||
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import unittest
|
||||
|
||||
from pystache.context import _NOT_FOUND, _get_value, KeyNotFoundError, ContextStack
|
||||
from pystache.tests.common import AssertIsMixin, AssertStringMixin, AssertExceptionMixin, Attachable
|
||||
|
||||
class SimpleObject(object):
|
||||
|
||||
"""A sample class that does not define __getitem__()."""
|
||||
|
||||
def __init__(self):
|
||||
self.foo = "bar"
|
||||
|
||||
def foo_callable(self):
|
||||
return "called..."
|
||||
|
||||
|
||||
class DictLike(object):
|
||||
|
||||
"""A sample class that implements __getitem__() and __contains__()."""
|
||||
|
||||
def __init__(self):
|
||||
self._dict = {'foo': 'bar'}
|
||||
self.fuzz = 'buzz'
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._dict
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._dict[key]
|
||||
|
||||
|
||||
class GetValueTestCase(unittest.TestCase, AssertIsMixin):
|
||||
|
||||
"""Test context._get_value()."""
|
||||
|
||||
def assertNotFound(self, item, key):
|
||||
"""
|
||||
Assert that a call to _get_value() returns _NOT_FOUND.
|
||||
|
||||
"""
|
||||
self.assertIs(_get_value(item, key), _NOT_FOUND)
|
||||
|
||||
### Case: the item is a dictionary.
|
||||
|
||||
def test_dictionary__key_present(self):
|
||||
"""
|
||||
Test getting a key from a dictionary.
|
||||
|
||||
"""
|
||||
item = {"foo": "bar"}
|
||||
self.assertEqual(_get_value(item, "foo"), "bar")
|
||||
|
||||
def test_dictionary__callable_not_called(self):
|
||||
"""
|
||||
Test that callable values are returned as-is (and in particular not called).
|
||||
|
||||
"""
|
||||
def foo_callable(self):
|
||||
return "bar"
|
||||
|
||||
item = {"foo": foo_callable}
|
||||
self.assertNotEqual(_get_value(item, "foo"), "bar")
|
||||
self.assertTrue(_get_value(item, "foo") is foo_callable)
|
||||
|
||||
def test_dictionary__key_missing(self):
|
||||
"""
|
||||
Test getting a missing key from a dictionary.
|
||||
|
||||
"""
|
||||
item = {}
|
||||
self.assertNotFound(item, "missing")
|
||||
|
||||
def test_dictionary__attributes_not_checked(self):
|
||||
"""
|
||||
Test that dictionary attributes are not checked.
|
||||
|
||||
"""
|
||||
item = {1: 2, 3: 4}
|
||||
# I was not able to find a "public" attribute of dict that is
|
||||
# the same across Python 2/3.
|
||||
attr_name = "__len__"
|
||||
self.assertEqual(getattr(item, attr_name)(), 2)
|
||||
self.assertNotFound(item, attr_name)
|
||||
|
||||
def test_dictionary__dict_subclass(self):
|
||||
"""
|
||||
Test that subclasses of dict are treated as dictionaries.
|
||||
|
||||
"""
|
||||
class DictSubclass(dict): pass
|
||||
|
||||
item = DictSubclass()
|
||||
item["foo"] = "bar"
|
||||
|
||||
self.assertEqual(_get_value(item, "foo"), "bar")
|
||||
|
||||
### Case: the item is an object.
|
||||
|
||||
def test_object__attribute_present(self):
|
||||
"""
|
||||
Test getting an attribute from an object.
|
||||
|
||||
"""
|
||||
item = SimpleObject()
|
||||
self.assertEqual(_get_value(item, "foo"), "bar")
|
||||
|
||||
def test_object__attribute_missing(self):
|
||||
"""
|
||||
Test getting a missing attribute from an object.
|
||||
|
||||
"""
|
||||
item = SimpleObject()
|
||||
self.assertNotFound(item, "missing")
|
||||
|
||||
def test_object__attribute_is_callable(self):
|
||||
"""
|
||||
Test getting a callable attribute from an object.
|
||||
|
||||
"""
|
||||
item = SimpleObject()
|
||||
self.assertEqual(_get_value(item, "foo_callable"), "called...")
|
||||
|
||||
def test_object__non_built_in_type(self):
|
||||
"""
|
||||
Test getting an attribute from an instance of a type that isn't built-in.
|
||||
|
||||
"""
|
||||
item = datetime(2012, 1, 2)
|
||||
self.assertEqual(_get_value(item, "day"), 2)
|
||||
|
||||
def test_object__dict_like(self):
|
||||
"""
|
||||
Test getting a key from a dict-like object (an object that implements '__getitem__').
|
||||
|
||||
"""
|
||||
item = DictLike()
|
||||
self.assertEqual(item["foo"], "bar")
|
||||
self.assertNotFound(item, "foo")
|
||||
|
||||
def test_object__property__raising_exception(self):
|
||||
"""
|
||||
Test getting a property that raises an exception.
|
||||
|
||||
"""
|
||||
class Foo(object):
|
||||
|
||||
@property
|
||||
def bar(self):
|
||||
return 1
|
||||
|
||||
@property
|
||||
def baz(self):
|
||||
raise ValueError("test")
|
||||
|
||||
foo = Foo()
|
||||
self.assertEqual(_get_value(foo, 'bar'), 1)
|
||||
self.assertNotFound(foo, 'missing')
|
||||
self.assertRaises(ValueError, _get_value, foo, 'baz')
|
||||
|
||||
### Case: the item is an instance of a built-in type.
|
||||
|
||||
def test_built_in_type__integer(self):
|
||||
"""
|
||||
Test getting from an integer.
|
||||
|
||||
"""
|
||||
class MyInt(int): pass
|
||||
|
||||
cust_int = MyInt(10)
|
||||
pure_int = 10
|
||||
|
||||
# We have to use a built-in method like __neg__ because "public"
|
||||
# attributes like "real" were not added to Python until Python 2.6,
|
||||
# when the numeric type hierarchy was added:
|
||||
#
|
||||
# http://docs.python.org/library/numbers.html
|
||||
#
|
||||
self.assertEqual(cust_int.__neg__(), -10)
|
||||
self.assertEqual(pure_int.__neg__(), -10)
|
||||
|
||||
self.assertEqual(_get_value(cust_int, '__neg__'), -10)
|
||||
self.assertNotFound(pure_int, '__neg__')
|
||||
|
||||
def test_built_in_type__string(self):
|
||||
"""
|
||||
Test getting from a string.
|
||||
|
||||
"""
|
||||
class MyStr(str): pass
|
||||
|
||||
item1 = MyStr('abc')
|
||||
item2 = 'abc'
|
||||
|
||||
self.assertEqual(item1.upper(), 'ABC')
|
||||
self.assertEqual(item2.upper(), 'ABC')
|
||||
|
||||
self.assertEqual(_get_value(item1, 'upper'), 'ABC')
|
||||
self.assertNotFound(item2, 'upper')
|
||||
|
||||
def test_built_in_type__list(self):
|
||||
"""
|
||||
Test getting from a list.
|
||||
|
||||
"""
|
||||
class MyList(list): pass
|
||||
|
||||
item1 = MyList([1, 2, 3])
|
||||
item2 = [1, 2, 3]
|
||||
|
||||
self.assertEqual(item1.pop(), 3)
|
||||
self.assertEqual(item2.pop(), 3)
|
||||
|
||||
self.assertEqual(_get_value(item1, 'pop'), 2)
|
||||
self.assertNotFound(item2, 'pop')
|
||||
|
||||
|
||||
class ContextStackTestCase(unittest.TestCase, AssertIsMixin, AssertStringMixin,
|
||||
AssertExceptionMixin):
|
||||
|
||||
"""
|
||||
Test the ContextStack class.
|
||||
|
||||
"""
|
||||
|
||||
def test_init__no_elements(self):
|
||||
"""
|
||||
Check that passing nothing to __init__() raises no exception.
|
||||
|
||||
"""
|
||||
context = ContextStack()
|
||||
|
||||
def test_init__many_elements(self):
|
||||
"""
|
||||
Check that passing more than two items to __init__() raises no exception.
|
||||
|
||||
"""
|
||||
context = ContextStack({}, {}, {})
|
||||
|
||||
def test__repr(self):
|
||||
context = ContextStack()
|
||||
self.assertEqual(repr(context), 'ContextStack()')
|
||||
|
||||
context = ContextStack({'foo': 'bar'})
|
||||
self.assertEqual(repr(context), "ContextStack({'foo': 'bar'},)")
|
||||
|
||||
context = ContextStack({'foo': 'bar'}, {'abc': 123})
|
||||
self.assertEqual(repr(context), "ContextStack({'foo': 'bar'}, {'abc': 123})")
|
||||
|
||||
def test__str(self):
|
||||
context = ContextStack()
|
||||
self.assertEqual(str(context), 'ContextStack()')
|
||||
|
||||
context = ContextStack({'foo': 'bar'})
|
||||
self.assertEqual(str(context), "ContextStack({'foo': 'bar'},)")
|
||||
|
||||
context = ContextStack({'foo': 'bar'}, {'abc': 123})
|
||||
self.assertEqual(str(context), "ContextStack({'foo': 'bar'}, {'abc': 123})")
|
||||
|
||||
## Test the static create() method.
|
||||
|
||||
def test_create__dictionary(self):
|
||||
"""
|
||||
Test passing a dictionary.
|
||||
|
||||
"""
|
||||
context = ContextStack.create({'foo': 'bar'})
|
||||
self.assertEqual(context.get('foo'), 'bar')
|
||||
|
||||
def test_create__none(self):
|
||||
"""
|
||||
Test passing None.
|
||||
|
||||
"""
|
||||
context = ContextStack.create({'foo': 'bar'}, None)
|
||||
self.assertEqual(context.get('foo'), 'bar')
|
||||
|
||||
def test_create__object(self):
|
||||
"""
|
||||
Test passing an object.
|
||||
|
||||
"""
|
||||
class Foo(object):
|
||||
foo = 'bar'
|
||||
context = ContextStack.create(Foo())
|
||||
self.assertEqual(context.get('foo'), 'bar')
|
||||
|
||||
def test_create__context(self):
|
||||
"""
|
||||
Test passing a ContextStack instance.
|
||||
|
||||
"""
|
||||
obj = ContextStack({'foo': 'bar'})
|
||||
context = ContextStack.create(obj)
|
||||
self.assertEqual(context.get('foo'), 'bar')
|
||||
|
||||
def test_create__kwarg(self):
|
||||
"""
|
||||
Test passing a keyword argument.
|
||||
|
||||
"""
|
||||
context = ContextStack.create(foo='bar')
|
||||
self.assertEqual(context.get('foo'), 'bar')
|
||||
|
||||
def test_create__precedence_positional(self):
|
||||
"""
|
||||
Test precedence of positional arguments.
|
||||
|
||||
"""
|
||||
context = ContextStack.create({'foo': 'bar'}, {'foo': 'buzz'})
|
||||
self.assertEqual(context.get('foo'), 'buzz')
|
||||
|
||||
def test_create__precedence_keyword(self):
|
||||
"""
|
||||
Test precedence of keyword arguments.
|
||||
|
||||
"""
|
||||
context = ContextStack.create({'foo': 'bar'}, foo='buzz')
|
||||
self.assertEqual(context.get('foo'), 'buzz')
|
||||
|
||||
## Test the get() method.
|
||||
|
||||
def test_get__single_dot(self):
|
||||
"""
|
||||
Test getting a single dot (".").
|
||||
|
||||
"""
|
||||
context = ContextStack("a", "b")
|
||||
self.assertEqual(context.get("."), "b")
|
||||
|
||||
def test_get__single_dot__missing(self):
|
||||
"""
|
||||
Test getting a single dot (".") with an empty context stack.
|
||||
|
||||
"""
|
||||
context = ContextStack()
|
||||
self.assertException(KeyNotFoundError, "Key '.' not found: empty context stack", context.get, ".")
|
||||
|
||||
def test_get__key_present(self):
|
||||
"""
|
||||
Test getting a key.
|
||||
|
||||
"""
|
||||
context = ContextStack({"foo": "bar"})
|
||||
self.assertEqual(context.get("foo"), "bar")
|
||||
|
||||
def test_get__key_missing(self):
|
||||
"""
|
||||
Test getting a missing key.
|
||||
|
||||
"""
|
||||
context = ContextStack()
|
||||
self.assertException(KeyNotFoundError, "Key 'foo' not found: first part", context.get, "foo")
|
||||
|
||||
def test_get__precedence(self):
|
||||
"""
|
||||
Test that get() respects the order of precedence (later items first).
|
||||
|
||||
"""
|
||||
context = ContextStack({"foo": "bar"}, {"foo": "buzz"})
|
||||
self.assertEqual(context.get("foo"), "buzz")
|
||||
|
||||
def test_get__fallback(self):
|
||||
"""
|
||||
Check that first-added stack items are queried on context misses.
|
||||
|
||||
"""
|
||||
context = ContextStack({"fuzz": "buzz"}, {"foo": "bar"})
|
||||
self.assertEqual(context.get("fuzz"), "buzz")
|
||||
|
||||
def test_push(self):
|
||||
"""
|
||||
Test push().
|
||||
|
||||
"""
|
||||
key = "foo"
|
||||
context = ContextStack({key: "bar"})
|
||||
self.assertEqual(context.get(key), "bar")
|
||||
|
||||
context.push({key: "buzz"})
|
||||
self.assertEqual(context.get(key), "buzz")
|
||||
|
||||
def test_pop(self):
|
||||
"""
|
||||
Test pop().
|
||||
|
||||
"""
|
||||
key = "foo"
|
||||
context = ContextStack({key: "bar"}, {key: "buzz"})
|
||||
self.assertEqual(context.get(key), "buzz")
|
||||
|
||||
item = context.pop()
|
||||
self.assertEqual(item, {"foo": "buzz"})
|
||||
self.assertEqual(context.get(key), "bar")
|
||||
|
||||
def test_top(self):
|
||||
key = "foo"
|
||||
context = ContextStack({key: "bar"}, {key: "buzz"})
|
||||
self.assertEqual(context.get(key), "buzz")
|
||||
|
||||
top = context.top()
|
||||
self.assertEqual(top, {"foo": "buzz"})
|
||||
# Make sure calling top() didn't remove the item from the stack.
|
||||
self.assertEqual(context.get(key), "buzz")
|
||||
|
||||
def test_copy(self):
|
||||
key = "foo"
|
||||
original = ContextStack({key: "bar"}, {key: "buzz"})
|
||||
self.assertEqual(original.get(key), "buzz")
|
||||
|
||||
new = original.copy()
|
||||
# Confirm that the copy behaves the same.
|
||||
self.assertEqual(new.get(key), "buzz")
|
||||
# Change the copy, and confirm it is changed.
|
||||
new.pop()
|
||||
self.assertEqual(new.get(key), "bar")
|
||||
# Confirm the original is unchanged.
|
||||
self.assertEqual(original.get(key), "buzz")
|
||||
|
||||
def test_dot_notation__dict(self):
|
||||
name = "foo.bar"
|
||||
stack = ContextStack({"foo": {"bar": "baz"}})
|
||||
self.assertEqual(stack.get(name), "baz")
|
||||
|
||||
# Works all the way down
|
||||
name = "a.b.c.d.e.f.g"
|
||||
stack = ContextStack({"a": {"b": {"c": {"d": {"e": {"f": {"g": "w00t!"}}}}}}})
|
||||
self.assertEqual(stack.get(name), "w00t!")
|
||||
|
||||
def test_dot_notation__user_object(self):
|
||||
name = "foo.bar"
|
||||
stack = ContextStack({"foo": Attachable(bar="baz")})
|
||||
self.assertEqual(stack.get(name), "baz")
|
||||
|
||||
# Works on multiple levels, too
|
||||
name = "a.b.c.d.e.f.g"
|
||||
A = Attachable
|
||||
stack = ContextStack({"a": A(b=A(c=A(d=A(e=A(f=A(g="w00t!"))))))})
|
||||
self.assertEqual(stack.get(name), "w00t!")
|
||||
|
||||
def test_dot_notation__mixed_dict_and_obj(self):
|
||||
name = "foo.bar.baz.bak"
|
||||
stack = ContextStack({"foo": Attachable(bar={"baz": Attachable(bak=42)})})
|
||||
self.assertEqual(stack.get(name), 42)
|
||||
|
||||
def test_dot_notation__missing_attr_or_key(self):
|
||||
name = "foo.bar.baz.bak"
|
||||
stack = ContextStack({"foo": {"bar": {}}})
|
||||
self.assertException(KeyNotFoundError, "Key 'foo.bar.baz.bak' not found: missing 'baz'", stack.get, name)
|
||||
|
||||
stack = ContextStack({"foo": Attachable(bar=Attachable())})
|
||||
self.assertException(KeyNotFoundError, "Key 'foo.bar.baz.bak' not found: missing 'baz'", stack.get, name)
|
||||
|
||||
def test_dot_notation__missing_part_terminates_search(self):
|
||||
"""
|
||||
Test that dotted name resolution terminates on a later part not found.
|
||||
|
||||
Check that if a later dotted name part is not found in the result from
|
||||
the former resolution, then name resolution terminates rather than
|
||||
starting the search over with the next element of the context stack.
|
||||
From the spec (interpolation section)--
|
||||
|
||||
5) If any name parts were retained in step 1, each should be resolved
|
||||
against a context stack containing only the result from the former
|
||||
resolution. If any part fails resolution, the result should be considered
|
||||
falsey, and should interpolate as the empty string.
|
||||
|
||||
This test case is equivalent to the test case in the following pull
|
||||
request:
|
||||
|
||||
https://github.com/mustache/spec/pull/48
|
||||
|
||||
"""
|
||||
stack = ContextStack({'a': {'b': 'A.B'}}, {'a': 'A'})
|
||||
self.assertEqual(stack.get('a'), 'A')
|
||||
self.assertException(KeyNotFoundError, "Key 'a.b' not found: missing 'b'", stack.get, "a.b")
|
||||
stack.pop()
|
||||
self.assertEqual(stack.get('a.b'), 'A.B')
|
||||
|
||||
def test_dot_notation__autocall(self):
|
||||
name = "foo.bar.baz"
|
||||
|
||||
# When any element in the path is callable, it should be automatically invoked
|
||||
stack = ContextStack({"foo": Attachable(bar=Attachable(baz=lambda: "Called!"))})
|
||||
self.assertEqual(stack.get(name), "Called!")
|
||||
|
||||
class Foo(object):
|
||||
def bar(self):
|
||||
return Attachable(baz='Baz')
|
||||
|
||||
stack = ContextStack({"foo": Foo()})
|
||||
self.assertEqual(stack.get(name), "Baz")
|
|
@ -0,0 +1,68 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests for defaults.py.
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
import pystache
|
||||
|
||||
from pystache.tests.common import AssertStringMixin
|
||||
|
||||
|
||||
# TODO: make sure each default has at least one test.
|
||||
class DefaultsConfigurableTestCase(unittest.TestCase, AssertStringMixin):
|
||||
|
||||
"""Tests that the user can change the defaults at runtime."""
|
||||
|
||||
# TODO: switch to using a context manager after 2.4 is deprecated.
|
||||
def setUp(self):
|
||||
"""Save the defaults."""
|
||||
defaults = [
|
||||
'DECODE_ERRORS', 'DELIMITERS',
|
||||
'FILE_ENCODING', 'MISSING_TAGS',
|
||||
'SEARCH_DIRS', 'STRING_ENCODING',
|
||||
'TAG_ESCAPE', 'TEMPLATE_EXTENSION'
|
||||
]
|
||||
self.saved = {}
|
||||
for e in defaults:
|
||||
self.saved[e] = getattr(pystache.defaults, e)
|
||||
|
||||
def tearDown(self):
|
||||
for key, value in self.saved.items():
|
||||
setattr(pystache.defaults, key, value)
|
||||
|
||||
def test_tag_escape(self):
|
||||
"""Test that changes to defaults.TAG_ESCAPE take effect."""
|
||||
template = u"{{foo}}"
|
||||
context = {'foo': '<'}
|
||||
actual = pystache.render(template, context)
|
||||
self.assertString(actual, u"<")
|
||||
|
||||
pystache.defaults.TAG_ESCAPE = lambda u: u
|
||||
actual = pystache.render(template, context)
|
||||
self.assertString(actual, u"<")
|
||||
|
||||
def test_delimiters(self):
|
||||
"""Test that changes to defaults.DELIMITERS take effect."""
|
||||
template = u"[[foo]]{{foo}}"
|
||||
context = {'foo': 'FOO'}
|
||||
actual = pystache.render(template, context)
|
||||
self.assertString(actual, u"[[foo]]FOO")
|
||||
|
||||
pystache.defaults.DELIMITERS = ('[[', ']]')
|
||||
actual = pystache.render(template, context)
|
||||
self.assertString(actual, u"FOO{{foo}}")
|
||||
|
||||
def test_missing_tags(self):
|
||||
"""Test that changes to defaults.MISSING_TAGS take effect."""
|
||||
template = u"{{foo}}"
|
||||
context = {}
|
||||
actual = pystache.render(template, context)
|
||||
self.assertString(actual, u"")
|
||||
|
||||
pystache.defaults.MISSING_TAGS = 'strict'
|
||||
self.assertRaises(pystache.context.KeyNotFoundError,
|
||||
pystache.render, template, context)
|
|
@ -0,0 +1,106 @@
|
|||
# encoding: utf-8
|
||||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from examples.comments import Comments
|
||||
from examples.double_section import DoubleSection
|
||||
from examples.escaped import Escaped
|
||||
from examples.unescaped import Unescaped
|
||||
from examples.template_partial import TemplatePartial
|
||||
from examples.delimiters import Delimiters
|
||||
from examples.unicode_output import UnicodeOutput
|
||||
from examples.unicode_input import UnicodeInput
|
||||
from examples.nested_context import NestedContext
|
||||
from pystache import Renderer
|
||||
from pystache.tests.common import EXAMPLES_DIR
|
||||
from pystache.tests.common import AssertStringMixin
|
||||
|
||||
|
||||
class TestView(unittest.TestCase, AssertStringMixin):
|
||||
|
||||
def _assert(self, obj, expected):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(obj)
|
||||
self.assertString(actual, expected)
|
||||
|
||||
def test_comments(self):
|
||||
self._assert(Comments(), u"<h1>A Comedy of Errors</h1>")
|
||||
|
||||
def test_double_section(self):
|
||||
self._assert(DoubleSection(), u"* first\n* second\n* third")
|
||||
|
||||
def test_unicode_output(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(UnicodeOutput())
|
||||
self.assertString(actual, u'<p>Name: Henri Poincaré</p>')
|
||||
|
||||
def test_unicode_input(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(UnicodeInput())
|
||||
self.assertString(actual, u'abcdé')
|
||||
|
||||
def test_escaping(self):
|
||||
self._assert(Escaped(), u"<h1>Bear > Shark</h1>")
|
||||
|
||||
def test_literal(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(Unescaped())
|
||||
self.assertString(actual, u"<h1>Bear > Shark</h1>")
|
||||
|
||||
def test_template_partial(self):
|
||||
renderer = Renderer(search_dirs=EXAMPLES_DIR)
|
||||
actual = renderer.render(TemplatePartial(renderer=renderer))
|
||||
|
||||
self.assertString(actual, u"""<h1>Welcome</h1>
|
||||
Again, Welcome!""")
|
||||
|
||||
def test_template_partial_extension(self):
|
||||
renderer = Renderer(search_dirs=EXAMPLES_DIR, file_extension='txt')
|
||||
|
||||
view = TemplatePartial(renderer=renderer)
|
||||
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u"""Welcome
|
||||
-------
|
||||
|
||||
## Again, Welcome! ##""")
|
||||
|
||||
def test_delimiters(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(Delimiters())
|
||||
self.assertString(actual, u"""\
|
||||
* It worked the first time.
|
||||
* And it worked the second time.
|
||||
* Then, surprisingly, it worked the third time.
|
||||
""")
|
||||
|
||||
def test_nested_context(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(NestedContext(renderer))
|
||||
self.assertString(actual, u"one and foo and two")
|
||||
|
||||
def test_nested_context_is_available_in_view(self):
|
||||
renderer = Renderer()
|
||||
|
||||
view = NestedContext(renderer)
|
||||
view.template = '{{#herp}}{{#derp}}{{nested_context_in_view}}{{/derp}}{{/herp}}'
|
||||
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u'it works!')
|
||||
|
||||
def test_partial_in_partial_has_access_to_grand_parent_context(self):
|
||||
renderer = Renderer(search_dirs=EXAMPLES_DIR)
|
||||
|
||||
view = TemplatePartial(renderer=renderer)
|
||||
view.template = '''{{>partial_in_partial}}'''
|
||||
|
||||
actual = renderer.render(view, {'prop': 'derp'})
|
||||
self.assertEqual(actual, 'Hi derp!')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,209 @@
|
|||
# encoding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests of loader.py.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from pystache.tests.common import AssertStringMixin, DATA_DIR, SetupDefaults
|
||||
from pystache import defaults
|
||||
from pystache.loader import Loader
|
||||
|
||||
|
||||
# We use the same directory as the locator tests for now.
|
||||
LOADER_DATA_DIR = os.path.join(DATA_DIR, 'locator')
|
||||
|
||||
|
||||
class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
|
||||
|
||||
def setUp(self):
|
||||
self.setup_defaults()
|
||||
|
||||
def tearDown(self):
|
||||
self.teardown_defaults()
|
||||
|
||||
def test_init__extension(self):
|
||||
loader = Loader(extension='foo')
|
||||
self.assertEqual(loader.extension, 'foo')
|
||||
|
||||
def test_init__extension__default(self):
|
||||
# Test the default value.
|
||||
loader = Loader()
|
||||
self.assertEqual(loader.extension, 'mustache')
|
||||
|
||||
def test_init__file_encoding(self):
|
||||
loader = Loader(file_encoding='bar')
|
||||
self.assertEqual(loader.file_encoding, 'bar')
|
||||
|
||||
def test_init__file_encoding__default(self):
|
||||
file_encoding = defaults.FILE_ENCODING
|
||||
try:
|
||||
defaults.FILE_ENCODING = 'foo'
|
||||
loader = Loader()
|
||||
self.assertEqual(loader.file_encoding, 'foo')
|
||||
finally:
|
||||
defaults.FILE_ENCODING = file_encoding
|
||||
|
||||
def test_init__to_unicode(self):
|
||||
to_unicode = lambda x: x
|
||||
loader = Loader(to_unicode=to_unicode)
|
||||
self.assertEqual(loader.to_unicode, to_unicode)
|
||||
|
||||
def test_init__to_unicode__default(self):
|
||||
loader = Loader()
|
||||
self.assertRaises(TypeError, loader.to_unicode, u"abc")
|
||||
|
||||
decode_errors = defaults.DECODE_ERRORS
|
||||
string_encoding = defaults.STRING_ENCODING
|
||||
|
||||
nonascii = u'abcdé'.encode('utf-8')
|
||||
|
||||
loader = Loader()
|
||||
self.assertRaises(UnicodeDecodeError, loader.to_unicode, nonascii)
|
||||
|
||||
defaults.DECODE_ERRORS = 'ignore'
|
||||
loader = Loader()
|
||||
self.assertString(loader.to_unicode(nonascii), u'abcd')
|
||||
|
||||
defaults.STRING_ENCODING = 'utf-8'
|
||||
loader = Loader()
|
||||
self.assertString(loader.to_unicode(nonascii), u'abcdé')
|
||||
|
||||
|
||||
def _get_path(self, filename):
|
||||
return os.path.join(DATA_DIR, filename)
|
||||
|
||||
def test_unicode__basic__input_str(self):
|
||||
"""
|
||||
Test unicode(): default arguments with str input.
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
actual = loader.unicode("foo")
|
||||
|
||||
self.assertString(actual, u"foo")
|
||||
|
||||
def test_unicode__basic__input_unicode(self):
|
||||
"""
|
||||
Test unicode(): default arguments with unicode input.
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
actual = loader.unicode(u"foo")
|
||||
|
||||
self.assertString(actual, u"foo")
|
||||
|
||||
def test_unicode__basic__input_unicode_subclass(self):
|
||||
"""
|
||||
Test unicode(): default arguments with unicode-subclass input.
|
||||
|
||||
"""
|
||||
class UnicodeSubclass(unicode):
|
||||
pass
|
||||
|
||||
s = UnicodeSubclass(u"foo")
|
||||
|
||||
loader = Loader()
|
||||
actual = loader.unicode(s)
|
||||
|
||||
self.assertString(actual, u"foo")
|
||||
|
||||
def test_unicode__to_unicode__attribute(self):
|
||||
"""
|
||||
Test unicode(): encoding attribute.
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
|
||||
non_ascii = u'abcdé'.encode('utf-8')
|
||||
self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii)
|
||||
|
||||
def to_unicode(s, encoding=None):
|
||||
if encoding is None:
|
||||
encoding = 'utf-8'
|
||||
return unicode(s, encoding)
|
||||
|
||||
loader.to_unicode = to_unicode
|
||||
self.assertString(loader.unicode(non_ascii), u"abcdé")
|
||||
|
||||
def test_unicode__encoding_argument(self):
|
||||
"""
|
||||
Test unicode(): encoding argument.
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
|
||||
non_ascii = u'abcdé'.encode('utf-8')
|
||||
|
||||
self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii)
|
||||
|
||||
actual = loader.unicode(non_ascii, encoding='utf-8')
|
||||
self.assertString(actual, u'abcdé')
|
||||
|
||||
# TODO: check the read() unit tests.
|
||||
def test_read(self):
|
||||
"""
|
||||
Test read().
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
path = self._get_path('ascii.mustache')
|
||||
actual = loader.read(path)
|
||||
self.assertString(actual, u'ascii: abc')
|
||||
|
||||
def test_read__file_encoding__attribute(self):
|
||||
"""
|
||||
Test read(): file_encoding attribute respected.
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
path = self._get_path('non_ascii.mustache')
|
||||
|
||||
self.assertRaises(UnicodeDecodeError, loader.read, path)
|
||||
|
||||
loader.file_encoding = 'utf-8'
|
||||
actual = loader.read(path)
|
||||
self.assertString(actual, u'non-ascii: é')
|
||||
|
||||
def test_read__encoding__argument(self):
|
||||
"""
|
||||
Test read(): encoding argument respected.
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
path = self._get_path('non_ascii.mustache')
|
||||
|
||||
self.assertRaises(UnicodeDecodeError, loader.read, path)
|
||||
|
||||
actual = loader.read(path, encoding='utf-8')
|
||||
self.assertString(actual, u'non-ascii: é')
|
||||
|
||||
def test_read__to_unicode__attribute(self):
|
||||
"""
|
||||
Test read(): to_unicode attribute respected.
|
||||
|
||||
"""
|
||||
loader = Loader()
|
||||
path = self._get_path('non_ascii.mustache')
|
||||
|
||||
self.assertRaises(UnicodeDecodeError, loader.read, path)
|
||||
|
||||
#loader.decode_errors = 'ignore'
|
||||
#actual = loader.read(path)
|
||||
#self.assertString(actual, u'non-ascii: ')
|
||||
|
||||
def test_load_file(self):
|
||||
loader = Loader(search_dirs=[DATA_DIR, LOADER_DATA_DIR])
|
||||
template = loader.load_file('template.txt')
|
||||
self.assertEqual(template, 'Test template file\n')
|
||||
|
||||
def test_load_name(self):
|
||||
loader = Loader(search_dirs=[DATA_DIR, LOADER_DATA_DIR],
|
||||
extension='txt')
|
||||
template = loader.load_name('template')
|
||||
self.assertEqual(template, 'Test template file\n')
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
# encoding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests for locator.py.
|
||||
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
# TODO: remove this alias.
|
||||
from pystache.common import TemplateNotFoundError
|
||||
from pystache.loader import Loader as Reader
|
||||
from pystache.locator import Locator
|
||||
|
||||
from pystache.tests.common import DATA_DIR, EXAMPLES_DIR, AssertExceptionMixin
|
||||
from pystache.tests.data.views import SayHello
|
||||
|
||||
|
||||
LOCATOR_DATA_DIR = os.path.join(DATA_DIR, 'locator')
|
||||
|
||||
|
||||
class LocatorTests(unittest.TestCase, AssertExceptionMixin):
|
||||
|
||||
def _locator(self):
|
||||
return Locator(search_dirs=DATA_DIR)
|
||||
|
||||
def test_init__extension(self):
|
||||
# Test the default value.
|
||||
locator = Locator()
|
||||
self.assertEqual(locator.template_extension, 'mustache')
|
||||
|
||||
locator = Locator(extension='txt')
|
||||
self.assertEqual(locator.template_extension, 'txt')
|
||||
|
||||
locator = Locator(extension=False)
|
||||
self.assertTrue(locator.template_extension is False)
|
||||
|
||||
def _assert_paths(self, actual, expected):
|
||||
"""
|
||||
Assert that two paths are the same.
|
||||
|
||||
"""
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_get_object_directory(self):
|
||||
locator = Locator()
|
||||
|
||||
obj = SayHello()
|
||||
actual = locator.get_object_directory(obj)
|
||||
|
||||
self._assert_paths(actual, DATA_DIR)
|
||||
|
||||
def test_get_object_directory__not_hasattr_module(self):
|
||||
locator = Locator()
|
||||
|
||||
# Previously, we used a genuine object -- a datetime instance --
|
||||
# because datetime instances did not have the __module__ attribute
|
||||
# in CPython. See, for example--
|
||||
#
|
||||
# http://bugs.python.org/issue15223
|
||||
#
|
||||
# However, since datetime instances do have the __module__ attribute
|
||||
# in PyPy, we needed to switch to something else once we added
|
||||
# support for PyPi. This was so that our test runs would pass
|
||||
# in all systems.
|
||||
obj = "abc"
|
||||
self.assertFalse(hasattr(obj, '__module__'))
|
||||
self.assertEqual(locator.get_object_directory(obj), None)
|
||||
|
||||
self.assertFalse(hasattr(None, '__module__'))
|
||||
self.assertEqual(locator.get_object_directory(None), None)
|
||||
|
||||
def test_make_file_name(self):
|
||||
locator = Locator()
|
||||
|
||||
locator.template_extension = 'bar'
|
||||
self.assertEqual(locator.make_file_name('foo'), 'foo.bar')
|
||||
|
||||
locator.template_extension = False
|
||||
self.assertEqual(locator.make_file_name('foo'), 'foo')
|
||||
|
||||
locator.template_extension = ''
|
||||
self.assertEqual(locator.make_file_name('foo'), 'foo.')
|
||||
|
||||
def test_make_file_name__template_extension_argument(self):
|
||||
locator = Locator()
|
||||
|
||||
self.assertEqual(locator.make_file_name('foo', template_extension='bar'), 'foo.bar')
|
||||
|
||||
def test_find_file(self):
|
||||
locator = Locator()
|
||||
path = locator.find_file('template.txt', [LOCATOR_DATA_DIR])
|
||||
|
||||
expected_path = os.path.join(LOCATOR_DATA_DIR, 'template.txt')
|
||||
self.assertEqual(path, expected_path)
|
||||
|
||||
def test_find_name(self):
|
||||
locator = Locator()
|
||||
path = locator.find_name(search_dirs=[EXAMPLES_DIR], template_name='simple')
|
||||
|
||||
self.assertEqual(os.path.basename(path), 'simple.mustache')
|
||||
|
||||
def test_find_name__using_list_of_paths(self):
|
||||
locator = Locator()
|
||||
path = locator.find_name(search_dirs=[EXAMPLES_DIR, 'doesnt_exist'], template_name='simple')
|
||||
|
||||
self.assertTrue(path)
|
||||
|
||||
def test_find_name__precedence(self):
|
||||
"""
|
||||
Test the order in which find_name() searches directories.
|
||||
|
||||
"""
|
||||
locator = Locator()
|
||||
|
||||
dir1 = DATA_DIR
|
||||
dir2 = LOCATOR_DATA_DIR
|
||||
|
||||
self.assertTrue(locator.find_name(search_dirs=[dir1], template_name='duplicate'))
|
||||
self.assertTrue(locator.find_name(search_dirs=[dir2], template_name='duplicate'))
|
||||
|
||||
path = locator.find_name(search_dirs=[dir2, dir1], template_name='duplicate')
|
||||
dirpath = os.path.dirname(path)
|
||||
dirname = os.path.split(dirpath)[-1]
|
||||
|
||||
self.assertEqual(dirname, 'locator')
|
||||
|
||||
def test_find_name__non_existent_template_fails(self):
|
||||
locator = Locator()
|
||||
|
||||
self.assertException(TemplateNotFoundError, "File 'doesnt_exist.mustache' not found in dirs: []",
|
||||
locator.find_name, search_dirs=[], template_name='doesnt_exist')
|
||||
|
||||
def test_find_object(self):
|
||||
locator = Locator()
|
||||
|
||||
obj = SayHello()
|
||||
|
||||
actual = locator.find_object(search_dirs=[], obj=obj, file_name='sample_view.mustache')
|
||||
expected = os.path.join(DATA_DIR, 'sample_view.mustache')
|
||||
|
||||
self._assert_paths(actual, expected)
|
||||
|
||||
def test_find_object__none_file_name(self):
|
||||
locator = Locator()
|
||||
|
||||
obj = SayHello()
|
||||
|
||||
actual = locator.find_object(search_dirs=[], obj=obj)
|
||||
expected = os.path.join(DATA_DIR, 'say_hello.mustache')
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_find_object__none_object_directory(self):
|
||||
locator = Locator()
|
||||
|
||||
obj = None
|
||||
self.assertEqual(None, locator.get_object_directory(obj))
|
||||
|
||||
actual = locator.find_object(search_dirs=[DATA_DIR], obj=obj, file_name='say_hello.mustache')
|
||||
expected = os.path.join(DATA_DIR, 'say_hello.mustache')
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_make_template_name(self):
|
||||
"""
|
||||
Test make_template_name().
|
||||
|
||||
"""
|
||||
locator = Locator()
|
||||
|
||||
class FooBar(object):
|
||||
pass
|
||||
foo = FooBar()
|
||||
|
||||
self.assertEqual(locator.make_template_name(foo), 'foo_bar')
|
|
@ -0,0 +1,27 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests of parser.py.
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from pystache.defaults import DELIMITERS
|
||||
from pystache.parser import _compile_template_re as make_re
|
||||
|
||||
|
||||
class RegularExpressionTestCase(unittest.TestCase):
|
||||
|
||||
"""Tests the regular expression returned by _compile_template_re()."""
|
||||
|
||||
def test_re(self):
|
||||
"""
|
||||
Test getting a key from a dictionary.
|
||||
|
||||
"""
|
||||
re = make_re(DELIMITERS)
|
||||
match = re.search("b {{test}}")
|
||||
|
||||
self.assertEqual(match.start(), 1)
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
# encoding: utf-8
|
||||
|
||||
import unittest
|
||||
|
||||
import pystache
|
||||
from pystache import defaults
|
||||
from pystache import renderer
|
||||
from pystache.tests.common import html_escape
|
||||
|
||||
|
||||
class PystacheTests(unittest.TestCase):
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.original_escape = defaults.TAG_ESCAPE
|
||||
defaults.TAG_ESCAPE = html_escape
|
||||
|
||||
def tearDown(self):
|
||||
defaults.TAG_ESCAPE = self.original_escape
|
||||
|
||||
def _assert_rendered(self, expected, template, context):
|
||||
actual = pystache.render(template, context)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_basic(self):
|
||||
ret = pystache.render("Hi {{thing}}!", { 'thing': 'world' })
|
||||
self.assertEqual(ret, "Hi world!")
|
||||
|
||||
def test_kwargs(self):
|
||||
ret = pystache.render("Hi {{thing}}!", thing='world')
|
||||
self.assertEqual(ret, "Hi world!")
|
||||
|
||||
def test_less_basic(self):
|
||||
template = "It's a nice day for {{beverage}}, right {{person}}?"
|
||||
context = { 'beverage': 'soda', 'person': 'Bob' }
|
||||
self._assert_rendered("It's a nice day for soda, right Bob?", template, context)
|
||||
|
||||
def test_even_less_basic(self):
|
||||
template = "I think {{name}} wants a {{thing}}, right {{name}}?"
|
||||
context = { 'name': 'Jon', 'thing': 'racecar' }
|
||||
self._assert_rendered("I think Jon wants a racecar, right Jon?", template, context)
|
||||
|
||||
def test_ignores_misses(self):
|
||||
template = "I think {{name}} wants a {{thing}}, right {{name}}?"
|
||||
context = { 'name': 'Jon' }
|
||||
self._assert_rendered("I think Jon wants a , right Jon?", template, context)
|
||||
|
||||
def test_render_zero(self):
|
||||
template = 'My value is {{value}}.'
|
||||
context = { 'value': 0 }
|
||||
self._assert_rendered('My value is 0.', template, context)
|
||||
|
||||
def test_comments(self):
|
||||
template = "What {{! the }} what?"
|
||||
actual = pystache.render(template)
|
||||
self.assertEqual("What what?", actual)
|
||||
|
||||
def test_false_sections_are_hidden(self):
|
||||
template = "Ready {{#set}}set {{/set}}go!"
|
||||
context = { 'set': False }
|
||||
self._assert_rendered("Ready go!", template, context)
|
||||
|
||||
def test_true_sections_are_shown(self):
|
||||
template = "Ready {{#set}}set{{/set}} go!"
|
||||
context = { 'set': True }
|
||||
self._assert_rendered("Ready set go!", template, context)
|
||||
|
||||
non_strings_expected = """(123 & ['something'])(chris & 0.9)"""
|
||||
|
||||
def test_non_strings(self):
|
||||
template = "{{#stats}}({{key}} & {{value}}){{/stats}}"
|
||||
stats = []
|
||||
stats.append({'key': 123, 'value': ['something']})
|
||||
stats.append({'key': u"chris", 'value': 0.900})
|
||||
context = { 'stats': stats }
|
||||
self._assert_rendered(self.non_strings_expected, template, context)
|
||||
|
||||
def test_unicode(self):
|
||||
template = 'Name: {{name}}; Age: {{age}}'
|
||||
context = {'name': u'Henri Poincaré', 'age': 156 }
|
||||
self._assert_rendered(u'Name: Henri Poincaré; Age: 156', template, context)
|
||||
|
||||
def test_sections(self):
|
||||
template = """<ul>{{#users}}<li>{{name}}</li>{{/users}}</ul>"""
|
||||
|
||||
context = { 'users': [ {'name': 'Chris'}, {'name': 'Tom'}, {'name': 'PJ'} ] }
|
||||
expected = """<ul><li>Chris</li><li>Tom</li><li>PJ</li></ul>"""
|
||||
self._assert_rendered(expected, template, context)
|
||||
|
||||
def test_implicit_iterator(self):
|
||||
template = """<ul>{{#users}}<li>{{.}}</li>{{/users}}</ul>"""
|
||||
context = { 'users': [ 'Chris', 'Tom','PJ' ] }
|
||||
expected = """<ul><li>Chris</li><li>Tom</li><li>PJ</li></ul>"""
|
||||
self._assert_rendered(expected, template, context)
|
||||
|
||||
# The spec says that sections should not alter surrounding whitespace.
|
||||
def test_surrounding_whitepace_not_altered(self):
|
||||
template = "first{{#spacing}} second {{/spacing}}third"
|
||||
context = {"spacing": True}
|
||||
self._assert_rendered("first second third", template, context)
|
||||
|
||||
def test__section__non_false_value(self):
|
||||
"""
|
||||
Test when a section value is a (non-list) "non-false value".
|
||||
|
||||
From mustache(5):
|
||||
|
||||
When the value [of a section key] is non-false but not a list, it
|
||||
will be used as the context for a single rendering of the block.
|
||||
|
||||
"""
|
||||
template = """{{#person}}Hi {{name}}{{/person}}"""
|
||||
context = {"person": {"name": "Jon"}}
|
||||
self._assert_rendered("Hi Jon", template, context)
|
||||
|
||||
def test_later_list_section_with_escapable_character(self):
|
||||
"""
|
||||
This is a simple test case intended to cover issue #53.
|
||||
|
||||
The test case failed with markupsafe enabled, as follows:
|
||||
|
||||
AssertionError: Markup(u'foo <') != 'foo <'
|
||||
|
||||
"""
|
||||
template = """{{#s1}}foo{{/s1}} {{#s2}}<{{/s2}}"""
|
||||
context = {'s1': True, 's2': [True]}
|
||||
self._assert_rendered("foo <", template, context)
|
|
@ -0,0 +1,769 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests of renderengine.py.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from pystache.context import ContextStack, KeyNotFoundError
|
||||
from pystache import defaults
|
||||
from pystache.parser import ParsingError
|
||||
from pystache.renderer import Renderer
|
||||
from pystache.renderengine import context_get, RenderEngine
|
||||
from pystache.tests.common import AssertStringMixin, AssertExceptionMixin, Attachable
|
||||
|
||||
|
||||
def _get_unicode_char():
|
||||
if sys.version_info < (3, ):
|
||||
return 'u'
|
||||
return ''
|
||||
|
||||
_UNICODE_CHAR = _get_unicode_char()
|
||||
|
||||
|
||||
def mock_literal(s):
|
||||
"""
|
||||
For use as the literal keyword argument to the RenderEngine constructor.
|
||||
|
||||
Arguments:
|
||||
|
||||
s: a byte string or unicode string.
|
||||
|
||||
"""
|
||||
if isinstance(s, unicode):
|
||||
# Strip off unicode super classes, if present.
|
||||
u = unicode(s)
|
||||
else:
|
||||
u = unicode(s, encoding='ascii')
|
||||
|
||||
# We apply upper() to make sure we are actually using our custom
|
||||
# function in the tests
|
||||
return u.upper()
|
||||
|
||||
|
||||
|
||||
class RenderEngineTestCase(unittest.TestCase):
|
||||
|
||||
"""Test the RenderEngine class."""
|
||||
|
||||
def test_init(self):
|
||||
"""
|
||||
Test that __init__() stores all of the arguments correctly.
|
||||
|
||||
"""
|
||||
# In real-life, these arguments would be functions
|
||||
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):
|
||||
|
||||
"""
|
||||
Tests RenderEngine.render().
|
||||
|
||||
Explicit spec-test-like tests best go in this class since the
|
||||
RenderEngine class contains all parsing logic. This way, the unit tests
|
||||
will be more focused and fail "closer to the code".
|
||||
|
||||
"""
|
||||
|
||||
def _engine(self):
|
||||
"""
|
||||
Create and return a default RenderEngine for testing.
|
||||
|
||||
"""
|
||||
renderer = Renderer(string_encoding='utf-8', missing_tags='strict')
|
||||
engine = renderer._make_render_engine()
|
||||
|
||||
return engine
|
||||
|
||||
def _assert_render(self, expected, template, *context, **kwargs):
|
||||
"""
|
||||
Test rendering the given template using the given context.
|
||||
|
||||
"""
|
||||
partials = kwargs.get('partials')
|
||||
engine = kwargs.get('engine', self._engine())
|
||||
|
||||
if partials is not None:
|
||||
engine.resolve_partial = lambda key: unicode(partials[key])
|
||||
|
||||
context = ContextStack(*context)
|
||||
|
||||
# RenderEngine.render() only accepts unicode template strings.
|
||||
actual = engine.render(unicode(template), context)
|
||||
|
||||
self.assertString(actual=actual, expected=expected)
|
||||
|
||||
def test_render(self):
|
||||
self._assert_render(u'Hi Mom', 'Hi {{person}}', {'person': 'Mom'})
|
||||
|
||||
def test__resolve_partial(self):
|
||||
"""
|
||||
Test that render() uses the load_template attribute.
|
||||
|
||||
"""
|
||||
engine = self._engine()
|
||||
partials = {'partial': u"{{person}}"}
|
||||
engine.resolve_partial = lambda key: partials[key]
|
||||
|
||||
self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine)
|
||||
|
||||
def test__literal(self):
|
||||
"""
|
||||
Test that render() uses the literal attribute.
|
||||
|
||||
"""
|
||||
engine = self._engine()
|
||||
engine.literal = lambda s: s.upper()
|
||||
|
||||
self._assert_render(u'BAR', '{{{foo}}}', {'foo': 'bar'}, engine=engine)
|
||||
|
||||
def test_literal__sigil(self):
|
||||
template = "<h1>{{& thing}}</h1>"
|
||||
context = {'thing': 'Bear > Giraffe'}
|
||||
|
||||
expected = u"<h1>Bear > Giraffe</h1>"
|
||||
|
||||
self._assert_render(expected, template, context)
|
||||
|
||||
def test__escape(self):
|
||||
"""
|
||||
Test that render() uses the escape attribute.
|
||||
|
||||
"""
|
||||
engine = self._engine()
|
||||
engine.escape = lambda s: "**" + s
|
||||
|
||||
self._assert_render(u'**bar', '{{foo}}', {'foo': 'bar'}, engine=engine)
|
||||
|
||||
def test__escape_does_not_call_literal(self):
|
||||
"""
|
||||
Test that render() does not call literal before or after calling escape.
|
||||
|
||||
"""
|
||||
engine = self._engine()
|
||||
engine.literal = lambda s: s.upper() # a test version
|
||||
engine.escape = lambda s: "**" + s
|
||||
|
||||
template = 'literal: {{{foo}}} escaped: {{foo}}'
|
||||
context = {'foo': 'bar'}
|
||||
|
||||
self._assert_render(u'literal: BAR escaped: **bar', template, context, engine=engine)
|
||||
|
||||
def test__escape_preserves_unicode_subclasses(self):
|
||||
"""
|
||||
Test that render() preserves unicode subclasses when passing to escape.
|
||||
|
||||
This is useful, for example, if one wants to respect whether a
|
||||
variable value is markupsafe.Markup when escaping.
|
||||
|
||||
"""
|
||||
class MyUnicode(unicode):
|
||||
pass
|
||||
|
||||
def escape(s):
|
||||
if type(s) is MyUnicode:
|
||||
return "**" + s
|
||||
else:
|
||||
return s + "**"
|
||||
|
||||
engine = self._engine()
|
||||
engine.escape = escape
|
||||
|
||||
template = '{{foo1}} {{foo2}}'
|
||||
context = {'foo1': MyUnicode('bar'), 'foo2': 'bar'}
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
engine = self._engine()
|
||||
engine.escape = mock_literal
|
||||
engine.literal = mock_literal
|
||||
|
||||
self.assertRaises(TypeError, engine.literal, 100)
|
||||
|
||||
template = '{{text}} {{int}} {{{int}}}'
|
||||
context = {'int': 100, 'text': 'foo'}
|
||||
|
||||
self._assert_render(u'FOO 100 100', template, context, engine=engine)
|
||||
|
||||
def test_tag__output_not_interpolated(self):
|
||||
"""
|
||||
Context values should not be treated as templates (issue #44).
|
||||
|
||||
"""
|
||||
template = '{{template}}: {{planet}}'
|
||||
context = {'template': '{{planet}}', 'planet': 'Earth'}
|
||||
self._assert_render(u'{{planet}}: Earth', template, context)
|
||||
|
||||
def test_tag__output_not_interpolated__section(self):
|
||||
"""
|
||||
Context values should not be treated as templates (issue #44).
|
||||
|
||||
"""
|
||||
template = '{{test}}'
|
||||
context = {'test': '{{#hello}}'}
|
||||
self._assert_render(u'{{#hello}}', template, context)
|
||||
|
||||
## Test interpolation with "falsey" values
|
||||
#
|
||||
# In these test cases, we test the part of the spec that says that
|
||||
# "data should be coerced into a string (and escaped, if appropriate)
|
||||
# before interpolation." We test this for data that is "falsey."
|
||||
|
||||
def test_interpolation__falsey__zero(self):
|
||||
template = '{{.}}'
|
||||
context = 0
|
||||
self._assert_render(u'0', template, context)
|
||||
|
||||
def test_interpolation__falsey__none(self):
|
||||
template = '{{.}}'
|
||||
context = None
|
||||
self._assert_render(u'None', template, context)
|
||||
|
||||
def test_interpolation__falsey__zero(self):
|
||||
template = '{{.}}'
|
||||
context = False
|
||||
self._assert_render(u'False', template, context)
|
||||
|
||||
# Built-in types:
|
||||
#
|
||||
# Confirm that we not treat instances of built-in types as objects,
|
||||
# for example by calling a method on a built-in type instance when it
|
||||
# has a method whose name matches the current key.
|
||||
#
|
||||
# Each test case puts an instance of a built-in type on top of the
|
||||
# context stack before interpolating a tag whose key matches an
|
||||
# attribute (method or property) of the instance.
|
||||
#
|
||||
|
||||
def _assert_builtin_attr(self, item, attr_name, expected_attr):
|
||||
self.assertTrue(hasattr(item, attr_name))
|
||||
actual = getattr(item, attr_name)
|
||||
if callable(actual):
|
||||
actual = actual()
|
||||
self.assertEqual(actual, expected_attr)
|
||||
|
||||
def _assert_builtin_type(self, item, attr_name, expected_attr, expected_template):
|
||||
self._assert_builtin_attr(item, attr_name, expected_attr)
|
||||
|
||||
template = '{{#section}}{{%s}}{{/section}}' % attr_name
|
||||
context = {'section': item, attr_name: expected_template}
|
||||
self._assert_render(expected_template, template, context)
|
||||
|
||||
def test_interpolation__built_in_type__string(self):
|
||||
"""
|
||||
Check tag interpolation with a built-in type: string.
|
||||
|
||||
"""
|
||||
self._assert_builtin_type('abc', 'upper', 'ABC', u'xyz')
|
||||
|
||||
def test_interpolation__built_in_type__integer(self):
|
||||
"""
|
||||
Check tag interpolation with a built-in type: integer.
|
||||
|
||||
"""
|
||||
# Since public attributes weren't added to integers until Python 2.6
|
||||
# (for example the "real" attribute of the numeric type hierarchy)--
|
||||
#
|
||||
# http://docs.python.org/library/numbers.html
|
||||
#
|
||||
# we need to resort to built-in attributes (double-underscored) on
|
||||
# the integer type.
|
||||
self._assert_builtin_type(15, '__neg__', -15, u'999')
|
||||
|
||||
def test_interpolation__built_in_type__list(self):
|
||||
"""
|
||||
Check tag interpolation with a built-in type: list.
|
||||
|
||||
"""
|
||||
item = [[1, 2, 3]]
|
||||
attr_name = 'pop'
|
||||
# Make a copy to prevent changes to item[0].
|
||||
self._assert_builtin_attr(list(item[0]), attr_name, 3)
|
||||
|
||||
template = '{{#section}}{{%s}}{{/section}}' % attr_name
|
||||
context = {'section': item, attr_name: 7}
|
||||
self._assert_render(u'7', template, context)
|
||||
|
||||
# This test is also important for testing 2to3.
|
||||
def test_interpolation__nonascii_nonunicode(self):
|
||||
"""
|
||||
Test a tag whose value is a non-ascii, non-unicode string.
|
||||
|
||||
"""
|
||||
template = '{{nonascii}}'
|
||||
context = {'nonascii': u'abcdé'.encode('utf-8')}
|
||||
self._assert_render(u'abcdé', template, context)
|
||||
|
||||
def test_implicit_iterator__literal(self):
|
||||
"""
|
||||
Test an implicit iterator in a literal tag.
|
||||
|
||||
"""
|
||||
template = """{{#test}}{{{.}}}{{/test}}"""
|
||||
context = {'test': ['<', '>']}
|
||||
|
||||
self._assert_render(u'<>', template, context)
|
||||
|
||||
def test_implicit_iterator__escaped(self):
|
||||
"""
|
||||
Test an implicit iterator in a normal tag.
|
||||
|
||||
"""
|
||||
template = """{{#test}}{{.}}{{/test}}"""
|
||||
context = {'test': ['<', '>']}
|
||||
|
||||
self._assert_render(u'<>', template, context)
|
||||
|
||||
def test_literal__in_section(self):
|
||||
"""
|
||||
Check that literals work in sections.
|
||||
|
||||
"""
|
||||
template = '{{#test}}1 {{{less_than}}} 2{{/test}}'
|
||||
context = {'test': {'less_than': '<'}}
|
||||
|
||||
self._assert_render(u'1 < 2', template, context)
|
||||
|
||||
def test_literal__in_partial(self):
|
||||
"""
|
||||
Check that literals work in partials.
|
||||
|
||||
"""
|
||||
template = '{{>partial}}'
|
||||
partials = {'partial': '1 {{{less_than}}} 2'}
|
||||
context = {'less_than': '<'}
|
||||
|
||||
self._assert_render(u'1 < 2', template, context, partials=partials)
|
||||
|
||||
def test_partial(self):
|
||||
partials = {'partial': "{{person}}"}
|
||||
self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, partials=partials)
|
||||
|
||||
def test_partial__context_values(self):
|
||||
"""
|
||||
Test that escape and literal work on context values in partials.
|
||||
|
||||
"""
|
||||
engine = self._engine()
|
||||
|
||||
template = '{{>partial}}'
|
||||
partials = {'partial': 'unescaped: {{{foo}}} escaped: {{foo}}'}
|
||||
context = {'foo': '<'}
|
||||
|
||||
self._assert_render(u'unescaped: < escaped: <', template, context, engine=engine, partials=partials)
|
||||
|
||||
## Test cases related specifically to lambdas.
|
||||
|
||||
# This test is also important for testing 2to3.
|
||||
def test_section__nonascii_nonunicode(self):
|
||||
"""
|
||||
Test a section whose value is a non-ascii, non-unicode string.
|
||||
|
||||
"""
|
||||
template = '{{#nonascii}}{{.}}{{/nonascii}}'
|
||||
context = {'nonascii': u'abcdé'.encode('utf-8')}
|
||||
self._assert_render(u'abcdé', template, context)
|
||||
|
||||
# This test is also important for testing 2to3.
|
||||
def test_lambda__returning_nonascii_nonunicode(self):
|
||||
"""
|
||||
Test a lambda tag value returning a non-ascii, non-unicode string.
|
||||
|
||||
"""
|
||||
template = '{{lambda}}'
|
||||
context = {'lambda': lambda: u'abcdé'.encode('utf-8')}
|
||||
self._assert_render(u'abcdé', template, context)
|
||||
|
||||
## Test cases related specifically to sections.
|
||||
|
||||
def test_section__end_tag_with_no_start_tag(self):
|
||||
"""
|
||||
Check what happens if there is an end tag with no start tag.
|
||||
|
||||
"""
|
||||
template = '{{/section}}'
|
||||
try:
|
||||
self._assert_render(None, template)
|
||||
except ParsingError, err:
|
||||
self.assertEqual(str(err), "Section end tag mismatch: section != None")
|
||||
|
||||
def test_section__end_tag_mismatch(self):
|
||||
"""
|
||||
Check what happens if the end tag doesn't match.
|
||||
|
||||
"""
|
||||
template = '{{#section_start}}{{/section_end}}'
|
||||
try:
|
||||
self._assert_render(None, template)
|
||||
except ParsingError, err:
|
||||
self.assertEqual(str(err), "Section end tag mismatch: section_end != section_start")
|
||||
|
||||
def test_section__context_values(self):
|
||||
"""
|
||||
Test that escape and literal work on context values in sections.
|
||||
|
||||
"""
|
||||
engine = self._engine()
|
||||
|
||||
template = '{{#test}}unescaped: {{{foo}}} escaped: {{foo}}{{/test}}'
|
||||
context = {'test': {'foo': '<'}}
|
||||
|
||||
self._assert_render(u'unescaped: < escaped: <', template, context, engine=engine)
|
||||
|
||||
def test_section__context_precedence(self):
|
||||
"""
|
||||
Check that items higher in the context stack take precedence.
|
||||
|
||||
"""
|
||||
template = '{{entree}} : {{#vegetarian}}{{entree}}{{/vegetarian}}'
|
||||
context = {'entree': 'chicken', 'vegetarian': {'entree': 'beans and rice'}}
|
||||
self._assert_render(u'chicken : beans and rice', template, context)
|
||||
|
||||
def test_section__list_referencing_outer_context(self):
|
||||
"""
|
||||
Check that list items can access the parent context.
|
||||
|
||||
For sections whose value is a list, check that items in the list
|
||||
have access to the values inherited from the parent context
|
||||
when rendering.
|
||||
|
||||
"""
|
||||
context = {
|
||||
"greeting": "Hi",
|
||||
"list": [{"name": "Al"}, {"name": "Bob"}],
|
||||
}
|
||||
|
||||
template = "{{#list}}{{greeting}} {{name}}, {{/list}}"
|
||||
|
||||
self._assert_render(u"Hi Al, Hi Bob, ", template, context)
|
||||
|
||||
def test_section__output_not_interpolated(self):
|
||||
"""
|
||||
Check that rendered section output is not interpolated.
|
||||
|
||||
"""
|
||||
template = '{{#section}}{{template}}{{/section}}: {{planet}}'
|
||||
context = {'section': True, 'template': '{{planet}}', 'planet': 'Earth'}
|
||||
self._assert_render(u'{{planet}}: Earth', template, context)
|
||||
|
||||
# TODO: have this test case added to the spec.
|
||||
def test_section__string_values_not_lists(self):
|
||||
"""
|
||||
Check that string section values are not interpreted as lists.
|
||||
|
||||
"""
|
||||
template = '{{#section}}foo{{/section}}'
|
||||
context = {'section': '123'}
|
||||
# If strings were interpreted as lists, this would give "foofoofoo".
|
||||
self._assert_render(u'foo', template, context)
|
||||
|
||||
def test_section__nested_truthy(self):
|
||||
"""
|
||||
Check that "nested truthy" sections get rendered.
|
||||
|
||||
Test case for issue #24: https://github.com/defunkt/pystache/issues/24
|
||||
|
||||
This test is copied from the spec. We explicitly include it to
|
||||
prevent regressions for those who don't pull down the spec tests.
|
||||
|
||||
"""
|
||||
template = '| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |'
|
||||
context = {'bool': True}
|
||||
self._assert_render(u'| A B C D E |', template, context)
|
||||
|
||||
def test_section__nested_with_same_keys(self):
|
||||
"""
|
||||
Check a doubly-nested section with the same context key.
|
||||
|
||||
Test case for issue #36: https://github.com/defunkt/pystache/issues/36
|
||||
|
||||
"""
|
||||
# Start with an easier, working case.
|
||||
template = '{{#x}}{{#z}}{{y}}{{/z}}{{/x}}'
|
||||
context = {'x': {'z': {'y': 1}}}
|
||||
self._assert_render(u'1', template, context)
|
||||
|
||||
template = '{{#x}}{{#x}}{{y}}{{/x}}{{/x}}'
|
||||
context = {'x': {'x': {'y': 1}}}
|
||||
self._assert_render(u'1', template, context)
|
||||
|
||||
def test_section__lambda(self):
|
||||
template = '{{#test}}Mom{{/test}}'
|
||||
context = {'test': (lambda text: 'Hi %s' % text)}
|
||||
self._assert_render(u'Hi Mom', template, context)
|
||||
|
||||
# This test is also important for testing 2to3.
|
||||
def test_section__lambda__returning_nonascii_nonunicode(self):
|
||||
"""
|
||||
Test a lambda section value returning a non-ascii, non-unicode string.
|
||||
|
||||
"""
|
||||
template = '{{#lambda}}{{/lambda}}'
|
||||
context = {'lambda': lambda text: u'abcdé'.encode('utf-8')}
|
||||
self._assert_render(u'abcdé', template, context)
|
||||
|
||||
def test_section__lambda__returning_nonstring(self):
|
||||
"""
|
||||
Test a lambda section value returning a non-string.
|
||||
|
||||
"""
|
||||
template = '{{#lambda}}foo{{/lambda}}'
|
||||
context = {'lambda': lambda text: len(text)}
|
||||
self._assert_render(u'3', template, context)
|
||||
|
||||
def test_section__iterable(self):
|
||||
"""
|
||||
Check that objects supporting iteration (aside from dicts) behave like lists.
|
||||
|
||||
"""
|
||||
template = '{{#iterable}}{{.}}{{/iterable}}'
|
||||
|
||||
context = {'iterable': (i for i in range(3))} # type 'generator'
|
||||
self._assert_render(u'012', template, context)
|
||||
|
||||
context = {'iterable': xrange(4)} # type 'xrange'
|
||||
self._assert_render(u'0123', template, context)
|
||||
|
||||
d = {'foo': 0, 'bar': 0}
|
||||
# We don't know what order of keys we'll be given, but from the
|
||||
# Python documentation:
|
||||
# "If items(), keys(), values(), iteritems(), iterkeys(), and
|
||||
# itervalues() are called with no intervening modifications to
|
||||
# the dictionary, the lists will directly correspond."
|
||||
expected = u''.join(d.keys())
|
||||
context = {'iterable': d.iterkeys()} # type 'dictionary-keyiterator'
|
||||
self._assert_render(expected, template, context)
|
||||
|
||||
def test_section__lambda__tag_in_output(self):
|
||||
"""
|
||||
Check that callable output is treated as a template string (issue #46).
|
||||
|
||||
The spec says--
|
||||
|
||||
When used as the data value for a Section tag, the lambda MUST
|
||||
be treatable as an arity 1 function, and invoked as such (passing
|
||||
a String containing the unprocessed section contents). The
|
||||
returned value MUST be rendered against the current delimiters,
|
||||
then interpolated in place of the section.
|
||||
|
||||
"""
|
||||
template = '{{#test}}Hi {{person}}{{/test}}'
|
||||
context = {'person': 'Mom', 'test': (lambda text: text + " :)")}
|
||||
self._assert_render(u'Hi Mom :)', template, context)
|
||||
|
||||
def test_section__lambda__list(self):
|
||||
"""
|
||||
Check that lists of lambdas are processed correctly for sections.
|
||||
|
||||
This test case is equivalent to a test submitted to the Mustache spec here:
|
||||
|
||||
https://github.com/mustache/spec/pull/47 .
|
||||
|
||||
"""
|
||||
template = '<{{#lambdas}}foo{{/lambdas}}>'
|
||||
context = {'foo': 'bar',
|
||||
'lambdas': [lambda text: "~{{%s}}~" % text,
|
||||
lambda text: "#{{%s}}#" % text]}
|
||||
|
||||
self._assert_render(u'<~bar~#bar#>', template, context)
|
||||
|
||||
def test_section__lambda__mixed_list(self):
|
||||
"""
|
||||
Test a mixed list of lambdas and non-lambdas as a section value.
|
||||
|
||||
This test case is equivalent to a test submitted to the Mustache spec here:
|
||||
|
||||
https://github.com/mustache/spec/pull/47 .
|
||||
|
||||
"""
|
||||
template = '<{{#lambdas}}foo{{/lambdas}}>'
|
||||
context = {'foo': 'bar',
|
||||
'lambdas': [lambda text: "~{{%s}}~" % text, 1]}
|
||||
|
||||
self._assert_render(u'<~bar~foo>', template, context)
|
||||
|
||||
def test_section__lambda__not_on_context_stack(self):
|
||||
"""
|
||||
Check that section lambdas are not pushed onto the context stack.
|
||||
|
||||
Even though the sections spec says that section data values should be
|
||||
pushed onto the context stack prior to rendering, this does not apply
|
||||
to lambdas. Lambdas obey their own special case.
|
||||
|
||||
This test case is equivalent to a test submitted to the Mustache spec here:
|
||||
|
||||
https://github.com/mustache/spec/pull/47 .
|
||||
|
||||
"""
|
||||
context = {'foo': 'bar', 'lambda': (lambda text: "{{.}}")}
|
||||
template = '{{#foo}}{{#lambda}}blah{{/lambda}}{{/foo}}'
|
||||
self._assert_render(u'bar', template, context)
|
||||
|
||||
def test_section__lambda__no_reinterpolation(self):
|
||||
"""
|
||||
Check that section lambda return values are not re-interpolated.
|
||||
|
||||
This test is a sanity check that the rendered lambda return value
|
||||
is not re-interpolated as could be construed by reading the
|
||||
section part of the Mustache spec.
|
||||
|
||||
This test case is equivalent to a test submitted to the Mustache spec here:
|
||||
|
||||
https://github.com/mustache/spec/pull/47 .
|
||||
|
||||
"""
|
||||
template = '{{#planet}}{{#lambda}}dot{{/lambda}}{{/planet}}'
|
||||
context = {'planet': 'Earth', 'dot': '~{{.}}~', 'lambda': (lambda text: "#{{%s}}#" % text)}
|
||||
self._assert_render(u'#~{{.}}~#', template, context)
|
||||
|
||||
def test_comment__multiline(self):
|
||||
"""
|
||||
Check that multiline comments are permitted.
|
||||
|
||||
"""
|
||||
self._assert_render(u'foobar', 'foo{{! baz }}bar')
|
||||
self._assert_render(u'foobar', 'foo{{! \nbaz }}bar')
|
||||
|
||||
def test_custom_delimiters__sections(self):
|
||||
"""
|
||||
Check that custom delimiters can be used to start a section.
|
||||
|
||||
Test case for issue #20: https://github.com/defunkt/pystache/issues/20
|
||||
|
||||
"""
|
||||
template = '{{=[[ ]]=}}[[#foo]]bar[[/foo]]'
|
||||
context = {'foo': True}
|
||||
self._assert_render(u'bar', template, context)
|
||||
|
||||
def test_custom_delimiters__not_retroactive(self):
|
||||
"""
|
||||
Check that changing custom delimiters back is not "retroactive."
|
||||
|
||||
Test case for issue #35: https://github.com/defunkt/pystache/issues/35
|
||||
|
||||
"""
|
||||
expected = u' {{foo}} '
|
||||
self._assert_render(expected, '{{=$ $=}} {{foo}} ')
|
||||
self._assert_render(expected, '{{=$ $=}} {{foo}} $={{ }}=$') # was yielding u' '.
|
||||
|
||||
def test_dot_notation(self):
|
||||
"""
|
||||
Test simple dot notation cases.
|
||||
|
||||
Check that we can use dot notation when the variable is a dict,
|
||||
user-defined object, or combination of both.
|
||||
|
||||
"""
|
||||
template = 'Hello, {{person.name}}. I see you are {{person.details.age}}.'
|
||||
person = Attachable(name='Biggles', details={'age': 42})
|
||||
context = {'person': person}
|
||||
self._assert_render(u'Hello, Biggles. I see you are 42.', template, context)
|
||||
|
||||
def test_dot_notation__multiple_levels(self):
|
||||
"""
|
||||
Test dot notation with multiple levels.
|
||||
|
||||
"""
|
||||
template = """Hello, Mr. {{person.name.lastname}}.
|
||||
I see you're back from {{person.travels.last.country.city}}."""
|
||||
expected = u"""Hello, Mr. Pither.
|
||||
I see you're back from Cornwall."""
|
||||
context = {'person': {'name': {'firstname': 'unknown', 'lastname': 'Pither'},
|
||||
'travels': {'last': {'country': {'city': 'Cornwall'}}},
|
||||
'details': {'public': 'likes cycling'}}}
|
||||
self._assert_render(expected, template, context)
|
||||
|
||||
# It should also work with user-defined objects
|
||||
context = {'person': Attachable(name={'firstname': 'unknown', 'lastname': 'Pither'},
|
||||
travels=Attachable(last=Attachable(country=Attachable(city='Cornwall'))),
|
||||
details=Attachable())}
|
||||
self._assert_render(expected, template, context)
|
||||
|
||||
def test_dot_notation__missing_part_terminates_search(self):
|
||||
"""
|
||||
Test that dotted name resolution terminates on a later part not found.
|
||||
|
||||
Check that if a later dotted name part is not found in the result from
|
||||
the former resolution, then name resolution terminates rather than
|
||||
starting the search over with the next element of the context stack.
|
||||
From the spec (interpolation section)--
|
||||
|
||||
5) If any name parts were retained in step 1, each should be resolved
|
||||
against a context stack containing only the result from the former
|
||||
resolution. If any part fails resolution, the result should be considered
|
||||
falsey, and should interpolate as the empty string.
|
||||
|
||||
This test case is equivalent to the test case in the following pull
|
||||
request:
|
||||
|
||||
https://github.com/mustache/spec/pull/48
|
||||
|
||||
"""
|
||||
context = {'a': {'b': 'A.B'}, 'c': {'a': 'A'} }
|
||||
|
||||
template = '{{a.b}}'
|
||||
self._assert_render(u'A.B', template, context)
|
||||
|
||||
template = '{{#c}}{{a}}{{/c}}'
|
||||
self._assert_render(u'A', template, context)
|
||||
|
||||
template = '{{#c}}{{a.b}}{{/c}}'
|
||||
self.assertException(KeyNotFoundError, "Key %(unicode)s'a.b' not found: missing %(unicode)s'b'" %
|
||||
{'unicode': _UNICODE_CHAR},
|
||||
self._assert_render, 'A.B :: (A :: )', template, context)
|
|
@ -0,0 +1,725 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests of template.py.
|
||||
|
||||
"""
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from examples.simple import Simple
|
||||
from pystache import Renderer
|
||||
from pystache import TemplateSpec
|
||||
from pystache.common import TemplateNotFoundError
|
||||
from pystache.context import ContextStack, KeyNotFoundError
|
||||
from pystache.loader import Loader
|
||||
|
||||
from pystache.tests.common import get_data_path, AssertStringMixin, AssertExceptionMixin
|
||||
from pystache.tests.data.views import SayHello
|
||||
|
||||
|
||||
def _make_renderer():
|
||||
"""
|
||||
Return a default Renderer instance for testing purposes.
|
||||
|
||||
"""
|
||||
renderer = Renderer(string_encoding='ascii', file_encoding='ascii')
|
||||
return renderer
|
||||
|
||||
|
||||
def mock_unicode(b, encoding=None):
|
||||
if encoding is None:
|
||||
encoding = 'ascii'
|
||||
u = unicode(b, encoding=encoding)
|
||||
return u.upper()
|
||||
|
||||
|
||||
class RendererInitTestCase(unittest.TestCase):
|
||||
|
||||
"""
|
||||
Tests the Renderer.__init__() method.
|
||||
|
||||
"""
|
||||
|
||||
def test_partials__default(self):
|
||||
"""
|
||||
Test the default value.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
self.assertTrue(renderer.partials is None)
|
||||
|
||||
def test_partials(self):
|
||||
"""
|
||||
Test that the attribute is set correctly.
|
||||
|
||||
"""
|
||||
renderer = Renderer(partials={'foo': 'bar'})
|
||||
self.assertEqual(renderer.partials, {'foo': 'bar'})
|
||||
|
||||
def test_escape__default(self):
|
||||
escape = Renderer().escape
|
||||
|
||||
self.assertEqual(escape(">"), ">")
|
||||
self.assertEqual(escape('"'), """)
|
||||
# Single quotes are escaped only in Python 3.2 and later.
|
||||
if sys.version_info < (3, 2):
|
||||
expected = "'"
|
||||
else:
|
||||
expected = '''
|
||||
self.assertEqual(escape("'"), expected)
|
||||
|
||||
def test_escape(self):
|
||||
escape = lambda s: "**" + s
|
||||
renderer = Renderer(escape=escape)
|
||||
self.assertEqual(renderer.escape("bar"), "**bar")
|
||||
|
||||
def test_decode_errors__default(self):
|
||||
"""
|
||||
Check the default value.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
self.assertEqual(renderer.decode_errors, 'strict')
|
||||
|
||||
def test_decode_errors(self):
|
||||
"""
|
||||
Check that the constructor sets the attribute correctly.
|
||||
|
||||
"""
|
||||
renderer = Renderer(decode_errors="foo")
|
||||
self.assertEqual(renderer.decode_errors, "foo")
|
||||
|
||||
def test_file_encoding__default(self):
|
||||
"""
|
||||
Check the file_encoding default.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
self.assertEqual(renderer.file_encoding, renderer.string_encoding)
|
||||
|
||||
def test_file_encoding(self):
|
||||
"""
|
||||
Check that the file_encoding attribute is set correctly.
|
||||
|
||||
"""
|
||||
renderer = Renderer(file_encoding='foo')
|
||||
self.assertEqual(renderer.file_encoding, 'foo')
|
||||
|
||||
def test_file_extension__default(self):
|
||||
"""
|
||||
Check the file_extension default.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
self.assertEqual(renderer.file_extension, 'mustache')
|
||||
|
||||
def test_file_extension(self):
|
||||
"""
|
||||
Check that the file_encoding attribute is set correctly.
|
||||
|
||||
"""
|
||||
renderer = Renderer(file_extension='foo')
|
||||
self.assertEqual(renderer.file_extension, 'foo')
|
||||
|
||||
def test_missing_tags(self):
|
||||
"""
|
||||
Check that the missing_tags attribute is set correctly.
|
||||
|
||||
"""
|
||||
renderer = Renderer(missing_tags='foo')
|
||||
self.assertEqual(renderer.missing_tags, 'foo')
|
||||
|
||||
def test_missing_tags__default(self):
|
||||
"""
|
||||
Check the missing_tags default.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
self.assertEqual(renderer.missing_tags, 'ignore')
|
||||
|
||||
def test_search_dirs__default(self):
|
||||
"""
|
||||
Check the search_dirs default.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
self.assertEqual(renderer.search_dirs, [os.curdir])
|
||||
|
||||
def test_search_dirs__string(self):
|
||||
"""
|
||||
Check that the search_dirs attribute is set correctly when a string.
|
||||
|
||||
"""
|
||||
renderer = Renderer(search_dirs='foo')
|
||||
self.assertEqual(renderer.search_dirs, ['foo'])
|
||||
|
||||
def test_search_dirs__list(self):
|
||||
"""
|
||||
Check that the search_dirs attribute is set correctly when a list.
|
||||
|
||||
"""
|
||||
renderer = Renderer(search_dirs=['foo'])
|
||||
self.assertEqual(renderer.search_dirs, ['foo'])
|
||||
|
||||
def test_string_encoding__default(self):
|
||||
"""
|
||||
Check the default value.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
self.assertEqual(renderer.string_encoding, sys.getdefaultencoding())
|
||||
|
||||
def test_string_encoding(self):
|
||||
"""
|
||||
Check that the constructor sets the attribute correctly.
|
||||
|
||||
"""
|
||||
renderer = Renderer(string_encoding="foo")
|
||||
self.assertEqual(renderer.string_encoding, "foo")
|
||||
|
||||
|
||||
class RendererTests(unittest.TestCase, AssertStringMixin):
|
||||
|
||||
"""Test the Renderer class."""
|
||||
|
||||
def _renderer(self):
|
||||
return Renderer()
|
||||
|
||||
## Test Renderer.unicode().
|
||||
|
||||
def test_unicode__string_encoding(self):
|
||||
"""
|
||||
Test that the string_encoding attribute is respected.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
b = u"é".encode('utf-8')
|
||||
|
||||
renderer.string_encoding = "ascii"
|
||||
self.assertRaises(UnicodeDecodeError, renderer.unicode, b)
|
||||
|
||||
renderer.string_encoding = "utf-8"
|
||||
self.assertEqual(renderer.unicode(b), u"é")
|
||||
|
||||
def test_unicode__decode_errors(self):
|
||||
"""
|
||||
Test that the decode_errors attribute is respected.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
renderer.string_encoding = "ascii"
|
||||
b = u"déf".encode('utf-8')
|
||||
|
||||
renderer.decode_errors = "ignore"
|
||||
self.assertEqual(renderer.unicode(b), "df")
|
||||
|
||||
renderer.decode_errors = "replace"
|
||||
# U+FFFD is the official Unicode replacement character.
|
||||
self.assertEqual(renderer.unicode(b), u'd\ufffd\ufffdf')
|
||||
|
||||
## Test the _make_loader() method.
|
||||
|
||||
def test__make_loader__return_type(self):
|
||||
"""
|
||||
Test that _make_loader() returns a Loader.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
loader = renderer._make_loader()
|
||||
|
||||
self.assertEqual(type(loader), Loader)
|
||||
|
||||
def test__make_loader__attributes(self):
|
||||
"""
|
||||
Test that _make_loader() sets all attributes correctly..
|
||||
|
||||
"""
|
||||
unicode_ = lambda x: x
|
||||
|
||||
renderer = self._renderer()
|
||||
renderer.file_encoding = 'enc'
|
||||
renderer.file_extension = 'ext'
|
||||
renderer.unicode = unicode_
|
||||
|
||||
loader = renderer._make_loader()
|
||||
|
||||
self.assertEqual(loader.extension, 'ext')
|
||||
self.assertEqual(loader.file_encoding, 'enc')
|
||||
self.assertEqual(loader.to_unicode, unicode_)
|
||||
|
||||
## Test the render() method.
|
||||
|
||||
def test_render__return_type(self):
|
||||
"""
|
||||
Check that render() returns a string of type unicode.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
rendered = renderer.render('foo')
|
||||
self.assertEqual(type(rendered), unicode)
|
||||
|
||||
def test_render__unicode(self):
|
||||
renderer = self._renderer()
|
||||
actual = renderer.render(u'foo')
|
||||
self.assertEqual(actual, u'foo')
|
||||
|
||||
def test_render__str(self):
|
||||
renderer = self._renderer()
|
||||
actual = renderer.render('foo')
|
||||
self.assertEqual(actual, 'foo')
|
||||
|
||||
def test_render__non_ascii_character(self):
|
||||
renderer = self._renderer()
|
||||
actual = renderer.render(u'Poincaré')
|
||||
self.assertEqual(actual, u'Poincaré')
|
||||
|
||||
def test_render__context(self):
|
||||
"""
|
||||
Test render(): passing a context.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
self.assertEqual(renderer.render('Hi {{person}}', {'person': 'Mom'}), 'Hi Mom')
|
||||
|
||||
def test_render__context_and_kwargs(self):
|
||||
"""
|
||||
Test render(): passing a context and **kwargs.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
template = 'Hi {{person1}} and {{person2}}'
|
||||
self.assertEqual(renderer.render(template, {'person1': 'Mom'}, person2='Dad'), 'Hi Mom and Dad')
|
||||
|
||||
def test_render__kwargs_and_no_context(self):
|
||||
"""
|
||||
Test render(): passing **kwargs and no context.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
self.assertEqual(renderer.render('Hi {{person}}', person='Mom'), 'Hi Mom')
|
||||
|
||||
def test_render__context_and_kwargs__precedence(self):
|
||||
"""
|
||||
Test render(): **kwargs takes precedence over context.
|
||||
|
||||
"""
|
||||
renderer = self._renderer()
|
||||
self.assertEqual(renderer.render('Hi {{person}}', {'person': 'Mom'}, person='Dad'), 'Hi Dad')
|
||||
|
||||
def test_render__kwargs_does_not_modify_context(self):
|
||||
"""
|
||||
Test render(): passing **kwargs does not modify the passed context.
|
||||
|
||||
"""
|
||||
context = {}
|
||||
renderer = self._renderer()
|
||||
renderer.render('Hi {{person}}', context=context, foo="bar")
|
||||
self.assertEqual(context, {})
|
||||
|
||||
def test_render__nonascii_template(self):
|
||||
"""
|
||||
Test passing a non-unicode template with non-ascii characters.
|
||||
|
||||
"""
|
||||
renderer = _make_renderer()
|
||||
template = u"déf".encode("utf-8")
|
||||
|
||||
# Check that decode_errors and string_encoding are both respected.
|
||||
renderer.decode_errors = 'ignore'
|
||||
renderer.string_encoding = 'ascii'
|
||||
self.assertEqual(renderer.render(template), "df")
|
||||
|
||||
renderer.string_encoding = 'utf_8'
|
||||
self.assertEqual(renderer.render(template), u"déf")
|
||||
|
||||
def test_make_resolve_partial(self):
|
||||
"""
|
||||
Test the _make_resolve_partial() method.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.partials = {'foo': 'bar'}
|
||||
resolve_partial = renderer._make_resolve_partial()
|
||||
|
||||
actual = resolve_partial('foo')
|
||||
self.assertEqual(actual, 'bar')
|
||||
self.assertEqual(type(actual), unicode, "RenderEngine requires that "
|
||||
"resolve_partial return unicode strings.")
|
||||
|
||||
def test_make_resolve_partial__unicode(self):
|
||||
"""
|
||||
Test _make_resolve_partial(): that resolve_partial doesn't "double-decode" Unicode.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
|
||||
renderer.partials = {'partial': 'foo'}
|
||||
resolve_partial = renderer._make_resolve_partial()
|
||||
self.assertEqual(resolve_partial("partial"), "foo")
|
||||
|
||||
# Now with a value that is already unicode.
|
||||
renderer.partials = {'partial': u'foo'}
|
||||
resolve_partial = renderer._make_resolve_partial()
|
||||
# If the next line failed, we would get the following error:
|
||||
# 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.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
path = get_data_path('say_hello.mustache')
|
||||
actual = renderer.render_path(path, to='foo')
|
||||
self.assertEqual(actual, "Hello, foo")
|
||||
|
||||
def test_render__object(self):
|
||||
"""
|
||||
Test rendering an object instance.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
|
||||
say_hello = SayHello()
|
||||
actual = renderer.render(say_hello)
|
||||
self.assertEqual('Hello, World', actual)
|
||||
|
||||
actual = renderer.render(say_hello, to='Mars')
|
||||
self.assertEqual('Hello, Mars', actual)
|
||||
|
||||
def test_render__template_spec(self):
|
||||
"""
|
||||
Test rendering a TemplateSpec instance.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
|
||||
class Spec(TemplateSpec):
|
||||
template = "hello, {{to}}"
|
||||
to = 'world'
|
||||
|
||||
spec = Spec()
|
||||
actual = renderer.render(spec)
|
||||
self.assertString(actual, u'hello, world')
|
||||
|
||||
def test_render__view(self):
|
||||
"""
|
||||
Test rendering a View instance.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
|
||||
view = Simple()
|
||||
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
|
||||
# the Renderer. It suffices to test rendering paths through the
|
||||
# RenderEngine for the same amount of code coverage.
|
||||
class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
|
||||
|
||||
"""
|
||||
Check the RenderEngine returned by Renderer._make_render_engine().
|
||||
|
||||
"""
|
||||
|
||||
def _make_renderer(self):
|
||||
"""
|
||||
Return a default Renderer instance for testing purposes.
|
||||
|
||||
"""
|
||||
return _make_renderer()
|
||||
|
||||
## Test the engine's resolve_partial attribute.
|
||||
|
||||
def test__resolve_partial__returns_unicode(self):
|
||||
"""
|
||||
Check that resolve_partial returns unicode (and not a subclass).
|
||||
|
||||
"""
|
||||
class MyUnicode(unicode):
|
||||
pass
|
||||
|
||||
renderer = Renderer()
|
||||
renderer.string_encoding = 'ascii'
|
||||
renderer.partials = {'str': 'foo', 'subclass': MyUnicode('abc')}
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
|
||||
actual = engine.resolve_partial('str')
|
||||
self.assertEqual(actual, "foo")
|
||||
self.assertEqual(type(actual), unicode)
|
||||
|
||||
# Check that unicode subclasses are not preserved.
|
||||
actual = engine.resolve_partial('subclass')
|
||||
self.assertEqual(actual, "abc")
|
||||
self.assertEqual(type(actual), unicode)
|
||||
|
||||
def test__resolve_partial__not_found(self):
|
||||
"""
|
||||
Check that resolve_partial returns the empty string when a template is not found.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
resolve_partial = engine.resolve_partial
|
||||
|
||||
self.assertString(resolve_partial('foo'), u'')
|
||||
|
||||
def test__resolve_partial__not_found__missing_tags_strict(self):
|
||||
"""
|
||||
Check that resolve_partial provides a nice message when a template is not found.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.missing_tags = 'strict'
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
resolve_partial = engine.resolve_partial
|
||||
|
||||
self.assertException(TemplateNotFoundError, "File 'foo.mustache' not found in dirs: ['.']",
|
||||
resolve_partial, "foo")
|
||||
|
||||
def test__resolve_partial__not_found__partials_dict(self):
|
||||
"""
|
||||
Check that resolve_partial returns the empty string when a template is not found.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.partials = {}
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
resolve_partial = engine.resolve_partial
|
||||
|
||||
self.assertString(resolve_partial('foo'), u'')
|
||||
|
||||
def test__resolve_partial__not_found__partials_dict__missing_tags_strict(self):
|
||||
"""
|
||||
Check that resolve_partial provides a nice message when a template is not found.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.missing_tags = 'strict'
|
||||
renderer.partials = {}
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
resolve_partial = engine.resolve_partial
|
||||
|
||||
# Include dict directly since str(dict) is different in Python 2 and 3:
|
||||
# <type 'dict'> versus <class 'dict'>, respectively.
|
||||
self.assertException(TemplateNotFoundError, "Name 'foo' not found in partials: %s" % dict,
|
||||
resolve_partial, "foo")
|
||||
|
||||
## Test the engine's literal attribute.
|
||||
|
||||
def test__literal__uses_renderer_unicode(self):
|
||||
"""
|
||||
Test that literal uses the renderer's unicode function.
|
||||
|
||||
"""
|
||||
renderer = self._make_renderer()
|
||||
renderer.unicode = mock_unicode
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
literal = engine.literal
|
||||
|
||||
b = u"foo".encode("ascii")
|
||||
self.assertEqual(literal(b), "FOO")
|
||||
|
||||
def test__literal__handles_unicode(self):
|
||||
"""
|
||||
Test that literal doesn't try to "double decode" unicode.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.string_encoding = 'ascii'
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
literal = engine.literal
|
||||
|
||||
self.assertEqual(literal(u"foo"), "foo")
|
||||
|
||||
def test__literal__returns_unicode(self):
|
||||
"""
|
||||
Test that literal returns unicode (and not a subclass).
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.string_encoding = 'ascii'
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
literal = engine.literal
|
||||
|
||||
self.assertEqual(type(literal("foo")), unicode)
|
||||
|
||||
class MyUnicode(unicode):
|
||||
pass
|
||||
|
||||
s = MyUnicode("abc")
|
||||
|
||||
self.assertEqual(type(s), MyUnicode)
|
||||
self.assertTrue(isinstance(s, unicode))
|
||||
self.assertEqual(type(literal(s)), unicode)
|
||||
|
||||
## Test the engine's escape attribute.
|
||||
|
||||
def test__escape__uses_renderer_escape(self):
|
||||
"""
|
||||
Test that escape uses the renderer's escape function.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.escape = lambda s: "**" + s
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
escape = engine.escape
|
||||
|
||||
self.assertEqual(escape("foo"), "**foo")
|
||||
|
||||
def test__escape__uses_renderer_unicode(self):
|
||||
"""
|
||||
Test that escape uses the renderer's unicode function.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.unicode = mock_unicode
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
escape = engine.escape
|
||||
|
||||
b = u"foo".encode('ascii')
|
||||
self.assertEqual(escape(b), "FOO")
|
||||
|
||||
def test__escape__has_access_to_original_unicode_subclass(self):
|
||||
"""
|
||||
Test that escape receives strings with the unicode subclass intact.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.escape = lambda s: unicode(type(s).__name__)
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
escape = engine.escape
|
||||
|
||||
class MyUnicode(unicode):
|
||||
pass
|
||||
|
||||
self.assertEqual(escape(u"foo".encode('ascii')), unicode.__name__)
|
||||
self.assertEqual(escape(u"foo"), unicode.__name__)
|
||||
self.assertEqual(escape(MyUnicode("foo")), MyUnicode.__name__)
|
||||
|
||||
def test__escape__returns_unicode(self):
|
||||
"""
|
||||
Test that literal returns unicode (and not a subclass).
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.string_encoding = 'ascii'
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
escape = engine.escape
|
||||
|
||||
self.assertEqual(type(escape("foo")), unicode)
|
||||
|
||||
# Check that literal doesn't preserve unicode subclasses.
|
||||
class MyUnicode(unicode):
|
||||
pass
|
||||
|
||||
s = MyUnicode("abc")
|
||||
|
||||
self.assertEqual(type(s), MyUnicode)
|
||||
self.assertTrue(isinstance(s, unicode))
|
||||
self.assertEqual(type(escape(s)), unicode)
|
||||
|
||||
## Test the missing_tags attribute.
|
||||
|
||||
def test__missing_tags__unknown_value(self):
|
||||
"""
|
||||
Check missing_tags attribute: setting an unknown value.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.missing_tags = 'foo'
|
||||
|
||||
self.assertException(Exception, "Unsupported 'missing_tags' value: 'foo'",
|
||||
renderer._make_render_engine)
|
||||
|
||||
## Test the engine's resolve_context attribute.
|
||||
|
||||
def test__resolve_context(self):
|
||||
"""
|
||||
Check resolve_context(): default arguments.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
|
||||
stack = ContextStack({'foo': 'bar'})
|
||||
|
||||
self.assertEqual('bar', engine.resolve_context(stack, 'foo'))
|
||||
self.assertString(u'', engine.resolve_context(stack, 'missing'))
|
||||
|
||||
def test__resolve_context__missing_tags_strict(self):
|
||||
"""
|
||||
Check resolve_context(): missing_tags 'strict'.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
renderer.missing_tags = 'strict'
|
||||
|
||||
engine = renderer._make_render_engine()
|
||||
|
||||
stack = ContextStack({'foo': 'bar'})
|
||||
|
||||
self.assertEqual('bar', engine.resolve_context(stack, 'foo'))
|
||||
self.assertException(KeyNotFoundError, "Key 'missing' not found: first part",
|
||||
engine.resolve_context, stack, 'missing')
|
|
@ -0,0 +1,83 @@
|
|||
import unittest
|
||||
|
||||
import pystache
|
||||
from pystache import Renderer
|
||||
from examples.nested_context import NestedContext
|
||||
from examples.complex import Complex
|
||||
from examples.lambdas import Lambdas
|
||||
from examples.template_partial import TemplatePartial
|
||||
from examples.simple import Simple
|
||||
|
||||
from pystache.tests.common import EXAMPLES_DIR
|
||||
from pystache.tests.common import AssertStringMixin
|
||||
|
||||
|
||||
class TestSimple(unittest.TestCase, AssertStringMixin):
|
||||
|
||||
def test_nested_context(self):
|
||||
renderer = Renderer()
|
||||
view = NestedContext(renderer)
|
||||
view.template = '{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}'
|
||||
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u"one and foo and two")
|
||||
|
||||
def test_looping_and_negation_context(self):
|
||||
template = '{{#item}}{{header}}: {{name}} {{/item}}{{^item}} Shouldnt see me{{/item}}'
|
||||
context = Complex()
|
||||
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(template, context)
|
||||
self.assertEqual(actual, "Colors: red Colors: green Colors: blue ")
|
||||
|
||||
def test_empty_context(self):
|
||||
template = '{{#empty_list}}Shouldnt see me {{/empty_list}}{{^empty_list}}Should see me{{/empty_list}}'
|
||||
self.assertEqual(pystache.Renderer().render(template), "Should see me")
|
||||
|
||||
def test_callables(self):
|
||||
view = Lambdas()
|
||||
view.template = '{{#replace_foo_with_bar}}foo != bar. oh, it does!{{/replace_foo_with_bar}}'
|
||||
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u'bar != bar. oh, it does!')
|
||||
|
||||
def test_rendering_partial(self):
|
||||
renderer = Renderer(search_dirs=EXAMPLES_DIR)
|
||||
|
||||
view = TemplatePartial(renderer=renderer)
|
||||
view.template = '{{>inner_partial}}'
|
||||
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u'Again, Welcome!')
|
||||
|
||||
view.template = '{{#looping}}{{>inner_partial}} {{/looping}}'
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u"Again, Welcome! Again, Welcome! Again, Welcome! ")
|
||||
|
||||
def test_non_existent_value_renders_blank(self):
|
||||
view = Simple()
|
||||
template = '{{not_set}} {{blank}}'
|
||||
self.assertEqual(pystache.Renderer().render(template), ' ')
|
||||
|
||||
|
||||
def test_template_partial_extension(self):
|
||||
"""
|
||||
Side note:
|
||||
|
||||
From the spec--
|
||||
|
||||
Partial tags SHOULD be treated as standalone when appropriate.
|
||||
|
||||
In particular, this means that trailing newlines should be removed.
|
||||
|
||||
"""
|
||||
renderer = Renderer(search_dirs=EXAMPLES_DIR, file_extension='txt')
|
||||
|
||||
view = TemplatePartial(renderer=renderer)
|
||||
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u"""Welcome
|
||||
-------
|
||||
|
||||
## Again, Welcome! ##""")
|
|
@ -0,0 +1,435 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Unit tests for template_spec.py.
|
||||
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import examples
|
||||
from examples.simple import Simple
|
||||
from examples.complex import Complex
|
||||
from examples.lambdas import Lambdas
|
||||
from examples.inverted import Inverted, InvertedLists
|
||||
from pystache import Renderer
|
||||
from pystache import TemplateSpec
|
||||
from pystache.common import TemplateNotFoundError
|
||||
from pystache.locator import Locator
|
||||
from pystache.loader import Loader
|
||||
from pystache.specloader import SpecLoader
|
||||
from pystache.tests.common import DATA_DIR, EXAMPLES_DIR
|
||||
from pystache.tests.common import AssertIsMixin, AssertStringMixin
|
||||
from pystache.tests.data.views import SampleView
|
||||
from pystache.tests.data.views import NonAscii
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Test that View.template_rel_directory is respected.
|
||||
|
||||
"""
|
||||
class Tagless(TemplateSpec):
|
||||
pass
|
||||
|
||||
view = Tagless()
|
||||
renderer = Renderer()
|
||||
|
||||
self.assertRaises(TemplateNotFoundError, renderer.render, view)
|
||||
|
||||
# TODO: change this test to remove the following brittle line.
|
||||
view.template_rel_directory = "examples"
|
||||
actual = renderer.render(view)
|
||||
self.assertEqual(actual, "No tags...")
|
||||
|
||||
def test_template_path_for_partials(self):
|
||||
"""
|
||||
Test that View.template_rel_path is respected for partials.
|
||||
|
||||
"""
|
||||
spec = TemplateSpec()
|
||||
spec.template = "Partial: {{>tagless}}"
|
||||
|
||||
renderer1 = Renderer()
|
||||
renderer2 = Renderer(search_dirs=EXAMPLES_DIR)
|
||||
|
||||
actual = renderer1.render(spec)
|
||||
self.assertString(actual, u"Partial: ")
|
||||
|
||||
actual = renderer2.render(spec)
|
||||
self.assertEqual(actual, "Partial: No tags...")
|
||||
|
||||
def test_basic_method_calls(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(Simple())
|
||||
|
||||
self.assertString(actual, u"Hi pizza!")
|
||||
|
||||
def test_non_callable_attributes(self):
|
||||
view = Simple()
|
||||
view.thing = 'Chris'
|
||||
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(view)
|
||||
self.assertEqual(actual, "Hi Chris!")
|
||||
|
||||
def test_complex(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(Complex())
|
||||
self.assertString(actual, u"""\
|
||||
<h1>Colors</h1>
|
||||
<ul>
|
||||
<li><strong>red</strong></li>
|
||||
<li><a href="#Green">green</a></li>
|
||||
<li><a href="#Blue">blue</a></li>
|
||||
</ul>""")
|
||||
|
||||
def test_higher_order_replace(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(Lambdas())
|
||||
self.assertEqual(actual, 'bar != bar. oh, it does!')
|
||||
|
||||
def test_higher_order_rot13(self):
|
||||
view = Lambdas()
|
||||
view.template = '{{#rot13}}abcdefghijklm{{/rot13}}'
|
||||
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u'nopqrstuvwxyz')
|
||||
|
||||
def test_higher_order_lambda(self):
|
||||
view = Lambdas()
|
||||
view.template = '{{#sort}}zyxwvutsrqponmlkjihgfedcba{{/sort}}'
|
||||
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u'abcdefghijklmnopqrstuvwxyz')
|
||||
|
||||
def test_partials_with_lambda(self):
|
||||
view = Lambdas()
|
||||
view.template = '{{>partial_with_lambda}}'
|
||||
|
||||
renderer = Renderer(search_dirs=EXAMPLES_DIR)
|
||||
actual = renderer.render(view)
|
||||
self.assertEqual(actual, u'nopqrstuvwxyz')
|
||||
|
||||
def test_hierarchical_partials_with_lambdas(self):
|
||||
view = Lambdas()
|
||||
view.template = '{{>partial_with_partial_and_lambda}}'
|
||||
|
||||
renderer = Renderer(search_dirs=EXAMPLES_DIR)
|
||||
actual = renderer.render(view)
|
||||
self.assertString(actual, u'nopqrstuvwxyznopqrstuvwxyz')
|
||||
|
||||
def test_inverted(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(Inverted())
|
||||
self.assertString(actual, u"""one, two, three, empty list""")
|
||||
|
||||
def test_accessing_properties_on_parent_object_from_child_objects(self):
|
||||
parent = Thing()
|
||||
parent.this = 'derp'
|
||||
parent.children = [Thing()]
|
||||
view = Simple()
|
||||
view.template = "{{#parent}}{{#children}}{{this}}{{/children}}{{/parent}}"
|
||||
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(view, {'parent': parent})
|
||||
|
||||
self.assertString(actual, u'derp')
|
||||
|
||||
def test_inverted_lists(self):
|
||||
renderer = Renderer()
|
||||
actual = renderer.render(InvertedLists())
|
||||
self.assertString(actual, u"""one, two, three, empty list""")
|
||||
|
||||
|
||||
def _make_specloader():
|
||||
"""
|
||||
Return a default SpecLoader instance for testing purposes.
|
||||
|
||||
"""
|
||||
# Python 2 and 3 have different default encodings. Thus, to have
|
||||
# consistent test results across both versions, we need to specify
|
||||
# the string and file encodings explicitly rather than relying on
|
||||
# the defaults.
|
||||
def to_unicode(s, encoding=None):
|
||||
"""
|
||||
Raises a TypeError exception if the given string is already unicode.
|
||||
|
||||
"""
|
||||
if encoding is None:
|
||||
encoding = 'ascii'
|
||||
return unicode(s, encoding, 'strict')
|
||||
|
||||
loader = Loader(file_encoding='ascii', to_unicode=to_unicode)
|
||||
return SpecLoader(loader=loader)
|
||||
|
||||
|
||||
class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
|
||||
AssertPathsMixin):
|
||||
|
||||
"""
|
||||
Tests template_spec.SpecLoader.
|
||||
|
||||
"""
|
||||
|
||||
def _make_specloader(self):
|
||||
return _make_specloader()
|
||||
|
||||
def test_init__defaults(self):
|
||||
spec_loader = SpecLoader()
|
||||
|
||||
# Check the loader attribute.
|
||||
loader = spec_loader.loader
|
||||
self.assertEqual(loader.extension, 'mustache')
|
||||
self.assertEqual(loader.file_encoding, sys.getdefaultencoding())
|
||||
# TODO: finish testing the other Loader attributes.
|
||||
to_unicode = loader.to_unicode
|
||||
|
||||
def test_init__loader(self):
|
||||
loader = Loader()
|
||||
custom = SpecLoader(loader=loader)
|
||||
|
||||
self.assertIs(custom.loader, loader)
|
||||
|
||||
# TODO: rename to something like _assert_load().
|
||||
def _assert_template(self, loader, custom, expected):
|
||||
self.assertString(loader.load(custom), expected)
|
||||
|
||||
def test_load__template__type_str(self):
|
||||
"""
|
||||
Test the template attribute: str string.
|
||||
|
||||
"""
|
||||
custom = TemplateSpec()
|
||||
custom.template = "abc"
|
||||
|
||||
spec_loader = self._make_specloader()
|
||||
self._assert_template(spec_loader, custom, u"abc")
|
||||
|
||||
def test_load__template__type_unicode(self):
|
||||
"""
|
||||
Test the template attribute: unicode string.
|
||||
|
||||
"""
|
||||
custom = TemplateSpec()
|
||||
custom.template = u"abc"
|
||||
|
||||
spec_loader = self._make_specloader()
|
||||
self._assert_template(spec_loader, custom, u"abc")
|
||||
|
||||
def test_load__template__unicode_non_ascii(self):
|
||||
"""
|
||||
Test the template attribute: non-ascii unicode string.
|
||||
|
||||
"""
|
||||
custom = TemplateSpec()
|
||||
custom.template = u"é"
|
||||
|
||||
spec_loader = self._make_specloader()
|
||||
self._assert_template(spec_loader, custom, u"é")
|
||||
|
||||
def test_load__template__with_template_encoding(self):
|
||||
"""
|
||||
Test the template attribute: with template encoding attribute.
|
||||
|
||||
"""
|
||||
custom = TemplateSpec()
|
||||
custom.template = u'é'.encode('utf-8')
|
||||
|
||||
spec_loader = self._make_specloader()
|
||||
|
||||
self.assertRaises(UnicodeDecodeError, self._assert_template, spec_loader, custom, u'é')
|
||||
|
||||
custom.template_encoding = 'utf-8'
|
||||
self._assert_template(spec_loader, custom, u'é')
|
||||
|
||||
# TODO: make this test complete.
|
||||
def test_load__template__correct_loader(self):
|
||||
"""
|
||||
Test that reader.unicode() is called correctly.
|
||||
|
||||
This test tests that the correct reader is called with the correct
|
||||
arguments. This is a catch-all test to supplement the other
|
||||
test cases. It tests SpecLoader.load() independent of reader.unicode()
|
||||
being implemented correctly (and tested).
|
||||
|
||||
"""
|
||||
class MockLoader(Loader):
|
||||
|
||||
def __init__(self):
|
||||
self.s = None
|
||||
self.encoding = None
|
||||
|
||||
# Overrides the existing method.
|
||||
def unicode(self, s, encoding=None):
|
||||
self.s = s
|
||||
self.encoding = encoding
|
||||
return u"foo"
|
||||
|
||||
loader = MockLoader()
|
||||
custom_loader = SpecLoader()
|
||||
custom_loader.loader = loader
|
||||
|
||||
view = TemplateSpec()
|
||||
view.template = "template-foo"
|
||||
view.template_encoding = "encoding-foo"
|
||||
|
||||
# Check that our unicode() above was called.
|
||||
self._assert_template(custom_loader, view, u'foo')
|
||||
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, AssertPathsMixin):
|
||||
|
||||
def _make_loader(self):
|
||||
return _make_specloader()
|
||||
|
||||
def _assert_template_location(self, view, expected):
|
||||
loader = self._make_loader()
|
||||
actual = loader._find_relative(view)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_find_relative(self):
|
||||
"""
|
||||
Test _find_relative(): default behavior (no attributes set).
|
||||
|
||||
"""
|
||||
view = SampleView()
|
||||
self._assert_template_location(view, (None, 'sample_view.mustache'))
|
||||
|
||||
def test_find_relative__template_rel_path__file_name_only(self):
|
||||
"""
|
||||
Test _find_relative(): template_rel_path attribute.
|
||||
|
||||
"""
|
||||
view = SampleView()
|
||||
view.template_rel_path = 'template.txt'
|
||||
self._assert_template_location(view, ('', 'template.txt'))
|
||||
|
||||
def test_find_relative__template_rel_path__file_name_with_directory(self):
|
||||
"""
|
||||
Test _find_relative(): template_rel_path attribute.
|
||||
|
||||
"""
|
||||
view = SampleView()
|
||||
view.template_rel_path = 'foo/bar/template.txt'
|
||||
self._assert_template_location(view, ('foo/bar', 'template.txt'))
|
||||
|
||||
def test_find_relative__template_rel_directory(self):
|
||||
"""
|
||||
Test _find_relative(): template_rel_directory attribute.
|
||||
|
||||
"""
|
||||
view = SampleView()
|
||||
view.template_rel_directory = 'foo'
|
||||
|
||||
self._assert_template_location(view, ('foo', 'sample_view.mustache'))
|
||||
|
||||
def test_find_relative__template_name(self):
|
||||
"""
|
||||
Test _find_relative(): template_name attribute.
|
||||
|
||||
"""
|
||||
view = SampleView()
|
||||
view.template_name = 'new_name'
|
||||
self._assert_template_location(view, (None, 'new_name.mustache'))
|
||||
|
||||
def test_find_relative__template_extension(self):
|
||||
"""
|
||||
Test _find_relative(): template_extension attribute.
|
||||
|
||||
"""
|
||||
view = SampleView()
|
||||
view.template_extension = 'txt'
|
||||
self._assert_template_location(view, (None, 'sample_view.txt'))
|
||||
|
||||
def test_find__with_directory(self):
|
||||
"""
|
||||
Test _find() with a view that has a directory specified.
|
||||
|
||||
"""
|
||||
loader = self._make_loader()
|
||||
|
||||
view = SampleView()
|
||||
view.template_rel_path = os.path.join('foo', 'bar.txt')
|
||||
self.assertTrue(loader._find_relative(view)[0] is not None)
|
||||
|
||||
actual = loader._find(view)
|
||||
expected = os.path.join(DATA_DIR, 'foo', 'bar.txt')
|
||||
|
||||
self.assertPaths(actual, expected)
|
||||
|
||||
def test_find__without_directory(self):
|
||||
"""
|
||||
Test _find() with a view that doesn't have a directory specified.
|
||||
|
||||
"""
|
||||
loader = self._make_loader()
|
||||
|
||||
view = SampleView()
|
||||
self.assertTrue(loader._find_relative(view)[0] is None)
|
||||
|
||||
actual = loader._find(view)
|
||||
expected = os.path.join(DATA_DIR, 'sample_view.mustache')
|
||||
|
||||
self.assertPaths(actual, expected)
|
||||
|
||||
def _assert_get_template(self, custom, expected):
|
||||
loader = self._make_loader()
|
||||
actual = loader.load(custom)
|
||||
|
||||
self.assertEqual(type(actual), unicode)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_get_template(self):
|
||||
"""
|
||||
Test get_template(): default behavior (no attributes set).
|
||||
|
||||
"""
|
||||
view = SampleView()
|
||||
|
||||
self._assert_get_template(view, u"ascii: abc")
|
||||
|
||||
def test_get_template__template_encoding(self):
|
||||
"""
|
||||
Test get_template(): template_encoding attribute.
|
||||
|
||||
"""
|
||||
view = NonAscii()
|
||||
|
||||
self.assertRaises(UnicodeDecodeError, self._assert_get_template, view, 'foo')
|
||||
|
||||
view.template_encoding = 'utf-8'
|
||||
self._assert_get_template(view, u"non-ascii: é")
|
|
@ -0,0 +1,5 @@
|
|||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
|
@ -36,6 +36,7 @@ pip-tools==5.5.0
|
|||
ply==3.10
|
||||
pyasn1==0.4.8
|
||||
pyasn1-modules==0.2.8
|
||||
pystache==0.5.4
|
||||
pytest==3.6.2
|
||||
python-hglib==2.4
|
||||
pytoml==0.1.10
|
||||
|
|
|
@ -214,6 +214,9 @@ pyasn1==0.4.8 \
|
|||
pyrsistent==0.16.0 \
|
||||
--hash=sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3
|
||||
# via jsonschema
|
||||
pystache==0.5.4 \
|
||||
--hash=sha256:f7bbc265fb957b4d6c7c042b336563179444ab313fb93a719759111eabd3b85a
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
pytest==3.6.2 \
|
||||
--hash=sha256:8ea01fc4fcc8e1b1e305252b4bc80a1528019ab99fd3b88666c9dc38d754406c \
|
||||
--hash=sha256:90898786b3d0b880b47645bae7b51aa9bbf1e9d1e4510c2cfd15dd65c70ea0cd
|
||||
|
|
Загрузка…
Ссылка в новой задаче