| Index: tools/telemetry/third_party/coverage/coverage/templite.py
|
| diff --git a/tools/telemetry/third_party/coverage/coverage/templite.py b/tools/telemetry/third_party/coverage/coverage/templite.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f131f748baefdbfc3375bcade3dd2ad374cb1f77
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/coverage/coverage/templite.py
|
| @@ -0,0 +1,276 @@
|
| +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
| +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
|
| +
|
| +"""A simple Python template renderer, for a nano-subset of Django syntax.
|
| +
|
| +For a detailed discussion of this code, see this chapter from 500 Lines:
|
| +http://aosabook.org/en/500L/a-template-engine.html
|
| +
|
| +"""
|
| +
|
| +# Coincidentally named the same as http://code.activestate.com/recipes/496702/
|
| +
|
| +import re
|
| +
|
| +from coverage import env
|
| +
|
| +
|
| +class TempliteSyntaxError(ValueError):
|
| + """Raised when a template has a syntax error."""
|
| + pass
|
| +
|
| +
|
| +class TempliteValueError(ValueError):
|
| + """Raised when an expression won't evaluate in a template."""
|
| + pass
|
| +
|
| +
|
| +class CodeBuilder(object):
|
| + """Build source code conveniently."""
|
| +
|
| + def __init__(self, indent=0):
|
| + self.code = []
|
| + self.indent_level = indent
|
| +
|
| + def __str__(self):
|
| + return "".join(str(c) for c in self.code)
|
| +
|
| + def add_line(self, line):
|
| + """Add a line of source to the code.
|
| +
|
| + Indentation and newline will be added for you, don't provide them.
|
| +
|
| + """
|
| + self.code.extend([" " * self.indent_level, line, "\n"])
|
| +
|
| + def add_section(self):
|
| + """Add a section, a sub-CodeBuilder."""
|
| + section = CodeBuilder(self.indent_level)
|
| + self.code.append(section)
|
| + return section
|
| +
|
| + INDENT_STEP = 4 # PEP8 says so!
|
| +
|
| + def indent(self):
|
| + """Increase the current indent for following lines."""
|
| + self.indent_level += self.INDENT_STEP
|
| +
|
| + def dedent(self):
|
| + """Decrease the current indent for following lines."""
|
| + self.indent_level -= self.INDENT_STEP
|
| +
|
| + def get_globals(self):
|
| + """Execute the code, and return a dict of globals it defines."""
|
| + # A check that the caller really finished all the blocks they started.
|
| + assert self.indent_level == 0
|
| + # Get the Python source as a single string.
|
| + python_source = str(self)
|
| + # Execute the source, defining globals, and return them.
|
| + global_namespace = {}
|
| + exec(python_source, global_namespace)
|
| + return global_namespace
|
| +
|
| +
|
| +class Templite(object):
|
| + """A simple template renderer, for a nano-subset of Django syntax.
|
| +
|
| + Supported constructs are extended variable access::
|
| +
|
| + {{var.modifier.modifier|filter|filter}}
|
| +
|
| + loops::
|
| +
|
| + {% for var in list %}...{% endfor %}
|
| +
|
| + and ifs::
|
| +
|
| + {% if var %}...{% endif %}
|
| +
|
| + Comments are within curly-hash markers::
|
| +
|
| + {# This will be ignored #}
|
| +
|
| + Construct a Templite with the template text, then use `render` against a
|
| + dictionary context to create a finished string::
|
| +
|
| + templite = Templite('''
|
| + <h1>Hello {{name|upper}}!</h1>
|
| + {% for topic in topics %}
|
| + <p>You are interested in {{topic}}.</p>
|
| + {% endif %}
|
| + ''',
|
| + {'upper': str.upper},
|
| + )
|
| + text = templite.render({
|
| + 'name': "Ned",
|
| + 'topics': ['Python', 'Geometry', 'Juggling'],
|
| + })
|
| +
|
| + """
|
| + def __init__(self, text, *contexts):
|
| + """Construct a Templite with the given `text`.
|
| +
|
| + `contexts` are dictionaries of values to use for future renderings.
|
| + These are good for filters and global values.
|
| +
|
| + """
|
| + self.context = {}
|
| + for context in contexts:
|
| + self.context.update(context)
|
| +
|
| + self.all_vars = set()
|
| + self.loop_vars = set()
|
| +
|
| + # We construct a function in source form, then compile it and hold onto
|
| + # it, and execute it to render the template.
|
| + code = CodeBuilder()
|
| +
|
| + code.add_line("def render_function(context, do_dots):")
|
| + code.indent()
|
| + vars_code = code.add_section()
|
| + code.add_line("result = []")
|
| + code.add_line("append_result = result.append")
|
| + code.add_line("extend_result = result.extend")
|
| + if env.PY2:
|
| + code.add_line("to_str = unicode")
|
| + else:
|
| + code.add_line("to_str = str")
|
| +
|
| + buffered = []
|
| +
|
| + def flush_output():
|
| + """Force `buffered` to the code builder."""
|
| + if len(buffered) == 1:
|
| + code.add_line("append_result(%s)" % buffered[0])
|
| + elif len(buffered) > 1:
|
| + code.add_line("extend_result([%s])" % ", ".join(buffered))
|
| + del buffered[:]
|
| +
|
| + ops_stack = []
|
| +
|
| + # Split the text to form a list of tokens.
|
| + tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)
|
| +
|
| + for token in tokens:
|
| + if token.startswith('{#'):
|
| + # Comment: ignore it and move on.
|
| + continue
|
| + elif token.startswith('{{'):
|
| + # An expression to evaluate.
|
| + expr = self._expr_code(token[2:-2].strip())
|
| + buffered.append("to_str(%s)" % expr)
|
| + elif token.startswith('{%'):
|
| + # Action tag: split into words and parse further.
|
| + flush_output()
|
| + words = token[2:-2].strip().split()
|
| + if words[0] == 'if':
|
| + # An if statement: evaluate the expression to determine if.
|
| + if len(words) != 2:
|
| + self._syntax_error("Don't understand if", token)
|
| + ops_stack.append('if')
|
| + code.add_line("if %s:" % self._expr_code(words[1]))
|
| + code.indent()
|
| + elif words[0] == 'for':
|
| + # A loop: iterate over expression result.
|
| + if len(words) != 4 or words[2] != 'in':
|
| + self._syntax_error("Don't understand for", token)
|
| + ops_stack.append('for')
|
| + self._variable(words[1], self.loop_vars)
|
| + code.add_line(
|
| + "for c_%s in %s:" % (
|
| + words[1],
|
| + self._expr_code(words[3])
|
| + )
|
| + )
|
| + code.indent()
|
| + elif words[0].startswith('end'):
|
| + # Endsomething. Pop the ops stack.
|
| + if len(words) != 1:
|
| + self._syntax_error("Don't understand end", token)
|
| + end_what = words[0][3:]
|
| + if not ops_stack:
|
| + self._syntax_error("Too many ends", token)
|
| + start_what = ops_stack.pop()
|
| + if start_what != end_what:
|
| + self._syntax_error("Mismatched end tag", end_what)
|
| + code.dedent()
|
| + else:
|
| + self._syntax_error("Don't understand tag", words[0])
|
| + else:
|
| + # Literal content. If it isn't empty, output it.
|
| + if token:
|
| + buffered.append(repr(token))
|
| +
|
| + if ops_stack:
|
| + self._syntax_error("Unmatched action tag", ops_stack[-1])
|
| +
|
| + flush_output()
|
| +
|
| + for var_name in self.all_vars - self.loop_vars:
|
| + vars_code.add_line("c_%s = context[%r]" % (var_name, var_name))
|
| +
|
| + code.add_line('return "".join(result)')
|
| + code.dedent()
|
| + self._render_function = code.get_globals()['render_function']
|
| +
|
| + def _expr_code(self, expr):
|
| + """Generate a Python expression for `expr`."""
|
| + if "|" in expr:
|
| + pipes = expr.split("|")
|
| + code = self._expr_code(pipes[0])
|
| + for func in pipes[1:]:
|
| + self._variable(func, self.all_vars)
|
| + code = "c_%s(%s)" % (func, code)
|
| + elif "." in expr:
|
| + dots = expr.split(".")
|
| + code = self._expr_code(dots[0])
|
| + args = ", ".join(repr(d) for d in dots[1:])
|
| + code = "do_dots(%s, %s)" % (code, args)
|
| + else:
|
| + self._variable(expr, self.all_vars)
|
| + code = "c_%s" % expr
|
| + return code
|
| +
|
| + def _syntax_error(self, msg, thing):
|
| + """Raise a syntax error using `msg`, and showing `thing`."""
|
| + raise TempliteSyntaxError("%s: %r" % (msg, thing))
|
| +
|
| + def _variable(self, name, vars_set):
|
| + """Track that `name` is used as a variable.
|
| +
|
| + Adds the name to `vars_set`, a set of variable names.
|
| +
|
| + Raises an syntax error if `name` is not a valid name.
|
| +
|
| + """
|
| + if not re.match(r"[_a-zA-Z][_a-zA-Z0-9]*$", name):
|
| + self._syntax_error("Not a valid name", name)
|
| + vars_set.add(name)
|
| +
|
| + def render(self, context=None):
|
| + """Render this template by applying it to `context`.
|
| +
|
| + `context` is a dictionary of values to use in this rendering.
|
| +
|
| + """
|
| + # Make the complete context we'll use.
|
| + render_context = dict(self.context)
|
| + if context:
|
| + render_context.update(context)
|
| + return self._render_function(render_context, self._do_dots)
|
| +
|
| + def _do_dots(self, value, *dots):
|
| + """Evaluate dotted expressions at run-time."""
|
| + for dot in dots:
|
| + try:
|
| + value = getattr(value, dot)
|
| + except AttributeError:
|
| + try:
|
| + value = value[dot]
|
| + except (TypeError, KeyError):
|
| + raise TempliteValueError(
|
| + "Couldn't evaluate %r.%s" % (value, dot)
|
| + )
|
| + if callable(value):
|
| + value = value()
|
| + return value
|
|
|