diff -r d28b92dabc2b -r bc64243b7672 DebugClients/Python/coverage/templite.py --- a/DebugClients/Python/coverage/templite.py Fri Sep 02 19:08:02 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,293 +0,0 @@ -# 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 #} - - Any of these constructs can have a hypen at the end (`-}}`, `-%}`, `-#}`), - which will collapse the whitespace following the tag. - - 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) - - squash = False - - for token in tokens: - if token.startswith('{'): - start, end = 2, -2 - squash = (token[-3] == '-') - if squash: - end = -3 - - if token.startswith('{#'): - # Comment: ignore it and move on. - continue - elif token.startswith('{{'): - # An expression to evaluate. - expr = self._expr_code(token[start:end].strip()) - buffered.append("to_str(%s)" % expr) - elif token.startswith('{%'): - # Action tag: split into words and parse further. - flush_output() - - words = token[start:end].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 squash: - token = token.lstrip() - 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 - -# -# eflag: FileType = Python2