step.py
Go to the documentation of this file.
1 """
2 A light and fast template engine.
3 
4 Copyright (c) 2012, Daniele Mazzocchio
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9 
10 * Redistributions of source code must retain the above copyright notice, this
11  list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright notice,
13  this list of conditions and the following disclaimer in the documentation
14  and/or other materials provided with the distribution.
15 * Neither the name of the developer nor the names of its contributors may be
16  used to endorse or promote products derived from this software without
17  specific prior written permission.
18 
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 ------------------------------------------------------------------------------
31 
32 Supplemented with escape and postprocess and buffer size options,
33 code object caching, get(), fixes and other tweaks, by Erki Suurjaak.
34 """
35 
36 import re
37 
38 
39 try: text_type, string_types = unicode, (bytes, unicode) # Py2
40 except Exception: text_type, string_types = str, (str, ) # Py3
41 
42 
43 class Template(object):
44 
45  TRANSPILED_TEMPLATES = {} # {(template string, compile options): compilable code string}
46  COMPILED_TEMPLATES = {} # {compilable code string: code object}
47  # Regexes for stripping all leading and interleaving, and all or rest of trailing whitespace.
48  RE_STRIP = re.compile("(^[ \t]+|[ \t]+$|(?<=[ \t])[ \t]+|\\A[\r\n]+|[ \t\r\n]+\\Z)", re.M)
49  RE_STRIP_STREAM = re.compile("(^[ \t]+|[ \t]+$|(?<=[ \t])[ \t]+|\\A[\r\n]+|"
50  "((?<=(\r\n))|(?<=[ \t\r\n]))[ \t\r\n]+\\Z)", re.M)
51 
52  def __init__(self, template, strip=True, escape=False, postprocess=None):
53  """Initialize class"""
54  super(Template, self).__init__()
55  pp = list([postprocess] if callable(postprocess) else postprocess or [])
56  self.template = template
57  self.options = {"strip": strip, "escape": escape, "postprocess": pp}
58  self.builtins = {"escape": escape_html, "setopt": self.options.__setitem__}
59  key = (template, bool(escape))
60  TPLS, CODES = Template.TRANSPILED_TEMPLATES, Template.COMPILED_TEMPLATES
61  src = TPLS.setdefault(key, TPLS.get(key) or self._process(self._preprocess(self.template)))
62  self.code = CODES.setdefault(src, CODES.get(src) or compile(src, "<string>", "exec"))
63 
64  def expand(self, namespace=None, **kw):
65  """Return the expanded template string"""
66  output = []
67  eval(self.code, self._make_namespace(namespace, output.append, **kw))
68  return self._postprocess("".join(map(to_unicode, output)))
69 
70  def stream(self, buffer, namespace=None, encoding="utf-8", buffer_size=65536, **kw):
71  """Expand the template and stream it to a file-like buffer."""
72 
73  def write_buffer(s, flush=False, cache=[""]):
74  # Cache output as a single string and write to buffer.
75  cache[0] += to_unicode(s)
76  if cache[0] and (flush or buffer_size < 1 or len(cache[0]) > buffer_size):
77  v = self._postprocess(cache[0], stream=not flush)
78  v and buffer.write(v.encode(encoding) if encoding else v)
79  cache[0] = ""
80 
81  eval(self.code, self._make_namespace(namespace, write_buffer, **kw))
82  write_buffer("", flush=True) # Flush any last cached bytes
83 
84  def _make_namespace(self, namespace, echo, **kw):
85  """Return template namespace dictionary, containing given values and template functions."""
86  namespace = dict(namespace or {}, **dict(kw, **self.builtins))
87  namespace.update(echo=echo, get=namespace.get, isdef=namespace.__contains__)
88  return namespace
89 
90  def _preprocess(self, template):
91  """Modify template string before code conversion"""
92  # Replace inline ('%') blocks for easier parsing
93  o = re.compile("(?m)^[ \t]*%((if|for|while|try).+:)")
94  c = re.compile("(?m)^[ \t]*%(((else|elif|except|finally).*:)|(end\\w+))")
95  template = c.sub(r"<%:\g<1>%>", o.sub(r"<%\g<1>%>", template))
96 
97  # Replace {{!x}} and {{x}} variables with '<%echo(x)%>'.
98  # If auto-escaping is enabled, use echo(escape(x)) for the second.
99  vars = r"\{\{\s*\!(.*?)\}\}", r"\{\{(.*?)\}\}"
100  subs = [r"<%echo(\g<1>)%>\n"] * 2
101  if self.options["escape"]: subs[1] = r"<%echo(escape(\g<1>))%>\n"
102  for v, s in zip(vars, subs): template = re.sub(v, s, template)
103 
104  return template
105 
106  def _process(self, template):
107  """Return the code generated from the template string"""
108  code_blk = re.compile(r"<%(.*?)%>\n?", re.DOTALL)
109  indent, n = 0, 0
110  code = []
111  for n, blk in enumerate(code_blk.split(template)):
112  # Replace '<\%' and '%>' escapes
113  blk = re.sub(r"<\\%", "<%", re.sub(r"%\\>", "%>", blk))
114  # Unescape '%{}' characters
115  blk = re.sub(r"\\(%|{|})", r"\g<1>", blk)
116 
117  if not (n % 2):
118  if not blk: continue
119  # Escape backslash characters
120  blk = re.sub(r'\\', r'\\\\', blk)
121  # Escape double-quote characters
122  blk = re.sub(r'"', r'\\"', blk)
123  blk = (" " * (indent*4)) + 'echo("""{0}""")'.format(blk)
124  else:
125  blk = blk.rstrip()
126  if blk.lstrip().startswith(":"):
127  if not indent:
128  err = "unexpected block ending"
129  raise SyntaxError("Line {0}: {1}".format(n, err))
130  indent -= 1
131  if blk.startswith(":end"):
132  continue
133  blk = blk.lstrip()[1:]
134 
135  blk = re.sub("(?m)^", " " * (indent * 4), blk)
136  if blk.endswith(":"):
137  indent += 1
138 
139  code.append(blk)
140 
141  if indent:
142  err = "Reached EOF before closing block"
143  raise EOFError("Line {0}: {1}".format(n, err))
144 
145  return "\n".join(code)
146 
147  def _postprocess(self, output, stream=False):
148  """Modify output string after variables and code evaluation"""
149  if self.options["strip"]:
150  output = (Template.RE_STRIP_STREAM if stream else Template.RE_STRIP).sub("", output)
151  for process in self.options["postprocess"]:
152  output = process(output)
153  return output
154 
155 
156 def escape_html(x):
157  """Escape HTML special characters &<> and quotes "'."""
158  CHARS, ENTITIES = "&<>\"'", ["&amp;", "&lt;", "&gt;", "&quot;", "&#39;"]
159  string = x if isinstance(x, string_types) else str(x)
160  for c, e in zip(CHARS, ENTITIES): string = string.replace(c, e)
161  return string
162 
163 
164 def to_unicode(x, encoding="utf-8"):
165  """Convert anything to Unicode."""
166  if isinstance(x, (bytes, bytearray)):
167  x = text_type(x, encoding, errors="replace")
168  elif not isinstance(x, string_types):
169  x = text_type(str(x))
170  return x
grepros.vendor.step.Template._postprocess
def _postprocess(self, output, stream=False)
Definition: step.py:147
grepros.vendor.step.Template.expand
def expand(self, namespace=None, **kw)
Definition: step.py:64
grepros.vendor.step.to_unicode
def to_unicode(x, encoding="utf-8")
Definition: step.py:164
grepros.vendor.step.Template.stream
def stream(self, buffer, namespace=None, encoding="utf-8", buffer_size=65536, **kw)
Definition: step.py:70
grepros.vendor.step.Template
Definition: step.py:43
grepros.vendor.step.Template.template
template
Definition: step.py:56
grepros.vendor.step.escape_html
def escape_html(x)
Definition: step.py:156
grepros.vendor.step.text_type
text_type
Definition: step.py:39
grepros.vendor.step.Template._make_namespace
def _make_namespace(self, namespace, echo, **kw)
Definition: step.py:84
grepros.vendor.step.Template.code
code
Definition: step.py:62
grepros.vendor.step.Template.__init__
def __init__(self, template, strip=True, escape=False, postprocess=None)
Definition: step.py:52
grepros.vendor.step.Template.options
options
Definition: step.py:57
grepros.vendor.step.Template._process
def _process(self, template)
Definition: step.py:106
grepros.vendor.step.Template.builtins
builtins
Definition: step.py:58
grepros.vendor.step.Template._preprocess
def _preprocess(self, template)
Definition: step.py:90


grepros
Author(s): Erki Suurjaak
autogenerated on Sat Jan 6 2024 03:11:29