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
35dc5e2b
Commit
35dc5e2b
authored
Jan 02, 2012
by
Chris Jerdonek
Browse files
Options
Browse Files
Download
Plain Diff
Merge 'renderengine-cleanup' into development: RenderEngine refactoring
parents
8189c762
9c2048a1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
123 additions
and
333 deletions
+123
-333
pystache/renderengine.py
+123
-333
No files found.
pystache/renderengine.py
View file @
35dc5e2b
...
...
@@ -12,38 +12,63 @@ import re
import
types
try
:
# The collections.Callable class is not available until Python 2.6.
import
collections.Callable
def
check_callable
(
it
):
return
isinstance
(
it
,
collections
.
Callable
)
except
ImportError
:
def
check_callable
(
it
):
return
hasattr
(
it
,
'__call__'
)
DEFAULT_TAG_OPENING
=
'{{'
DEFAULT_TAG_CLOSING
=
'}}'
END_OF_LINE_CHARACTERS
=
[
'
\r
'
,
'
\n
'
]
class
Modifiers
(
dict
):
def
render_parse_tree
(
parse_tree
,
context
):
"""
Returns: a string of type unicode.
The elements of parse_tree can be any of the following:
* a unicode string
* the return value of a call to any of the following:
* RenderEngine._make_get_literal():
Args: context
Returns: unicode
* RenderEngine._make_get_escaped():
Args: context
Returns: unicode
* RenderEngine._make_get_partial()
Args: context
Returns: unicode
* RenderEngine._make_get_section()
Args: context
Returns: unicode
* _make_get_inverse()
Args: context
Returns: unicode
"""Dictionary with a decorator for assigning functions to keys."""
"""
get_unicode
=
lambda
val
:
val
(
context
)
if
callable
(
val
)
else
val
parts
=
map
(
get_unicode
,
parse_tree
)
s
=
''
.
join
(
parts
)
def
set
(
self
,
key
):
"""
Return a decorator that assigns the given function to the given key.
return
unicode
(
s
)
>>> modifiers = Modifiers()
>>>
>>> @modifiers.set('P')
... def render_tongue(tag_name, context):
... return "
%
s :P"
%
context.get(tag_name)
>>>
>>> modifiers['P']('text', {'text': 'hello!'})
'hello! :P'
def
_make_get_inverse
(
name
,
parsed
):
def
get_inverse
(
context
):
"""
def
decorate
(
func
):
self
[
key
]
=
func
return
func
return
decorate
Returns a string with type unicode.
"""
data
=
context
.
get
(
name
)
if
data
:
return
u''
return
render_parse_tree
(
parsed
,
context
)
return
get_inverse
class
EndOfSection
(
Exception
):
def
__init__
(
self
,
parse_tree
,
template
,
position
):
self
.
parse_tree
=
parse_tree
self
.
template
=
template
self
.
position
=
position
class
RenderEngine
(
object
):
...
...
@@ -64,11 +89,11 @@ class RenderEngine(object):
unicode subclasses (e.g. markupsafe.Markup).
"""
tag_re
=
None
otag
=
'{{'
ctag
=
'}}'
modifiers
=
Modifiers
()
otag
=
DEFAULT_TAG_OPENING
ctag
=
DEFAULT_TAG_CLOSING
def
__init__
(
self
,
load_partial
=
None
,
literal
=
None
,
escape
=
None
):
"""
...
...
@@ -121,254 +146,37 @@ class RenderEngine(object):
# don't use self.literal).
template
=
unicode
(
template
)
_template
=
Template
(
template
=
template
)
_template
.
to_unicode
=
self
.
literal
_template
.
escape
=
self
.
escape
_template
.
get_partial
=
self
.
load_partial
return
_template
.
render_template
(
template
=
template
,
context
=
context
)
return
self
.
_render_template
(
template
=
template
,
context
=
context
)
def
_
compile_regexps
(
self
):
def
_
render_template
(
self
,
template
,
context
):
"""
Compile and set the regular expression attributes.
This method uses the current values for the otag and ctag attributes.
"""
tags
=
{
'otag'
:
re
.
escape
(
self
.
otag
),
'ctag'
:
re
.
escape
(
self
.
ctag
)
}
# The section contents include white space to comply with the spec's
# requirement that sections not alter surrounding whitespace.
section
=
r"
%(otag)
s([#|^])([^\}]*)
%(ctag)
s(.+?)
%(otag)
s/\2
%(ctag)
s"
%
tags
self
.
section_re
=
re
.
compile
(
section
,
re
.
M
|
re
.
S
)
tag
=
r"
%(otag)
s(#|=|&|!|>|\{)?(.+?)\1?
%(ctag)
s+"
%
tags
# We use re.DOTALL to permit multiline comments, in accordance with the spec.
self
.
tag_re
=
re
.
compile
(
tag
,
re
.
DOTALL
)
def
_render_tags
(
self
,
template
):
output
=
[]
while
True
:
parts
=
self
.
tag_re
.
split
(
template
,
maxsplit
=
1
)
output
.
append
(
parts
[
0
])
if
len
(
parts
)
<
2
:
# Then there was no match.
break
tag_type
,
tag_name
,
template
=
parts
[
1
:]
tag_name
=
tag_name
.
strip
()
func
=
self
.
modifiers
[
tag_type
]
tag_value
=
func
(
self
,
tag_name
)
# Appending the tag value to the output prevents treating the
# value as a template string (bug: issue #44).
output
.
append
(
tag_value
)
output
=
""
.
join
(
output
)
return
output
def
_render_dictionary
(
self
,
template
,
context
):
self
.
context
.
push
(
context
)
out
=
self
.
_render
(
template
)
self
.
context
.
pop
()
return
out
Returns: a string of type unicode.
def
_render_list
(
self
,
template
,
listing
):
insides
=
[]
for
item
in
listing
:
insides
.
append
(
self
.
_render_dictionary
(
template
,
item
))
return
''
.
join
(
insides
)
def
_get_string_context
(
self
,
tag_name
):
"""
Get a value from the current context as a basestring instance.
"""
val
=
self
.
context
.
get
(
tag_name
)
# We use "==" rather than "is" to compare integers, as using "is"
# relies on an implementation detail of CPython. The test about
# rendering zeroes failed while using PyPy when using "is".
# See issue #34: https://github.com/defunkt/pystache/issues/34
if
not
val
and
val
!=
0
:
if
tag_name
!=
'.'
:
return
''
val
=
self
.
context
.
top
()
if
not
isinstance
(
val
,
basestring
):
val
=
str
(
val
)
return
val
@modifiers.set
(
None
)
def
_render_escaped
(
self
,
tag_name
):
"""
Return a variable value as an escaped unicode string.
"""
s
=
self
.
_get_string_context
(
tag_name
)
return
self
.
escape
(
s
)
@modifiers.set
(
'{'
)
@modifiers.set
(
'&'
)
def
_render_literal
(
self
,
tag_name
):
"""
Return a variable value as a unicode string (unescaped).
"""
s
=
self
.
_get_string_context
(
tag_name
)
return
self
.
literal
(
s
)
@modifiers.set
(
'!'
)
def
_render_comment
(
self
,
tag_name
):
return
''
@modifiers.set
(
'>'
)
def
_render_partial
(
self
,
template_name
):
template
=
self
.
load_partial
(
template_name
)
return
self
.
_render
(
template
)
@modifiers.set
(
'='
)
def
_change_delimiter
(
self
,
tag_name
):
"""
Change the current delimiter.
"""
self
.
otag
,
self
.
ctag
=
tag_name
.
split
(
' '
)
self
.
_compile_regexps
()
return
''
def
_render
(
self
,
template
):
"""
Arguments:
template: a template string with type unicode.
template: template string
context: a Context instance
"""
output
=
[]
while
True
:
parts
=
self
.
section_re
.
split
(
template
,
maxsplit
=
1
)
start
=
self
.
_render_tags
(
parts
[
0
])
output
.
append
(
start
)
if
len
(
parts
)
<
2
:
# Then there was no match.
break
section_type
,
section_key
,
section_contents
,
template
=
parts
[
1
:]
section_key
=
section_key
.
strip
()
section_value
=
self
.
context
.
get
(
section_key
,
None
)
rendered
=
''
# Callable
if
section_value
and
check_callable
(
section_value
):
rendered
=
section_value
(
section_contents
)
# Dictionary
elif
section_value
and
hasattr
(
section_value
,
'keys'
)
and
hasattr
(
section_value
,
'__getitem__'
):
if
section_type
!=
'^'
:
rendered
=
self
.
_render_dictionary
(
section_contents
,
section_value
)
# Lists
elif
section_value
and
hasattr
(
section_value
,
'__iter__'
):
if
section_type
!=
'^'
:
rendered
=
self
.
_render_list
(
section_contents
,
section_value
)
# Other objects
elif
section_value
and
isinstance
(
section_value
,
object
):
if
section_type
!=
'^'
:
rendered
=
self
.
_render_dictionary
(
section_contents
,
section_value
)
# Falsey and Negated or Truthy and Not Negated
elif
(
not
section_value
and
section_type
==
'^'
)
or
(
section_value
and
section_type
!=
'^'
):
rendered
=
self
.
_render_dictionary
(
section_contents
,
section_value
)
# Render template prior to section too
output
.
append
(
rendered
)
output
=
""
.
join
(
output
)
return
output
#
END_OF_LINE_CHARACTERS
=
[
'
\r
'
,
'
\n
'
]
# TODO: what are the possibilities for val?
def
call
(
val
,
view
,
template
=
None
):
if
callable
(
val
):
(
args
,
_
,
_
,
_
)
=
inspect
.
getargspec
(
val
)
args_count
=
len
(
args
)
if
not
isinstance
(
val
,
types
.
FunctionType
):
# Then val is an instance method. Subtract one from the
# argument count because Python will automatically prepend
# self to the argument list when calling.
args_count
-=
1
if
args_count
is
0
:
val
=
val
()
elif
args_count
is
1
and
args
[
0
]
in
[
'self'
,
'context'
]:
val
=
val
(
view
)
elif
args_count
is
1
:
val
=
val
(
template
)
else
:
val
=
val
(
view
,
template
)
if
callable
(
val
):
val
=
val
(
template
)
if
val
is
None
:
val
=
''
return
unicode
(
val
)
if
type
(
template
)
is
not
unicode
:
raise
Exception
(
"Argument 'template' not unicode:
%
s:
%
s"
%
(
type
(
template
),
repr
(
template
)))
def
render_parse_tree
(
parse_tree
,
context
,
template
):
"""
Convert a parse-tree into a string.
parse_tree
=
self
.
parse_string_to_tree
(
template_string
=
template
)
return
render_parse_tree
(
parse_tree
,
context
)
"""
get_string
=
lambda
val
:
call
(
val
,
context
,
template
)
parts
=
map
(
get_string
,
parse_tree
)
return
''
.
join
(
parts
)
def
parse_string_to_tree
(
self
,
template_string
,
delims
=
None
):
def
inverseTag
(
name
,
parsed
,
template
,
delims
):
def
func
(
self
):
data
=
self
.
get
(
name
)
if
data
:
return
''
return
render_parse_tree
(
parsed
,
self
,
delims
)
return
func
engine
=
RenderEngine
(
load_partial
=
self
.
load_partial
,
literal
=
self
.
literal
,
escape
=
self
.
escape
)
class
EndOfSection
(
Exception
):
def
__init__
(
self
,
parse_tree
,
template
,
position
):
self
.
parse_tree
=
parse_tree
self
.
template
=
template
self
.
position
=
position
if
delims
is
not
None
:
engine
.
otag
=
delims
[
0
]
engine
.
ctag
=
delims
[
1
]
class
Template
(
object
):
tag_re
=
None
otag
,
ctag
=
'{{'
,
'}}'
engine
.
_compile_regexps
()
def
__init__
(
self
,
template
=
None
):
self
.
template
=
template
return
engine
.
parse_to_tree
(
template
=
template_string
)
def
_compile_regexps
(
self
):
...
...
@@ -395,15 +203,6 @@ class Template(object):
self
.
tag_re
=
re
.
compile
(
tag
,
re
.
M
|
re
.
X
)
def
to_unicode
(
self
,
text
):
return
unicode
(
text
)
def
escape
(
self
,
text
):
return
cgi
.
escape
(
text
,
True
)
def
get_partial
(
self
,
name
):
pass
def
_get_string_value
(
self
,
context
,
tag_name
):
"""
Get a value from the given context as a basestring instance.
...
...
@@ -433,41 +232,60 @@ class Template(object):
# In case the template is an integer, for example.
template
=
str
(
template
)
if
type
(
template
)
is
not
unicode
:
template
=
self
.
to_unicode
(
template
)
val
=
self
.
render_template
(
template
,
context
)
template
=
self
.
literal
(
template
)
val
=
self
.
_
render_template
(
template
,
context
)
if
not
isinstance
(
val
,
basestring
):
val
=
str
(
val
)
return
val
def
escape_tag_function
(
self
,
name
):
get_literal
=
self
.
literal_tag_function
(
name
)
def
func
(
context
):
def
_make_get_literal
(
self
,
name
):
def
get_literal
(
context
):
"""
Returns: a string of type unicode.
"""
s
=
self
.
_get_string_value
(
context
,
name
)
s
=
self
.
escape
(
s
)
s
=
self
.
literal
(
s
)
return
s
return
func
def
literal_tag_function
(
self
,
name
):
def
func
(
context
):
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
)
s
=
self
.
to_unicod
e
(
s
)
s
=
self
.
escap
e
(
s
)
return
s
return
func
return
get_escaped
def
partial_tag_function
(
self
,
name
,
indentation
=
''
):
def
func
(
context
):
def
_make_get_partial
(
self
,
name
,
indentation
=
''
):
def
get_partial
(
context
):
"""
Returns: a string of type unicode.
"""
nonblank
=
re
.
compile
(
r'^(.)'
,
re
.
M
)
template
=
self
.
get
_partial
(
name
)
template
=
self
.
load
_partial
(
name
)
# Indent before rendering.
template
=
re
.
sub
(
nonblank
,
indentation
+
r'\1'
,
template
)
return
self
.
render_template
(
template
,
context
)
return
func
return
self
.
_render_template
(
template
,
context
)
return
get_partial
def
section_tag_function
(
self
,
name
,
parse_tree_
,
template_
,
delims
):
def
func
(
context
):
def
_make_get_section
(
self
,
name
,
parse_tree_
,
template_
,
delims
):
def
get_section
(
context
):
"""
Returns: a string of type unicode.
"""
template
=
template_
parse_tree
=
parse_tree_
data
=
context
.
get
(
name
)
...
...
@@ -476,7 +294,7 @@ class Template(object):
elif
callable
(
data
):
# TODO: should we check the arity?
template
=
data
(
template
)
parse_tree
=
self
.
parse_string_to_tree
(
template
,
delims
)
parse_tree
=
self
.
parse_string_to_tree
(
template
_string
=
template
,
delims
=
delims
)
data
=
[
data
]
elif
type
(
data
)
not
in
[
list
,
tuple
]:
data
=
[
data
]
...
...
@@ -484,34 +302,19 @@ class Template(object):
parts
=
[]
for
element
in
data
:
context
.
push
(
element
)
parts
.
append
(
render_parse_tree
(
parse_tree
,
context
,
delims
))
parts
.
append
(
render_parse_tree
(
parse_tree
,
context
))
context
.
pop
()
return
''
.
join
(
parts
)
return
func
def
parse_string_to_tree
(
self
,
template
,
delims
=
(
'{{'
,
'}}'
)):
template
=
Template
(
template
)
template
.
otag
=
delims
[
0
]
template
.
ctag
=
delims
[
1
]
template
.
escape
=
self
.
escape
template
.
get_partial
=
self
.
get_partial
template
.
to_unicode
=
self
.
to_unicode
return
unicode
(
''
.
join
(
parts
))
template
.
_compile_regexps
()
return
get_section
return
template
.
parse_to_tree
()
def
parse_to_tree
(
self
,
index
=
0
):
def
parse_to_tree
(
self
,
template
,
index
=
0
):
"""
Parse a template into a syntax tree.
"""
parse_tree
=
[]
template
=
self
.
template
start_index
=
index
while
True
:
...
...
@@ -524,15 +327,14 @@ class Template(object):
match_index
=
match
.
end
(
'content'
)
end_index
=
match
.
end
()
index
=
self
.
_handle_match
(
parse_tree
,
captures
,
start_index
,
match_index
,
end_index
)
index
=
self
.
_handle_match
(
template
,
parse_tree
,
captures
,
start_index
,
match_index
,
end_index
)
# Save the rest of the template.
parse_tree
.
append
(
template
[
index
:])
return
parse_tree
def
_handle_match
(
self
,
parse_tree
,
captures
,
start_index
,
match_index
,
end_index
):
template
=
self
.
template
def
_handle_match
(
self
,
template
,
parse_tree
,
captures
,
start_index
,
match_index
,
end_index
):
# Normalize the captures dictionary.
if
captures
[
'change'
]
is
not
None
:
...
...
@@ -569,26 +371,28 @@ class Template(object):
return
end_index
if
captures
[
'tag'
]
==
'>'
:
func
=
self
.
partial_tag_function
(
name
,
captures
[
'whitespace'
])
func
=
self
.
_make_get_partial
(
name
,
captures
[
'whitespace'
])
elif
captures
[
'tag'
]
in
[
'#'
,
'^'
]:
try
:
self
.
parse_to_tree
(
index
=
end_index
)
self
.
parse_to_tree
(
template
=
template
,
index
=
end_index
)
except
EndOfSection
as
e
:
bufr
=
e
.
parse_tree
tmpl
=
e
.
template
end_index
=
e
.
position
tag
=
self
.
section_tag_function
if
captures
[
'tag'
]
==
'#'
else
inverseTag
func
=
tag
(
name
,
bufr
,
tmpl
,
(
self
.
otag
,
self
.
ctag
))
if
captures
[
'tag'
]
==
'#'
:
func
=
self
.
_make_get_section
(
name
,
bufr
,
tmpl
,
(
self
.
otag
,
self
.
ctag
))
else
:
func
=
_make_get_inverse
(
name
,
bufr
)
elif
captures
[
'tag'
]
in
[
'{'
,
'&'
]:
func
=
self
.
literal_tag_function
(
name
)
func
=
self
.
_make_get_literal
(
name
)
elif
captures
[
'tag'
]
==
''
:
func
=
self
.
escape_tag_function
(
name
)
func
=
self
.
_make_get_escaped
(
name
)
elif
captures
[
'tag'
]
==
'/'
:
...
...
@@ -602,17 +406,3 @@ class Template(object):
return
end_index
def
render_template
(
self
,
template
,
context
,
delims
=
(
'{{'
,
'}}'
)):
"""
Arguments:
template: template string
context: a Context instance
"""
if
type
(
template
)
is
not
unicode
:
raise
Exception
(
"Argument 'template' not unicode:
%
s:
%
s"
%
(
type
(
template
),
repr
(
template
)))
parse_tree
=
self
.
parse_string_to_tree
(
template
=
template
,
delims
=
delims
)
return
render_parse_tree
(
parse_tree
,
context
,
template
)
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