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
aa8ad43d
Commit
aa8ad43d
authored
Dec 29, 2011
by
Chris Jerdonek
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'issue_70' into development: closing issue #70
parents
f5c8a965
383acd31
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
194 additions
and
129 deletions
+194
-129
pystache/locator.py
+58
-46
pystache/renderer.py
+64
-19
pystache/view.py
+4
-2
tests/data/__init__.py
+0
-0
tests/data/say_hello.mustache
+2
-2
tests/data/templates.py
+7
-0
tests/spec_cases.py
+3
-2
tests/test_locator.py
+30
-34
tests/test_renderer.py
+26
-24
No files found.
pystache/locator.py
View file @
aa8ad43d
# coding: utf-8
"""
This module provides a Locator class.
This module provides a Locator class
for finding template files
.
"""
...
...
@@ -13,42 +13,14 @@ import sys
DEFAULT_EXTENSION
=
'mustache'
def
make_template_name
(
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()
>>> 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
:]
class
Locator
(
object
):
def
__init__
(
self
,
search_dirs
=
None
,
extension
=
None
):
def
__init__
(
self
,
extension
=
None
):
"""
Construct a template locator.
Arguments:
search_dirs: the list of directories in which to search for templates,
for example when looking for partials. Defaults to the current
working directory. If given a string, the string is interpreted
as a single directory.
extension: the template file extension. Defaults to "mustache".
Pass False for no extension (i.e. extensionless template files).
...
...
@@ -56,14 +28,35 @@ class Locator(object):
if
extension
is
None
:
extension
=
DEFAULT_EXTENSION
if
search_dirs
is
None
:
search_dirs
=
os
.
curdir
# i.e. "."
self
.
template_extension
=
extension
if
isinstance
(
search_dirs
,
basestring
):
search_dirs
=
[
search_dirs
]
def
_find_path
(
self
,
file_name
,
search_dirs
):
"""
Search for the given file, and return the path.
self
.
search_dirs
=
search_dirs
self
.
template_extension
=
extension
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
get_object_directory
(
self
,
obj
):
"""
Return the directory containing an object's defining class.
"""
module
=
sys
.
modules
[
obj
.
__module__
]
# TODO: should we handle the case of __file__ not existing, for
# example when using the interpreter or using a module in the
# standard library)?
path
=
module
.
__file__
return
os
.
path
.
dirname
(
path
)
def
make_file_name
(
self
,
template_name
):
file_name
=
template_name
...
...
@@ -72,22 +65,41 @@ class Locator(object):
return
file_name
def
locate_path
(
self
,
template_name
):
def
make_template_name
(
self
,
obj
):
"""
Find and return the path to the template with the given name.
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
()
Raises an IOError if the template cannot be found.
return
re
.
sub
(
'[A-Z]'
,
repl
,
template_name
)[
1
:]
def
locate_path
(
self
,
template_name
,
search_dirs
):
"""
search_dirs
=
self
.
search_dirs
Find and return the path to the template with the given name.
"""
file_name
=
self
.
make_file_name
(
template_name
)
path
=
self
.
_find_path
(
file_name
,
search_dirs
)
for
dir_path
in
search_dirs
:
file_path
=
os
.
path
.
join
(
dir_path
,
file_name
)
if
os
.
path
.
exists
(
file_path
):
return
file_path
if
path
is
not
None
:
return
path
# TODO: we should probably raise an exception of our own type.
raise
IOError
(
'
"
%
s" not found in "
%
s"'
%
(
template_name
,
':'
.
join
(
search_dirs
),))
raise
IOError
(
'
Template
%
s not found in directories:
%
s'
%
(
repr
(
template_name
),
repr
(
search_dirs
)))
pystache/renderer.py
View file @
aa8ad43d
...
...
@@ -174,12 +174,12 @@ class Renderer(object):
"""
return
Reader
(
encoding
=
self
.
file_encoding
,
decode_errors
=
self
.
decode_errors
)
def
_
make_locator
(
self
):
def
make_locator
(
self
):
"""
Create a Locator instance using current attributes.
"""
return
Locator
(
search_dirs
=
self
.
search_dirs
,
extension
=
self
.
file_extension
)
return
Locator
(
extension
=
self
.
file_extension
)
def
_make_load_template
(
self
):
"""
...
...
@@ -187,10 +187,10 @@ class Renderer(object):
"""
reader
=
self
.
_make_reader
()
locator
=
self
.
_
make_locator
()
locator
=
self
.
make_locator
()
def
load_template
(
template_name
):
path
=
locator
.
locate_path
(
template_name
)
path
=
locator
.
locate_path
(
template_name
=
template_name
,
search_dirs
=
self
.
search_dirs
)
return
reader
.
read
(
path
)
return
load_template
...
...
@@ -255,6 +255,48 @@ class Renderer(object):
load_template
=
self
.
_make_load_template
()
return
load_template
(
template_name
)
def
get_associated_template
(
self
,
obj
):
"""
Find and return the template associated with an object.
The function first searches the directory containing the object's
class definition.
"""
locator
=
self
.
make_locator
()
template_name
=
locator
.
make_template_name
(
obj
)
directory
=
locator
.
get_object_directory
(
obj
)
search_dirs
=
[
directory
]
+
self
.
search_dirs
path
=
locator
.
locate_path
(
template_name
=
template_name
,
search_dirs
=
search_dirs
)
return
self
.
read
(
path
)
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
)
context
=
Context
.
create
(
*
context
,
**
kwargs
)
engine
=
self
.
_make_render_engine
()
rendered
=
engine
.
render
(
template
,
context
)
return
unicode
(
rendered
)
def
_render_object
(
self
,
obj
,
*
context
,
**
kwargs
):
"""
Render the template associated with the given object.
"""
context
=
[
obj
]
+
list
(
context
)
template
=
self
.
get_associated_template
(
obj
)
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.
...
...
@@ -263,20 +305,27 @@ class Renderer(object):
"""
template
=
self
.
read
(
template_path
)
return
self
.
render
(
template
,
*
context
,
**
kwargs
)
return
self
.
_render_string
(
template
,
*
context
,
**
kwargs
)
def
render
(
self
,
template
,
*
context
,
**
kwargs
):
"""
Render the given template using the given context.
Render the given template (or templated object) using the given context.
Returns the rendering as a unicode string.
Returns a unicode string.
Prior to rendering, templates of type str are converted to unicode
using the default_encoding and decode_errors attributes. See the
constructor docstring for more information.
Arguments:
template: a template string that is either unicode or of type str.
If the string has type str, it is first converted to unicode
using this instance's default_encoding and decode_errors
attributes. See the constructor docstring for more information.
template: a template string of type unicode or str, or an object
instance. If the argument is an object, for the template string
the function attempts to find a template associated to the
object by calling the get_associated_template() method. The
argument in this case is also used as the first element of the
context stack when rendering the associated template.
*context: zero or more dictionaries, Context instances, or objects
with which to populate the initial context stack. None
...
...
@@ -290,12 +339,8 @@ class Renderer(object):
all items in the *context list.
"""
engine
=
self
.
_make_render_engine
()
context
=
Context
.
create
(
*
context
,
**
kwargs
)
# RenderEngine.render() requires that the template string be unicode.
template
=
self
.
_to_unicode_hard
(
template
)
rendered
=
engine
.
render
(
template
,
context
)
if
not
isinstance
(
template
,
basestring
):
# Then we assume the template is an object.
return
self
.
_render_object
(
template
,
*
context
,
**
kwargs
)
return
unicode
(
rendered
)
return
self
.
_render_string
(
template
,
*
context
,
**
kwargs
)
pystache/view.py
View file @
aa8ad43d
...
...
@@ -6,7 +6,7 @@ This module provides a View class.
"""
from
.context
import
Context
from
.locator
import
make_template_name
from
.locator
import
Locator
from
.renderer
import
Renderer
...
...
@@ -20,6 +20,8 @@ class View(object):
_renderer
=
None
locator
=
Locator
()
def
__init__
(
self
,
template
=
None
,
context
=
None
,
partials
=
None
,
**
kwargs
):
"""
Construct a View instance.
...
...
@@ -85,7 +87,7 @@ class View(object):
if
self
.
template_name
:
return
self
.
template_name
return
make_template_name
(
self
)
return
self
.
locator
.
make_template_name
(
self
)
def
render
(
self
):
"""
...
...
tests/data/__init__.py
0 → 100644
View file @
aa8ad43d
tests/data/say_hello.mustache
View file @
aa8ad43d
Hello
{{
to
}}
\ No newline at end of file
Hello,
{{
to
}}
\ No newline at end of file
tests/data/templates.py
0 → 100644
View file @
aa8ad43d
# coding: utf-8
class
SayHello
(
object
):
def
to
(
self
):
return
"World"
tests/spec_cases.py
View file @
aa8ad43d
...
...
@@ -46,8 +46,9 @@ def buildTest(testData, spec_filename):
expected
=
testData
[
'expected'
]
data
=
testData
[
'data'
]
renderer
=
Renderer
(
loader
=
partials
)
actual
=
renderer
.
render
(
template
,
data
)
.
encode
(
'utf-8'
)
renderer
=
Renderer
(
partials
=
partials
)
actual
=
renderer
.
render
(
template
,
data
)
actual
=
actual
.
encode
(
'utf-8'
)
message
=
"""
%
s
...
...
tests/test_locator.py
View file @
aa8ad43d
...
...
@@ -9,27 +9,12 @@ import os
import
sys
import
unittest
from
pystache.locator
import
make_template_name
from
pystache.locator
import
Locator
from
pystache.reader
import
Reader
from
.common
import
DATA_DIR
class
MakeTemplateNameTests
(
unittest
.
TestCase
):
"""
Test the make_template_name() function.
"""
def
test
(
self
):
class
FooBar
(
object
):
pass
foo
=
FooBar
()
self
.
assertEquals
(
make_template_name
(
foo
),
'foo_bar'
)
class
LocatorTests
(
unittest
.
TestCase
):
search_dirs
=
'examples'
...
...
@@ -37,14 +22,6 @@ class LocatorTests(unittest.TestCase):
def
_locator
(
self
):
return
Locator
(
search_dirs
=
DATA_DIR
)
def
test_init__search_dirs
(
self
):
# Test the default value.
locator
=
Locator
()
self
.
assertEquals
(
locator
.
search_dirs
,
[
os
.
curdir
])
locator
=
Locator
(
search_dirs
=
[
'foo'
])
self
.
assertEquals
(
locator
.
search_dirs
,
[
'foo'
])
def
test_init__extension
(
self
):
# Test the default value.
locator
=
Locator
()
...
...
@@ -56,6 +33,16 @@ class LocatorTests(unittest.TestCase):
locator
=
Locator
(
extension
=
False
)
self
.
assertTrue
(
locator
.
template_extension
is
False
)
def
test_get_object_directory
(
self
):
locator
=
Locator
()
reader
=
Reader
()
actual
=
locator
.
get_object_directory
(
reader
)
expected
=
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
os
.
pardir
,
'pystache'
)
self
.
assertEquals
(
os
.
path
.
normpath
(
actual
),
os
.
path
.
normpath
(
expected
))
def
test_make_file_name
(
self
):
locator
=
Locator
()
...
...
@@ -69,14 +56,14 @@ class LocatorTests(unittest.TestCase):
self
.
assertEquals
(
locator
.
make_file_name
(
'foo'
),
'foo.'
)
def
test_locate_path
(
self
):
locator
=
Locator
(
search_dirs
=
'examples'
)
path
=
locator
.
locate_path
(
'simple'
)
locator
=
Locator
()
path
=
locator
.
locate_path
(
'simple'
,
search_dirs
=
[
'examples'
]
)
self
.
assertEquals
(
os
.
path
.
basename
(
path
),
'simple.mustache'
)
def
test_locate_path__using_list_of_paths
(
self
):
locator
=
Locator
(
search_dirs
=
[
'doesnt_exist'
,
'examples'
]
)
path
=
locator
.
locate_path
(
'simple'
)
locator
=
Locator
()
path
=
locator
.
locate_path
(
'simple'
,
search_dirs
=
[
'doesnt_exist'
,
'examples'
]
)
self
.
assertTrue
(
path
)
...
...
@@ -90,13 +77,10 @@ class LocatorTests(unittest.TestCase):
dir1
=
DATA_DIR
dir2
=
os
.
path
.
join
(
DATA_DIR
,
'locator'
)
locator
.
search_dirs
=
[
dir1
]
self
.
assertTrue
(
locator
.
locate_path
(
'duplicate'
))
locator
.
search_dirs
=
[
dir2
]
self
.
assertTrue
(
locator
.
locate_path
(
'duplicate'
))
self
.
assertTrue
(
locator
.
locate_path
(
'duplicate'
,
search_dirs
=
[
dir1
]))
self
.
assertTrue
(
locator
.
locate_path
(
'duplicate'
,
search_dirs
=
[
dir2
]))
locator
.
search_dirs
=
[
dir2
,
dir1
]
path
=
locator
.
locate_path
(
'duplicate'
)
path
=
locator
.
locate_path
(
'duplicate'
,
search_dirs
=
[
dir2
,
dir1
])
dirpath
=
os
.
path
.
dirname
(
path
)
dirname
=
os
.
path
.
split
(
dirpath
)[
-
1
]
...
...
@@ -105,5 +89,17 @@ class LocatorTests(unittest.TestCase):
def
test_locate_path__non_existent_template_fails
(
self
):
locator
=
Locator
()
self
.
assertRaises
(
IOError
,
locator
.
locate_path
,
'doesnt_exist'
)
self
.
assertRaises
(
IOError
,
locator
.
locate_path
,
'doesnt_exist'
,
search_dirs
=
[])
def
test_make_template_name
(
self
):
"""
Test make_template_name().
"""
locator
=
Locator
()
class
FooBar
(
object
):
pass
foo
=
FooBar
()
self
.
assertEquals
(
locator
.
make_template_name
(
foo
),
'foo_bar'
)
tests/test_renderer.py
View file @
aa8ad43d
...
...
@@ -15,6 +15,8 @@ from pystache.renderer import Renderer
from
pystache.locator
import
Locator
from
.common
import
get_data_path
from
.data.templates
import
SayHello
class
RendererInitTestCase
(
unittest
.
TestCase
):
...
...
@@ -216,53 +218,40 @@ class RendererTestCase(unittest.TestCase):
actual
=
self
.
_read
(
renderer
,
filename
)
self
.
assertEquals
(
actual
,
'non-ascii: '
)
## Test the
_
make_locator() method.
## Test the make_locator() method.
def
test_
_
make_locator__return_type
(
self
):
def
test_make_locator__return_type
(
self
):
"""
Test that
_
make_locator() returns a Locator.
Test that make_locator() returns a Locator.
"""
renderer
=
Renderer
()
locator
=
renderer
.
_
make_locator
()
locator
=
renderer
.
make_locator
()
self
.
assertEquals
(
type
(
locator
),
Locator
)
def
test_
_
make_locator__file_extension
(
self
):
def
test_make_locator__file_extension
(
self
):
"""
Test that
_
make_locator() respects the file_extension attribute.
Test that make_locator() respects the file_extension attribute.
"""
renderer
=
Renderer
()
renderer
.
file_extension
=
'foo'
locator
=
renderer
.
_
make_locator
()
locator
=
renderer
.
make_locator
()
self
.
assertEquals
(
locator
.
template_extension
,
'foo'
)
def
test__make_locator__search_dirs
(
self
):
"""
Test that _make_locator() respects the search_dirs attribute.
"""
renderer
=
Renderer
()
renderer
.
search_dirs
=
[
'foo'
]
locator
=
renderer
.
_make_locator
()
self
.
assertEquals
(
locator
.
search_dirs
,
[
'foo'
])
# This test is a sanity check. Strictly speaking, it shouldn't
# be necessary based on our tests above.
def
test_
_
make_locator__default
(
self
):
def
test_make_locator__default
(
self
):
renderer
=
Renderer
()
actual
=
renderer
.
_
make_locator
()
actual
=
renderer
.
make_locator
()
expected
=
Locator
()
self
.
assertEquals
(
type
(
actual
),
type
(
expected
))
self
.
assertEquals
(
actual
.
template_extension
,
expected
.
template_extension
)
self
.
assertEquals
(
actual
.
search_dirs
,
expected
.
search_dirs
)
## Test the render() method.
...
...
@@ -388,9 +377,22 @@ class RendererTestCase(unittest.TestCase):
"""
renderer
=
Renderer
()
path
=
get_data_path
(
'say_hello.mustache'
)
actual
=
renderer
.
render_path
(
path
,
to
=
'world'
)
self
.
assertEquals
(
actual
,
"Hello world"
)
actual
=
renderer
.
render_path
(
path
,
to
=
'foo'
)
self
.
assertEquals
(
actual
,
"Hello, foo"
)
def
test_render__object
(
self
):
"""
Test rendering an object instance.
"""
renderer
=
Renderer
()
say_hello
=
SayHello
()
actual
=
renderer
.
render
(
say_hello
)
self
.
assertEquals
(
'Hello, World'
,
actual
)
actual
=
renderer
.
render
(
say_hello
,
to
=
'Mars'
)
self
.
assertEquals
(
'Hello, Mars'
,
actual
)
# By testing that Renderer.render() constructs the right RenderEngine,
# we no longer need to exercise all rendering code paths through
...
...
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