aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/cython/Cython/Tempita/_tempita.py
diff options
context:
space:
mode:
authoralexv-smirnov <alex@ydb.tech>2023-06-13 11:05:01 +0300
committeralexv-smirnov <alex@ydb.tech>2023-06-13 11:05:01 +0300
commitbf0f13dd39ee3e65092ba3572bb5b1fcd125dcd0 (patch)
tree1d1df72c0541a59a81439842f46d95396d3e7189 /contrib/tools/cython/Cython/Tempita/_tempita.py
parent8bfdfa9a9bd19bddbc58d888e180fbd1218681be (diff)
downloadydb-bf0f13dd39ee3e65092ba3572bb5b1fcd125dcd0.tar.gz
add ymake export to ydb
Diffstat (limited to 'contrib/tools/cython/Cython/Tempita/_tempita.py')
-rw-r--r--contrib/tools/cython/Cython/Tempita/_tempita.py1188
1 files changed, 1188 insertions, 0 deletions
diff --git a/contrib/tools/cython/Cython/Tempita/_tempita.py b/contrib/tools/cython/Cython/Tempita/_tempita.py
new file mode 100644
index 0000000000..587f6e4841
--- /dev/null
+++ b/contrib/tools/cython/Cython/Tempita/_tempita.py
@@ -0,0 +1,1188 @@
+"""
+A small templating language
+
+This implements a small templating language. This language implements
+if/elif/else, for/continue/break, expressions, and blocks of Python
+code. The syntax is::
+
+ {{any expression (function calls etc)}}
+ {{any expression | filter}}
+ {{for x in y}}...{{endfor}}
+ {{if x}}x{{elif y}}y{{else}}z{{endif}}
+ {{py:x=1}}
+ {{py:
+ def foo(bar):
+ return 'baz'
+ }}
+ {{default var = default_value}}
+ {{# comment}}
+
+You use this with the ``Template`` class or the ``sub`` shortcut.
+The ``Template`` class takes the template string and the name of
+the template (for errors) and a default namespace. Then (like
+``string.Template``) you can call the ``tmpl.substitute(**kw)``
+method to make a substitution (or ``tmpl.substitute(a_dict)``).
+
+``sub(content, **kw)`` substitutes the template immediately. You
+can use ``__name='tmpl.html'`` to set the name of the template.
+
+If there are syntax errors ``TemplateError`` will be raised.
+"""
+
+from __future__ import absolute_import
+
+import re
+import sys
+try:
+ import cgi
+except ImportError:
+ pass
+try:
+ from urllib import quote as url_quote
+except ImportError: # Py3
+ try:
+ from urllib.parse import quote as url_quote
+ except ImportError:
+ pass
+import os
+import tokenize
+from io import StringIO
+
+from ._looper import looper
+from .compat3 import bytes, unicode_, basestring_, next, is_unicode, coerce_text
+
+__all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate',
+ 'sub_html', 'html', 'bunch']
+
+in_re = re.compile(r'\s+in\s+')
+var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)
+
+
+class TemplateError(Exception):
+ """Exception raised while parsing a template
+ """
+
+ def __init__(self, message, position, name=None):
+ Exception.__init__(self, message)
+ self.position = position
+ self.name = name
+
+ def __str__(self):
+ msg = ' '.join(self.args)
+ if self.position:
+ msg = '%s at line %s column %s' % (
+ msg, self.position[0], self.position[1])
+ if self.name:
+ msg += ' in %s' % self.name
+ return msg
+
+
+class _TemplateContinue(Exception):
+ pass
+
+
+class _TemplateBreak(Exception):
+ pass
+
+
+def get_file_template(name, from_template):
+ path = os.path.join(os.path.dirname(from_template.name), name)
+ return from_template.__class__.from_filename(
+ path, namespace=from_template.namespace,
+ get_template=from_template.get_template)
+
+
+class Template(object):
+
+ default_namespace = {
+ 'start_braces': '{{',
+ 'end_braces': '}}',
+ 'looper': looper,
+ }
+
+ default_encoding = 'utf8'
+ default_inherit = None
+
+ def __init__(self, content, name=None, namespace=None, stacklevel=None,
+ get_template=None, default_inherit=None, line_offset=0,
+ delimeters=None):
+ self.content = content
+
+ # set delimeters
+ if delimeters is None:
+ delimeters = (self.default_namespace['start_braces'],
+ self.default_namespace['end_braces'])
+ else:
+ #assert len(delimeters) == 2 and all([isinstance(delimeter, basestring)
+ # for delimeter in delimeters])
+ self.default_namespace = self.__class__.default_namespace.copy()
+ self.default_namespace['start_braces'] = delimeters[0]
+ self.default_namespace['end_braces'] = delimeters[1]
+ self.delimeters = delimeters
+
+ self._unicode = is_unicode(content)
+ if name is None and stacklevel is not None:
+ try:
+ caller = sys._getframe(stacklevel)
+ except ValueError:
+ pass
+ else:
+ globals = caller.f_globals
+ lineno = caller.f_lineno
+ if '__file__' in globals:
+ name = globals['__file__']
+ if name.endswith('.pyc') or name.endswith('.pyo'):
+ name = name[:-1]
+ elif '__name__' in globals:
+ name = globals['__name__']
+ else:
+ name = '<string>'
+ if lineno:
+ name += ':%s' % lineno
+ self.name = name
+ self._parsed = parse(content, name=name, line_offset=line_offset, delimeters=self.delimeters)
+ if namespace is None:
+ namespace = {}
+ self.namespace = namespace
+ self.get_template = get_template
+ if default_inherit is not None:
+ self.default_inherit = default_inherit
+
+ def from_filename(cls, filename, namespace=None, encoding=None,
+ default_inherit=None, get_template=get_file_template):
+ f = open(filename, 'rb')
+ c = f.read()
+ f.close()
+ if encoding:
+ c = c.decode(encoding)
+ return cls(content=c, name=filename, namespace=namespace,
+ default_inherit=default_inherit, get_template=get_template)
+
+ from_filename = classmethod(from_filename)
+
+ def __repr__(self):
+ return '<%s %s name=%r>' % (
+ self.__class__.__name__,
+ hex(id(self))[2:], self.name)
+
+ def substitute(self, *args, **kw):
+ if args:
+ if kw:
+ raise TypeError(
+ "You can only give positional *or* keyword arguments")
+ if len(args) > 1:
+ raise TypeError(
+ "You can only give one positional argument")
+ if not hasattr(args[0], 'items'):
+ raise TypeError(
+ "If you pass in a single argument, you must pass in a dictionary-like object (with a .items() method); you gave %r"
+ % (args[0],))
+ kw = args[0]
+ ns = kw
+ ns['__template_name__'] = self.name
+ if self.namespace:
+ ns.update(self.namespace)
+ result, defs, inherit = self._interpret(ns)
+ if not inherit:
+ inherit = self.default_inherit
+ if inherit:
+ result = self._interpret_inherit(result, defs, inherit, ns)
+ return result
+
+ def _interpret(self, ns):
+ __traceback_hide__ = True
+ parts = []
+ defs = {}
+ self._interpret_codes(self._parsed, ns, out=parts, defs=defs)
+ if '__inherit__' in defs:
+ inherit = defs.pop('__inherit__')
+ else:
+ inherit = None
+ return ''.join(parts), defs, inherit
+
+ def _interpret_inherit(self, body, defs, inherit_template, ns):
+ __traceback_hide__ = True
+ if not self.get_template:
+ raise TemplateError(
+ 'You cannot use inheritance without passing in get_template',
+ position=None, name=self.name)
+ templ = self.get_template(inherit_template, self)
+ self_ = TemplateObject(self.name)
+ for name, value in defs.items():
+ setattr(self_, name, value)
+ self_.body = body
+ ns = ns.copy()
+ ns['self'] = self_
+ return templ.substitute(ns)
+
+ def _interpret_codes(self, codes, ns, out, defs):
+ __traceback_hide__ = True
+ for item in codes:
+ if isinstance(item, basestring_):
+ out.append(item)
+ else:
+ self._interpret_code(item, ns, out, defs)
+
+ def _interpret_code(self, code, ns, out, defs):
+ __traceback_hide__ = True
+ name, pos = code[0], code[1]
+ if name == 'py':
+ self._exec(code[2], ns, pos)
+ elif name == 'continue':
+ raise _TemplateContinue()
+ elif name == 'break':
+ raise _TemplateBreak()
+ elif name == 'for':
+ vars, expr, content = code[2], code[3], code[4]
+ expr = self._eval(expr, ns, pos)
+ self._interpret_for(vars, expr, content, ns, out, defs)
+ elif name == 'cond':
+ parts = code[2:]
+ self._interpret_if(parts, ns, out, defs)
+ elif name == 'expr':
+ parts = code[2].split('|')
+ base = self._eval(parts[0], ns, pos)
+ for part in parts[1:]:
+ func = self._eval(part, ns, pos)
+ base = func(base)
+ out.append(self._repr(base, pos))
+ elif name == 'default':
+ var, expr = code[2], code[3]
+ if var not in ns:
+ result = self._eval(expr, ns, pos)
+ ns[var] = result
+ elif name == 'inherit':
+ expr = code[2]
+ value = self._eval(expr, ns, pos)
+ defs['__inherit__'] = value
+ elif name == 'def':
+ name = code[2]
+ signature = code[3]
+ parts = code[4]
+ ns[name] = defs[name] = TemplateDef(self, name, signature, body=parts, ns=ns,
+ pos=pos)
+ elif name == 'comment':
+ return
+ else:
+ assert 0, "Unknown code: %r" % name
+
+ def _interpret_for(self, vars, expr, content, ns, out, defs):
+ __traceback_hide__ = True
+ for item in expr:
+ if len(vars) == 1:
+ ns[vars[0]] = item
+ else:
+ if len(vars) != len(item):
+ raise ValueError(
+ 'Need %i items to unpack (got %i items)'
+ % (len(vars), len(item)))
+ for name, value in zip(vars, item):
+ ns[name] = value
+ try:
+ self._interpret_codes(content, ns, out, defs)
+ except _TemplateContinue:
+ continue
+ except _TemplateBreak:
+ break
+
+ def _interpret_if(self, parts, ns, out, defs):
+ __traceback_hide__ = True
+ # @@: if/else/else gets through
+ for part in parts:
+ assert not isinstance(part, basestring_)
+ name, pos = part[0], part[1]
+ if name == 'else':
+ result = True
+ else:
+ result = self._eval(part[2], ns, pos)
+ if result:
+ self._interpret_codes(part[3], ns, out, defs)
+ break
+
+ def _eval(self, code, ns, pos):
+ __traceback_hide__ = True
+ try:
+ try:
+ value = eval(code, self.default_namespace, ns)
+ except SyntaxError as e:
+ raise SyntaxError(
+ 'invalid syntax in expression: %s' % code)
+ return value
+ except Exception as e:
+ if getattr(e, 'args', None):
+ arg0 = e.args[0]
+ else:
+ arg0 = coerce_text(e)
+ e.args = (self._add_line_info(arg0, pos),)
+ raise
+
+ def _exec(self, code, ns, pos):
+ __traceback_hide__ = True
+ try:
+ exec(code, self.default_namespace, ns)
+ except Exception as e:
+ if e.args:
+ e.args = (self._add_line_info(e.args[0], pos),)
+ else:
+ e.args = (self._add_line_info(None, pos),)
+ raise
+
+ def _repr(self, value, pos):
+ __traceback_hide__ = True
+ try:
+ if value is None:
+ return ''
+ if self._unicode:
+ try:
+ value = unicode_(value)
+ except UnicodeDecodeError:
+ value = bytes(value)
+ else:
+ if not isinstance(value, basestring_):
+ value = coerce_text(value)
+ if (is_unicode(value)
+ and self.default_encoding):
+ value = value.encode(self.default_encoding)
+ except Exception as e:
+ e.args = (self._add_line_info(e.args[0], pos),)
+ raise
+ else:
+ if self._unicode and isinstance(value, bytes):
+ if not self.default_encoding:
+ raise UnicodeDecodeError(
+ 'Cannot decode bytes value %r into unicode '
+ '(no default_encoding provided)' % value)
+ try:
+ value = value.decode(self.default_encoding)
+ except UnicodeDecodeError as e:
+ raise UnicodeDecodeError(
+ e.encoding,
+ e.object,
+ e.start,
+ e.end,
+ e.reason + ' in string %r' % value)
+ elif not self._unicode and is_unicode(value):
+ if not self.default_encoding:
+ raise UnicodeEncodeError(
+ 'Cannot encode unicode value %r into bytes '
+ '(no default_encoding provided)' % value)
+ value = value.encode(self.default_encoding)
+ return value
+
+ def _add_line_info(self, msg, pos):
+ msg = "%s at line %s column %s" % (
+ msg, pos[0], pos[1])
+ if self.name:
+ msg += " in file %s" % self.name
+ return msg
+
+
+def sub(content, delimeters=None, **kw):
+ name = kw.get('__name')
+ tmpl = Template(content, name=name, delimeters=delimeters)
+ return tmpl.substitute(kw)
+
+
+def paste_script_template_renderer(content, vars, filename=None):
+ tmpl = Template(content, name=filename)
+ return tmpl.substitute(vars)
+
+
+class bunch(dict):
+
+ def __init__(self, **kw):
+ for name, value in kw.items():
+ setattr(self, name, value)
+
+ def __setattr__(self, name, value):
+ self[name] = value
+
+ def __getattr__(self, name):
+ try:
+ return self[name]
+ except KeyError:
+ raise AttributeError(name)
+
+ def __getitem__(self, key):
+ if 'default' in self:
+ try:
+ return dict.__getitem__(self, key)
+ except KeyError:
+ return dict.__getitem__(self, 'default')
+ else:
+ return dict.__getitem__(self, key)
+
+ def __repr__(self):
+ return '<%s %s>' % (
+ self.__class__.__name__,
+ ' '.join(['%s=%r' % (k, v) for k, v in sorted(self.items())]))
+
+############################################################
+## HTML Templating
+############################################################
+
+
+class html(object):
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return self.value
+
+ def __html__(self):
+ return self.value
+
+ def __repr__(self):
+ return '<%s %r>' % (
+ self.__class__.__name__, self.value)
+
+
+def html_quote(value, force=True):
+ if not force and hasattr(value, '__html__'):
+ return value.__html__()
+ if value is None:
+ return ''
+ if not isinstance(value, basestring_):
+ value = coerce_text(value)
+ if sys.version >= "3" and isinstance(value, bytes):
+ value = cgi.escape(value.decode('latin1'), 1)
+ value = value.encode('latin1')
+ else:
+ value = cgi.escape(value, 1)
+ if sys.version < "3":
+ if is_unicode(value):
+ value = value.encode('ascii', 'xmlcharrefreplace')
+ return value
+
+
+def url(v):
+ v = coerce_text(v)
+ if is_unicode(v):
+ v = v.encode('utf8')
+ return url_quote(v)
+
+
+def attr(**kw):
+ parts = []
+ for name, value in sorted(kw.items()):
+ if value is None:
+ continue
+ if name.endswith('_'):
+ name = name[:-1]
+ parts.append('%s="%s"' % (html_quote(name), html_quote(value)))
+ return html(' '.join(parts))
+
+
+class HTMLTemplate(Template):
+
+ default_namespace = Template.default_namespace.copy()
+ default_namespace.update(dict(
+ html=html,
+ attr=attr,
+ url=url,
+ html_quote=html_quote,
+ ))
+
+ def _repr(self, value, pos):
+ if hasattr(value, '__html__'):
+ value = value.__html__()
+ quote = False
+ else:
+ quote = True
+ plain = Template._repr(self, value, pos)
+ if quote:
+ return html_quote(plain)
+ else:
+ return plain
+
+
+def sub_html(content, **kw):
+ name = kw.get('__name')
+ tmpl = HTMLTemplate(content, name=name)
+ return tmpl.substitute(kw)
+
+
+class TemplateDef(object):
+ def __init__(self, template, func_name, func_signature,
+ body, ns, pos, bound_self=None):
+ self._template = template
+ self._func_name = func_name
+ self._func_signature = func_signature
+ self._body = body
+ self._ns = ns
+ self._pos = pos
+ self._bound_self = bound_self
+
+ def __repr__(self):
+ return '<tempita function %s(%s) at %s:%s>' % (
+ self._func_name, self._func_signature,
+ self._template.name, self._pos)
+
+ def __str__(self):
+ return self()
+
+ def __call__(self, *args, **kw):
+ values = self._parse_signature(args, kw)
+ ns = self._ns.copy()
+ ns.update(values)
+ if self._bound_self is not None:
+ ns['self'] = self._bound_self
+ out = []
+ subdefs = {}
+ self._template._interpret_codes(self._body, ns, out, subdefs)
+ return ''.join(out)
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ return self.__class__(
+ self._template, self._func_name, self._func_signature,
+ self._body, self._ns, self._pos, bound_self=obj)
+
+ def _parse_signature(self, args, kw):
+ values = {}
+ sig_args, var_args, var_kw, defaults = self._func_signature
+ extra_kw = {}
+ for name, value in kw.items():
+ if not var_kw and name not in sig_args:
+ raise TypeError(
+ 'Unexpected argument %s' % name)
+ if name in sig_args:
+ values[sig_args] = value
+ else:
+ extra_kw[name] = value
+ args = list(args)
+ sig_args = list(sig_args)
+ while args:
+ while sig_args and sig_args[0] in values:
+ sig_args.pop(0)
+ if sig_args:
+ name = sig_args.pop(0)
+ values[name] = args.pop(0)
+ elif var_args:
+ values[var_args] = tuple(args)
+ break
+ else:
+ raise TypeError(
+ 'Extra position arguments: %s'
+ % ', '.join([repr(v) for v in args]))
+ for name, value_expr in defaults.items():
+ if name not in values:
+ values[name] = self._template._eval(
+ value_expr, self._ns, self._pos)
+ for name in sig_args:
+ if name not in values:
+ raise TypeError(
+ 'Missing argument: %s' % name)
+ if var_kw:
+ values[var_kw] = extra_kw
+ return values
+
+
+class TemplateObject(object):
+
+ def __init__(self, name):
+ self.__name = name
+ self.get = TemplateObjectGetter(self)
+
+ def __repr__(self):
+ return '<%s %s>' % (self.__class__.__name__, self.__name)
+
+
+class TemplateObjectGetter(object):
+
+ def __init__(self, template_obj):
+ self.__template_obj = template_obj
+
+ def __getattr__(self, attr):
+ return getattr(self.__template_obj, attr, Empty)
+
+ def __repr__(self):
+ return '<%s around %r>' % (self.__class__.__name__, self.__template_obj)
+
+
+class _Empty(object):
+ def __call__(self, *args, **kw):
+ return self
+
+ def __str__(self):
+ return ''
+
+ def __repr__(self):
+ return 'Empty'
+
+ def __unicode__(self):
+ return u''
+
+ def __iter__(self):
+ return iter(())
+
+ def __bool__(self):
+ return False
+
+ if sys.version < "3":
+ __nonzero__ = __bool__
+
+Empty = _Empty()
+del _Empty
+
+############################################################
+## Lexing and Parsing
+############################################################
+
+
+def lex(s, name=None, trim_whitespace=True, line_offset=0, delimeters=None):
+ """
+ Lex a string into chunks:
+
+ >>> lex('hey')
+ ['hey']
+ >>> lex('hey {{you}}')
+ ['hey ', ('you', (1, 7))]
+ >>> lex('hey {{')
+ Traceback (most recent call last):
+ ...
+ TemplateError: No }} to finish last expression at line 1 column 7
+ >>> lex('hey }}')
+ Traceback (most recent call last):
+ ...
+ TemplateError: }} outside expression at line 1 column 7
+ >>> lex('hey {{ {{')
+ Traceback (most recent call last):
+ ...
+ TemplateError: {{ inside expression at line 1 column 10
+
+ """
+ if delimeters is None:
+ delimeters = ( Template.default_namespace['start_braces'],
+ Template.default_namespace['end_braces'] )
+ in_expr = False
+ chunks = []
+ last = 0
+ last_pos = (line_offset + 1, 1)
+
+ token_re = re.compile(r'%s|%s' % (re.escape(delimeters[0]),
+ re.escape(delimeters[1])))
+ for match in token_re.finditer(s):
+ expr = match.group(0)
+ pos = find_position(s, match.end(), last, last_pos)
+ if expr == delimeters[0] and in_expr:
+ raise TemplateError('%s inside expression' % delimeters[0],
+ position=pos,
+ name=name)
+ elif expr == delimeters[1] and not in_expr:
+ raise TemplateError('%s outside expression' % delimeters[1],
+ position=pos,
+ name=name)
+ if expr == delimeters[0]:
+ part = s[last:match.start()]
+ if part:
+ chunks.append(part)
+ in_expr = True
+ else:
+ chunks.append((s[last:match.start()], last_pos))
+ in_expr = False
+ last = match.end()
+ last_pos = pos
+ if in_expr:
+ raise TemplateError('No %s to finish last expression' % delimeters[1],
+ name=name, position=last_pos)
+ part = s[last:]
+ if part:
+ chunks.append(part)
+ if trim_whitespace:
+ chunks = trim_lex(chunks)
+ return chunks
+
+statement_re = re.compile(r'^(?:if |elif |for |def |inherit |default |py:)')
+single_statements = ['else', 'endif', 'endfor', 'enddef', 'continue', 'break']
+trail_whitespace_re = re.compile(r'\n\r?[\t ]*$')
+lead_whitespace_re = re.compile(r'^[\t ]*\n')
+
+
+def trim_lex(tokens):
+ r"""
+ Takes a lexed set of tokens, and removes whitespace when there is
+ a directive on a line by itself:
+
+ >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False)
+ >>> tokens
+ [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny']
+ >>> trim_lex(tokens)
+ [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y']
+ """
+ last_trim = None
+ for i, current in enumerate(tokens):
+ if isinstance(current, basestring_):
+ # we don't trim this
+ continue
+ item = current[0]
+ if not statement_re.search(item) and item not in single_statements:
+ continue
+ if not i:
+ prev = ''
+ else:
+ prev = tokens[i - 1]
+ if i + 1 >= len(tokens):
+ next_chunk = ''
+ else:
+ next_chunk = tokens[i + 1]
+ if (not isinstance(next_chunk, basestring_)
+ or not isinstance(prev, basestring_)):
+ continue
+ prev_ok = not prev or trail_whitespace_re.search(prev)
+ if i == 1 and not prev.strip():
+ prev_ok = True
+ if last_trim is not None and last_trim + 2 == i and not prev.strip():
+ prev_ok = 'last'
+ if (prev_ok
+ and (not next_chunk or lead_whitespace_re.search(next_chunk)
+ or (i == len(tokens) - 2 and not next_chunk.strip()))):
+ if prev:
+ if ((i == 1 and not prev.strip())
+ or prev_ok == 'last'):
+ tokens[i - 1] = ''
+ else:
+ m = trail_whitespace_re.search(prev)
+ # +1 to leave the leading \n on:
+ prev = prev[:m.start() + 1]
+ tokens[i - 1] = prev
+ if next_chunk:
+ last_trim = i
+ if i == len(tokens) - 2 and not next_chunk.strip():
+ tokens[i + 1] = ''
+ else:
+ m = lead_whitespace_re.search(next_chunk)
+ next_chunk = next_chunk[m.end():]
+ tokens[i + 1] = next_chunk
+ return tokens
+
+
+def find_position(string, index, last_index, last_pos):
+ """Given a string and index, return (line, column)"""
+ lines = string.count('\n', last_index, index)
+ if lines > 0:
+ column = index - string.rfind('\n', last_index, index)
+ else:
+ column = last_pos[1] + (index - last_index)
+ return (last_pos[0] + lines, column)
+
+
+def parse(s, name=None, line_offset=0, delimeters=None):
+ r"""
+ Parses a string into a kind of AST
+
+ >>> parse('{{x}}')
+ [('expr', (1, 3), 'x')]
+ >>> parse('foo')
+ ['foo']
+ >>> parse('{{if x}}test{{endif}}')
+ [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))]
+ >>> parse('series->{{for x in y}}x={{x}}{{endfor}}')
+ ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])]
+ >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}')
+ [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])]
+ >>> parse('{{py:x=1}}')
+ [('py', (1, 3), 'x=1')]
+ >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}')
+ [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))]
+
+ Some exceptions::
+
+ >>> parse('{{continue}}')
+ Traceback (most recent call last):
+ ...
+ TemplateError: continue outside of for loop at line 1 column 3
+ >>> parse('{{if x}}foo')
+ Traceback (most recent call last):
+ ...
+ TemplateError: No {{endif}} at line 1 column 3
+ >>> parse('{{else}}')
+ Traceback (most recent call last):
+ ...
+ TemplateError: else outside of an if block at line 1 column 3
+ >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}')
+ Traceback (most recent call last):
+ ...
+ TemplateError: Unexpected endif at line 1 column 25
+ >>> parse('{{if}}{{endif}}')
+ Traceback (most recent call last):
+ ...
+ TemplateError: if with no expression at line 1 column 3
+ >>> parse('{{for x y}}{{endfor}}')
+ Traceback (most recent call last):
+ ...
+ TemplateError: Bad for (no "in") in 'x y' at line 1 column 3
+ >>> parse('{{py:x=1\ny=2}}')
+ Traceback (most recent call last):
+ ...
+ TemplateError: Multi-line py blocks must start with a newline at line 1 column 3
+ """
+ if delimeters is None:
+ delimeters = ( Template.default_namespace['start_braces'],
+ Template.default_namespace['end_braces'] )
+ tokens = lex(s, name=name, line_offset=line_offset, delimeters=delimeters)
+ result = []
+ while tokens:
+ next_chunk, tokens = parse_expr(tokens, name)
+ result.append(next_chunk)
+ return result
+
+
+def parse_expr(tokens, name, context=()):
+ if isinstance(tokens[0], basestring_):
+ return tokens[0], tokens[1:]
+ expr, pos = tokens[0]
+ expr = expr.strip()
+ if expr.startswith('py:'):
+ expr = expr[3:].lstrip(' \t')
+ if expr.startswith('\n') or expr.startswith('\r'):
+ expr = expr.lstrip('\r\n')
+ if '\r' in expr:
+ expr = expr.replace('\r\n', '\n')
+ expr = expr.replace('\r', '')
+ expr += '\n'
+ else:
+ if '\n' in expr:
+ raise TemplateError(
+ 'Multi-line py blocks must start with a newline',
+ position=pos, name=name)
+ return ('py', pos, expr), tokens[1:]
+ elif expr in ('continue', 'break'):
+ if 'for' not in context:
+ raise TemplateError(
+ 'continue outside of for loop',
+ position=pos, name=name)
+ return (expr, pos), tokens[1:]
+ elif expr.startswith('if '):
+ return parse_cond(tokens, name, context)
+ elif (expr.startswith('elif ')
+ or expr == 'else'):
+ raise TemplateError(
+ '%s outside of an if block' % expr.split()[0],
+ position=pos, name=name)
+ elif expr in ('if', 'elif', 'for'):
+ raise TemplateError(
+ '%s with no expression' % expr,
+ position=pos, name=name)
+ elif expr in ('endif', 'endfor', 'enddef'):
+ raise TemplateError(
+ 'Unexpected %s' % expr,
+ position=pos, name=name)
+ elif expr.startswith('for '):
+ return parse_for(tokens, name, context)
+ elif expr.startswith('default '):
+ return parse_default(tokens, name, context)
+ elif expr.startswith('inherit '):
+ return parse_inherit(tokens, name, context)
+ elif expr.startswith('def '):
+ return parse_def(tokens, name, context)
+ elif expr.startswith('#'):
+ return ('comment', pos, tokens[0][0]), tokens[1:]
+ return ('expr', pos, tokens[0][0]), tokens[1:]
+
+
+def parse_cond(tokens, name, context):
+ start = tokens[0][1]
+ pieces = []
+ context = context + ('if',)
+ while 1:
+ if not tokens:
+ raise TemplateError(
+ 'Missing {{endif}}',
+ position=start, name=name)
+ if (isinstance(tokens[0], tuple)
+ and tokens[0][0] == 'endif'):
+ return ('cond', start) + tuple(pieces), tokens[1:]
+ next_chunk, tokens = parse_one_cond(tokens, name, context)
+ pieces.append(next_chunk)
+
+
+def parse_one_cond(tokens, name, context):
+ (first, pos), tokens = tokens[0], tokens[1:]
+ content = []
+ if first.endswith(':'):
+ first = first[:-1]
+ if first.startswith('if '):
+ part = ('if', pos, first[3:].lstrip(), content)
+ elif first.startswith('elif '):
+ part = ('elif', pos, first[5:].lstrip(), content)
+ elif first == 'else':
+ part = ('else', pos, None, content)
+ else:
+ assert 0, "Unexpected token %r at %s" % (first, pos)
+ while 1:
+ if not tokens:
+ raise TemplateError(
+ 'No {{endif}}',
+ position=pos, name=name)
+ if (isinstance(tokens[0], tuple)
+ and (tokens[0][0] == 'endif'
+ or tokens[0][0].startswith('elif ')
+ or tokens[0][0] == 'else')):
+ return part, tokens
+ next_chunk, tokens = parse_expr(tokens, name, context)
+ content.append(next_chunk)
+
+
+def parse_for(tokens, name, context):
+ first, pos = tokens[0]
+ tokens = tokens[1:]
+ context = ('for',) + context
+ content = []
+ assert first.startswith('for ')
+ if first.endswith(':'):
+ first = first[:-1]
+ first = first[3:].strip()
+ match = in_re.search(first)
+ if not match:
+ raise TemplateError(
+ 'Bad for (no "in") in %r' % first,
+ position=pos, name=name)
+ vars = first[:match.start()]
+ if '(' in vars:
+ raise TemplateError(
+ 'You cannot have () in the variable section of a for loop (%r)'
+ % vars, position=pos, name=name)
+ vars = tuple([
+ v.strip() for v in first[:match.start()].split(',')
+ if v.strip()])
+ expr = first[match.end():]
+ while 1:
+ if not tokens:
+ raise TemplateError(
+ 'No {{endfor}}',
+ position=pos, name=name)
+ if (isinstance(tokens[0], tuple)
+ and tokens[0][0] == 'endfor'):
+ return ('for', pos, vars, expr, content), tokens[1:]
+ next_chunk, tokens = parse_expr(tokens, name, context)
+ content.append(next_chunk)
+
+
+def parse_default(tokens, name, context):
+ first, pos = tokens[0]
+ assert first.startswith('default ')
+ first = first.split(None, 1)[1]
+ parts = first.split('=', 1)
+ if len(parts) == 1:
+ raise TemplateError(
+ "Expression must be {{default var=value}}; no = found in %r" % first,
+ position=pos, name=name)
+ var = parts[0].strip()
+ if ',' in var:
+ raise TemplateError(
+ "{{default x, y = ...}} is not supported",
+ position=pos, name=name)
+ if not var_re.search(var):
+ raise TemplateError(
+ "Not a valid variable name for {{default}}: %r"
+ % var, position=pos, name=name)
+ expr = parts[1].strip()
+ return ('default', pos, var, expr), tokens[1:]
+
+
+def parse_inherit(tokens, name, context):
+ first, pos = tokens[0]
+ assert first.startswith('inherit ')
+ expr = first.split(None, 1)[1]
+ return ('inherit', pos, expr), tokens[1:]
+
+
+def parse_def(tokens, name, context):
+ first, start = tokens[0]
+ tokens = tokens[1:]
+ assert first.startswith('def ')
+ first = first.split(None, 1)[1]
+ if first.endswith(':'):
+ first = first[:-1]
+ if '(' not in first:
+ func_name = first
+ sig = ((), None, None, {})
+ elif not first.endswith(')'):
+ raise TemplateError("Function definition doesn't end with ): %s" % first,
+ position=start, name=name)
+ else:
+ first = first[:-1]
+ func_name, sig_text = first.split('(', 1)
+ sig = parse_signature(sig_text, name, start)
+ context = context + ('def',)
+ content = []
+ while 1:
+ if not tokens:
+ raise TemplateError(
+ 'Missing {{enddef}}',
+ position=start, name=name)
+ if (isinstance(tokens[0], tuple)
+ and tokens[0][0] == 'enddef'):
+ return ('def', start, func_name, sig, content), tokens[1:]
+ next_chunk, tokens = parse_expr(tokens, name, context)
+ content.append(next_chunk)
+
+
+def parse_signature(sig_text, name, pos):
+ tokens = tokenize.generate_tokens(StringIO(sig_text).readline)
+ sig_args = []
+ var_arg = None
+ var_kw = None
+ defaults = {}
+
+ def get_token(pos=False):
+ try:
+ tok_type, tok_string, (srow, scol), (erow, ecol), line = next(tokens)
+ except StopIteration:
+ return tokenize.ENDMARKER, ''
+ if pos:
+ return tok_type, tok_string, (srow, scol), (erow, ecol)
+ else:
+ return tok_type, tok_string
+ while 1:
+ var_arg_type = None
+ tok_type, tok_string = get_token()
+ if tok_type == tokenize.ENDMARKER:
+ break
+ if tok_type == tokenize.OP and (tok_string == '*' or tok_string == '**'):
+ var_arg_type = tok_string
+ tok_type, tok_string = get_token()
+ if tok_type != tokenize.NAME:
+ raise TemplateError('Invalid signature: (%s)' % sig_text,
+ position=pos, name=name)
+ var_name = tok_string
+ tok_type, tok_string = get_token()
+ if tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','):
+ if var_arg_type == '*':
+ var_arg = var_name
+ elif var_arg_type == '**':
+ var_kw = var_name
+ else:
+ sig_args.append(var_name)
+ if tok_type == tokenize.ENDMARKER:
+ break
+ continue
+ if var_arg_type is not None:
+ raise TemplateError('Invalid signature: (%s)' % sig_text,
+ position=pos, name=name)
+ if tok_type == tokenize.OP and tok_string == '=':
+ nest_type = None
+ unnest_type = None
+ nest_count = 0
+ start_pos = end_pos = None
+ parts = []
+ while 1:
+ tok_type, tok_string, s, e = get_token(True)
+ if start_pos is None:
+ start_pos = s
+ end_pos = e
+ if tok_type == tokenize.ENDMARKER and nest_count:
+ raise TemplateError('Invalid signature: (%s)' % sig_text,
+ position=pos, name=name)
+ if (not nest_count and
+ (tok_type == tokenize.ENDMARKER or (tok_type == tokenize.OP and tok_string == ','))):
+ default_expr = isolate_expression(sig_text, start_pos, end_pos)
+ defaults[var_name] = default_expr
+ sig_args.append(var_name)
+ break
+ parts.append((tok_type, tok_string))
+ if nest_count and tok_type == tokenize.OP and tok_string == nest_type:
+ nest_count += 1
+ elif nest_count and tok_type == tokenize.OP and tok_string == unnest_type:
+ nest_count -= 1
+ if not nest_count:
+ nest_type = unnest_type = None
+ elif not nest_count and tok_type == tokenize.OP and tok_string in ('(', '[', '{'):
+ nest_type = tok_string
+ nest_count = 1
+ unnest_type = {'(': ')', '[': ']', '{': '}'}[nest_type]
+ return sig_args, var_arg, var_kw, defaults
+
+
+def isolate_expression(string, start_pos, end_pos):
+ srow, scol = start_pos
+ srow -= 1
+ erow, ecol = end_pos
+ erow -= 1
+ lines = string.splitlines(True)
+ if srow == erow:
+ return lines[srow][scol:ecol]
+ parts = [lines[srow][scol:]]
+ parts.extend(lines[srow+1:erow])
+ if erow < len(lines):
+ # It'll sometimes give (end_row_past_finish, 0)
+ parts.append(lines[erow][:ecol])
+ return ''.join(parts)
+
+_fill_command_usage = """\
+%prog [OPTIONS] TEMPLATE arg=value
+
+Use py:arg=value to set a Python value; otherwise all values are
+strings.
+"""
+
+
+def fill_command(args=None):
+ import sys
+ import optparse
+ import pkg_resources
+ import os
+ if args is None:
+ args = sys.argv[1:]
+ dist = pkg_resources.get_distribution('Paste')
+ parser = optparse.OptionParser(
+ version=coerce_text(dist),
+ usage=_fill_command_usage)
+ parser.add_option(
+ '-o', '--output',
+ dest='output',
+ metavar="FILENAME",
+ help="File to write output to (default stdout)")
+ parser.add_option(
+ '--html',
+ dest='use_html',
+ action='store_true',
+ help="Use HTML style filling (including automatic HTML quoting)")
+ parser.add_option(
+ '--env',
+ dest='use_env',
+ action='store_true',
+ help="Put the environment in as top-level variables")
+ options, args = parser.parse_args(args)
+ if len(args) < 1:
+ print('You must give a template filename')
+ sys.exit(2)
+ template_name = args[0]
+ args = args[1:]
+ vars = {}
+ if options.use_env:
+ vars.update(os.environ)
+ for value in args:
+ if '=' not in value:
+ print('Bad argument: %r' % value)
+ sys.exit(2)
+ name, value = value.split('=', 1)
+ if name.startswith('py:'):
+ name = name[:3]
+ value = eval(value)
+ vars[name] = value
+ if template_name == '-':
+ template_content = sys.stdin.read()
+ template_name = '<stdin>'
+ else:
+ f = open(template_name, 'rb')
+ template_content = f.read()
+ f.close()
+ if options.use_html:
+ TemplateClass = HTMLTemplate
+ else:
+ TemplateClass = Template
+ template = TemplateClass(template_content, name=template_name)
+ result = template.substitute(vars)
+ if options.output:
+ f = open(options.output, 'wb')
+ f.write(result)
+ f.close()
+ else:
+ sys.stdout.write(result)
+
+if __name__ == '__main__':
+ fill_command()