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
c8a1f2e1
Commit
c8a1f2e1
authored
May 05, 2012
by
Chris Jerdonek
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'decouple-parser' into 'development':
Parsing a template no longer requires a RenderEngine instance.
parents
7eef0a68
5dd7fa7c
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
164 additions
and
137 deletions
+164
-137
pystache/defaults.py
+3
-0
pystache/parsed.py
+3
-4
pystache/parser.py
+146
-27
pystache/renderengine.py
+10
-105
pystache/tests/test_parser.py
+2
-1
No files found.
pystache/defaults.py
View file @
c8a1f2e1
...
...
@@ -38,6 +38,9 @@ STRING_ENCODING = sys.getdefaultencoding()
# 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
...
...
pystache/parsed.py
View file @
c8a1f2e1
...
...
@@ -38,18 +38,17 @@ class ParsedTemplate(object):
def
add
(
self
,
node
):
self
.
_parse_tree
.
append
(
node
)
def
render
(
self
,
context
):
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
(
val
):
if
callable
(
val
):
return
val
(
context
)
if
type
(
val
)
is
unicode
:
return
val
return
val
.
render
(
engine
,
context
)
parts
=
map
(
get_unicode
,
self
.
_parse_tree
)
s
=
''
.
join
(
parts
)
return
unicode
(
s
)
pystache/parser.py
View file @
c8a1f2e1
# coding: utf-8
"""
Provides a class for parsing template strings.
This module is only meant for internal use by the renderengine module.
Exposes a parse() function to parse template strings.
"""
import
re
from
pystache.defaults
import
DELIMITERS
from
pystache.parsed
import
ParsedTemplate
DEFAULT_DELIMITERS
=
(
u'{{'
,
u'}}'
)
END_OF_LINE_CHARACTERS
=
[
u'
\r
'
,
u'
\n
'
]
NON_BLANK_RE
=
re
.
compile
(
ur'^(.)'
,
re
.
M
)
def
_compile_template_re
(
delimiters
=
None
):
# TODO: add some unit tests for this.
def
parse
(
template
,
delimiters
=
None
):
"""
Return a regular expresssion object (re.RegexObject) instance.
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.
"""
if
delimiters
is
None
:
delimiters
=
DEFAULT_DELIMITERS
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
=
"!>&/#^"
...
...
@@ -52,25 +64,131 @@ class ParsingError(Exception):
pass
class
Parser
(
object
):
## Node types
_delimiters
=
None
_template_re
=
None
def
__init__
(
self
,
engine
,
delimiters
=
None
):
"""
Construct an instance.
class
_CommentNode
(
object
):
Arguments:
def
render
(
self
,
engine
,
context
):
return
u''
engine: a RenderEngine instance.
"""
class
_ChangeNode
(
object
):
def
__init__
(
self
,
delimiters
):
self
.
delimiters
=
delimiters
def
render
(
self
,
engine
,
context
):
return
u''
class
_TagNode
(
object
):
def
__init__
(
self
,
key
):
self
.
key
=
key
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
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
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
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
engine
.
render_parsed
(
self
.
parsed_section
,
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_contents
,
delimiters
,
template
,
section_begin_index
,
section_end_index
):
self
.
delimiters
=
delimiters
self
.
key
=
key
self
.
parsed_contents
=
parsed_contents
self
.
template
=
template
self
.
section_begin_index
=
section_begin_index
self
.
section_end_index
=
section_end_index
def
render
(
self
,
engine
,
context
):
data
=
engine
.
fetch_section_data
(
context
,
self
.
key
)
parts
=
[]
for
val
in
data
:
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
.
section_begin_index
:
self
.
section_end_index
])
val
=
engine
.
_render_value
(
val
,
context
,
delimiters
=
self
.
delimiters
)
parts
.
append
(
val
)
continue
context
.
push
(
val
)
parts
.
append
(
engine
.
render_parsed
(
self
.
parsed_contents
,
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
=
DE
FAULT_DE
LIMITERS
delimiters
=
DELIMITERS
self
.
_delimiters
=
delimiters
self
.
engine
=
engine
def
_compile_delimiters
(
self
):
self
.
_template_re
=
_compile_template_re
(
self
.
_delimiters
)
...
...
@@ -172,7 +290,8 @@ class Parser(object):
parsed_template
.
add
(
node
)
# Add the remainder of the template.
# Avoid adding spurious empty strings to the parse tree.
if
start_index
!=
len
(
template
):
parsed_template
.
add
(
template
[
start_index
:])
return
parsed_template
...
...
@@ -184,21 +303,21 @@ class Parser(object):
"""
# TODO: switch to using a dictionary instead of a bunch of ifs and elifs.
if
tag_type
==
'!'
:
return
u''
return
_CommentNode
()
if
tag_type
==
'='
:
delimiters
=
tag_key
.
split
()
self
.
_change_delimiters
(
delimiters
)
return
u''
return
_ChangeNode
(
delimiters
)
if
tag_type
==
''
:
return
self
.
engine
.
_make_get_escaped
(
tag_key
)
return
_TagNode
(
tag_key
)
if
tag_type
==
'&'
:
return
self
.
engine
.
_make_get_literal
(
tag_key
)
return
_LiteralNode
(
tag_key
)
if
tag_type
==
'>'
:
return
self
.
engine
.
_make_get_partial
(
tag_key
,
leading_whitespace
)
return
_PartialNode
(
tag_key
,
leading_whitespace
)
raise
Exception
(
"Invalid symbol for interpolation tag:
%
s"
%
repr
(
tag_type
))
...
...
@@ -209,10 +328,10 @@ class Parser(object):
"""
if
tag_type
==
'#'
:
return
self
.
engine
.
_make_get_section
(
tag_key
,
parsed_section
,
self
.
_delimiters
,
return
_SectionNode
(
tag_key
,
parsed_section
,
self
.
_delimiters
,
template
,
section_start_index
,
section_end_index
)
if
tag_type
==
'^'
:
return
self
.
engine
.
_make_get_invers
e
(
tag_key
,
parsed_section
)
return
_InvertedNod
e
(
tag_key
,
parsed_section
)
raise
Exception
(
"Invalid symbol for section tag:
%
s"
%
repr
(
tag_type
))
pystache/renderengine.py
View file @
c8a1f2e1
...
...
@@ -8,10 +8,7 @@ Defines a class responsible for rendering logic.
import
re
from
pystache.common
import
is_string
from
pystache.parser
import
Parser
NON_BLANK_RE
=
re
.
compile
(
ur'^(.)'
,
re
.
M
)
from
pystache.parser
import
parse
def
context_get
(
stack
,
name
):
...
...
@@ -90,12 +87,12 @@ class RenderEngine(object):
# The returned value MUST be rendered against the default delimiters,
# then interpolated in place of the lambda.
#
def
_get_string_value
(
self
,
context
,
tag_
name
):
def
fetch_string
(
self
,
context
,
name
):
"""
Get a value from the given context as a basestring instance.
"""
val
=
self
.
resolve_context
(
context
,
tag_
name
)
val
=
self
.
resolve_context
(
context
,
name
)
if
callable
(
val
):
# Return because _render_value() is already a string.
...
...
@@ -106,72 +103,7 @@ class RenderEngine(object):
return
val
def
_make_get_literal
(
self
,
name
):
def
get_literal
(
context
):
"""
Returns: a string of type unicode.
"""
s
=
self
.
_get_string_value
(
context
,
name
)
return
self
.
literal
(
s
)
return
get_literal
def
_make_get_escaped
(
self
,
name
):
get_literal
=
self
.
_make_get_literal
(
name
)
def
get_escaped
(
context
):
"""
Returns: a string of type unicode.
"""
s
=
self
.
_get_string_value
(
context
,
name
)
return
self
.
escape
(
s
)
return
get_escaped
def
_make_get_partial
(
self
,
tag_key
,
leading_whitespace
):
template
=
self
.
resolve_partial
(
tag_key
)
# Indent before rendering.
template
=
re
.
sub
(
NON_BLANK_RE
,
leading_whitespace
+
ur'\1'
,
template
)
def
get_partial
(
context
):
"""
Returns: a string of type unicode.
"""
# TODO: can we do the parsing before calling this function?
return
self
.
render
(
template
,
context
)
return
get_partial
def
_make_get_inverse
(
self
,
name
,
parsed_template
):
def
get_inverse
(
context
):
"""
Returns a string with type unicode.
"""
# TODO: is there a bug because we are not using the same
# logic as in _get_string_value()?
data
=
self
.
resolve_context
(
context
,
name
)
# Per the spec, lambdas in inverted sections are considered truthy.
if
data
:
return
u''
return
parsed_template
.
render
(
context
)
return
get_inverse
# 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
_make_get_section
(
self
,
name
,
parsed_template
,
delims
,
template
,
section_start_index
,
section_end_index
):
def
get_section_value
(
context
):
"""
Returns: a string of type unicode.
"""
def
fetch_section_data
(
self
,
context
,
name
):
data
=
self
.
resolve_context
(
context
,
name
)
# From the spec:
...
...
@@ -204,36 +136,7 @@ class RenderEngine(object):
data
=
[
data
]
# Otherwise, treat the value as a list.
parts
=
[]
for
val
in
data
:
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
(
template
[
section_start_index
:
section_end_index
])
val
=
self
.
_render_value
(
val
,
context
,
delimiters
=
delims
)
parts
.
append
(
val
)
continue
context
.
push
(
val
)
parts
.
append
(
parsed_template
.
render
(
context
))
context
.
pop
()
return
unicode
(
''
.
join
(
parts
))
return
get_section_value
return
data
def
_render_value
(
self
,
val
,
context
,
delimiters
=
None
):
"""
...
...
@@ -247,6 +150,9 @@ class RenderEngine(object):
val
=
self
.
literal
(
val
)
return
self
.
render
(
val
,
context
,
delimiters
)
def
render_parsed
(
self
,
parsed_template
,
context_stack
):
return
parsed_template
.
render
(
self
,
context_stack
)
def
render
(
self
,
template
,
context_stack
,
delimiters
=
None
):
"""
Render a unicode template string, and return as unicode.
...
...
@@ -259,7 +165,6 @@ class RenderEngine(object):
context_stack: a ContextStack instance.
"""
parser
=
Parser
(
self
,
delimiters
=
delimiters
)
parsed_template
=
parser
.
parse
(
template
)
parsed_template
=
parse
(
template
,
delimiters
)
return
parsed_template
.
render
(
context_stack
)
return
self
.
render_parsed
(
parsed_template
,
context_stack
)
pystache/tests/test_parser.py
View file @
c8a1f2e1
...
...
@@ -7,6 +7,7 @@ Unit tests of parser.py.
import
unittest
from
pystache.defaults
import
DELIMITERS
from
pystache.parser
import
_compile_template_re
as
make_re
...
...
@@ -19,7 +20,7 @@ class RegularExpressionTestCase(unittest.TestCase):
Test getting a key from a dictionary.
"""
re
=
make_re
()
re
=
make_re
(
DELIMITERS
)
match
=
re
.
search
(
"b {{test}}"
)
self
.
assertEqual
(
match
.
start
(),
1
)
...
...
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