зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1732787: Clean up vendored "pystache" and "funcsigs" pkgs r=ahal
* `pystache` is unused. * `funcsigs` is only needed for WPT - use the WPT version instead. * `moz.build` has a bunch of obsolete/redundant references, clean them up. * `mohawk` isn't used directly, but is rather depended-on via `taskcluster`. So, remove it from `requirements.in`. Differential Revision: https://phabricator.services.mozilla.com/D126732
This commit is contained in:
Родитель
35d35bc9ff
Коммит
4f9389c204
|
@ -44,6 +44,7 @@ pth:testing/mozbase/mozversion
|
|||
pth:testing/raptor
|
||||
pth:testing/talos
|
||||
pth:testing/web-platform
|
||||
vendored:testing/web-platform/tests/tools/third_party/funcsigs
|
||||
vendored:testing/web-platform/tests/tools/third_party/h2
|
||||
vendored:testing/web-platform/tests/tools/third_party/hpack
|
||||
vendored:testing/web-platform/tests/tools/third_party/html5lib
|
||||
|
@ -72,7 +73,6 @@ vendored:third_party/python/ecdsa
|
|||
vendored:third_party/python/esprima
|
||||
vendored:third_party/python/fluent.migrate
|
||||
vendored:third_party/python/fluent.syntax
|
||||
vendored:third_party/python/funcsigs
|
||||
vendored:third_party/python/gyp/pylib
|
||||
vendored:third_party/python/idna
|
||||
vendored:third_party/python/idna-ssl
|
||||
|
@ -95,7 +95,6 @@ vendored:third_party/python/pyasn1_modules
|
|||
vendored:third_party/python/pylru
|
||||
vendored:third_party/python/pyparsing
|
||||
vendored:third_party/python/pyrsistent
|
||||
vendored:third_party/python/pystache
|
||||
vendored:third_party/python/python-hglib
|
||||
vendored:third_party/python/pytoml
|
||||
vendored:third_party/python/PyYAML/lib3/
|
||||
|
|
|
@ -1,355 +0,0 @@
|
|||
.. funcsigs documentation master file, created by
|
||||
sphinx-quickstart on Fri Apr 20 20:27:52 2012.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Introducing funcsigs
|
||||
====================
|
||||
|
||||
The Funcsigs Package
|
||||
--------------------
|
||||
|
||||
``funcsigs`` is a backport of the `PEP 362`_ function signature features from
|
||||
Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7
|
||||
as well as 3.3 and up. 3.2 was supported by version 0.4, but with setuptools and
|
||||
pip no longer supporting 3.2, we cannot make any statement about 3.2
|
||||
compatibility.
|
||||
|
||||
Compatibility
|
||||
`````````````
|
||||
|
||||
The ``funcsigs`` backport has been tested against:
|
||||
|
||||
* CPython 2.6
|
||||
* CPython 2.7
|
||||
* CPython 3.3
|
||||
* CPython 3.4
|
||||
* CPython 3.5
|
||||
* CPython nightlies
|
||||
* PyPy and PyPy3(currently failing CI)
|
||||
|
||||
Continuous integration testing is provided by `Travis CI`_.
|
||||
|
||||
Under Python 2.x there is a compatibility issue when a function is assigned to
|
||||
the ``__wrapped__`` property of a class after it has been constructed.
|
||||
Similiarily there under PyPy directly passing the ``__call__`` method of a
|
||||
builtin is also a compatibility issues. Otherwise the functionality is
|
||||
believed to be uniform between both Python2 and Python3.
|
||||
|
||||
Issues
|
||||
``````
|
||||
|
||||
Source code for ``funcsigs`` is hosted on `GitHub`_. Any bug reports or feature
|
||||
requests can be made using GitHub's `issues system`_. |build_status| |coverage|
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
To obtain a `Signature` object, pass the target function to the
|
||||
``funcsigs.signature`` function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from funcsigs import signature
|
||||
>>> def foo(a, b=None, *args, **kwargs):
|
||||
... pass
|
||||
...
|
||||
>>> sig = signature(foo)
|
||||
>>> sig
|
||||
<funcsigs.Signature object at 0x...>
|
||||
>>> sig.parameters
|
||||
OrderedDict([('a', <Parameter at 0x... 'a'>), ('b', <Parameter at 0x... 'b'>), ('args', <Parameter at 0x... 'args'>), ('kwargs', <Parameter at 0x... 'kwargs'>)])
|
||||
>>> sig.return_annotation
|
||||
<class 'funcsigs._empty'>
|
||||
|
||||
Introspecting callables with the Signature object
|
||||
-------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section of documentation is a direct reproduction of the Python
|
||||
standard library documentation for the inspect module.
|
||||
|
||||
The Signature object represents the call signature of a callable object and its
|
||||
return annotation. To retrieve a Signature object, use the :func:`signature`
|
||||
function.
|
||||
|
||||
.. function:: signature(callable)
|
||||
|
||||
Return a :class:`Signature` object for the given ``callable``::
|
||||
|
||||
>>> from funcsigs import signature
|
||||
>>> def foo(a, *, b:int, **kwargs):
|
||||
... pass
|
||||
|
||||
>>> sig = signature(foo)
|
||||
|
||||
>>> str(sig)
|
||||
'(a, *, b:int, **kwargs)'
|
||||
|
||||
>>> str(sig.parameters['b'])
|
||||
'b:int'
|
||||
|
||||
>>> sig.parameters['b'].annotation
|
||||
<class 'int'>
|
||||
|
||||
Accepts a wide range of python callables, from plain functions and classes to
|
||||
:func:`functools.partial` objects.
|
||||
|
||||
.. note::
|
||||
|
||||
Some callables may not be introspectable in certain implementations of
|
||||
Python. For example, in CPython, built-in functions defined in C provide
|
||||
no metadata about their arguments.
|
||||
|
||||
|
||||
.. class:: Signature
|
||||
|
||||
A Signature object represents the call signature of a function and its return
|
||||
annotation. For each parameter accepted by the function it stores a
|
||||
:class:`Parameter` object in its :attr:`parameters` collection.
|
||||
|
||||
Signature objects are *immutable*. Use :meth:`Signature.replace` to make a
|
||||
modified copy.
|
||||
|
||||
.. attribute:: Signature.empty
|
||||
|
||||
A special class-level marker to specify absence of a return annotation.
|
||||
|
||||
.. attribute:: Signature.parameters
|
||||
|
||||
An ordered mapping of parameters' names to the corresponding
|
||||
:class:`Parameter` objects.
|
||||
|
||||
.. attribute:: Signature.return_annotation
|
||||
|
||||
The "return" annotation for the callable. If the callable has no "return"
|
||||
annotation, this attribute is set to :attr:`Signature.empty`.
|
||||
|
||||
.. method:: Signature.bind(*args, **kwargs)
|
||||
|
||||
Create a mapping from positional and keyword arguments to parameters.
|
||||
Returns :class:`BoundArguments` if ``*args`` and ``**kwargs`` match the
|
||||
signature, or raises a :exc:`TypeError`.
|
||||
|
||||
.. method:: Signature.bind_partial(*args, **kwargs)
|
||||
|
||||
Works the same way as :meth:`Signature.bind`, but allows the omission of
|
||||
some required arguments (mimics :func:`functools.partial` behavior.)
|
||||
Returns :class:`BoundArguments`, or raises a :exc:`TypeError` if the
|
||||
passed arguments do not match the signature.
|
||||
|
||||
.. method:: Signature.replace(*[, parameters][, return_annotation])
|
||||
|
||||
Create a new Signature instance based on the instance replace was invoked
|
||||
on. It is possible to pass different ``parameters`` and/or
|
||||
``return_annotation`` to override the corresponding properties of the base
|
||||
signature. To remove return_annotation from the copied Signature, pass in
|
||||
:attr:`Signature.empty`.
|
||||
|
||||
::
|
||||
|
||||
>>> def test(a, b):
|
||||
... pass
|
||||
>>> sig = signature(test)
|
||||
>>> new_sig = sig.replace(return_annotation="new return anno")
|
||||
>>> str(new_sig)
|
||||
"(a, b) -> 'new return anno'"
|
||||
|
||||
|
||||
.. class:: Parameter
|
||||
|
||||
Parameter objects are *immutable*. Instead of modifying a Parameter object,
|
||||
you can use :meth:`Parameter.replace` to create a modified copy.
|
||||
|
||||
.. attribute:: Parameter.empty
|
||||
|
||||
A special class-level marker to specify absence of default values and
|
||||
annotations.
|
||||
|
||||
.. attribute:: Parameter.name
|
||||
|
||||
The name of the parameter as a string. Must be a valid python identifier
|
||||
name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have
|
||||
it set to ``None``).
|
||||
|
||||
.. attribute:: Parameter.default
|
||||
|
||||
The default value for the parameter. If the parameter has no default
|
||||
value, this attribute is set to :attr:`Parameter.empty`.
|
||||
|
||||
.. attribute:: Parameter.annotation
|
||||
|
||||
The annotation for the parameter. If the parameter has no annotation,
|
||||
this attribute is set to :attr:`Parameter.empty`.
|
||||
|
||||
.. attribute:: Parameter.kind
|
||||
|
||||
Describes how argument values are bound to the parameter. Possible values
|
||||
(accessible via :class:`Parameter`, like ``Parameter.KEYWORD_ONLY``):
|
||||
|
||||
+------------------------+----------------------------------------------+
|
||||
| Name | Meaning |
|
||||
+========================+==============================================+
|
||||
| *POSITIONAL_ONLY* | Value must be supplied as a positional |
|
||||
| | argument. |
|
||||
| | |
|
||||
| | Python has no explicit syntax for defining |
|
||||
| | positional-only parameters, but many built-in|
|
||||
| | and extension module functions (especially |
|
||||
| | those that accept only one or two parameters)|
|
||||
| | accept them. |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or |
|
||||
| | positional argument (this is the standard |
|
||||
| | binding behaviour for functions implemented |
|
||||
| | in Python.) |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *VAR_POSITIONAL* | A tuple of positional arguments that aren't |
|
||||
| | bound to any other parameter. This |
|
||||
| | corresponds to a ``*args`` parameter in a |
|
||||
| | Python function definition. |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *KEYWORD_ONLY* | Value must be supplied as a keyword argument.|
|
||||
| | Keyword only parameters are those which |
|
||||
| | appear after a ``*`` or ``*args`` entry in a |
|
||||
| | Python function definition. |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *VAR_KEYWORD* | A dict of keyword arguments that aren't bound|
|
||||
| | to any other parameter. This corresponds to a|
|
||||
| | ``**kwargs`` parameter in a Python function |
|
||||
| | definition. |
|
||||
+------------------------+----------------------------------------------+
|
||||
|
||||
Example: print all keyword-only arguments without default values::
|
||||
|
||||
>>> def foo(a, b, *, c, d=10):
|
||||
... pass
|
||||
|
||||
>>> sig = signature(foo)
|
||||
>>> for param in sig.parameters.values():
|
||||
... if (param.kind == param.KEYWORD_ONLY and
|
||||
... param.default is param.empty):
|
||||
... print('Parameter:', param)
|
||||
Parameter: c
|
||||
|
||||
.. method:: Parameter.replace(*[, name][, kind][, default][, annotation])
|
||||
|
||||
Create a new Parameter instance based on the instance replaced was invoked
|
||||
on. To override a :class:`Parameter` attribute, pass the corresponding
|
||||
argument. To remove a default value or/and an annotation from a
|
||||
Parameter, pass :attr:`Parameter.empty`.
|
||||
|
||||
::
|
||||
|
||||
>>> from funcsigs import Parameter
|
||||
>>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42)
|
||||
>>> str(param)
|
||||
'foo=42'
|
||||
|
||||
>>> str(param.replace()) # Will create a shallow copy of 'param'
|
||||
'foo=42'
|
||||
|
||||
>>> str(param.replace(default=Parameter.empty, annotation='spam'))
|
||||
"foo:'spam'"
|
||||
|
||||
|
||||
.. class:: BoundArguments
|
||||
|
||||
Result of a :meth:`Signature.bind` or :meth:`Signature.bind_partial` call.
|
||||
Holds the mapping of arguments to the function's parameters.
|
||||
|
||||
.. attribute:: BoundArguments.arguments
|
||||
|
||||
An ordered, mutable mapping (:class:`collections.OrderedDict`) of
|
||||
parameters' names to arguments' values. Contains only explicitly bound
|
||||
arguments. Changes in :attr:`arguments` will reflect in :attr:`args` and
|
||||
:attr:`kwargs`.
|
||||
|
||||
Should be used in conjunction with :attr:`Signature.parameters` for any
|
||||
argument processing purposes.
|
||||
|
||||
.. note::
|
||||
|
||||
Arguments for which :meth:`Signature.bind` or
|
||||
:meth:`Signature.bind_partial` relied on a default value are skipped.
|
||||
However, if needed, it is easy to include them.
|
||||
|
||||
::
|
||||
|
||||
>>> def foo(a, b=10):
|
||||
... pass
|
||||
|
||||
>>> sig = signature(foo)
|
||||
>>> ba = sig.bind(5)
|
||||
|
||||
>>> ba.args, ba.kwargs
|
||||
((5,), {})
|
||||
|
||||
>>> for param in sig.parameters.values():
|
||||
... if param.name not in ba.arguments:
|
||||
... ba.arguments[param.name] = param.default
|
||||
|
||||
>>> ba.args, ba.kwargs
|
||||
((5, 10), {})
|
||||
|
||||
|
||||
.. attribute:: BoundArguments.args
|
||||
|
||||
A tuple of positional arguments values. Dynamically computed from the
|
||||
:attr:`arguments` attribute.
|
||||
|
||||
.. attribute:: BoundArguments.kwargs
|
||||
|
||||
A dict of keyword arguments values. Dynamically computed from the
|
||||
:attr:`arguments` attribute.
|
||||
|
||||
The :attr:`args` and :attr:`kwargs` properties can be used to invoke
|
||||
functions::
|
||||
|
||||
def test(a, *, b):
|
||||
...
|
||||
|
||||
sig = signature(test)
|
||||
ba = sig.bind(10, b=20)
|
||||
test(*ba.args, **ba.kwargs)
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
:pep:`362` - Function Signature Object.
|
||||
The detailed specification, implementation details and examples.
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
||||
*funcsigs* is a derived work of CPython under the terms of the `PSF License
|
||||
Agreement`_. The original CPython inspect module, its unit tests and
|
||||
documentation are the copyright of the Python Software Foundation. The derived
|
||||
work is distributed under the `Apache License Version 2.0`_.
|
||||
|
||||
.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
|
||||
.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0
|
||||
.. _GitHub: https://github.com/testing-cabal/funcsigs
|
||||
.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
|
||||
.. _Travis CI: http://travis-ci.org/
|
||||
.. _Read The Docs: http://funcsigs.readthedocs.org/
|
||||
.. _PEP 362: http://www.python.org/dev/peps/pep-0362/
|
||||
.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object
|
||||
.. _issues system: https://github.com/testing-cabal/funcsigs/issues
|
||||
|
||||
.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master
|
||||
:target: http://travis-ci.org/#!/aliles/funcsigs
|
||||
:alt: Current build status
|
||||
|
||||
.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/aliles/funcsigs?branch=master
|
||||
:alt: Coverage status
|
||||
|
||||
.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png
|
||||
:target: https://crate.io/packages/funcsigs/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,381 +0,0 @@
|
|||
Metadata-Version: 2.0
|
||||
Name: funcsigs
|
||||
Version: 1.0.2
|
||||
Summary: Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+
|
||||
Home-page: http://funcsigs.readthedocs.org
|
||||
Author: Testing Cabal
|
||||
Author-email: testing-in-python@lists.idyll.org
|
||||
License: ASL
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Dist: ordereddict; python_version<"2.7"
|
||||
|
||||
.. funcsigs documentation master file, created by
|
||||
sphinx-quickstart on Fri Apr 20 20:27:52 2012.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Introducing funcsigs
|
||||
====================
|
||||
|
||||
The Funcsigs Package
|
||||
--------------------
|
||||
|
||||
``funcsigs`` is a backport of the `PEP 362`_ function signature features from
|
||||
Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7
|
||||
as well as 3.3 and up. 3.2 was supported by version 0.4, but with setuptools and
|
||||
pip no longer supporting 3.2, we cannot make any statement about 3.2
|
||||
compatibility.
|
||||
|
||||
Compatibility
|
||||
`````````````
|
||||
|
||||
The ``funcsigs`` backport has been tested against:
|
||||
|
||||
* CPython 2.6
|
||||
* CPython 2.7
|
||||
* CPython 3.3
|
||||
* CPython 3.4
|
||||
* CPython 3.5
|
||||
* CPython nightlies
|
||||
* PyPy and PyPy3(currently failing CI)
|
||||
|
||||
Continuous integration testing is provided by `Travis CI`_.
|
||||
|
||||
Under Python 2.x there is a compatibility issue when a function is assigned to
|
||||
the ``__wrapped__`` property of a class after it has been constructed.
|
||||
Similiarily there under PyPy directly passing the ``__call__`` method of a
|
||||
builtin is also a compatibility issues. Otherwise the functionality is
|
||||
believed to be uniform between both Python2 and Python3.
|
||||
|
||||
Issues
|
||||
``````
|
||||
|
||||
Source code for ``funcsigs`` is hosted on `GitHub`_. Any bug reports or feature
|
||||
requests can be made using GitHub's `issues system`_. |build_status| |coverage|
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
To obtain a `Signature` object, pass the target function to the
|
||||
``funcsigs.signature`` function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from funcsigs import signature
|
||||
>>> def foo(a, b=None, *args, **kwargs):
|
||||
... pass
|
||||
...
|
||||
>>> sig = signature(foo)
|
||||
>>> sig
|
||||
<funcsigs.Signature object at 0x...>
|
||||
>>> sig.parameters
|
||||
OrderedDict([('a', <Parameter at 0x... 'a'>), ('b', <Parameter at 0x... 'b'>), ('args', <Parameter at 0x... 'args'>), ('kwargs', <Parameter at 0x... 'kwargs'>)])
|
||||
>>> sig.return_annotation
|
||||
<class 'funcsigs._empty'>
|
||||
|
||||
Introspecting callables with the Signature object
|
||||
-------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
This section of documentation is a direct reproduction of the Python
|
||||
standard library documentation for the inspect module.
|
||||
|
||||
The Signature object represents the call signature of a callable object and its
|
||||
return annotation. To retrieve a Signature object, use the :func:`signature`
|
||||
function.
|
||||
|
||||
.. function:: signature(callable)
|
||||
|
||||
Return a :class:`Signature` object for the given ``callable``::
|
||||
|
||||
>>> from funcsigs import signature
|
||||
>>> def foo(a, *, b:int, **kwargs):
|
||||
... pass
|
||||
|
||||
>>> sig = signature(foo)
|
||||
|
||||
>>> str(sig)
|
||||
'(a, *, b:int, **kwargs)'
|
||||
|
||||
>>> str(sig.parameters['b'])
|
||||
'b:int'
|
||||
|
||||
>>> sig.parameters['b'].annotation
|
||||
<class 'int'>
|
||||
|
||||
Accepts a wide range of python callables, from plain functions and classes to
|
||||
:func:`functools.partial` objects.
|
||||
|
||||
.. note::
|
||||
|
||||
Some callables may not be introspectable in certain implementations of
|
||||
Python. For example, in CPython, built-in functions defined in C provide
|
||||
no metadata about their arguments.
|
||||
|
||||
|
||||
.. class:: Signature
|
||||
|
||||
A Signature object represents the call signature of a function and its return
|
||||
annotation. For each parameter accepted by the function it stores a
|
||||
:class:`Parameter` object in its :attr:`parameters` collection.
|
||||
|
||||
Signature objects are *immutable*. Use :meth:`Signature.replace` to make a
|
||||
modified copy.
|
||||
|
||||
.. attribute:: Signature.empty
|
||||
|
||||
A special class-level marker to specify absence of a return annotation.
|
||||
|
||||
.. attribute:: Signature.parameters
|
||||
|
||||
An ordered mapping of parameters' names to the corresponding
|
||||
:class:`Parameter` objects.
|
||||
|
||||
.. attribute:: Signature.return_annotation
|
||||
|
||||
The "return" annotation for the callable. If the callable has no "return"
|
||||
annotation, this attribute is set to :attr:`Signature.empty`.
|
||||
|
||||
.. method:: Signature.bind(*args, **kwargs)
|
||||
|
||||
Create a mapping from positional and keyword arguments to parameters.
|
||||
Returns :class:`BoundArguments` if ``*args`` and ``**kwargs`` match the
|
||||
signature, or raises a :exc:`TypeError`.
|
||||
|
||||
.. method:: Signature.bind_partial(*args, **kwargs)
|
||||
|
||||
Works the same way as :meth:`Signature.bind`, but allows the omission of
|
||||
some required arguments (mimics :func:`functools.partial` behavior.)
|
||||
Returns :class:`BoundArguments`, or raises a :exc:`TypeError` if the
|
||||
passed arguments do not match the signature.
|
||||
|
||||
.. method:: Signature.replace(*[, parameters][, return_annotation])
|
||||
|
||||
Create a new Signature instance based on the instance replace was invoked
|
||||
on. It is possible to pass different ``parameters`` and/or
|
||||
``return_annotation`` to override the corresponding properties of the base
|
||||
signature. To remove return_annotation from the copied Signature, pass in
|
||||
:attr:`Signature.empty`.
|
||||
|
||||
::
|
||||
|
||||
>>> def test(a, b):
|
||||
... pass
|
||||
>>> sig = signature(test)
|
||||
>>> new_sig = sig.replace(return_annotation="new return anno")
|
||||
>>> str(new_sig)
|
||||
"(a, b) -> 'new return anno'"
|
||||
|
||||
|
||||
.. class:: Parameter
|
||||
|
||||
Parameter objects are *immutable*. Instead of modifying a Parameter object,
|
||||
you can use :meth:`Parameter.replace` to create a modified copy.
|
||||
|
||||
.. attribute:: Parameter.empty
|
||||
|
||||
A special class-level marker to specify absence of default values and
|
||||
annotations.
|
||||
|
||||
.. attribute:: Parameter.name
|
||||
|
||||
The name of the parameter as a string. Must be a valid python identifier
|
||||
name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have
|
||||
it set to ``None``).
|
||||
|
||||
.. attribute:: Parameter.default
|
||||
|
||||
The default value for the parameter. If the parameter has no default
|
||||
value, this attribute is set to :attr:`Parameter.empty`.
|
||||
|
||||
.. attribute:: Parameter.annotation
|
||||
|
||||
The annotation for the parameter. If the parameter has no annotation,
|
||||
this attribute is set to :attr:`Parameter.empty`.
|
||||
|
||||
.. attribute:: Parameter.kind
|
||||
|
||||
Describes how argument values are bound to the parameter. Possible values
|
||||
(accessible via :class:`Parameter`, like ``Parameter.KEYWORD_ONLY``):
|
||||
|
||||
+------------------------+----------------------------------------------+
|
||||
| Name | Meaning |
|
||||
+========================+==============================================+
|
||||
| *POSITIONAL_ONLY* | Value must be supplied as a positional |
|
||||
| | argument. |
|
||||
| | |
|
||||
| | Python has no explicit syntax for defining |
|
||||
| | positional-only parameters, but many built-in|
|
||||
| | and extension module functions (especially |
|
||||
| | those that accept only one or two parameters)|
|
||||
| | accept them. |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or |
|
||||
| | positional argument (this is the standard |
|
||||
| | binding behaviour for functions implemented |
|
||||
| | in Python.) |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *VAR_POSITIONAL* | A tuple of positional arguments that aren't |
|
||||
| | bound to any other parameter. This |
|
||||
| | corresponds to a ``*args`` parameter in a |
|
||||
| | Python function definition. |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *KEYWORD_ONLY* | Value must be supplied as a keyword argument.|
|
||||
| | Keyword only parameters are those which |
|
||||
| | appear after a ``*`` or ``*args`` entry in a |
|
||||
| | Python function definition. |
|
||||
+------------------------+----------------------------------------------+
|
||||
| *VAR_KEYWORD* | A dict of keyword arguments that aren't bound|
|
||||
| | to any other parameter. This corresponds to a|
|
||||
| | ``**kwargs`` parameter in a Python function |
|
||||
| | definition. |
|
||||
+------------------------+----------------------------------------------+
|
||||
|
||||
Example: print all keyword-only arguments without default values::
|
||||
|
||||
>>> def foo(a, b, *, c, d=10):
|
||||
... pass
|
||||
|
||||
>>> sig = signature(foo)
|
||||
>>> for param in sig.parameters.values():
|
||||
... if (param.kind == param.KEYWORD_ONLY and
|
||||
... param.default is param.empty):
|
||||
... print('Parameter:', param)
|
||||
Parameter: c
|
||||
|
||||
.. method:: Parameter.replace(*[, name][, kind][, default][, annotation])
|
||||
|
||||
Create a new Parameter instance based on the instance replaced was invoked
|
||||
on. To override a :class:`Parameter` attribute, pass the corresponding
|
||||
argument. To remove a default value or/and an annotation from a
|
||||
Parameter, pass :attr:`Parameter.empty`.
|
||||
|
||||
::
|
||||
|
||||
>>> from funcsigs import Parameter
|
||||
>>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42)
|
||||
>>> str(param)
|
||||
'foo=42'
|
||||
|
||||
>>> str(param.replace()) # Will create a shallow copy of 'param'
|
||||
'foo=42'
|
||||
|
||||
>>> str(param.replace(default=Parameter.empty, annotation='spam'))
|
||||
"foo:'spam'"
|
||||
|
||||
|
||||
.. class:: BoundArguments
|
||||
|
||||
Result of a :meth:`Signature.bind` or :meth:`Signature.bind_partial` call.
|
||||
Holds the mapping of arguments to the function's parameters.
|
||||
|
||||
.. attribute:: BoundArguments.arguments
|
||||
|
||||
An ordered, mutable mapping (:class:`collections.OrderedDict`) of
|
||||
parameters' names to arguments' values. Contains only explicitly bound
|
||||
arguments. Changes in :attr:`arguments` will reflect in :attr:`args` and
|
||||
:attr:`kwargs`.
|
||||
|
||||
Should be used in conjunction with :attr:`Signature.parameters` for any
|
||||
argument processing purposes.
|
||||
|
||||
.. note::
|
||||
|
||||
Arguments for which :meth:`Signature.bind` or
|
||||
:meth:`Signature.bind_partial` relied on a default value are skipped.
|
||||
However, if needed, it is easy to include them.
|
||||
|
||||
::
|
||||
|
||||
>>> def foo(a, b=10):
|
||||
... pass
|
||||
|
||||
>>> sig = signature(foo)
|
||||
>>> ba = sig.bind(5)
|
||||
|
||||
>>> ba.args, ba.kwargs
|
||||
((5,), {})
|
||||
|
||||
>>> for param in sig.parameters.values():
|
||||
... if param.name not in ba.arguments:
|
||||
... ba.arguments[param.name] = param.default
|
||||
|
||||
>>> ba.args, ba.kwargs
|
||||
((5, 10), {})
|
||||
|
||||
|
||||
.. attribute:: BoundArguments.args
|
||||
|
||||
A tuple of positional arguments values. Dynamically computed from the
|
||||
:attr:`arguments` attribute.
|
||||
|
||||
.. attribute:: BoundArguments.kwargs
|
||||
|
||||
A dict of keyword arguments values. Dynamically computed from the
|
||||
:attr:`arguments` attribute.
|
||||
|
||||
The :attr:`args` and :attr:`kwargs` properties can be used to invoke
|
||||
functions::
|
||||
|
||||
def test(a, *, b):
|
||||
...
|
||||
|
||||
sig = signature(test)
|
||||
ba = sig.bind(10, b=20)
|
||||
test(*ba.args, **ba.kwargs)
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
:pep:`362` - Function Signature Object.
|
||||
The detailed specification, implementation details and examples.
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
||||
*funcsigs* is a derived work of CPython under the terms of the `PSF License
|
||||
Agreement`_. The original CPython inspect module, its unit tests and
|
||||
documentation are the copyright of the Python Software Foundation. The derived
|
||||
work is distributed under the `Apache License Version 2.0`_.
|
||||
|
||||
.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
|
||||
.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0
|
||||
.. _GitHub: https://github.com/testing-cabal/funcsigs
|
||||
.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python
|
||||
.. _Travis CI: http://travis-ci.org/
|
||||
.. _Read The Docs: http://funcsigs.readthedocs.org/
|
||||
.. _PEP 362: http://www.python.org/dev/peps/pep-0362/
|
||||
.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object
|
||||
.. _issues system: https://github.com/testing-cabal/funcsigs/issues
|
||||
|
||||
.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master
|
||||
:target: http://travis-ci.org/#!/aliles/funcsigs
|
||||
:alt: Current build status
|
||||
|
||||
.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/aliles/funcsigs?branch=master
|
||||
:alt: Coverage status
|
||||
|
||||
.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png
|
||||
:target: https://crate.io/packages/funcsigs/
|
||||
:alt: Latest PyPI version
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
funcsigs/__init__.py,sha256=GV4YulgeGW1IDS3l4__glZnbTJolLD_UGAFAhSGWOC8,30390
|
||||
funcsigs/version.py,sha256=Y3LSfRioSl2xch70pq_ULlvyECXyEtN3krVaWeGyaxk,22
|
||||
funcsigs-1.0.2.dist-info/DESCRIPTION.rst,sha256=aVg6hYTYjY6A9-oI4-lhQ9UEqeu0L3kn7LthaPgOYtY,13297
|
||||
funcsigs-1.0.2.dist-info/METADATA,sha256=fIBrN18etIHPSCZe0aq8SUSlmBwfvtl02Ce_thKVFtk,14420
|
||||
funcsigs-1.0.2.dist-info/RECORD,,
|
||||
funcsigs-1.0.2.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
|
||||
funcsigs-1.0.2.dist-info/metadata.json,sha256=NDDdI0osHQ-zi-4gvTivhTHSrTxME8fHKRCAfJ5NXj0,1294
|
||||
funcsigs-1.0.2.dist-info/pbr.json,sha256=TM9nSbjgR_z-OvEdNqbILWOYhlmpDbujt8yM_OHBwxM,46
|
||||
funcsigs-1.0.2.dist-info/top_level.txt,sha256=p0FFcT9rWjPboZWPK-LlnMEST6D8xzf6RvETJeNIsNs,9
|
|
@ -1,6 +0,0 @@
|
|||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules"], "extensions": {"python.details": {"contacts": [{"email": "testing-in-python@lists.idyll.org", "name": "Testing Cabal", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://funcsigs.readthedocs.org"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "license": "ASL", "metadata_version": "2.0", "name": "funcsigs", "run_requires": [{"environment": "python_version<\"2.7\"", "requires": ["ordereddict"]}], "summary": "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+", "test_requires": [{"requires": ["unittest2"]}], "version": "1.0.2"}
|
|
@ -1 +0,0 @@
|
|||
{"is_release": true, "git_version": "1b88d78"}
|
|
@ -1 +0,0 @@
|
|||
funcsigs
|
|
@ -1,829 +0,0 @@
|
|||
# Copyright 2001-2013 Python Software Foundation; All Rights Reserved
|
||||
"""Function signature objects for callables
|
||||
|
||||
Back port of Python 3.3's function signature tools from the inspect module,
|
||||
modified to be compatible with Python 2.6, 2.7 and 3.3+.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import itertools
|
||||
import functools
|
||||
import re
|
||||
import types
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from funcsigs.version import __version__
|
||||
|
||||
__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
|
||||
|
||||
|
||||
_WrapperDescriptor = type(type.__call__)
|
||||
_MethodWrapper = type(all.__call__)
|
||||
|
||||
_NonUserDefinedCallables = (_WrapperDescriptor,
|
||||
_MethodWrapper,
|
||||
types.BuiltinFunctionType)
|
||||
|
||||
|
||||
def formatannotation(annotation, base_module=None):
|
||||
if isinstance(annotation, type):
|
||||
if annotation.__module__ in ('builtins', '__builtin__', base_module):
|
||||
return annotation.__name__
|
||||
return annotation.__module__+'.'+annotation.__name__
|
||||
return repr(annotation)
|
||||
|
||||
|
||||
def _get_user_defined_method(cls, method_name, *nested):
|
||||
try:
|
||||
if cls is type:
|
||||
return
|
||||
meth = getattr(cls, method_name)
|
||||
for name in nested:
|
||||
meth = getattr(meth, name, meth)
|
||||
except AttributeError:
|
||||
return
|
||||
else:
|
||||
if not isinstance(meth, _NonUserDefinedCallables):
|
||||
# Once '__signature__' will be added to 'C'-level
|
||||
# callables, this check won't be necessary
|
||||
return meth
|
||||
|
||||
|
||||
def signature(obj):
|
||||
'''Get a signature object for the passed callable.'''
|
||||
|
||||
if not callable(obj):
|
||||
raise TypeError('{0!r} is not a callable object'.format(obj))
|
||||
|
||||
if isinstance(obj, types.MethodType):
|
||||
sig = signature(obj.__func__)
|
||||
if obj.__self__ is None:
|
||||
# Unbound method - preserve as-is.
|
||||
return sig
|
||||
else:
|
||||
# Bound method. Eat self - if we can.
|
||||
params = tuple(sig.parameters.values())
|
||||
|
||||
if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
|
||||
raise ValueError('invalid method signature')
|
||||
|
||||
kind = params[0].kind
|
||||
if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY):
|
||||
# Drop first parameter:
|
||||
# '(p1, p2[, ...])' -> '(p2[, ...])'
|
||||
params = params[1:]
|
||||
else:
|
||||
if kind is not _VAR_POSITIONAL:
|
||||
# Unless we add a new parameter type we never
|
||||
# get here
|
||||
raise ValueError('invalid argument type')
|
||||
# It's a var-positional parameter.
|
||||
# Do nothing. '(*args[, ...])' -> '(*args[, ...])'
|
||||
|
||||
return sig.replace(parameters=params)
|
||||
|
||||
try:
|
||||
sig = obj.__signature__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if sig is not None:
|
||||
return sig
|
||||
|
||||
try:
|
||||
# Was this function wrapped by a decorator?
|
||||
wrapped = obj.__wrapped__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
return signature(wrapped)
|
||||
|
||||
if isinstance(obj, types.FunctionType):
|
||||
return Signature.from_function(obj)
|
||||
|
||||
if isinstance(obj, functools.partial):
|
||||
sig = signature(obj.func)
|
||||
|
||||
new_params = OrderedDict(sig.parameters.items())
|
||||
|
||||
partial_args = obj.args or ()
|
||||
partial_keywords = obj.keywords or {}
|
||||
try:
|
||||
ba = sig.bind_partial(*partial_args, **partial_keywords)
|
||||
except TypeError as ex:
|
||||
msg = 'partial object {0!r} has incorrect arguments'.format(obj)
|
||||
raise ValueError(msg)
|
||||
|
||||
for arg_name, arg_value in ba.arguments.items():
|
||||
param = new_params[arg_name]
|
||||
if arg_name in partial_keywords:
|
||||
# We set a new default value, because the following code
|
||||
# is correct:
|
||||
#
|
||||
# >>> def foo(a): print(a)
|
||||
# >>> print(partial(partial(foo, a=10), a=20)())
|
||||
# 20
|
||||
# >>> print(partial(partial(foo, a=10), a=20)(a=30))
|
||||
# 30
|
||||
#
|
||||
# So, with 'partial' objects, passing a keyword argument is
|
||||
# like setting a new default value for the corresponding
|
||||
# parameter
|
||||
#
|
||||
# We also mark this parameter with '_partial_kwarg'
|
||||
# flag. Later, in '_bind', the 'default' value of this
|
||||
# parameter will be added to 'kwargs', to simulate
|
||||
# the 'functools.partial' real call.
|
||||
new_params[arg_name] = param.replace(default=arg_value,
|
||||
_partial_kwarg=True)
|
||||
|
||||
elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
|
||||
not param._partial_kwarg):
|
||||
new_params.pop(arg_name)
|
||||
|
||||
return sig.replace(parameters=new_params.values())
|
||||
|
||||
sig = None
|
||||
if isinstance(obj, type):
|
||||
# obj is a class or a metaclass
|
||||
|
||||
# First, let's see if it has an overloaded __call__ defined
|
||||
# in its metaclass
|
||||
call = _get_user_defined_method(type(obj), '__call__')
|
||||
if call is not None:
|
||||
sig = signature(call)
|
||||
else:
|
||||
# Now we check if the 'obj' class has a '__new__' method
|
||||
new = _get_user_defined_method(obj, '__new__')
|
||||
if new is not None:
|
||||
sig = signature(new)
|
||||
else:
|
||||
# Finally, we should have at least __init__ implemented
|
||||
init = _get_user_defined_method(obj, '__init__')
|
||||
if init is not None:
|
||||
sig = signature(init)
|
||||
elif not isinstance(obj, _NonUserDefinedCallables):
|
||||
# An object with __call__
|
||||
# We also check that the 'obj' is not an instance of
|
||||
# _WrapperDescriptor or _MethodWrapper to avoid
|
||||
# infinite recursion (and even potential segfault)
|
||||
call = _get_user_defined_method(type(obj), '__call__', 'im_func')
|
||||
if call is not None:
|
||||
sig = signature(call)
|
||||
|
||||
if sig is not None:
|
||||
# For classes and objects we skip the first parameter of their
|
||||
# __call__, __new__, or __init__ methods
|
||||
return sig.replace(parameters=tuple(sig.parameters.values())[1:])
|
||||
|
||||
if isinstance(obj, types.BuiltinFunctionType):
|
||||
# Raise a nicer error message for builtins
|
||||
msg = 'no signature found for builtin function {0!r}'.format(obj)
|
||||
raise ValueError(msg)
|
||||
|
||||
raise ValueError('callable {0!r} is not supported by signature'.format(obj))
|
||||
|
||||
|
||||
class _void(object):
|
||||
'''A private marker - used in Parameter & Signature'''
|
||||
|
||||
|
||||
class _empty(object):
|
||||
pass
|
||||
|
||||
|
||||
class _ParameterKind(int):
|
||||
def __new__(self, *args, **kwargs):
|
||||
obj = int.__new__(self, *args)
|
||||
obj._name = kwargs['name']
|
||||
return obj
|
||||
|
||||
def __str__(self):
|
||||
return self._name
|
||||
|
||||
def __repr__(self):
|
||||
return '<_ParameterKind: {0!r}>'.format(self._name)
|
||||
|
||||
|
||||
_POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY')
|
||||
_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
|
||||
_VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL')
|
||||
_KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY')
|
||||
_VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD')
|
||||
|
||||
|
||||
class Parameter(object):
|
||||
'''Represents a parameter in a function signature.
|
||||
|
||||
Has the following public attributes:
|
||||
|
||||
* name : str
|
||||
The name of the parameter as a string.
|
||||
* default : object
|
||||
The default value for the parameter if specified. If the
|
||||
parameter has no default value, this attribute is not set.
|
||||
* annotation
|
||||
The annotation for the parameter if specified. If the
|
||||
parameter has no annotation, this attribute is not set.
|
||||
* kind : str
|
||||
Describes how argument values are bound to the parameter.
|
||||
Possible values: `Parameter.POSITIONAL_ONLY`,
|
||||
`Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
|
||||
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
|
||||
'''
|
||||
|
||||
__slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
|
||||
|
||||
POSITIONAL_ONLY = _POSITIONAL_ONLY
|
||||
POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
|
||||
VAR_POSITIONAL = _VAR_POSITIONAL
|
||||
KEYWORD_ONLY = _KEYWORD_ONLY
|
||||
VAR_KEYWORD = _VAR_KEYWORD
|
||||
|
||||
empty = _empty
|
||||
|
||||
def __init__(self, name, kind, default=_empty, annotation=_empty,
|
||||
_partial_kwarg=False):
|
||||
|
||||
if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
|
||||
_VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
|
||||
raise ValueError("invalid value for 'Parameter.kind' attribute")
|
||||
self._kind = kind
|
||||
|
||||
if default is not _empty:
|
||||
if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
|
||||
msg = '{0} parameters cannot have default values'.format(kind)
|
||||
raise ValueError(msg)
|
||||
self._default = default
|
||||
self._annotation = annotation
|
||||
|
||||
if name is None:
|
||||
if kind != _POSITIONAL_ONLY:
|
||||
raise ValueError("None is not a valid name for a "
|
||||
"non-positional-only parameter")
|
||||
self._name = name
|
||||
else:
|
||||
name = str(name)
|
||||
if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
|
||||
msg = '{0!r} is not a valid parameter name'.format(name)
|
||||
raise ValueError(msg)
|
||||
self._name = name
|
||||
|
||||
self._partial_kwarg = _partial_kwarg
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
return self._default
|
||||
|
||||
@property
|
||||
def annotation(self):
|
||||
return self._annotation
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
return self._kind
|
||||
|
||||
def replace(self, name=_void, kind=_void, annotation=_void,
|
||||
default=_void, _partial_kwarg=_void):
|
||||
'''Creates a customized copy of the Parameter.'''
|
||||
|
||||
if name is _void:
|
||||
name = self._name
|
||||
|
||||
if kind is _void:
|
||||
kind = self._kind
|
||||
|
||||
if annotation is _void:
|
||||
annotation = self._annotation
|
||||
|
||||
if default is _void:
|
||||
default = self._default
|
||||
|
||||
if _partial_kwarg is _void:
|
||||
_partial_kwarg = self._partial_kwarg
|
||||
|
||||
return type(self)(name, kind, default=default, annotation=annotation,
|
||||
_partial_kwarg=_partial_kwarg)
|
||||
|
||||
def __str__(self):
|
||||
kind = self.kind
|
||||
|
||||
formatted = self._name
|
||||
if kind == _POSITIONAL_ONLY:
|
||||
if formatted is None:
|
||||
formatted = ''
|
||||
formatted = '<{0}>'.format(formatted)
|
||||
|
||||
# Add annotation and default value
|
||||
if self._annotation is not _empty:
|
||||
formatted = '{0}:{1}'.format(formatted,
|
||||
formatannotation(self._annotation))
|
||||
|
||||
if self._default is not _empty:
|
||||
formatted = '{0}={1}'.format(formatted, repr(self._default))
|
||||
|
||||
if kind == _VAR_POSITIONAL:
|
||||
formatted = '*' + formatted
|
||||
elif kind == _VAR_KEYWORD:
|
||||
formatted = '**' + formatted
|
||||
|
||||
return formatted
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
|
||||
id(self), self.name)
|
||||
|
||||
def __hash__(self):
|
||||
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
|
||||
raise TypeError(msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (issubclass(other.__class__, Parameter) and
|
||||
self._name == other._name and
|
||||
self._kind == other._kind and
|
||||
self._default == other._default and
|
||||
self._annotation == other._annotation)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class BoundArguments(object):
|
||||
'''Result of `Signature.bind` call. Holds the mapping of arguments
|
||||
to the function's parameters.
|
||||
|
||||
Has the following public attributes:
|
||||
|
||||
* arguments : OrderedDict
|
||||
An ordered mutable mapping of parameters' names to arguments' values.
|
||||
Does not contain arguments' default values.
|
||||
* signature : Signature
|
||||
The Signature object that created this instance.
|
||||
* args : tuple
|
||||
Tuple of positional arguments values.
|
||||
* kwargs : dict
|
||||
Dict of keyword arguments values.
|
||||
'''
|
||||
|
||||
def __init__(self, signature, arguments):
|
||||
self.arguments = arguments
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
def signature(self):
|
||||
return self._signature
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
args = []
|
||||
for param_name, param in self._signature.parameters.items():
|
||||
if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
|
||||
param._partial_kwarg):
|
||||
# Keyword arguments mapped by 'functools.partial'
|
||||
# (Parameter._partial_kwarg is True) are mapped
|
||||
# in 'BoundArguments.kwargs', along with VAR_KEYWORD &
|
||||
# KEYWORD_ONLY
|
||||
break
|
||||
|
||||
try:
|
||||
arg = self.arguments[param_name]
|
||||
except KeyError:
|
||||
# We're done here. Other arguments
|
||||
# will be mapped in 'BoundArguments.kwargs'
|
||||
break
|
||||
else:
|
||||
if param.kind == _VAR_POSITIONAL:
|
||||
# *args
|
||||
args.extend(arg)
|
||||
else:
|
||||
# plain argument
|
||||
args.append(arg)
|
||||
|
||||
return tuple(args)
|
||||
|
||||
@property
|
||||
def kwargs(self):
|
||||
kwargs = {}
|
||||
kwargs_started = False
|
||||
for param_name, param in self._signature.parameters.items():
|
||||
if not kwargs_started:
|
||||
if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
|
||||
param._partial_kwarg):
|
||||
kwargs_started = True
|
||||
else:
|
||||
if param_name not in self.arguments:
|
||||
kwargs_started = True
|
||||
continue
|
||||
|
||||
if not kwargs_started:
|
||||
continue
|
||||
|
||||
try:
|
||||
arg = self.arguments[param_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if param.kind == _VAR_KEYWORD:
|
||||
# **kwargs
|
||||
kwargs.update(arg)
|
||||
else:
|
||||
# plain keyword argument
|
||||
kwargs[param_name] = arg
|
||||
|
||||
return kwargs
|
||||
|
||||
def __hash__(self):
|
||||
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
|
||||
raise TypeError(msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (issubclass(other.__class__, BoundArguments) and
|
||||
self.signature == other.signature and
|
||||
self.arguments == other.arguments)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class Signature(object):
|
||||
'''A Signature object represents the overall signature of a function.
|
||||
It stores a Parameter object for each parameter accepted by the
|
||||
function, as well as information specific to the function itself.
|
||||
|
||||
A Signature object has the following public attributes and methods:
|
||||
|
||||
* parameters : OrderedDict
|
||||
An ordered mapping of parameters' names to the corresponding
|
||||
Parameter objects (keyword-only arguments are in the same order
|
||||
as listed in `code.co_varnames`).
|
||||
* return_annotation : object
|
||||
The annotation for the return type of the function if specified.
|
||||
If the function has no annotation for its return type, this
|
||||
attribute is not set.
|
||||
* bind(*args, **kwargs) -> BoundArguments
|
||||
Creates a mapping from positional and keyword arguments to
|
||||
parameters.
|
||||
* bind_partial(*args, **kwargs) -> BoundArguments
|
||||
Creates a partial mapping from positional and keyword arguments
|
||||
to parameters (simulating 'functools.partial' behavior.)
|
||||
'''
|
||||
|
||||
__slots__ = ('_return_annotation', '_parameters')
|
||||
|
||||
_parameter_cls = Parameter
|
||||
_bound_arguments_cls = BoundArguments
|
||||
|
||||
empty = _empty
|
||||
|
||||
def __init__(self, parameters=None, return_annotation=_empty,
|
||||
__validate_parameters__=True):
|
||||
'''Constructs Signature from the given list of Parameter
|
||||
objects and 'return_annotation'. All arguments are optional.
|
||||
'''
|
||||
|
||||
if parameters is None:
|
||||
params = OrderedDict()
|
||||
else:
|
||||
if __validate_parameters__:
|
||||
params = OrderedDict()
|
||||
top_kind = _POSITIONAL_ONLY
|
||||
|
||||
for idx, param in enumerate(parameters):
|
||||
kind = param.kind
|
||||
if kind < top_kind:
|
||||
msg = 'wrong parameter order: {0} before {1}'
|
||||
msg = msg.format(top_kind, param.kind)
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
top_kind = kind
|
||||
|
||||
name = param.name
|
||||
if name is None:
|
||||
name = str(idx)
|
||||
param = param.replace(name=name)
|
||||
|
||||
if name in params:
|
||||
msg = 'duplicate parameter name: {0!r}'.format(name)
|
||||
raise ValueError(msg)
|
||||
params[name] = param
|
||||
else:
|
||||
params = OrderedDict(((param.name, param)
|
||||
for param in parameters))
|
||||
|
||||
self._parameters = params
|
||||
self._return_annotation = return_annotation
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, func):
|
||||
'''Constructs Signature for the given python function'''
|
||||
|
||||
if not isinstance(func, types.FunctionType):
|
||||
raise TypeError('{0!r} is not a Python function'.format(func))
|
||||
|
||||
Parameter = cls._parameter_cls
|
||||
|
||||
# Parameter information.
|
||||
func_code = func.__code__
|
||||
pos_count = func_code.co_argcount
|
||||
arg_names = func_code.co_varnames
|
||||
positional = tuple(arg_names[:pos_count])
|
||||
keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
|
||||
keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
|
||||
annotations = getattr(func, '__annotations__', {})
|
||||
defaults = func.__defaults__
|
||||
kwdefaults = getattr(func, '__kwdefaults__', None)
|
||||
|
||||
if defaults:
|
||||
pos_default_count = len(defaults)
|
||||
else:
|
||||
pos_default_count = 0
|
||||
|
||||
parameters = []
|
||||
|
||||
# Non-keyword-only parameters w/o defaults.
|
||||
non_default_count = pos_count - pos_default_count
|
||||
for name in positional[:non_default_count]:
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_POSITIONAL_OR_KEYWORD))
|
||||
|
||||
# ... w/ defaults.
|
||||
for offset, name in enumerate(positional[non_default_count:]):
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_POSITIONAL_OR_KEYWORD,
|
||||
default=defaults[offset]))
|
||||
|
||||
# *args
|
||||
if func_code.co_flags & 0x04:
|
||||
name = arg_names[pos_count + keyword_only_count]
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_VAR_POSITIONAL))
|
||||
|
||||
# Keyword-only parameters.
|
||||
for name in keyword_only:
|
||||
default = _empty
|
||||
if kwdefaults is not None:
|
||||
default = kwdefaults.get(name, _empty)
|
||||
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_KEYWORD_ONLY,
|
||||
default=default))
|
||||
# **kwargs
|
||||
if func_code.co_flags & 0x08:
|
||||
index = pos_count + keyword_only_count
|
||||
if func_code.co_flags & 0x04:
|
||||
index += 1
|
||||
|
||||
name = arg_names[index]
|
||||
annotation = annotations.get(name, _empty)
|
||||
parameters.append(Parameter(name, annotation=annotation,
|
||||
kind=_VAR_KEYWORD))
|
||||
|
||||
return cls(parameters,
|
||||
return_annotation=annotations.get('return', _empty),
|
||||
__validate_parameters__=False)
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
try:
|
||||
return types.MappingProxyType(self._parameters)
|
||||
except AttributeError:
|
||||
return OrderedDict(self._parameters.items())
|
||||
|
||||
@property
|
||||
def return_annotation(self):
|
||||
return self._return_annotation
|
||||
|
||||
def replace(self, parameters=_void, return_annotation=_void):
|
||||
'''Creates a customized copy of the Signature.
|
||||
Pass 'parameters' and/or 'return_annotation' arguments
|
||||
to override them in the new copy.
|
||||
'''
|
||||
|
||||
if parameters is _void:
|
||||
parameters = self.parameters.values()
|
||||
|
||||
if return_annotation is _void:
|
||||
return_annotation = self._return_annotation
|
||||
|
||||
return type(self)(parameters,
|
||||
return_annotation=return_annotation)
|
||||
|
||||
def __hash__(self):
|
||||
msg = "unhashable type: '{0}'".format(self.__class__.__name__)
|
||||
raise TypeError(msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
if (not issubclass(type(other), Signature) or
|
||||
self.return_annotation != other.return_annotation or
|
||||
len(self.parameters) != len(other.parameters)):
|
||||
return False
|
||||
|
||||
other_positions = dict((param, idx)
|
||||
for idx, param in enumerate(other.parameters.keys()))
|
||||
|
||||
for idx, (param_name, param) in enumerate(self.parameters.items()):
|
||||
if param.kind == _KEYWORD_ONLY:
|
||||
try:
|
||||
other_param = other.parameters[param_name]
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
if param != other_param:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
other_idx = other_positions[param_name]
|
||||
except KeyError:
|
||||
return False
|
||||
else:
|
||||
if (idx != other_idx or
|
||||
param != other.parameters[param_name]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def _bind(self, args, kwargs, partial=False):
|
||||
'''Private method. Don't use directly.'''
|
||||
|
||||
arguments = OrderedDict()
|
||||
|
||||
parameters = iter(self.parameters.values())
|
||||
parameters_ex = ()
|
||||
arg_vals = iter(args)
|
||||
|
||||
if partial:
|
||||
# Support for binding arguments to 'functools.partial' objects.
|
||||
# See 'functools.partial' case in 'signature()' implementation
|
||||
# for details.
|
||||
for param_name, param in self.parameters.items():
|
||||
if (param._partial_kwarg and param_name not in kwargs):
|
||||
# Simulating 'functools.partial' behavior
|
||||
kwargs[param_name] = param.default
|
||||
|
||||
while True:
|
||||
# Let's iterate through the positional arguments and corresponding
|
||||
# parameters
|
||||
try:
|
||||
arg_val = next(arg_vals)
|
||||
except StopIteration:
|
||||
# No more positional arguments
|
||||
try:
|
||||
param = next(parameters)
|
||||
except StopIteration:
|
||||
# No more parameters. That's it. Just need to check that
|
||||
# we have no `kwargs` after this while loop
|
||||
break
|
||||
else:
|
||||
if param.kind == _VAR_POSITIONAL:
|
||||
# That's OK, just empty *args. Let's start parsing
|
||||
# kwargs
|
||||
break
|
||||
elif param.name in kwargs:
|
||||
if param.kind == _POSITIONAL_ONLY:
|
||||
msg = '{arg!r} parameter is positional only, ' \
|
||||
'but was passed as a keyword'
|
||||
msg = msg.format(arg=param.name)
|
||||
raise TypeError(msg)
|
||||
parameters_ex = (param,)
|
||||
break
|
||||
elif (param.kind == _VAR_KEYWORD or
|
||||
param.default is not _empty):
|
||||
# That's fine too - we have a default value for this
|
||||
# parameter. So, lets start parsing `kwargs`, starting
|
||||
# with the current parameter
|
||||
parameters_ex = (param,)
|
||||
break
|
||||
else:
|
||||
if partial:
|
||||
parameters_ex = (param,)
|
||||
break
|
||||
else:
|
||||
msg = '{arg!r} parameter lacking default value'
|
||||
msg = msg.format(arg=param.name)
|
||||
raise TypeError(msg)
|
||||
else:
|
||||
# We have a positional argument to process
|
||||
try:
|
||||
param = next(parameters)
|
||||
except StopIteration:
|
||||
raise TypeError('too many positional arguments')
|
||||
else:
|
||||
if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
|
||||
# Looks like we have no parameter for this positional
|
||||
# argument
|
||||
raise TypeError('too many positional arguments')
|
||||
|
||||
if param.kind == _VAR_POSITIONAL:
|
||||
# We have an '*args'-like argument, let's fill it with
|
||||
# all positional arguments we have left and move on to
|
||||
# the next phase
|
||||
values = [arg_val]
|
||||
values.extend(arg_vals)
|
||||
arguments[param.name] = tuple(values)
|
||||
break
|
||||
|
||||
if param.name in kwargs:
|
||||
raise TypeError('multiple values for argument '
|
||||
'{arg!r}'.format(arg=param.name))
|
||||
|
||||
arguments[param.name] = arg_val
|
||||
|
||||
# Now, we iterate through the remaining parameters to process
|
||||
# keyword arguments
|
||||
kwargs_param = None
|
||||
for param in itertools.chain(parameters_ex, parameters):
|
||||
if param.kind == _POSITIONAL_ONLY:
|
||||
# This should never happen in case of a properly built
|
||||
# Signature object (but let's have this check here
|
||||
# to ensure correct behaviour just in case)
|
||||
raise TypeError('{arg!r} parameter is positional only, '
|
||||
'but was passed as a keyword'. \
|
||||
format(arg=param.name))
|
||||
|
||||
if param.kind == _VAR_KEYWORD:
|
||||
# Memorize that we have a '**kwargs'-like parameter
|
||||
kwargs_param = param
|
||||
continue
|
||||
|
||||
param_name = param.name
|
||||
try:
|
||||
arg_val = kwargs.pop(param_name)
|
||||
except KeyError:
|
||||
# We have no value for this parameter. It's fine though,
|
||||
# if it has a default value, or it is an '*args'-like
|
||||
# parameter, left alone by the processing of positional
|
||||
# arguments.
|
||||
if (not partial and param.kind != _VAR_POSITIONAL and
|
||||
param.default is _empty):
|
||||
raise TypeError('{arg!r} parameter lacking default value'. \
|
||||
format(arg=param_name))
|
||||
|
||||
else:
|
||||
arguments[param_name] = arg_val
|
||||
|
||||
if kwargs:
|
||||
if kwargs_param is not None:
|
||||
# Process our '**kwargs'-like parameter
|
||||
arguments[kwargs_param.name] = kwargs
|
||||
else:
|
||||
raise TypeError('too many keyword arguments %r' % kwargs)
|
||||
|
||||
return self._bound_arguments_cls(self, arguments)
|
||||
|
||||
def bind(*args, **kwargs):
|
||||
'''Get a BoundArguments object, that maps the passed `args`
|
||||
and `kwargs` to the function's signature. Raises `TypeError`
|
||||
if the passed arguments can not be bound.
|
||||
'''
|
||||
return args[0]._bind(args[1:], kwargs)
|
||||
|
||||
def bind_partial(self, *args, **kwargs):
|
||||
'''Get a BoundArguments object, that partially maps the
|
||||
passed `args` and `kwargs` to the function's signature.
|
||||
Raises `TypeError` if the passed arguments can not be bound.
|
||||
'''
|
||||
return self._bind(args, kwargs, partial=True)
|
||||
|
||||
def __str__(self):
|
||||
result = []
|
||||
render_kw_only_separator = True
|
||||
for idx, param in enumerate(self.parameters.values()):
|
||||
formatted = str(param)
|
||||
|
||||
kind = param.kind
|
||||
if kind == _VAR_POSITIONAL:
|
||||
# OK, we have an '*args'-like parameter, so we won't need
|
||||
# a '*' to separate keyword-only arguments
|
||||
render_kw_only_separator = False
|
||||
elif kind == _KEYWORD_ONLY and render_kw_only_separator:
|
||||
# We have a keyword-only parameter to render and we haven't
|
||||
# rendered an '*args'-like parameter before, so add a '*'
|
||||
# separator to the parameters list ("foo(arg1, *, arg2)" case)
|
||||
result.append('*')
|
||||
# This condition should be only triggered once, so
|
||||
# reset the flag
|
||||
render_kw_only_separator = False
|
||||
|
||||
result.append(formatted)
|
||||
|
||||
rendered = '({0})'.format(', '.join(result))
|
||||
|
||||
if self.return_annotation is not _empty:
|
||||
anno = formatannotation(self.return_annotation)
|
||||
rendered += ' -> {0}'.format(anno)
|
||||
|
||||
return rendered
|
|
@ -1 +0,0 @@
|
|||
__version__ = "1.0.2"
|
|
@ -11,15 +11,9 @@ with Files('**'):
|
|||
with Files('attrs/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'Task Configuration')
|
||||
|
||||
with Files('blessings/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('compare-locales/**'):
|
||||
with Files('compare_locales/**'):
|
||||
BUG_COMPONENT = ('Localization Infrastructure and Tools', 'compare-locales')
|
||||
|
||||
with Files('dlmanager/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('fluent.migrate/**'):
|
||||
BUG_COMPONENT = ('Localization Infrastructure and Tools', 'Fluent Migration')
|
||||
|
||||
|
@ -27,57 +21,30 @@ with Files('fluent.migrate/**'):
|
|||
with Files('fluent.syntax/**'):
|
||||
BUG_COMPONENT = ('Localization Infrastructure and Tools', 'General')
|
||||
|
||||
with Files('futures/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('jsmin/**'):
|
||||
BUG_COMPONENT = ('GeckoView', 'General')
|
||||
|
||||
with Files('mock-1.0.0/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('mohawk/**'):
|
||||
BUG_COMPONENT = ('Taskcluster', 'Platform Libraries')
|
||||
|
||||
with Files('mozilla-version/**'):
|
||||
with Files('mozilla_version/**'):
|
||||
BUG_COMPONENT = ('Release Engineering', 'General')
|
||||
|
||||
with Files('psutil/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('py/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('pyasn1/**'):
|
||||
BUG_COMPONENT = ('Release Engineering', 'General')
|
||||
|
||||
with Files('pyasn1-modules/**'):
|
||||
with Files('pyasn1_modules/**'):
|
||||
BUG_COMPONENT = ('Core', 'Security: PSM')
|
||||
|
||||
with Files('pylru/**'):
|
||||
BUG_COMPONENT = ('mozilla.org', 'MozillaBuild')
|
||||
|
||||
with Files('pystache/**'):
|
||||
BUG_COMPONENT = ('Taskcluster', 'General')
|
||||
|
||||
with Files('pytest/**'):
|
||||
BUG_COMPONENT = ('Testing', 'General')
|
||||
|
||||
with Files('pytoml/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('pyyaml/**'):
|
||||
BUG_COMPONENT = ('Taskcluster', 'General')
|
||||
|
||||
with Files('redo/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('requests*/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('requirements.*'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('rsa/**'):
|
||||
BUG_COMPONENT = ('Core', 'Security: PSM')
|
||||
|
||||
|
@ -87,8 +54,5 @@ with Files('slugid/**'):
|
|||
with Files('taskcluster/**'):
|
||||
BUG_COMPONENT = ('Taskcluster', 'Platform Libraries')
|
||||
|
||||
with Files('virtualenv/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'General')
|
||||
|
||||
with Files('voluptuous/**'):
|
||||
BUG_COMPONENT = ('Firefox Build System', 'Task Configuration')
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
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
|
|
@ -1,22 +0,0 @@
|
|||
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.
|
|
@ -1,13 +0,0 @@
|
|||
include README.md
|
||||
include HISTORY.md
|
||||
include LICENSE
|
||||
include TODO.md
|
||||
include setup_description.rst
|
||||
include tox.ini
|
||||
include test_pystache.py
|
||||
# You cannot use package_data, for example, to include data files in a
|
||||
# source distribution when using Distribute.
|
||||
recursive-include pystache/tests *.mustache *.txt
|
||||
# We deliberately exclude the gh/ directory because it contains copies
|
||||
# of resources needed only for the web page hosted on GitHub (via the
|
||||
# gh-pages branch).
|
|
@ -1,536 +0,0 @@
|
|||
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
|
|
@ -1,276 +0,0 @@
|
|||
Pystache
|
||||
========
|
||||
|
||||
<!-- Since PyPI rejects reST long descriptions that contain HTML, -->
|
||||
<!-- HTML comments must be removed when converting this file to reST. -->
|
||||
<!-- For more information on PyPI's behavior in this regard, see: -->
|
||||
<!-- http://docs.python.org/distutils/uploading.html#pypi-package-display -->
|
||||
<!-- The Pystache setup script strips 1-line HTML comments prior -->
|
||||
<!-- to converting to reST, so all HTML comments should be one line. -->
|
||||
<!-- -->
|
||||
<!-- We leave the leading brackets empty here. Otherwise, unwanted -->
|
||||
<!-- caption text shows up in the reST version converted by pandoc. -->
|
||||
![](http://defunkt.github.com/pystache/images/logo_phillips.png "mustachioed, monocled snake by David Phillips")
|
||||
|
||||
![](https://secure.travis-ci.org/defunkt/pystache.png "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).
|
||||
![](http://i.creativecommons.org/l/by-sa/3.0/88x31.png "Creative
|
||||
Commons Attribution-ShareAlike 3.0 Unported License")
|
|
@ -1,16 +0,0 @@
|
|||
TODO
|
||||
====
|
||||
|
||||
In development branch:
|
||||
|
||||
* Figure out a way to suppress center alignment of images in reST output.
|
||||
* Add a unit test for the change made in 7ea8e7180c41. This is with regard
|
||||
to not requiring spec tests when running tests from a downloaded sdist.
|
||||
* End support for Python 2.4.
|
||||
* Add Python 3.3 to tox file (after deprecating 2.4).
|
||||
* Turn the benchmarking script at pystache/tests/benchmark.py into a command
|
||||
in pystache/commands, or make it a subcommand of one of the existing
|
||||
commands (i.e. using a command argument).
|
||||
* Provide support for logging in at least one of the commands.
|
||||
* Make sure command parsing to pystache-test doesn't break with Python 2.4 and earlier.
|
||||
* Combine pystache-test with the main command.
|
|
@ -1,536 +0,0 @@
|
|||
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
|
|
@ -1,97 +0,0 @@
|
|||
HISTORY.md
|
||||
LICENSE
|
||||
MANIFEST.in
|
||||
README.md
|
||||
TODO.md
|
||||
setup.py
|
||||
setup_description.rst
|
||||
test_pystache.py
|
||||
tox.ini
|
||||
pystache/__init__.py
|
||||
pystache/common.py
|
||||
pystache/context.py
|
||||
pystache/defaults.py
|
||||
pystache/init.py
|
||||
pystache/loader.py
|
||||
pystache/locator.py
|
||||
pystache/parsed.py
|
||||
pystache/parser.py
|
||||
pystache/renderengine.py
|
||||
pystache/renderer.py
|
||||
pystache/specloader.py
|
||||
pystache/template_spec.py
|
||||
pystache.egg-info/PKG-INFO
|
||||
pystache.egg-info/SOURCES.txt
|
||||
pystache.egg-info/dependency_links.txt
|
||||
pystache.egg-info/entry_points.txt
|
||||
pystache.egg-info/top_level.txt
|
||||
pystache/commands/__init__.py
|
||||
pystache/commands/render.py
|
||||
pystache/commands/test.py
|
||||
pystache/tests/__init__.py
|
||||
pystache/tests/benchmark.py
|
||||
pystache/tests/common.py
|
||||
pystache/tests/doctesting.py
|
||||
pystache/tests/main.py
|
||||
pystache/tests/spectesting.py
|
||||
pystache/tests/test___init__.py
|
||||
pystache/tests/test_commands.py
|
||||
pystache/tests/test_context.py
|
||||
pystache/tests/test_defaults.py
|
||||
pystache/tests/test_examples.py
|
||||
pystache/tests/test_loader.py
|
||||
pystache/tests/test_locator.py
|
||||
pystache/tests/test_parser.py
|
||||
pystache/tests/test_pystache.py
|
||||
pystache/tests/test_renderengine.py
|
||||
pystache/tests/test_renderer.py
|
||||
pystache/tests/test_simple.py
|
||||
pystache/tests/test_specloader.py
|
||||
pystache/tests/data/__init__.py
|
||||
pystache/tests/data/ascii.mustache
|
||||
pystache/tests/data/duplicate.mustache
|
||||
pystache/tests/data/non_ascii.mustache
|
||||
pystache/tests/data/sample_view.mustache
|
||||
pystache/tests/data/say_hello.mustache
|
||||
pystache/tests/data/views.py
|
||||
pystache/tests/data/locator/__init__.py
|
||||
pystache/tests/data/locator/duplicate.mustache
|
||||
pystache/tests/data/locator/template.txt
|
||||
pystache/tests/examples/__init__.py
|
||||
pystache/tests/examples/comments.mustache
|
||||
pystache/tests/examples/comments.py
|
||||
pystache/tests/examples/complex.mustache
|
||||
pystache/tests/examples/complex.py
|
||||
pystache/tests/examples/delimiters.mustache
|
||||
pystache/tests/examples/delimiters.py
|
||||
pystache/tests/examples/double_section.mustache
|
||||
pystache/tests/examples/double_section.py
|
||||
pystache/tests/examples/escaped.mustache
|
||||
pystache/tests/examples/escaped.py
|
||||
pystache/tests/examples/inner_partial.mustache
|
||||
pystache/tests/examples/inner_partial.txt
|
||||
pystache/tests/examples/inverted.mustache
|
||||
pystache/tests/examples/inverted.py
|
||||
pystache/tests/examples/lambdas.mustache
|
||||
pystache/tests/examples/lambdas.py
|
||||
pystache/tests/examples/looping_partial.mustache
|
||||
pystache/tests/examples/nested_context.mustache
|
||||
pystache/tests/examples/nested_context.py
|
||||
pystache/tests/examples/partial_in_partial.mustache
|
||||
pystache/tests/examples/partial_with_lambda.mustache
|
||||
pystache/tests/examples/partial_with_partial_and_lambda.mustache
|
||||
pystache/tests/examples/partials_with_lambdas.py
|
||||
pystache/tests/examples/readme.py
|
||||
pystache/tests/examples/say_hello.mustache
|
||||
pystache/tests/examples/simple.mustache
|
||||
pystache/tests/examples/simple.py
|
||||
pystache/tests/examples/tagless.mustache
|
||||
pystache/tests/examples/template_partial.mustache
|
||||
pystache/tests/examples/template_partial.py
|
||||
pystache/tests/examples/template_partial.txt
|
||||
pystache/tests/examples/unescaped.mustache
|
||||
pystache/tests/examples/unescaped.py
|
||||
pystache/tests/examples/unicode_input.mustache
|
||||
pystache/tests/examples/unicode_input.py
|
||||
pystache/tests/examples/unicode_output.mustache
|
||||
pystache/tests/examples/unicode_output.py
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
[console_scripts]
|
||||
pystache = pystache.commands.render:main
|
||||
pystache-test = pystache.commands.test:main
|
||||
|
|
@ -1 +0,0 @@
|
|||
pystache
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
# We keep all initialization code in a separate module.
|
||||
|
||||
from pystache.init import parse, render, Renderer, TemplateSpec
|
||||
|
||||
__all__ = ['parse', 'render', 'Renderer', 'TemplateSpec']
|
||||
|
||||
__version__ = '0.5.4' # Also change in setup.py.
|
|
@ -1,4 +0,0 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -1,95 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
This module provides command-line access to pystache.
|
||||
|
||||
Run this script using the -h option for command-line help.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
# The json module is new in Python 2.6, whereas simplejson is
|
||||
# compatible with earlier versions.
|
||||
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
|
||||
|
||||
# The optparse module is deprecated in Python 2.7 in favor of argparse.
|
||||
# However, argparse is not available in Python 2.6 and earlier.
|
||||
from optparse import OptionParser
|
||||
import sys
|
||||
|
||||
# We use absolute imports here to allow use of this script from its
|
||||
# location in source control (e.g. for development purposes).
|
||||
# Otherwise, the following error occurs:
|
||||
#
|
||||
# ValueError: Attempted relative import in non-package
|
||||
#
|
||||
from pystache.common import TemplateNotFoundError
|
||||
from pystache.renderer import Renderer
|
||||
|
||||
|
||||
USAGE = """\
|
||||
%prog [-h] template context
|
||||
|
||||
Render a mustache template with the given context.
|
||||
|
||||
positional arguments:
|
||||
template A filename or template string.
|
||||
context A filename or JSON string."""
|
||||
|
||||
|
||||
def parse_args(sys_argv, usage):
|
||||
"""
|
||||
Return an OptionParser for the script.
|
||||
|
||||
"""
|
||||
args = sys_argv[1:]
|
||||
|
||||
parser = OptionParser(usage=usage)
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
template, context = args
|
||||
|
||||
return template, context
|
||||
|
||||
|
||||
# TODO: verify whether the setup() method's entry_points argument
|
||||
# supports passing arguments to main:
|
||||
#
|
||||
# http://packages.python.org/distribute/setuptools.html#automatic-script-creation
|
||||
#
|
||||
def main(sys_argv=sys.argv):
|
||||
template, context = parse_args(sys_argv, USAGE)
|
||||
|
||||
if template.endswith('.mustache'):
|
||||
template = template[:-9]
|
||||
|
||||
renderer = Renderer()
|
||||
|
||||
try:
|
||||
template = renderer.load_template(template)
|
||||
except TemplateNotFoundError:
|
||||
pass
|
||||
|
||||
try:
|
||||
context = json.load(open(context))
|
||||
except IOError:
|
||||
context = json.loads(context)
|
||||
|
||||
rendered = renderer.render(template, context)
|
||||
print rendered
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
|
@ -1,18 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
This module provides a command to test pystache (unit tests, doctests, etc).
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from pystache.tests.main import main as run_tests
|
||||
|
||||
|
||||
def main(sys_argv=sys.argv):
|
||||
run_tests(sys_argv=sys_argv)
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
|
@ -1,71 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Exposes functionality needed throughout the project.
|
||||
|
||||
"""
|
||||
|
||||
from sys import version_info
|
||||
|
||||
def _get_string_types():
|
||||
# TODO: come up with a better solution for this. One of the issues here
|
||||
# is that in Python 3 there is no common base class for unicode strings
|
||||
# and byte strings, and 2to3 seems to convert all of "str", "unicode",
|
||||
# and "basestring" to Python 3's "str".
|
||||
if version_info < (3, ):
|
||||
return basestring
|
||||
# The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3.
|
||||
return (unicode, type(u"a".encode('utf-8')))
|
||||
|
||||
|
||||
_STRING_TYPES = _get_string_types()
|
||||
|
||||
|
||||
def is_string(obj):
|
||||
"""
|
||||
Return whether the given object is a byte string or unicode string.
|
||||
|
||||
This function is provided for compatibility with both Python 2 and 3
|
||||
when using 2to3.
|
||||
|
||||
"""
|
||||
return isinstance(obj, _STRING_TYPES)
|
||||
|
||||
|
||||
# This function was designed to be portable across Python versions -- both
|
||||
# with older versions and with Python 3 after applying 2to3.
|
||||
def read(path):
|
||||
"""
|
||||
Return the contents of a text file as a byte string.
|
||||
|
||||
"""
|
||||
# Opening in binary mode is necessary for compatibility across Python
|
||||
# 2 and 3. In both Python 2 and 3, open() defaults to opening files in
|
||||
# text mode. However, in Python 2, open() returns file objects whose
|
||||
# read() method returns byte strings (strings of type `str` in Python 2),
|
||||
# whereas in Python 3, the file object returns unicode strings (strings
|
||||
# of type `str` in Python 3).
|
||||
f = open(path, 'rb')
|
||||
# We avoid use of the with keyword for Python 2.4 support.
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
|
||||
class MissingTags(object):
|
||||
|
||||
"""Contains the valid values for Renderer.missing_tags."""
|
||||
|
||||
ignore = 'ignore'
|
||||
strict = 'strict'
|
||||
|
||||
|
||||
class PystacheError(Exception):
|
||||
"""Base class for Pystache exceptions."""
|
||||
pass
|
||||
|
||||
|
||||
class TemplateNotFoundError(PystacheError):
|
||||
"""An exception raised when a template is not found."""
|
||||
pass
|
|
@ -1,342 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Exposes a ContextStack class.
|
||||
|
||||
The Mustache spec makes a special distinction between two types of context
|
||||
stack elements: hashes and objects. For the purposes of interpreting the
|
||||
spec, we define these categories mutually exclusively as follows:
|
||||
|
||||
(1) Hash: an item whose type is a subclass of dict.
|
||||
|
||||
(2) Object: an item that is neither a hash nor an instance of a
|
||||
built-in type.
|
||||
|
||||
"""
|
||||
|
||||
from pystache.common import PystacheError
|
||||
|
||||
|
||||
# This equals '__builtin__' in Python 2 and 'builtins' in Python 3.
|
||||
_BUILTIN_MODULE = type(0).__module__
|
||||
|
||||
|
||||
# We use this private global variable as a return value to represent a key
|
||||
# not being found on lookup. This lets us distinguish between the case
|
||||
# of a key's value being None with the case of a key not being found --
|
||||
# without having to rely on exceptions (e.g. KeyError) for flow control.
|
||||
#
|
||||
# TODO: eliminate the need for a private global variable, e.g. by using the
|
||||
# preferred Python approach of "easier to ask for forgiveness than permission":
|
||||
# http://docs.python.org/glossary.html#term-eafp
|
||||
class NotFound(object):
|
||||
pass
|
||||
_NOT_FOUND = NotFound()
|
||||
|
||||
|
||||
def _get_value(context, key):
|
||||
"""
|
||||
Retrieve a key's value from a context item.
|
||||
|
||||
Returns _NOT_FOUND if the key does not exist.
|
||||
|
||||
The ContextStack.get() docstring documents this function's intended behavior.
|
||||
|
||||
"""
|
||||
if isinstance(context, dict):
|
||||
# Then we consider the argument a "hash" for the purposes of the spec.
|
||||
#
|
||||
# We do a membership test to avoid using exceptions for flow control
|
||||
# (e.g. catching KeyError).
|
||||
if key in context:
|
||||
return context[key]
|
||||
elif type(context).__module__ != _BUILTIN_MODULE:
|
||||
# Then we consider the argument an "object" for the purposes of
|
||||
# the spec.
|
||||
#
|
||||
# The elif test above lets us avoid treating instances of built-in
|
||||
# types like integers and strings as objects (cf. issue #81).
|
||||
# Instances of user-defined classes on the other hand, for example,
|
||||
# are considered objects by the test above.
|
||||
try:
|
||||
attr = getattr(context, key)
|
||||
except AttributeError:
|
||||
# TODO: distinguish the case of the attribute not existing from
|
||||
# an AttributeError being raised by the call to the attribute.
|
||||
# See the following issue for implementation ideas:
|
||||
# http://bugs.python.org/issue7559
|
||||
pass
|
||||
else:
|
||||
# TODO: consider using EAFP here instead.
|
||||
# http://docs.python.org/glossary.html#term-eafp
|
||||
if callable(attr):
|
||||
return attr()
|
||||
return attr
|
||||
|
||||
return _NOT_FOUND
|
||||
|
||||
|
||||
class KeyNotFoundError(PystacheError):
|
||||
|
||||
"""
|
||||
An exception raised when a key is not found in a context stack.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, key, details):
|
||||
self.key = key
|
||||
self.details = details
|
||||
|
||||
def __str__(self):
|
||||
return "Key %s not found: %s" % (repr(self.key), self.details)
|
||||
|
||||
|
||||
class ContextStack(object):
|
||||
|
||||
"""
|
||||
Provides dictionary-like access to a stack of zero or more items.
|
||||
|
||||
Instances of this class are meant to act as the rendering context
|
||||
when rendering Mustache templates in accordance with mustache(5)
|
||||
and the Mustache spec.
|
||||
|
||||
Instances encapsulate a private stack of hashes, objects, and built-in
|
||||
type instances. Querying the stack for the value of a key queries
|
||||
the items in the stack in order from last-added objects to first
|
||||
(last in, first out).
|
||||
|
||||
Caution: this class does not currently support recursive nesting in
|
||||
that items in the stack cannot themselves be ContextStack instances.
|
||||
|
||||
See the docstrings of the methods of this class for more details.
|
||||
|
||||
"""
|
||||
|
||||
# We reserve keyword arguments for future options (e.g. a "strict=True"
|
||||
# option for enabling a strict mode).
|
||||
def __init__(self, *items):
|
||||
"""
|
||||
Construct an instance, and initialize the private stack.
|
||||
|
||||
The *items arguments are the items with which to populate the
|
||||
initial stack. Items in the argument list are added to the
|
||||
stack in order so that, in particular, items at the end of
|
||||
the argument list are queried first when querying the stack.
|
||||
|
||||
Caution: items should not themselves be ContextStack instances, as
|
||||
recursive nesting does not behave as one might expect.
|
||||
|
||||
"""
|
||||
self._stack = list(items)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Return a string representation of the instance.
|
||||
|
||||
For example--
|
||||
|
||||
>>> context = ContextStack({'alpha': 'abc'}, {'numeric': 123})
|
||||
>>> repr(context)
|
||||
"ContextStack({'alpha': 'abc'}, {'numeric': 123})"
|
||||
|
||||
"""
|
||||
return "%s%s" % (self.__class__.__name__, tuple(self._stack))
|
||||
|
||||
@staticmethod
|
||||
def create(*context, **kwargs):
|
||||
"""
|
||||
Build a ContextStack instance from a sequence of context-like items.
|
||||
|
||||
This factory-style method is more general than the ContextStack class's
|
||||
constructor in that, unlike the constructor, the argument list
|
||||
can itself contain ContextStack instances.
|
||||
|
||||
Here is an example illustrating various aspects of this method:
|
||||
|
||||
>>> obj1 = {'animal': 'cat', 'vegetable': 'carrot', 'mineral': 'copper'}
|
||||
>>> obj2 = ContextStack({'vegetable': 'spinach', 'mineral': 'silver'})
|
||||
>>>
|
||||
>>> context = ContextStack.create(obj1, None, obj2, mineral='gold')
|
||||
>>>
|
||||
>>> context.get('animal')
|
||||
'cat'
|
||||
>>> context.get('vegetable')
|
||||
'spinach'
|
||||
>>> context.get('mineral')
|
||||
'gold'
|
||||
|
||||
Arguments:
|
||||
|
||||
*context: zero or more dictionaries, ContextStack instances, or objects
|
||||
with which to populate the initial context stack. None
|
||||
arguments will be skipped. Items in the *context list are
|
||||
added to the stack in order so that later items in the argument
|
||||
list take precedence over earlier items. This behavior is the
|
||||
same as the constructor's.
|
||||
|
||||
**kwargs: additional key-value data to add to the context stack.
|
||||
As these arguments appear after all items in the *context list,
|
||||
in the case of key conflicts these values take precedence over
|
||||
all items in the *context list. This behavior is the same as
|
||||
the constructor's.
|
||||
|
||||
"""
|
||||
items = context
|
||||
|
||||
context = ContextStack()
|
||||
|
||||
for item in items:
|
||||
if item is None:
|
||||
continue
|
||||
if isinstance(item, ContextStack):
|
||||
context._stack.extend(item._stack)
|
||||
else:
|
||||
context.push(item)
|
||||
|
||||
if kwargs:
|
||||
context.push(kwargs)
|
||||
|
||||
return context
|
||||
|
||||
# TODO: add more unit tests for this.
|
||||
# TODO: update the docstring for dotted names.
|
||||
def get(self, name):
|
||||
"""
|
||||
Resolve a dotted name against the current context stack.
|
||||
|
||||
This function follows the rules outlined in the section of the
|
||||
spec regarding tag interpolation. This function returns the value
|
||||
as is and does not coerce the return value to a string.
|
||||
|
||||
Arguments:
|
||||
|
||||
name: a dotted or non-dotted name.
|
||||
|
||||
default: the value to return if name resolution fails at any point.
|
||||
Defaults to the empty string per the Mustache spec.
|
||||
|
||||
This method queries items in the stack in order from last-added
|
||||
objects to first (last in, first out). The value returned is
|
||||
the value of the key in the first item that contains the key.
|
||||
If the key is not found in any item in the stack, then the default
|
||||
value is returned. The default value defaults to None.
|
||||
|
||||
In accordance with the spec, this method queries items in the
|
||||
stack for a key differently depending on whether the item is a
|
||||
hash, object, or neither (as defined in the module docstring):
|
||||
|
||||
(1) Hash: if the item is a hash, then the key's value is the
|
||||
dictionary value of the key. If the dictionary doesn't contain
|
||||
the key, then the key is considered not found.
|
||||
|
||||
(2) Object: if the item is an an object, then the method looks for
|
||||
an attribute with the same name as the key. If an attribute
|
||||
with that name exists, the value of the attribute is returned.
|
||||
If the attribute is callable, however (i.e. if the attribute
|
||||
is a method), then the attribute is called with no arguments
|
||||
and that value is returned. If there is no attribute with
|
||||
the same name as the key, then the key is considered not found.
|
||||
|
||||
(3) Neither: if the item is neither a hash nor an object, then
|
||||
the key is considered not found.
|
||||
|
||||
*Caution*:
|
||||
|
||||
Callables are handled differently depending on whether they are
|
||||
dictionary values, as in (1) above, or attributes, as in (2).
|
||||
The former are returned as-is, while the latter are first
|
||||
called and that value returned.
|
||||
|
||||
Here is an example to illustrate:
|
||||
|
||||
>>> def greet():
|
||||
... return "Hi Bob!"
|
||||
>>>
|
||||
>>> class Greeter(object):
|
||||
... greet = None
|
||||
>>>
|
||||
>>> dct = {'greet': greet}
|
||||
>>> obj = Greeter()
|
||||
>>> obj.greet = greet
|
||||
>>>
|
||||
>>> dct['greet'] is obj.greet
|
||||
True
|
||||
>>> ContextStack(dct).get('greet') #doctest: +ELLIPSIS
|
||||
<function greet at 0x...>
|
||||
>>> ContextStack(obj).get('greet')
|
||||
'Hi Bob!'
|
||||
|
||||
TODO: explain the rationale for this difference in treatment.
|
||||
|
||||
"""
|
||||
if name == '.':
|
||||
try:
|
||||
return self.top()
|
||||
except IndexError:
|
||||
raise KeyNotFoundError(".", "empty context stack")
|
||||
|
||||
parts = name.split('.')
|
||||
|
||||
try:
|
||||
result = self._get_simple(parts[0])
|
||||
except KeyNotFoundError:
|
||||
raise KeyNotFoundError(name, "first part")
|
||||
|
||||
for part in parts[1:]:
|
||||
# The full context stack is not used to resolve the remaining parts.
|
||||
# From the spec--
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# TODO: make sure we have a test case for the above point.
|
||||
result = _get_value(result, part)
|
||||
# TODO: consider using EAFP here instead.
|
||||
# http://docs.python.org/glossary.html#term-eafp
|
||||
if result is _NOT_FOUND:
|
||||
raise KeyNotFoundError(name, "missing %s" % repr(part))
|
||||
|
||||
return result
|
||||
|
||||
def _get_simple(self, name):
|
||||
"""
|
||||
Query the stack for a non-dotted name.
|
||||
|
||||
"""
|
||||
for item in reversed(self._stack):
|
||||
result = _get_value(item, name)
|
||||
if result is not _NOT_FOUND:
|
||||
return result
|
||||
|
||||
raise KeyNotFoundError(name, "part missing")
|
||||
|
||||
def push(self, item):
|
||||
"""
|
||||
Push an item onto the stack.
|
||||
|
||||
"""
|
||||
self._stack.append(item)
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Pop an item off of the stack, and return it.
|
||||
|
||||
"""
|
||||
return self._stack.pop()
|
||||
|
||||
def top(self):
|
||||
"""
|
||||
Return the item last added to the stack.
|
||||
|
||||
"""
|
||||
return self._stack[-1]
|
||||
|
||||
def copy(self):
|
||||
"""
|
||||
Return a copy of this instance.
|
||||
|
||||
"""
|
||||
return ContextStack(*self._stack)
|
|
@ -1,65 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
This module provides a central location for defining default behavior.
|
||||
|
||||
Throughout the package, these defaults take effect only when the user
|
||||
does not otherwise specify a value.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
# Python 3.2 adds html.escape() and deprecates cgi.escape().
|
||||
from html import escape
|
||||
except ImportError:
|
||||
from cgi import escape
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pystache.common import MissingTags
|
||||
|
||||
|
||||
# How to handle encoding errors when decoding strings from str to unicode.
|
||||
#
|
||||
# This value is passed as the "errors" argument to Python's built-in
|
||||
# unicode() function:
|
||||
#
|
||||
# http://docs.python.org/library/functions.html#unicode
|
||||
#
|
||||
DECODE_ERRORS = 'strict'
|
||||
|
||||
# The name of the encoding to use when converting to unicode any strings of
|
||||
# type str encountered during the rendering process.
|
||||
STRING_ENCODING = sys.getdefaultencoding()
|
||||
|
||||
# The name of the encoding to use when converting file contents to unicode.
|
||||
# This default takes precedence over the STRING_ENCODING default for
|
||||
# strings that arise from files.
|
||||
FILE_ENCODING = sys.getdefaultencoding()
|
||||
|
||||
# The delimiters to start with when parsing.
|
||||
DELIMITERS = (u'{{', u'}}')
|
||||
|
||||
# How to handle missing tags when rendering a template.
|
||||
MISSING_TAGS = MissingTags.ignore
|
||||
|
||||
# The starting list of directories in which to search for templates when
|
||||
# loading a template by file name.
|
||||
SEARCH_DIRS = [os.curdir] # i.e. ['.']
|
||||
|
||||
# The escape function to apply to strings that require escaping when
|
||||
# rendering templates (e.g. for tags enclosed in double braces).
|
||||
# Only unicode strings will be passed to this function.
|
||||
#
|
||||
# The quote=True argument causes double but not single quotes to be escaped
|
||||
# in Python 3.1 and earlier, and both double and single quotes to be
|
||||
# escaped in Python 3.2 and later:
|
||||
#
|
||||
# http://docs.python.org/library/cgi.html#cgi.escape
|
||||
# http://docs.python.org/dev/library/html.html#html.escape
|
||||
#
|
||||
TAG_ESCAPE = lambda u: escape(u, quote=True)
|
||||
|
||||
# The default template extension, without the leading dot.
|
||||
TEMPLATE_EXTENSION = 'mustache'
|
|
@ -1,19 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
"""
|
||||
This module contains the initialization logic called by __init__.py.
|
||||
|
||||
"""
|
||||
|
||||
from pystache.parser import parse
|
||||
from pystache.renderer import Renderer
|
||||
from pystache.template_spec import TemplateSpec
|
||||
|
||||
|
||||
def render(template, context=None, **kwargs):
|
||||
"""
|
||||
Return the given template string rendered using the given context.
|
||||
|
||||
"""
|
||||
renderer = Renderer()
|
||||
return renderer.render(template, context, **kwargs)
|
|
@ -1,170 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
This module provides a Loader class for locating and reading templates.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pystache import common
|
||||
from pystache import defaults
|
||||
from pystache.locator import Locator
|
||||
|
||||
|
||||
# We make a function so that the current defaults take effect.
|
||||
# TODO: revisit whether this is necessary.
|
||||
|
||||
def _make_to_unicode():
|
||||
def to_unicode(s, encoding=None):
|
||||
"""
|
||||
Raises a TypeError exception if the given string is already unicode.
|
||||
|
||||
"""
|
||||
if encoding is None:
|
||||
encoding = defaults.STRING_ENCODING
|
||||
return unicode(s, encoding, defaults.DECODE_ERRORS)
|
||||
return to_unicode
|
||||
|
||||
|
||||
class Loader(object):
|
||||
|
||||
"""
|
||||
Loads the template associated to a name or user-defined object.
|
||||
|
||||
All load_*() methods return the template as a unicode string.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_encoding=None, extension=None, to_unicode=None,
|
||||
search_dirs=None):
|
||||
"""
|
||||
Construct a template loader instance.
|
||||
|
||||
Arguments:
|
||||
|
||||
extension: the template file extension, without the leading dot.
|
||||
Pass False for no extension (e.g. to use extensionless template
|
||||
files). Defaults to the package default.
|
||||
|
||||
file_encoding: the name of the encoding to use when converting file
|
||||
contents to unicode. Defaults to the package default.
|
||||
|
||||
search_dirs: the list of directories in which to search when loading
|
||||
a template by name or file name. Defaults to the package default.
|
||||
|
||||
to_unicode: the function to use when converting strings of type
|
||||
str to unicode. The function should have the signature:
|
||||
|
||||
to_unicode(s, encoding=None)
|
||||
|
||||
It should accept a string of type str and an optional encoding
|
||||
name and return a string of type unicode. Defaults to calling
|
||||
Python's built-in function unicode() using the package string
|
||||
encoding and decode errors defaults.
|
||||
|
||||
"""
|
||||
if extension is None:
|
||||
extension = defaults.TEMPLATE_EXTENSION
|
||||
|
||||
if file_encoding is None:
|
||||
file_encoding = defaults.FILE_ENCODING
|
||||
|
||||
if search_dirs is None:
|
||||
search_dirs = defaults.SEARCH_DIRS
|
||||
|
||||
if to_unicode is None:
|
||||
to_unicode = _make_to_unicode()
|
||||
|
||||
self.extension = extension
|
||||
self.file_encoding = file_encoding
|
||||
# TODO: unit test setting this attribute.
|
||||
self.search_dirs = search_dirs
|
||||
self.to_unicode = to_unicode
|
||||
|
||||
def _make_locator(self):
|
||||
return Locator(extension=self.extension)
|
||||
|
||||
def unicode(self, s, encoding=None):
|
||||
"""
|
||||
Convert a string to unicode using the given encoding, and return it.
|
||||
|
||||
This function uses the underlying to_unicode attribute.
|
||||
|
||||
Arguments:
|
||||
|
||||
s: a basestring instance to convert to unicode. Unlike Python's
|
||||
built-in unicode() function, it is okay to pass unicode strings
|
||||
to this function. (Passing a unicode string to Python's unicode()
|
||||
with the encoding argument throws the error, "TypeError: decoding
|
||||
Unicode is not supported.")
|
||||
|
||||
encoding: the encoding to pass to the to_unicode attribute.
|
||||
Defaults to None.
|
||||
|
||||
"""
|
||||
if isinstance(s, unicode):
|
||||
return unicode(s)
|
||||
|
||||
return self.to_unicode(s, encoding)
|
||||
|
||||
def read(self, path, encoding=None):
|
||||
"""
|
||||
Read the template at the given path, and return it as a unicode string.
|
||||
|
||||
"""
|
||||
b = common.read(path)
|
||||
|
||||
if encoding is None:
|
||||
encoding = self.file_encoding
|
||||
|
||||
return self.unicode(b, encoding)
|
||||
|
||||
def load_file(self, file_name):
|
||||
"""
|
||||
Find and return the template with the given file name.
|
||||
|
||||
Arguments:
|
||||
|
||||
file_name: the file name of the template.
|
||||
|
||||
"""
|
||||
locator = self._make_locator()
|
||||
|
||||
path = locator.find_file(file_name, self.search_dirs)
|
||||
|
||||
return self.read(path)
|
||||
|
||||
def load_name(self, name):
|
||||
"""
|
||||
Find and return the template with the given template name.
|
||||
|
||||
Arguments:
|
||||
|
||||
name: the name of the template.
|
||||
|
||||
"""
|
||||
locator = self._make_locator()
|
||||
|
||||
path = locator.find_name(name, self.search_dirs)
|
||||
|
||||
return self.read(path)
|
||||
|
||||
# TODO: unit-test this method.
|
||||
def load_object(self, obj):
|
||||
"""
|
||||
Find and return the template associated to the given object.
|
||||
|
||||
Arguments:
|
||||
|
||||
obj: an instance of a user-defined class.
|
||||
|
||||
search_dirs: the list of directories in which to search.
|
||||
|
||||
"""
|
||||
locator = self._make_locator()
|
||||
|
||||
path = locator.find_object(obj, self.search_dirs)
|
||||
|
||||
return self.read(path)
|
|
@ -1,171 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
This module provides a Locator class for finding template files.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pystache.common import TemplateNotFoundError
|
||||
from pystache import defaults
|
||||
|
||||
|
||||
class Locator(object):
|
||||
|
||||
def __init__(self, extension=None):
|
||||
"""
|
||||
Construct a template locator.
|
||||
|
||||
Arguments:
|
||||
|
||||
extension: the template file extension, without the leading dot.
|
||||
Pass False for no extension (e.g. to use extensionless template
|
||||
files). Defaults to the package default.
|
||||
|
||||
"""
|
||||
if extension is None:
|
||||
extension = defaults.TEMPLATE_EXTENSION
|
||||
|
||||
self.template_extension = extension
|
||||
|
||||
def get_object_directory(self, obj):
|
||||
"""
|
||||
Return the directory containing an object's defining class.
|
||||
|
||||
Returns None if there is no such directory, for example if the
|
||||
class was defined in an interactive Python session, or in a
|
||||
doctest that appears in a text file (rather than a Python file).
|
||||
|
||||
"""
|
||||
if not hasattr(obj, '__module__'):
|
||||
return None
|
||||
|
||||
module = sys.modules[obj.__module__]
|
||||
|
||||
if not hasattr(module, '__file__'):
|
||||
# TODO: add a unit test for this case.
|
||||
return None
|
||||
|
||||
path = module.__file__
|
||||
|
||||
return os.path.dirname(path)
|
||||
|
||||
def make_template_name(self, obj):
|
||||
"""
|
||||
Return the canonical template name for an object instance.
|
||||
|
||||
This method converts Python-style class names (PEP 8's recommended
|
||||
CamelCase, aka CapWords) to lower_case_with_underscords. Here
|
||||
is an example with code:
|
||||
|
||||
>>> class HelloWorld(object):
|
||||
... pass
|
||||
>>> hi = HelloWorld()
|
||||
>>>
|
||||
>>> locator = Locator()
|
||||
>>> locator.make_template_name(hi)
|
||||
'hello_world'
|
||||
|
||||
"""
|
||||
template_name = obj.__class__.__name__
|
||||
|
||||
def repl(match):
|
||||
return '_' + match.group(0).lower()
|
||||
|
||||
return re.sub('[A-Z]', repl, template_name)[1:]
|
||||
|
||||
def make_file_name(self, template_name, template_extension=None):
|
||||
"""
|
||||
Generate and return the file name for the given template name.
|
||||
|
||||
Arguments:
|
||||
|
||||
template_extension: defaults to the instance's extension.
|
||||
|
||||
"""
|
||||
file_name = template_name
|
||||
|
||||
if template_extension is None:
|
||||
template_extension = self.template_extension
|
||||
|
||||
if template_extension is not False:
|
||||
file_name += os.path.extsep + template_extension
|
||||
|
||||
return file_name
|
||||
|
||||
def _find_path(self, search_dirs, file_name):
|
||||
"""
|
||||
Search for the given file, and return the path.
|
||||
|
||||
Returns None if the file is not found.
|
||||
|
||||
"""
|
||||
for dir_path in search_dirs:
|
||||
file_path = os.path.join(dir_path, file_name)
|
||||
if os.path.exists(file_path):
|
||||
return file_path
|
||||
|
||||
return None
|
||||
|
||||
def _find_path_required(self, search_dirs, file_name):
|
||||
"""
|
||||
Return the path to a template with the given file name.
|
||||
|
||||
"""
|
||||
path = self._find_path(search_dirs, file_name)
|
||||
|
||||
if path is None:
|
||||
raise TemplateNotFoundError('File %s not found in dirs: %s' %
|
||||
(repr(file_name), repr(search_dirs)))
|
||||
|
||||
return path
|
||||
|
||||
def find_file(self, file_name, search_dirs):
|
||||
"""
|
||||
Return the path to a template with the given file name.
|
||||
|
||||
Arguments:
|
||||
|
||||
file_name: the file name of the template.
|
||||
|
||||
search_dirs: the list of directories in which to search.
|
||||
|
||||
"""
|
||||
return self._find_path_required(search_dirs, file_name)
|
||||
|
||||
def find_name(self, template_name, search_dirs):
|
||||
"""
|
||||
Return the path to a template with the given name.
|
||||
|
||||
Arguments:
|
||||
|
||||
template_name: the name of the template.
|
||||
|
||||
search_dirs: the list of directories in which to search.
|
||||
|
||||
"""
|
||||
file_name = self.make_file_name(template_name)
|
||||
|
||||
return self._find_path_required(search_dirs, file_name)
|
||||
|
||||
def find_object(self, obj, search_dirs, file_name=None):
|
||||
"""
|
||||
Return the path to a template associated with the given object.
|
||||
|
||||
"""
|
||||
if file_name is None:
|
||||
# TODO: should we define a make_file_name() method?
|
||||
template_name = self.make_template_name(obj)
|
||||
file_name = self.make_file_name(template_name)
|
||||
|
||||
dir_path = self.get_object_directory(obj)
|
||||
|
||||
if dir_path is not None:
|
||||
search_dirs = [dir_path] + search_dirs
|
||||
|
||||
path = self._find_path_required(search_dirs, file_name)
|
||||
|
||||
return path
|
|
@ -1,50 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Exposes a class that represents a parsed (or compiled) template.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ParsedTemplate(object):
|
||||
|
||||
"""
|
||||
Represents a parsed or compiled template.
|
||||
|
||||
An instance wraps a list of unicode strings and node objects. A node
|
||||
object must have a `render(engine, stack)` method that accepts a
|
||||
RenderEngine instance and a ContextStack instance and returns a unicode
|
||||
string.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._parse_tree = []
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._parse_tree)
|
||||
|
||||
def add(self, node):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
node: a unicode string or node object instance. See the class
|
||||
docstring for information.
|
||||
|
||||
"""
|
||||
self._parse_tree.append(node)
|
||||
|
||||
def render(self, engine, context):
|
||||
"""
|
||||
Returns: a string of type unicode.
|
||||
|
||||
"""
|
||||
# We avoid use of the ternary operator for Python 2.4 support.
|
||||
def get_unicode(node):
|
||||
if type(node) is unicode:
|
||||
return node
|
||||
return node.render(engine, context)
|
||||
parts = map(get_unicode, self._parse_tree)
|
||||
s = ''.join(parts)
|
||||
|
||||
return unicode(s)
|
|
@ -1,378 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Exposes a parse() function to parse template strings.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from pystache import defaults
|
||||
from pystache.parsed import ParsedTemplate
|
||||
|
||||
|
||||
END_OF_LINE_CHARACTERS = [u'\r', u'\n']
|
||||
NON_BLANK_RE = re.compile(ur'^(.)', re.M)
|
||||
|
||||
|
||||
# TODO: add some unit tests for this.
|
||||
# TODO: add a test case that checks for spurious spaces.
|
||||
# TODO: add test cases for delimiters.
|
||||
def parse(template, delimiters=None):
|
||||
"""
|
||||
Parse a unicode template string and return a ParsedTemplate instance.
|
||||
|
||||
Arguments:
|
||||
|
||||
template: a unicode template string.
|
||||
|
||||
delimiters: a 2-tuple of delimiters. Defaults to the package default.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> parsed = parse(u"Hey {{#who}}{{name}}!{{/who}}")
|
||||
>>> print str(parsed).replace('u', '') # This is a hack to get the test to pass both in Python 2 and 3.
|
||||
['Hey ', _SectionNode(key='who', index_begin=12, index_end=21, parsed=[_EscapeNode(key='name'), '!'])]
|
||||
|
||||
"""
|
||||
if type(template) is not unicode:
|
||||
raise Exception("Template is not unicode: %s" % type(template))
|
||||
parser = _Parser(delimiters)
|
||||
return parser.parse(template)
|
||||
|
||||
|
||||
def _compile_template_re(delimiters):
|
||||
"""
|
||||
Return a regular expresssion object (re.RegexObject) instance.
|
||||
|
||||
"""
|
||||
# The possible tag type characters following the opening tag,
|
||||
# excluding "=" and "{".
|
||||
tag_types = "!>&/#^"
|
||||
|
||||
# TODO: are we following this in the spec?
|
||||
#
|
||||
# The tag's content MUST be a non-whitespace character sequence
|
||||
# NOT containing the current closing delimiter.
|
||||
#
|
||||
tag = r"""
|
||||
(?P<whitespace>[\ \t]*)
|
||||
%(otag)s \s*
|
||||
(?:
|
||||
(?P<change>=) \s* (?P<delims>.+?) \s* = |
|
||||
(?P<raw>{) \s* (?P<raw_name>.+?) \s* } |
|
||||
(?P<tag>[%(tag_types)s]?) \s* (?P<tag_key>[\s\S]+?)
|
||||
)
|
||||
\s* %(ctag)s
|
||||
""" % {'tag_types': tag_types, 'otag': re.escape(delimiters[0]), 'ctag': re.escape(delimiters[1])}
|
||||
|
||||
return re.compile(tag, re.VERBOSE)
|
||||
|
||||
|
||||
class ParsingError(Exception):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
## Node types
|
||||
|
||||
def _format(obj, exclude=None):
|
||||
if exclude is None:
|
||||
exclude = []
|
||||
exclude.append('key')
|
||||
attrs = obj.__dict__
|
||||
names = list(set(attrs.keys()) - set(exclude))
|
||||
names.sort()
|
||||
names.insert(0, 'key')
|
||||
args = ["%s=%s" % (name, repr(attrs[name])) for name in names]
|
||||
return "%s(%s)" % (obj.__class__.__name__, ", ".join(args))
|
||||
|
||||
|
||||
class _CommentNode(object):
|
||||
|
||||
def __repr__(self):
|
||||
return _format(self)
|
||||
|
||||
def render(self, engine, context):
|
||||
return u''
|
||||
|
||||
|
||||
class _ChangeNode(object):
|
||||
|
||||
def __init__(self, delimiters):
|
||||
self.delimiters = delimiters
|
||||
|
||||
def __repr__(self):
|
||||
return _format(self)
|
||||
|
||||
def render(self, engine, context):
|
||||
return u''
|
||||
|
||||
|
||||
class _EscapeNode(object):
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
|
||||
def __repr__(self):
|
||||
return _format(self)
|
||||
|
||||
def render(self, engine, context):
|
||||
s = engine.fetch_string(context, self.key)
|
||||
return engine.escape(s)
|
||||
|
||||
|
||||
class _LiteralNode(object):
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
|
||||
def __repr__(self):
|
||||
return _format(self)
|
||||
|
||||
def render(self, engine, context):
|
||||
s = engine.fetch_string(context, self.key)
|
||||
return engine.literal(s)
|
||||
|
||||
|
||||
class _PartialNode(object):
|
||||
|
||||
def __init__(self, key, indent):
|
||||
self.key = key
|
||||
self.indent = indent
|
||||
|
||||
def __repr__(self):
|
||||
return _format(self)
|
||||
|
||||
def render(self, engine, context):
|
||||
template = engine.resolve_partial(self.key)
|
||||
# Indent before rendering.
|
||||
template = re.sub(NON_BLANK_RE, self.indent + ur'\1', template)
|
||||
|
||||
return engine.render(template, context)
|
||||
|
||||
|
||||
class _InvertedNode(object):
|
||||
|
||||
def __init__(self, key, parsed_section):
|
||||
self.key = key
|
||||
self.parsed_section = parsed_section
|
||||
|
||||
def __repr__(self):
|
||||
return _format(self)
|
||||
|
||||
def render(self, engine, context):
|
||||
# TODO: is there a bug because we are not using the same
|
||||
# logic as in fetch_string()?
|
||||
data = engine.resolve_context(context, self.key)
|
||||
# Note that lambdas are considered truthy for inverted sections
|
||||
# per the spec.
|
||||
if data:
|
||||
return u''
|
||||
return self.parsed_section.render(engine, context)
|
||||
|
||||
|
||||
class _SectionNode(object):
|
||||
|
||||
# TODO: the template_ and parsed_template_ arguments don't both seem
|
||||
# to be necessary. Can we remove one of them? For example, if
|
||||
# callable(data) is True, then the initial parsed_template isn't used.
|
||||
def __init__(self, key, parsed, delimiters, template, index_begin, index_end):
|
||||
self.delimiters = delimiters
|
||||
self.key = key
|
||||
self.parsed = parsed
|
||||
self.template = template
|
||||
self.index_begin = index_begin
|
||||
self.index_end = index_end
|
||||
|
||||
def __repr__(self):
|
||||
return _format(self, exclude=['delimiters', 'template'])
|
||||
|
||||
def render(self, engine, context):
|
||||
values = engine.fetch_section_data(context, self.key)
|
||||
|
||||
parts = []
|
||||
for val in values:
|
||||
if callable(val):
|
||||
# Lambdas special case section rendering and bypass pushing
|
||||
# the data value onto the context stack. From the spec--
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Also see--
|
||||
#
|
||||
# https://github.com/defunkt/pystache/issues/113
|
||||
#
|
||||
# TODO: should we check the arity?
|
||||
val = val(self.template[self.index_begin:self.index_end])
|
||||
val = engine._render_value(val, context, delimiters=self.delimiters)
|
||||
parts.append(val)
|
||||
continue
|
||||
|
||||
context.push(val)
|
||||
parts.append(self.parsed.render(engine, context))
|
||||
context.pop()
|
||||
|
||||
return unicode(''.join(parts))
|
||||
|
||||
|
||||
class _Parser(object):
|
||||
|
||||
_delimiters = None
|
||||
_template_re = None
|
||||
|
||||
def __init__(self, delimiters=None):
|
||||
if delimiters is None:
|
||||
delimiters = defaults.DELIMITERS
|
||||
|
||||
self._delimiters = delimiters
|
||||
|
||||
def _compile_delimiters(self):
|
||||
self._template_re = _compile_template_re(self._delimiters)
|
||||
|
||||
def _change_delimiters(self, delimiters):
|
||||
self._delimiters = delimiters
|
||||
self._compile_delimiters()
|
||||
|
||||
def parse(self, template):
|
||||
"""
|
||||
Parse a template string starting at some index.
|
||||
|
||||
This method uses the current tag delimiter.
|
||||
|
||||
Arguments:
|
||||
|
||||
template: a unicode string that is the template to parse.
|
||||
|
||||
index: the index at which to start parsing.
|
||||
|
||||
Returns:
|
||||
|
||||
a ParsedTemplate instance.
|
||||
|
||||
"""
|
||||
self._compile_delimiters()
|
||||
|
||||
start_index = 0
|
||||
content_end_index, parsed_section, section_key = None, None, None
|
||||
parsed_template = ParsedTemplate()
|
||||
|
||||
states = []
|
||||
|
||||
while True:
|
||||
match = self._template_re.search(template, start_index)
|
||||
|
||||
if match is None:
|
||||
break
|
||||
|
||||
match_index = match.start()
|
||||
end_index = match.end()
|
||||
|
||||
matches = match.groupdict()
|
||||
|
||||
# Normalize the matches dictionary.
|
||||
if matches['change'] is not None:
|
||||
matches.update(tag='=', tag_key=matches['delims'])
|
||||
elif matches['raw'] is not None:
|
||||
matches.update(tag='&', tag_key=matches['raw_name'])
|
||||
|
||||
tag_type = matches['tag']
|
||||
tag_key = matches['tag_key']
|
||||
leading_whitespace = matches['whitespace']
|
||||
|
||||
# Standalone (non-interpolation) tags consume the entire line,
|
||||
# both leading whitespace and trailing newline.
|
||||
did_tag_begin_line = match_index == 0 or template[match_index - 1] in END_OF_LINE_CHARACTERS
|
||||
did_tag_end_line = end_index == len(template) or template[end_index] in END_OF_LINE_CHARACTERS
|
||||
is_tag_interpolating = tag_type in ['', '&']
|
||||
|
||||
if did_tag_begin_line and did_tag_end_line and not is_tag_interpolating:
|
||||
if end_index < len(template):
|
||||
end_index += template[end_index] == '\r' and 1 or 0
|
||||
if end_index < len(template):
|
||||
end_index += template[end_index] == '\n' and 1 or 0
|
||||
elif leading_whitespace:
|
||||
match_index += len(leading_whitespace)
|
||||
leading_whitespace = ''
|
||||
|
||||
# Avoid adding spurious empty strings to the parse tree.
|
||||
if start_index != match_index:
|
||||
parsed_template.add(template[start_index:match_index])
|
||||
|
||||
start_index = end_index
|
||||
|
||||
if tag_type in ('#', '^'):
|
||||
# Cache current state.
|
||||
state = (tag_type, end_index, section_key, parsed_template)
|
||||
states.append(state)
|
||||
|
||||
# Initialize new state
|
||||
section_key, parsed_template = tag_key, ParsedTemplate()
|
||||
continue
|
||||
|
||||
if tag_type == '/':
|
||||
if tag_key != section_key:
|
||||
raise ParsingError("Section end tag mismatch: %s != %s" % (tag_key, section_key))
|
||||
|
||||
# Restore previous state with newly found section data.
|
||||
parsed_section = parsed_template
|
||||
|
||||
(tag_type, section_start_index, section_key, parsed_template) = states.pop()
|
||||
node = self._make_section_node(template, tag_type, tag_key, parsed_section,
|
||||
section_start_index, match_index)
|
||||
|
||||
else:
|
||||
node = self._make_interpolation_node(tag_type, tag_key, leading_whitespace)
|
||||
|
||||
parsed_template.add(node)
|
||||
|
||||
# Avoid adding spurious empty strings to the parse tree.
|
||||
if start_index != len(template):
|
||||
parsed_template.add(template[start_index:])
|
||||
|
||||
return parsed_template
|
||||
|
||||
def _make_interpolation_node(self, tag_type, tag_key, leading_whitespace):
|
||||
"""
|
||||
Create and return a non-section node for the parse tree.
|
||||
|
||||
"""
|
||||
# TODO: switch to using a dictionary instead of a bunch of ifs and elifs.
|
||||
if tag_type == '!':
|
||||
return _CommentNode()
|
||||
|
||||
if tag_type == '=':
|
||||
delimiters = tag_key.split()
|
||||
self._change_delimiters(delimiters)
|
||||
return _ChangeNode(delimiters)
|
||||
|
||||
if tag_type == '':
|
||||
return _EscapeNode(tag_key)
|
||||
|
||||
if tag_type == '&':
|
||||
return _LiteralNode(tag_key)
|
||||
|
||||
if tag_type == '>':
|
||||
return _PartialNode(tag_key, leading_whitespace)
|
||||
|
||||
raise Exception("Invalid symbol for interpolation tag: %s" % repr(tag_type))
|
||||
|
||||
def _make_section_node(self, template, tag_type, tag_key, parsed_section,
|
||||
section_start_index, section_end_index):
|
||||
"""
|
||||
Create and return a section node for the parse tree.
|
||||
|
||||
"""
|
||||
if tag_type == '#':
|
||||
return _SectionNode(tag_key, parsed_section, self._delimiters,
|
||||
template, section_start_index, section_end_index)
|
||||
|
||||
if tag_type == '^':
|
||||
return _InvertedNode(tag_key, parsed_section)
|
||||
|
||||
raise Exception("Invalid symbol for section tag: %s" % repr(tag_type))
|
|
@ -1,181 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Defines a class responsible for rendering logic.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from pystache.common import is_string
|
||||
from pystache.parser import parse
|
||||
|
||||
|
||||
def context_get(stack, name):
|
||||
"""
|
||||
Find and return a name from a ContextStack instance.
|
||||
|
||||
"""
|
||||
return stack.get(name)
|
||||
|
||||
|
||||
class RenderEngine(object):
|
||||
|
||||
"""
|
||||
Provides a render() method.
|
||||
|
||||
This class is meant only for internal use.
|
||||
|
||||
As a rule, the code in this class operates on unicode strings where
|
||||
possible rather than, say, strings of type str or markupsafe.Markup.
|
||||
This means that strings obtained from "external" sources like partials
|
||||
and variable tag values are immediately converted to unicode (or
|
||||
escaped and converted to unicode) before being operated on further.
|
||||
This makes maintaining, reasoning about, and testing the correctness
|
||||
of the code much simpler. In particular, it keeps the implementation
|
||||
of this class independent of the API details of one (or possibly more)
|
||||
unicode subclasses (e.g. markupsafe.Markup).
|
||||
|
||||
"""
|
||||
|
||||
# TODO: it would probably be better for the constructor to accept
|
||||
# and set as an attribute a single RenderResolver instance
|
||||
# that encapsulates the customizable aspects of converting
|
||||
# strings and resolving partials and names from context.
|
||||
def __init__(self, literal=None, escape=None, resolve_context=None,
|
||||
resolve_partial=None, to_str=None):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
literal: the function used to convert unescaped variable tag
|
||||
values to unicode, e.g. the value corresponding to a tag
|
||||
"{{{name}}}". The function should accept a string of type
|
||||
str or unicode (or a subclass) and return a string of type
|
||||
unicode (but not a proper subclass of unicode).
|
||||
This class will only pass basestring instances to this
|
||||
function. For example, it will call str() on integer variable
|
||||
values prior to passing them to this function.
|
||||
|
||||
escape: the function used to escape and convert variable tag
|
||||
values to unicode, e.g. the value corresponding to a tag
|
||||
"{{name}}". The function should obey the same properties
|
||||
described above for the "literal" function argument.
|
||||
This function should take care to convert any str
|
||||
arguments to unicode just as the literal function should, as
|
||||
this class will not pass tag values to literal prior to passing
|
||||
them to this function. This allows for more flexibility,
|
||||
for example using a custom escape function that handles
|
||||
incoming strings of type markupsafe.Markup differently
|
||||
from plain unicode strings.
|
||||
|
||||
resolve_context: the function to call to resolve a name against
|
||||
a context stack. The function should accept two positional
|
||||
arguments: a ContextStack instance and a name to resolve.
|
||||
|
||||
resolve_partial: the function to call when loading a partial.
|
||||
The function should accept a template name string and return a
|
||||
template string of type unicode (not a subclass).
|
||||
|
||||
to_str: a function that accepts an object and returns a string (e.g.
|
||||
the built-in function str). This function is used for string
|
||||
coercion whenever a string is required (e.g. for converting None
|
||||
or 0 to a string).
|
||||
|
||||
"""
|
||||
self.escape = escape
|
||||
self.literal = literal
|
||||
self.resolve_context = resolve_context
|
||||
self.resolve_partial = resolve_partial
|
||||
self.to_str = to_str
|
||||
|
||||
# TODO: Rename context to stack throughout this module.
|
||||
|
||||
# From the spec:
|
||||
#
|
||||
# When used as the data value for an Interpolation tag, the lambda
|
||||
# MUST be treatable as an arity 0 function, and invoked as such.
|
||||
# The returned value MUST be rendered against the default delimiters,
|
||||
# then interpolated in place of the lambda.
|
||||
#
|
||||
def fetch_string(self, context, name):
|
||||
"""
|
||||
Get a value from the given context as a basestring instance.
|
||||
|
||||
"""
|
||||
val = self.resolve_context(context, name)
|
||||
|
||||
if callable(val):
|
||||
# Return because _render_value() is already a string.
|
||||
return self._render_value(val(), context)
|
||||
|
||||
if not is_string(val):
|
||||
return self.to_str(val)
|
||||
|
||||
return val
|
||||
|
||||
def fetch_section_data(self, context, name):
|
||||
"""
|
||||
Fetch the value of a section as a list.
|
||||
|
||||
"""
|
||||
data = self.resolve_context(context, name)
|
||||
|
||||
# From the spec:
|
||||
#
|
||||
# If the data is not of a list type, it is coerced into a list
|
||||
# as follows: if the data is truthy (e.g. `!!data == true`),
|
||||
# use a single-element list containing the data, otherwise use
|
||||
# an empty list.
|
||||
#
|
||||
if not data:
|
||||
data = []
|
||||
else:
|
||||
# The least brittle way to determine whether something
|
||||
# supports iteration is by trying to call iter() on it:
|
||||
#
|
||||
# http://docs.python.org/library/functions.html#iter
|
||||
#
|
||||
# It is not sufficient, for example, to check whether the item
|
||||
# implements __iter__ () (the iteration protocol). There is
|
||||
# also __getitem__() (the sequence protocol). In Python 2,
|
||||
# strings do not implement __iter__(), but in Python 3 they do.
|
||||
try:
|
||||
iter(data)
|
||||
except TypeError:
|
||||
# Then the value does not support iteration.
|
||||
data = [data]
|
||||
else:
|
||||
if is_string(data) or isinstance(data, dict):
|
||||
# Do not treat strings and dicts (which are iterable) as lists.
|
||||
data = [data]
|
||||
# Otherwise, treat the value as a list.
|
||||
|
||||
return data
|
||||
|
||||
def _render_value(self, val, context, delimiters=None):
|
||||
"""
|
||||
Render an arbitrary value.
|
||||
|
||||
"""
|
||||
if not is_string(val):
|
||||
# In case the template is an integer, for example.
|
||||
val = self.to_str(val)
|
||||
if type(val) is not unicode:
|
||||
val = self.literal(val)
|
||||
return self.render(val, context, delimiters)
|
||||
|
||||
def render(self, template, context_stack, delimiters=None):
|
||||
"""
|
||||
Render a unicode template string, and return as unicode.
|
||||
|
||||
Arguments:
|
||||
|
||||
template: a template string of type unicode (but not a proper
|
||||
subclass of unicode).
|
||||
|
||||
context_stack: a ContextStack instance.
|
||||
|
||||
"""
|
||||
parsed_template = parse(template, delimiters)
|
||||
|
||||
return parsed_template.render(self, context_stack)
|
|
@ -1,460 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
This module provides a Renderer class to render templates.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from pystache import defaults
|
||||
from pystache.common import TemplateNotFoundError, MissingTags, is_string
|
||||
from pystache.context import ContextStack, KeyNotFoundError
|
||||
from pystache.loader import Loader
|
||||
from pystache.parsed import ParsedTemplate
|
||||
from pystache.renderengine import context_get, RenderEngine
|
||||
from pystache.specloader import SpecLoader
|
||||
from pystache.template_spec import TemplateSpec
|
||||
|
||||
|
||||
class Renderer(object):
|
||||
|
||||
"""
|
||||
A class for rendering mustache templates.
|
||||
|
||||
This class supports several rendering options which are described in
|
||||
the constructor's docstring. Other behavior can be customized by
|
||||
subclassing this class.
|
||||
|
||||
For example, one can pass a string-string dictionary to the constructor
|
||||
to bypass loading partials from the file system:
|
||||
|
||||
>>> partials = {'partial': 'Hello, {{thing}}!'}
|
||||
>>> renderer = Renderer(partials=partials)
|
||||
>>> # We apply print to make the test work in Python 3 after 2to3.
|
||||
>>> print renderer.render('{{>partial}}', {'thing': 'world'})
|
||||
Hello, world!
|
||||
|
||||
To customize string coercion (e.g. to render False values as ''), one can
|
||||
subclass this class. For example:
|
||||
|
||||
class MyRenderer(Renderer):
|
||||
def str_coerce(self, val):
|
||||
if not val:
|
||||
return ''
|
||||
else:
|
||||
return str(val)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_encoding=None, string_encoding=None,
|
||||
decode_errors=None, search_dirs=None, file_extension=None,
|
||||
escape=None, partials=None, missing_tags=None):
|
||||
"""
|
||||
Construct an instance.
|
||||
|
||||
Arguments:
|
||||
|
||||
file_encoding: the name of the encoding to use by default when
|
||||
reading template files. All templates are converted to unicode
|
||||
prior to parsing. Defaults to the package default.
|
||||
|
||||
string_encoding: the name of the encoding to use when converting
|
||||
to unicode any byte strings (type str in Python 2) encountered
|
||||
during the rendering process. This name will be passed as the
|
||||
encoding argument to the built-in function unicode().
|
||||
Defaults to the package default.
|
||||
|
||||
decode_errors: the string to pass as the errors argument to the
|
||||
built-in function unicode() when converting byte strings to
|
||||
unicode. Defaults to the package default.
|
||||
|
||||
search_dirs: the list of directories in which to search when
|
||||
loading a template by name or file name. If given a string,
|
||||
the method interprets the string as a single directory.
|
||||
Defaults to the package default.
|
||||
|
||||
file_extension: the template file extension. Pass False for no
|
||||
extension (i.e. to use extensionless template files).
|
||||
Defaults to the package default.
|
||||
|
||||
partials: an object (e.g. a dictionary) for custom partial loading
|
||||
during the rendering process.
|
||||
The object should have a get() method that accepts a string
|
||||
and returns the corresponding template as a string, preferably
|
||||
as a unicode string. If there is no template with that name,
|
||||
the get() method should either return None (as dict.get() does)
|
||||
or raise an exception.
|
||||
If this argument is None, the rendering process will use
|
||||
the normal procedure of locating and reading templates from
|
||||
the file system -- using relevant instance attributes like
|
||||
search_dirs, file_encoding, etc.
|
||||
|
||||
escape: the function used to escape variable tag values when
|
||||
rendering a template. The function should accept a unicode
|
||||
string (or subclass of unicode) and return an escaped string
|
||||
that is again unicode (or a subclass of unicode).
|
||||
This function need not handle strings of type `str` because
|
||||
this class will only pass it unicode strings. The constructor
|
||||
assigns this function to the constructed instance's escape()
|
||||
method.
|
||||
To disable escaping entirely, one can pass `lambda u: u`
|
||||
as the escape function, for example. One may also wish to
|
||||
consider using markupsafe's escape function: markupsafe.escape().
|
||||
This argument defaults to the package default.
|
||||
|
||||
missing_tags: a string specifying how to handle missing tags.
|
||||
If 'strict', an error is raised on a missing tag. If 'ignore',
|
||||
the value of the tag is the empty string. Defaults to the
|
||||
package default.
|
||||
|
||||
"""
|
||||
if decode_errors is None:
|
||||
decode_errors = defaults.DECODE_ERRORS
|
||||
|
||||
if escape is None:
|
||||
escape = defaults.TAG_ESCAPE
|
||||
|
||||
if file_encoding is None:
|
||||
file_encoding = defaults.FILE_ENCODING
|
||||
|
||||
if file_extension is None:
|
||||
file_extension = defaults.TEMPLATE_EXTENSION
|
||||
|
||||
if missing_tags is None:
|
||||
missing_tags = defaults.MISSING_TAGS
|
||||
|
||||
if search_dirs is None:
|
||||
search_dirs = defaults.SEARCH_DIRS
|
||||
|
||||
if string_encoding is None:
|
||||
string_encoding = defaults.STRING_ENCODING
|
||||
|
||||
if isinstance(search_dirs, basestring):
|
||||
search_dirs = [search_dirs]
|
||||
|
||||
self._context = None
|
||||
self.decode_errors = decode_errors
|
||||
self.escape = escape
|
||||
self.file_encoding = file_encoding
|
||||
self.file_extension = file_extension
|
||||
self.missing_tags = missing_tags
|
||||
self.partials = partials
|
||||
self.search_dirs = search_dirs
|
||||
self.string_encoding = string_encoding
|
||||
|
||||
# This is an experimental way of giving views access to the current context.
|
||||
# TODO: consider another approach of not giving access via a property,
|
||||
# but instead letting the caller pass the initial context to the
|
||||
# main render() method by reference. This approach would probably
|
||||
# be less likely to be misused.
|
||||
@property
|
||||
def context(self):
|
||||
"""
|
||||
Return the current rendering context [experimental].
|
||||
|
||||
"""
|
||||
return self._context
|
||||
|
||||
# We could not choose str() as the name because 2to3 renames the unicode()
|
||||
# method of this class to str().
|
||||
def str_coerce(self, val):
|
||||
"""
|
||||
Coerce a non-string value to a string.
|
||||
|
||||
This method is called whenever a non-string is encountered during the
|
||||
rendering process when a string is needed (e.g. if a context value
|
||||
for string interpolation is not a string). To customize string
|
||||
coercion, you can override this method.
|
||||
|
||||
"""
|
||||
return str(val)
|
||||
|
||||
def _to_unicode_soft(self, s):
|
||||
"""
|
||||
Convert a basestring to unicode, preserving any unicode subclass.
|
||||
|
||||
"""
|
||||
# We type-check to avoid "TypeError: decoding Unicode is not supported".
|
||||
# We avoid the Python ternary operator for Python 2.4 support.
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
return self.unicode(s)
|
||||
|
||||
def _to_unicode_hard(self, s):
|
||||
"""
|
||||
Convert a basestring to a string with type unicode (not subclass).
|
||||
|
||||
"""
|
||||
return unicode(self._to_unicode_soft(s))
|
||||
|
||||
def _escape_to_unicode(self, s):
|
||||
"""
|
||||
Convert a basestring to unicode (preserving any unicode subclass), and escape it.
|
||||
|
||||
Returns a unicode string (not subclass).
|
||||
|
||||
"""
|
||||
return unicode(self.escape(self._to_unicode_soft(s)))
|
||||
|
||||
def unicode(self, b, encoding=None):
|
||||
"""
|
||||
Convert a byte string to unicode, using string_encoding and decode_errors.
|
||||
|
||||
Arguments:
|
||||
|
||||
b: a byte string.
|
||||
|
||||
encoding: the name of an encoding. Defaults to the string_encoding
|
||||
attribute for this instance.
|
||||
|
||||
Raises:
|
||||
|
||||
TypeError: Because this method calls Python's built-in unicode()
|
||||
function, this method raises the following exception if the
|
||||
given string is already unicode:
|
||||
|
||||
TypeError: decoding Unicode is not supported
|
||||
|
||||
"""
|
||||
if encoding is None:
|
||||
encoding = self.string_encoding
|
||||
|
||||
# TODO: Wrap UnicodeDecodeErrors with a message about setting
|
||||
# the string_encoding and decode_errors attributes.
|
||||
return unicode(b, encoding, self.decode_errors)
|
||||
|
||||
def _make_loader(self):
|
||||
"""
|
||||
Create a Loader instance using current attributes.
|
||||
|
||||
"""
|
||||
return Loader(file_encoding=self.file_encoding, extension=self.file_extension,
|
||||
to_unicode=self.unicode, search_dirs=self.search_dirs)
|
||||
|
||||
def _make_load_template(self):
|
||||
"""
|
||||
Return a function that loads a template by name.
|
||||
|
||||
"""
|
||||
loader = self._make_loader()
|
||||
|
||||
def load_template(template_name):
|
||||
return loader.load_name(template_name)
|
||||
|
||||
return load_template
|
||||
|
||||
def _make_load_partial(self):
|
||||
"""
|
||||
Return a function that loads a partial by name.
|
||||
|
||||
"""
|
||||
if self.partials is None:
|
||||
return self._make_load_template()
|
||||
|
||||
# Otherwise, create a function from the custom partial loader.
|
||||
partials = self.partials
|
||||
|
||||
def load_partial(name):
|
||||
# TODO: consider using EAFP here instead.
|
||||
# http://docs.python.org/glossary.html#term-eafp
|
||||
# This would mean requiring that the custom partial loader
|
||||
# raise a KeyError on name not found.
|
||||
template = partials.get(name)
|
||||
if template is None:
|
||||
raise TemplateNotFoundError("Name %s not found in partials: %s" %
|
||||
(repr(name), type(partials)))
|
||||
|
||||
# RenderEngine requires that the return value be unicode.
|
||||
return self._to_unicode_hard(template)
|
||||
|
||||
return load_partial
|
||||
|
||||
def _is_missing_tags_strict(self):
|
||||
"""
|
||||
Return whether missing_tags is set to strict.
|
||||
|
||||
"""
|
||||
val = self.missing_tags
|
||||
|
||||
if val == MissingTags.strict:
|
||||
return True
|
||||
elif val == MissingTags.ignore:
|
||||
return False
|
||||
|
||||
raise Exception("Unsupported 'missing_tags' value: %s" % repr(val))
|
||||
|
||||
def _make_resolve_partial(self):
|
||||
"""
|
||||
Return the resolve_partial function to pass to RenderEngine.__init__().
|
||||
|
||||
"""
|
||||
load_partial = self._make_load_partial()
|
||||
|
||||
if self._is_missing_tags_strict():
|
||||
return load_partial
|
||||
# Otherwise, ignore missing tags.
|
||||
|
||||
def resolve_partial(name):
|
||||
try:
|
||||
return load_partial(name)
|
||||
except TemplateNotFoundError:
|
||||
return u''
|
||||
|
||||
return resolve_partial
|
||||
|
||||
def _make_resolve_context(self):
|
||||
"""
|
||||
Return the resolve_context function to pass to RenderEngine.__init__().
|
||||
|
||||
"""
|
||||
if self._is_missing_tags_strict():
|
||||
return context_get
|
||||
# Otherwise, ignore missing tags.
|
||||
|
||||
def resolve_context(stack, name):
|
||||
try:
|
||||
return context_get(stack, name)
|
||||
except KeyNotFoundError:
|
||||
return u''
|
||||
|
||||
return resolve_context
|
||||
|
||||
def _make_render_engine(self):
|
||||
"""
|
||||
Return a RenderEngine instance for rendering.
|
||||
|
||||
"""
|
||||
resolve_context = self._make_resolve_context()
|
||||
resolve_partial = self._make_resolve_partial()
|
||||
|
||||
engine = RenderEngine(literal=self._to_unicode_hard,
|
||||
escape=self._escape_to_unicode,
|
||||
resolve_context=resolve_context,
|
||||
resolve_partial=resolve_partial,
|
||||
to_str=self.str_coerce)
|
||||
return engine
|
||||
|
||||
# TODO: add unit tests for this method.
|
||||
def load_template(self, template_name):
|
||||
"""
|
||||
Load a template by name from the file system.
|
||||
|
||||
"""
|
||||
load_template = self._make_load_template()
|
||||
return load_template(template_name)
|
||||
|
||||
def _render_object(self, obj, *context, **kwargs):
|
||||
"""
|
||||
Render the template associated with the given object.
|
||||
|
||||
"""
|
||||
loader = self._make_loader()
|
||||
|
||||
# TODO: consider an approach that does not require using an if
|
||||
# block here. For example, perhaps this class's loader can be
|
||||
# a SpecLoader in all cases, and the SpecLoader instance can
|
||||
# check the object's type. Or perhaps Loader and SpecLoader
|
||||
# can be refactored to implement the same interface.
|
||||
if isinstance(obj, TemplateSpec):
|
||||
loader = SpecLoader(loader)
|
||||
template = loader.load(obj)
|
||||
else:
|
||||
template = loader.load_object(obj)
|
||||
|
||||
context = [obj] + list(context)
|
||||
|
||||
return self._render_string(template, *context, **kwargs)
|
||||
|
||||
def render_name(self, template_name, *context, **kwargs):
|
||||
"""
|
||||
Render the template with the given name using the given context.
|
||||
|
||||
See the render() docstring for more information.
|
||||
|
||||
"""
|
||||
loader = self._make_loader()
|
||||
template = loader.load_name(template_name)
|
||||
return self._render_string(template, *context, **kwargs)
|
||||
|
||||
def render_path(self, template_path, *context, **kwargs):
|
||||
"""
|
||||
Render the template at the given path using the given context.
|
||||
|
||||
Read the render() docstring for more information.
|
||||
|
||||
"""
|
||||
loader = self._make_loader()
|
||||
template = loader.read(template_path)
|
||||
|
||||
return self._render_string(template, *context, **kwargs)
|
||||
|
||||
def _render_string(self, template, *context, **kwargs):
|
||||
"""
|
||||
Render the given template string using the given context.
|
||||
|
||||
"""
|
||||
# RenderEngine.render() requires that the template string be unicode.
|
||||
template = self._to_unicode_hard(template)
|
||||
|
||||
render_func = lambda engine, stack: engine.render(template, stack)
|
||||
|
||||
return self._render_final(render_func, *context, **kwargs)
|
||||
|
||||
# All calls to render() should end here because it prepares the
|
||||
# context stack correctly.
|
||||
def _render_final(self, render_func, *context, **kwargs):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
render_func: a function that accepts a RenderEngine and ContextStack
|
||||
instance and returns a template rendering as a unicode string.
|
||||
|
||||
"""
|
||||
stack = ContextStack.create(*context, **kwargs)
|
||||
self._context = stack
|
||||
|
||||
engine = self._make_render_engine()
|
||||
|
||||
return render_func(engine, stack)
|
||||
|
||||
def render(self, template, *context, **kwargs):
|
||||
"""
|
||||
Render the given template string, view template, or parsed template.
|
||||
|
||||
Returns a unicode string.
|
||||
|
||||
Prior to rendering, this method will convert a template that is a
|
||||
byte string (type str in Python 2) to unicode using the string_encoding
|
||||
and decode_errors attributes. See the constructor docstring for
|
||||
more information.
|
||||
|
||||
Arguments:
|
||||
|
||||
template: a template string that is unicode or a byte string,
|
||||
a ParsedTemplate instance, or another object instance. In the
|
||||
final case, the function first looks for the template associated
|
||||
to the object by calling this class's get_associated_template()
|
||||
method. The rendering process also uses the passed object as
|
||||
the first element of the context stack when rendering.
|
||||
|
||||
*context: zero or more dictionaries, ContextStack instances, or objects
|
||||
with which to populate the initial context stack. None
|
||||
arguments are skipped. Items in the *context list are added to
|
||||
the context stack in order so that later items in the argument
|
||||
list take precedence over earlier items.
|
||||
|
||||
**kwargs: additional key-value data to add to the context stack.
|
||||
As these arguments appear after all items in the *context list,
|
||||
in the case of key conflicts these values take precedence over
|
||||
all items in the *context list.
|
||||
|
||||
"""
|
||||
if is_string(template):
|
||||
return self._render_string(template, *context, **kwargs)
|
||||
if isinstance(template, ParsedTemplate):
|
||||
render_func = lambda engine, stack: template.render(engine, stack)
|
||||
return self._render_final(render_func, *context, **kwargs)
|
||||
# Otherwise, we assume the template is an object.
|
||||
|
||||
return self._render_object(template, *context, **kwargs)
|
|
@ -1,90 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
This module supports customized (aka special or specified) template loading.
|
||||
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
from pystache.loader import Loader
|
||||
|
||||
|
||||
# TODO: add test cases for this class.
|
||||
class SpecLoader(object):
|
||||
|
||||
"""
|
||||
Supports loading custom-specified templates (from TemplateSpec instances).
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, loader=None):
|
||||
if loader is None:
|
||||
loader = Loader()
|
||||
|
||||
self.loader = loader
|
||||
|
||||
def _find_relative(self, spec):
|
||||
"""
|
||||
Return the path to the template as a relative (dir, file_name) pair.
|
||||
|
||||
The directory returned is relative to the directory containing the
|
||||
class definition of the given object. The method returns None for
|
||||
this directory if the directory is unknown without first searching
|
||||
the search directories.
|
||||
|
||||
"""
|
||||
if spec.template_rel_path is not None:
|
||||
return os.path.split(spec.template_rel_path)
|
||||
# Otherwise, determine the file name separately.
|
||||
|
||||
locator = self.loader._make_locator()
|
||||
|
||||
# We do not use the ternary operator for Python 2.4 support.
|
||||
if spec.template_name is not None:
|
||||
template_name = spec.template_name
|
||||
else:
|
||||
template_name = locator.make_template_name(spec)
|
||||
|
||||
file_name = locator.make_file_name(template_name, spec.template_extension)
|
||||
|
||||
return (spec.template_rel_directory, file_name)
|
||||
|
||||
def _find(self, spec):
|
||||
"""
|
||||
Find and return the path to the template associated to the instance.
|
||||
|
||||
"""
|
||||
if spec.template_path is not None:
|
||||
return spec.template_path
|
||||
|
||||
dir_path, file_name = self._find_relative(spec)
|
||||
|
||||
locator = self.loader._make_locator()
|
||||
|
||||
if dir_path is None:
|
||||
# Then we need to search for the path.
|
||||
path = locator.find_object(spec, self.loader.search_dirs, file_name=file_name)
|
||||
else:
|
||||
obj_dir = locator.get_object_directory(spec)
|
||||
path = os.path.join(obj_dir, dir_path, file_name)
|
||||
|
||||
return path
|
||||
|
||||
def load(self, spec):
|
||||
"""
|
||||
Find and return the template associated to a TemplateSpec instance.
|
||||
|
||||
Returns the template as a unicode string.
|
||||
|
||||
Arguments:
|
||||
|
||||
spec: a TemplateSpec instance.
|
||||
|
||||
"""
|
||||
if spec.template is not None:
|
||||
return self.loader.unicode(spec.template, spec.template_encoding)
|
||||
|
||||
path = self._find(spec)
|
||||
|
||||
return self.loader.read(path, spec.template_encoding)
|
|
@ -1,53 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Provides a class to customize template information on a per-view basis.
|
||||
|
||||
To customize template properties for a particular view, create that view
|
||||
from a class that subclasses TemplateSpec. The "spec" in TemplateSpec
|
||||
stands for "special" or "specified" template information.
|
||||
|
||||
"""
|
||||
|
||||
class TemplateSpec(object):
|
||||
|
||||
"""
|
||||
A mixin or interface for specifying custom template information.
|
||||
|
||||
The "spec" in TemplateSpec can be taken to mean that the template
|
||||
information is either "specified" or "special."
|
||||
|
||||
A view should subclass this class only if customized template loading
|
||||
is needed. The following attributes allow one to customize/override
|
||||
template information on a per view basis. A None value means to use
|
||||
default behavior for that value and perform no customization. All
|
||||
attributes are initialized to None.
|
||||
|
||||
Attributes:
|
||||
|
||||
template: the template as a string.
|
||||
|
||||
template_encoding: the encoding used by the template.
|
||||
|
||||
template_extension: the template file extension. Defaults to "mustache".
|
||||
Pass False for no extension (i.e. extensionless template files).
|
||||
|
||||
template_name: the name of the template.
|
||||
|
||||
template_path: absolute path to the template.
|
||||
|
||||
template_rel_directory: the directory containing the template file,
|
||||
relative to the directory containing the module defining the class.
|
||||
|
||||
template_rel_path: the path to the template file, relative to the
|
||||
directory containing the module defining the class.
|
||||
|
||||
"""
|
||||
|
||||
template = None
|
||||
template_encoding = None
|
||||
template_extension = None
|
||||
template_name = None
|
||||
template_path = None
|
||||
template_rel_directory = None
|
||||
template_rel_path = None
|
|
@ -1,4 +0,0 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -1,94 +0,0 @@
|
|||
#!/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)
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
# 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()))
|
|
@ -1,4 +0,0 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -1 +0,0 @@
|
|||
ascii: abc
|
|
@ -1 +0,0 @@
|
|||
This file is used to test locate_path()'s search order.
|
|
@ -1,4 +0,0 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -1 +0,0 @@
|
|||
This file is used to test locate_path()'s search order.
|
|
@ -1 +0,0 @@
|
|||
Test template file
|
|
@ -1 +0,0 @@
|
|||
non-ascii: é
|
|
@ -1 +0,0 @@
|
|||
ascii: abc
|
|
@ -1 +0,0 @@
|
|||
Hello, {{to}}
|
|
@ -1,21 +0,0 @@
|
|||
# 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
|
|
@ -1,94 +0,0 @@
|
|||
# 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
|
|
@ -1,4 +0,0 @@
|
|||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
|
@ -1 +0,0 @@
|
|||
<h1>{{title}}{{! just something interesting... #or not... }}</h1>
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Comments(object):
|
||||
|
||||
def title(self):
|
||||
return "A Comedy of Errors"
|
|
@ -1,6 +0,0 @@
|
|||
<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}}
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
"""
|
||||
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 [];
|
|
@ -1,6 +0,0 @@
|
|||
{{=<% %>=}}
|
||||
* <% first %>
|
||||
<%=| |=%>
|
||||
* | second |
|
||||
|={{ }}=|
|
||||
* {{ third }}
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
"""
|
||||
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."
|
|
@ -1,3 +0,0 @@
|
|||
{{#t}}* first{{/t}}
|
||||
* {{two}}
|
||||
{{#t}}* third{{/t}}
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class DoubleSection(object):
|
||||
|
||||
def t(self):
|
||||
return True
|
||||
|
||||
def two(self):
|
||||
return "second"
|
|
@ -1 +0,0 @@
|
|||
<h1>{{title}}</h1>
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Escaped(object):
|
||||
|
||||
def title(self):
|
||||
return "Bear > Shark"
|
|
@ -1 +0,0 @@
|
|||
Again, {{title}}!
|
|
@ -1 +0,0 @@
|
|||
## Again, {{title}}! ##
|
|
@ -1 +0,0 @@
|
|||
{{^f}}one{{/f}}, {{ two }}, {{^f}}three{{/f}}{{^t}}, four!{{/t}}{{^empty_list}}, empty list{{/empty_list}}{{^populated_list}}, shouldn't see me{{/populated_list}}
|
|
@ -1,33 +0,0 @@
|
|||
|
||||
"""
|
||||
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 []
|
|
@ -1 +0,0 @@
|
|||
{{#replace_foo_with_bar}}foo != bar. oh, it does!{{/replace_foo_with_bar}}
|
|
@ -1,38 +0,0 @@
|
|||
|
||||
"""
|
||||
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 +0,0 @@
|
|||
Looping partial {{item}}!
|
|
@ -1 +0,0 @@
|
|||
{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
"""
|
||||
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 +0,0 @@
|
|||
{{>simple}}
|
|
@ -1 +0,0 @@
|
|||
{{#rot13}}abcdefghijklm{{/rot13}}
|
|
@ -1 +0,0 @@
|
|||
{{>partial_with_lambda}}{{#rot13}}abcdefghijklm{{/rot13}}
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache.tests.examples.lambdas import rot
|
||||
|
||||
class PartialsWithLambdas(object):
|
||||
|
||||
def rot(self):
|
||||
return rot
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class SayHello(object):
|
||||
def to(self):
|
||||
return "Pizza"
|
|
@ -1 +0,0 @@
|
|||
Hello, {{to}}!
|
|
@ -1 +0,0 @@
|
|||
Hi {{thing}}!{{blank}}
|
|
@ -1,15 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class Simple(TemplateSpec):
|
||||
|
||||
def thing(self):
|
||||
return "pizza"
|
||||
|
||||
def blank(self):
|
||||
return ''
|
|
@ -1 +0,0 @@
|
|||
No tags...
|
|
@ -1,2 +0,0 @@
|
|||
<h1>{{title}}</h1>
|
||||
{{>inner_partial}}
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
"""
|
||||
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')
|
|
@ -1,4 +0,0 @@
|
|||
{{title}}
|
||||
{{title_bars}}
|
||||
|
||||
{{>inner_partial}}
|
|
@ -1 +0,0 @@
|
|||
<h1>{{{title}}}</h1>
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class Unescaped(object):
|
||||
|
||||
def title(self):
|
||||
return "Bear > Shark"
|
|
@ -1 +0,0 @@
|
|||
abcdé
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
from pystache import TemplateSpec
|
||||
|
||||
class UnicodeInput(TemplateSpec):
|
||||
|
||||
template_encoding = 'utf8'
|
||||
|
||||
def age(self):
|
||||
return 156
|
|
@ -1 +0,0 @@
|
|||
<p>Name: {{name}}</p>
|
|
@ -1,11 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
"""
|
||||
TODO: add a docstring.
|
||||
|
||||
"""
|
||||
|
||||
class UnicodeOutput(object):
|
||||
|
||||
def name(self):
|
||||
return u'Henri Poincaré'
|
|
@ -1,190 +0,0 @@
|
|||
# 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
|
|
@ -1,285 +0,0 @@
|
|||
# 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)
|
|
@ -1,36 +0,0 @@
|
|||
# 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)
|
|
@ -1,45 +0,0 @@
|
|||
# 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
|
|
@ -1,499 +0,0 @@
|
|||
# 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")
|
|
@ -1,68 +0,0 @@
|
|||
# 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)
|
|
@ -1,106 +0,0 @@
|
|||
# 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()
|
|
@ -1,209 +0,0 @@
|
|||
# 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')
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
# 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')
|
|
@ -1,27 +0,0 @@
|
|||
# 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)
|
||||
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче