Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pystache_custom
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
pystache_custom
Commits
384475a5
Commit
384475a5
authored
May 02, 2012
by
Chris Jerdonek
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'issue-99-dot-notation' into development:
Addresses issue #99: "Mustache spec v1.1.2 compliance"
parents
29ac86c0
bd6b5f04
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
251 additions
and
39 deletions
+251
-39
HISTORY.rst
+3
-3
README.rst
+2
-2
ext/spec
+1
-1
pystache/context.py
+67
-27
pystache/renderengine.py
+1
-2
pystache/tests/common.py
+25
-0
pystache/tests/test_context.py
+76
-3
pystache/tests/test_renderengine.py
+76
-1
No files found.
HISTORY.rst
View file @
384475a5
...
...
@@ -4,10 +4,10 @@ History
0.6.0 (TBD)
-----------
* Added support for dot notation and version 1.1.2 of the spec (issue #99). [rbp]
* Bugfix: falsey values now coerced to strings using str().
* Bugfix: issue #113: lambda return values for sections no longer pushed
onto context stack.
* Bugfix: issue #114: lists of lambdas for sections were not rendered.
* 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)
------------------
...
...
README.rst
View file @
384475a5
...
...
@@ -15,7 +15,7 @@ syntax. For a more complete (and more current) description of Mustache's
behavior, see the official `Mustache spec`_.
Pystache is `semantically versioned`_ and can be found on PyPI_. This
version of Pystache passes all tests in `version 1.
0.3
`_ of the spec.
version of Pystache passes all tests in `version 1.
1.2
`_ of the spec.
Logo: `David Phillips`_
...
...
@@ -230,5 +230,5 @@ Authors
.. _TemplateSpec: https://github.com/defunkt/pystache/blob/master/pystache/template_spec.py
.. _test: http://packages.python.org/distribute/setuptools.html#test
.. _tox: http://pypi.python.org/pypi/tox
.. _version 1.
0.3: https://github.com/mustache/spec/tree/48c933b0bb780875acbfd15816297e263c53d6f7
.. _version 1.
1.2: https://github.com/mustache/spec/tree/v1.1.2
.. _version 2.0.9: http://pypi.python.org/pypi/simplejson/2.0.9
spec
@
bf6288ed
Subproject commit
48c933b0bb780875acbfd15816297e263c53d6f7
Subproject commit
bf6288ed6bd0ce8ccea6f1dac070b3d779132c3b
pystache/context.py
View file @
384475a5
# coding: utf-8
"""
Exposes a ContextStack class
and functions to retrieve names from context
.
Exposes a ContextStack class.
"""
...
...
@@ -27,6 +27,8 @@ def _is_callable(obj):
return
hasattr
(
obj
,
'__call__'
)
# TODO: rename item to context (now that we have a separate notion of context stack).
# TODO: document what a "context" is as opposed to a context stack.
def
_get_value
(
item
,
key
):
"""
Retrieve a key's value from an item.
...
...
@@ -60,25 +62,6 @@ def _get_value(item, key):
return
_NOT_FOUND
# TODO: add some unit tests for this.
def
resolve
(
context
,
name
):
"""
Resolve the given name against the given context stack.
This function follows the rules outlined in the section of the spec
regarding tag interpolation.
This function does not coerce the return value to a string.
"""
if
name
==
'.'
:
return
context
.
top
()
# The spec says that if the name fails resolution, the result should be
# considered falsey, and should interpolate as the empty string.
return
context
.
get
(
name
,
''
)
class
ContextStack
(
object
):
"""
...
...
@@ -186,9 +169,65 @@ class ContextStack(object):
return
context
def
get
(
self
,
key
,
default
=
None
):
# TODO: add some unit tests for this.
def
get
(
self
,
name
,
default
=
u''
):
"""
Query the stack for the given key, and return the resulting value.
Resolve a dotted name against the current context stack.
This function follows the rules outlined in the section of the spec
regarding tag interpolation.
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 since the Mustache spec says that if
name resolution fails at any point, the result should be considered
falsey, and should interpolate as the empty string.
This function does not coerce the return value to a string.
"""
if
name
==
'.'
:
# TODO: should we add a test case for an empty context stack?
return
self
.
top
()
parts
=
name
.
split
(
'.'
)
value
=
self
.
_get_simple
(
parts
[
0
])
for
part
in
parts
[
1
:]:
# TODO: consider using EAFP here instead.
# http://docs.python.org/glossary.html#term-eafp
if
value
is
_NOT_FOUND
:
break
# 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.
value
=
_get_value
(
value
,
part
)
if
value
is
_NOT_FOUND
:
return
default
return
value
# TODO: combine the docstring for this method with the docstring for
# the get() method.
def
_get_simple
(
self
,
key
):
"""
Query the stack for a non-dotted key, and return the resulting value.
Arguments:
key: a non-dotted name.
This method queries items in the stack in order from last-added
objects to first (last in, first out). The value returned is
...
...
@@ -253,15 +292,16 @@ class ContextStack(object):
TODO: explain the rationale for this difference in treatment.
"""
for
obj
in
reversed
(
self
.
_stack
):
val
=
_get_value
(
obj
,
key
)
val
=
_NOT_FOUND
for
item
in
reversed
(
self
.
_stack
):
val
=
_get_value
(
item
,
key
)
if
val
is
_NOT_FOUND
:
continue
# Otherwise, the key was found.
return
val
# Otherwise, no item in the stack contained the key.
break
return
default
return
val
def
push
(
self
,
item
):
"""
...
...
pystache/renderengine.py
View file @
384475a5
...
...
@@ -7,7 +7,6 @@ Defines a class responsible for rendering logic.
import
re
from
pystache.context
import
resolve
from
pystache.parser
import
Parser
...
...
@@ -69,7 +68,7 @@ class RenderEngine(object):
Get a value from the given context as a basestring instance.
"""
val
=
resolve
(
context
,
tag_name
)
val
=
context
.
get
(
tag_name
)
if
callable
(
val
):
# According to the spec:
...
...
pystache/tests/common.py
View file @
384475a5
...
...
@@ -191,3 +191,28 @@ class SetupDefaults(object):
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
()))
pystache/tests/test_context.py
View file @
384475a5
...
...
@@ -11,7 +11,7 @@ import unittest
from
pystache.context
import
_NOT_FOUND
from
pystache.context
import
_get_value
from
pystache.context
import
ContextStack
from
pystache.tests.common
import
AssertIsMixin
from
pystache.tests.common
import
AssertIsMixin
,
AssertStringMixin
,
Attachable
class
SimpleObject
(
object
):
...
...
@@ -204,7 +204,7 @@ class GetValueTests(unittest.TestCase, AssertIsMixin):
self
.
assertNotFound
(
item2
,
'pop'
)
class
ContextStackTests
(
unittest
.
TestCase
,
AssertIsMixin
):
class
ContextStackTests
(
unittest
.
TestCase
,
AssertIsMixin
,
AssertStringMixin
):
"""
Test the ContextStack class.
...
...
@@ -320,7 +320,7 @@ class ContextStackTests(unittest.TestCase, AssertIsMixin):
"""
context
=
ContextStack
()
self
.
assert
True
(
context
.
get
(
"foo"
)
is
None
)
self
.
assert
String
(
context
.
get
(
"foo"
),
u''
)
def
test_get__default
(
self
):
"""
...
...
@@ -395,3 +395,76 @@ class ContextStackTests(unittest.TestCase, AssertIsMixin):
# 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
.
assertEquals
(
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
.
assertEquals
(
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
.
assertEquals
(
stack
.
get
(
name
),
42
)
def
test_dot_notation__missing_attr_or_key
(
self
):
name
=
"foo.bar.baz.bak"
stack
=
ContextStack
({
"foo"
:
{
"bar"
:
{}}})
self
.
assertString
(
stack
.
get
(
name
),
u''
)
stack
=
ContextStack
({
"foo"
:
Attachable
(
bar
=
Attachable
())})
self
.
assertString
(
stack
.
get
(
name
),
u''
)
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
.
assertString
(
stack
.
get
(
'a.b'
),
u''
)
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
.
assertEquals
(
stack
.
get
(
name
),
"Called!"
)
class
Foo
(
object
):
def
bar
(
self
):
return
Attachable
(
baz
=
'Baz'
)
stack
=
ContextStack
({
"foo"
:
Foo
()})
self
.
assertEquals
(
stack
.
get
(
name
),
"Baz"
)
pystache/tests/test_renderengine.py
View file @
384475a5
...
...
@@ -11,7 +11,7 @@ from pystache.context import ContextStack
from
pystache
import
defaults
from
pystache.parser
import
ParsingError
from
pystache.renderengine
import
RenderEngine
from
pystache.tests.common
import
AssertStringMixin
from
pystache.tests.common
import
AssertStringMixin
,
Attachable
def
mock_literal
(
s
):
...
...
@@ -580,3 +580,78 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
expected
=
u' {{foo}} '
self
.
_assert_render
(
expected
,
'{{=$ $=}} {{foo}} '
)
self
.
_assert_render
(
expected
,
'{{=$ $=}} {{foo}} $={{ }}=$'
)
# was yielding u' '.
def
test_dot_notation
(
self
):
"""
Test simple dot notation cases.
Check that we can use dot notation when the variable is a dict,
user-defined object, or combination of both.
"""
template
=
'Hello, {{person.name}}. I see you are {{person.details.age}}.'
person
=
Attachable
(
name
=
'Biggles'
,
details
=
{
'age'
:
42
})
context
=
{
'person'
:
person
}
self
.
_assert_render
(
u'Hello, Biggles. I see you are 42.'
,
template
,
context
)
def
test_dot_notation__missing_attributes_or_keys
(
self
):
"""
Test dot notation with missing keys or attributes.
Check that if a key or attribute in a dotted name does not exist, then
the tag renders as the empty string.
"""
template
=
"""I cannot see {{person.name}}'s age: {{person.age}}.
Nor {{other_person.name}}'s: ."""
expected
=
u"""I cannot see Biggles's age: .
Nor Mr. Bradshaw's: ."""
context
=
{
'person'
:
{
'name'
:
'Biggles'
},
'other_person'
:
Attachable
(
name
=
'Mr. Bradshaw'
)}
self
.
_assert_render
(
expected
,
template
,
context
)
def
test_dot_notation__multiple_levels
(
self
):
"""
Test dot notation with multiple levels.
"""
template
=
"""Hello, Mr. {{person.name.lastname}}.
I see you're back from {{person.travels.last.country.city}}.
I'm missing some of your details: {{person.details.private.editor}}."""
expected
=
u"""Hello, Mr. Pither.
I see you're back from Cornwall.
I'm missing some of your details: ."""
context
=
{
'person'
:
{
'name'
:
{
'firstname'
:
'unknown'
,
'lastname'
:
'Pither'
},
'travels'
:
{
'last'
:
{
'country'
:
{
'city'
:
'Cornwall'
}}},
'details'
:
{
'public'
:
'likes cycling'
}}}
self
.
_assert_render
(
expected
,
template
,
context
)
# It should also work with user-defined objects
context
=
{
'person'
:
Attachable
(
name
=
{
'firstname'
:
'unknown'
,
'lastname'
:
'Pither'
},
travels
=
Attachable
(
last
=
Attachable
(
country
=
Attachable
(
city
=
'Cornwall'
))),
details
=
Attachable
())}
self
.
_assert_render
(
expected
,
template
,
context
)
def
test_dot_notation__missing_part_terminates_search
(
self
):
"""
Test that dotted name resolution terminates on a later part not found.
Check that if a later dotted name part is not found in the result from
the former resolution, then name resolution terminates rather than
starting the search over with the next element of the context stack.
From the spec (interpolation section)--
5) If any name parts were retained in step 1, each should be resolved
against a context stack containing only the result from the former
resolution. If any part fails resolution, the result should be considered
falsey, and should interpolate as the empty string.
This test case is equivalent to the test case in the following pull
request:
https://github.com/mustache/spec/pull/48
"""
template
=
'{{a.b}} :: ({{#c}}{{a}} :: {{a.b}}{{/c}})'
context
=
{
'a'
:
{
'b'
:
'A.B'
},
'c'
:
{
'a'
:
'A'
}
}
self
.
_assert_render
(
u'A.B :: (A :: )'
,
template
,
context
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment