mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 12:21:36 +00:00
55f81f2210
- Add `.style.yapf` with configuration to use Google style. - Run `yapf` on all `.py` files in this repo. - Manually fix one trailing space in a doc string. - Run `git cl format runtime` to satisfy presubmit. Change-Id: I7e6bd11e91f07926b9188362599af398551eed79 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/111600 Commit-Queue: Nate Bosch <nbosch@google.com> Reviewed-by: Alexander Thomas <athom@google.com>
221 lines
8 KiB
Python
Executable file
221 lines
8 KiB
Python
Executable file
#!/usr/bin/python
|
|
# Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
|
|
# for details. All rights reserved. Use of this source code is governed by a
|
|
# BSD-style license that can be found in the LICENSE file.
|
|
"""Templating to help generate structured text."""
|
|
|
|
import logging
|
|
import re
|
|
|
|
_logger = logging.getLogger('emitter')
|
|
|
|
|
|
def Format(template, **parameters):
|
|
"""Create a string using the same template syntax as Emitter.Emit."""
|
|
e = Emitter()
|
|
e._Emit(template, parameters)
|
|
return ''.join(e.Fragments())
|
|
|
|
|
|
class Emitter(object):
|
|
"""An Emitter collects string fragments to be assembled into a single string.
|
|
"""
|
|
|
|
def __init__(self, bindings=None):
|
|
self._items = [] # A new list
|
|
self._bindings = bindings or Emitter.Frame({}, None)
|
|
|
|
def EmitRaw(self, item):
|
|
"""Emits literal string with no substitition."""
|
|
self._items.append(item)
|
|
|
|
def Emit(self, template_source, **parameters):
|
|
"""Emits a template, substituting named parameters and returning emitters to
|
|
fill the named holes.
|
|
|
|
Ordinary substitution occurs at $NAME or $(NAME). If there is no parameter
|
|
called NAME, the text is left as-is. So long as you don't bind FOO as a
|
|
parameter, $FOO in the template will pass through to the generated text.
|
|
|
|
Substitution of $?NAME and $(?NAME) yields an empty string if NAME is not a
|
|
parameter.
|
|
|
|
Values passed as named parameters should be strings or simple integral
|
|
values (int or long).
|
|
|
|
Named holes are created at $!NAME or $(!NAME). A hole marks a position in
|
|
the template that may be filled in later. An Emitter is returned for each
|
|
named hole in the template. The holes are filled by emitting to the
|
|
corresponding emitter.
|
|
|
|
Emit returns either a single Emitter if the template contains one hole or a
|
|
tuple of emitters for several holes, in the order that the holes occur in
|
|
the template.
|
|
|
|
The emitters for the holes remember the parameters passed to the initial
|
|
call to Emit. Holes can be used to provide a binding context.
|
|
"""
|
|
return self._Emit(template_source, parameters)
|
|
|
|
def _Emit(self, template_source, parameters):
|
|
"""Implementation of Emit, with map in place of named parameters."""
|
|
template = self._ParseTemplate(template_source)
|
|
|
|
parameter_bindings = self._bindings.Extend(parameters)
|
|
|
|
hole_names = template._holes
|
|
|
|
if hole_names:
|
|
hole_map = {}
|
|
replacements = {}
|
|
for name in hole_names:
|
|
emitter = Emitter(parameter_bindings)
|
|
replacements[name] = emitter._items
|
|
hole_map[name] = emitter
|
|
full_bindings = parameter_bindings.Extend(replacements)
|
|
else:
|
|
full_bindings = parameter_bindings
|
|
|
|
self._ApplyTemplate(template, full_bindings)
|
|
|
|
# Return None, a singleton or tuple of the hole names.
|
|
if not hole_names:
|
|
return None
|
|
if len(hole_names) == 1:
|
|
return hole_map[hole_names[0]]
|
|
else:
|
|
return tuple(hole_map[name] for name in hole_names)
|
|
|
|
def Fragments(self):
|
|
"""Returns a list of all the string fragments emitted."""
|
|
|
|
def _FlattenTo(item, output):
|
|
if isinstance(item, list):
|
|
for subitem in item:
|
|
_FlattenTo(subitem, output)
|
|
elif isinstance(item, Emitter.DeferredLookup):
|
|
value = item._environment.Lookup(item._lookup._name,
|
|
item._lookup._value_if_missing)
|
|
_FlattenTo(value, output)
|
|
else:
|
|
output.append(str(item))
|
|
|
|
output = []
|
|
_FlattenTo(self._items, output)
|
|
return output
|
|
|
|
def Bind(self, var, template_source, **parameters):
|
|
"""Adds a binding for var to this emitter."""
|
|
template = self._ParseTemplate(template_source)
|
|
if template._holes:
|
|
raise RuntimeError('Cannot have holes in Emitter.Bind')
|
|
bindings = self._bindings.Extend(parameters)
|
|
value = Emitter(bindings)
|
|
value._ApplyTemplate(template, bindings)
|
|
self._bindings = self._bindings.Extend({var: value._items})
|
|
return value
|
|
|
|
def _ParseTemplate(self, source):
|
|
"""Converts the template string into a Template object."""
|
|
# TODO(sra): Cache the parsing.
|
|
items = []
|
|
holes = []
|
|
|
|
# Break source into a sequence of text fragments and substitution lookups.
|
|
pos = 0
|
|
while True:
|
|
match = Emitter._SUBST_RE.search(source, pos)
|
|
if not match:
|
|
items.append(source[pos:])
|
|
break
|
|
text_fragment = source[pos:match.start()]
|
|
if text_fragment:
|
|
items.append(text_fragment)
|
|
pos = match.end()
|
|
term = match.group()
|
|
name = match.group(1) or match.group(2) # $NAME and $(NAME)
|
|
if name:
|
|
item = Emitter.Lookup(name, term, term)
|
|
items.append(item)
|
|
continue
|
|
name = match.group(3) or match.group(4) # $!NAME and $(!NAME)
|
|
if name:
|
|
item = Emitter.Lookup(name, term, term)
|
|
items.append(item)
|
|
holes.append(name)
|
|
continue
|
|
name = match.group(5) or match.group(6) # $?NAME and $(?NAME)
|
|
if name:
|
|
item = Emitter.Lookup(name, term, '')
|
|
items.append(item)
|
|
holes.append(name)
|
|
continue
|
|
raise RuntimeError('Unexpected group')
|
|
|
|
if len(holes) != len(set(holes)):
|
|
raise RuntimeError('Cannot have repeated holes %s' % holes)
|
|
return Emitter.Template(items, holes)
|
|
|
|
_SUBST_RE = re.compile(
|
|
# $FOO $(FOO) $!FOO $(!FOO) $?FOO $(?FOO)
|
|
r'\$(\w+)|\$\((\w+)\)|\$!(\w+)|\$\(!(\w+)\)|\$\?(\w+)|\$\(\?(\w+)\)')
|
|
|
|
def _ApplyTemplate(self, template, bindings):
|
|
"""Emits the items from the parsed template."""
|
|
result = []
|
|
for item in template._items:
|
|
if isinstance(item, str):
|
|
if item:
|
|
result.append(item)
|
|
elif isinstance(item, Emitter.Lookup):
|
|
# Bind lookup to the current environment (bindings)
|
|
# TODO(sra): More space efficient to do direct lookup.
|
|
result.append(Emitter.DeferredLookup(item, bindings))
|
|
else:
|
|
raise RuntimeError('Unexpected template element')
|
|
# Collected fragments are in a sublist, so self._items contains one element
|
|
# (sublist) per template application.
|
|
self._items.append(result)
|
|
|
|
class Lookup(object):
|
|
"""An element of a parsed template."""
|
|
|
|
def __init__(self, name, original, default):
|
|
self._name = name
|
|
self._original = original
|
|
self._value_if_missing = default
|
|
|
|
class DeferredLookup(object):
|
|
"""A lookup operation that is deferred until final string generation."""
|
|
|
|
# TODO(sra): A deferred lookup will be useful when we add expansions that
|
|
# have behaviour condtional on the contents, e.g. adding separators between
|
|
# a list of items.
|
|
def __init__(self, lookup, environment):
|
|
self._lookup = lookup
|
|
self._environment = environment
|
|
|
|
class Template(object):
|
|
"""A parsed template."""
|
|
|
|
def __init__(self, items, holes):
|
|
self._items = items # strings and lookups
|
|
self._holes = holes
|
|
|
|
class Frame(object):
|
|
"""A Frame is a set of bindings derived from a parent."""
|
|
|
|
def __init__(self, map, parent):
|
|
self._map = map
|
|
self._parent = parent
|
|
|
|
def Lookup(self, name, default):
|
|
if name in self._map:
|
|
return self._map[name]
|
|
if self._parent:
|
|
return self._parent.Lookup(name, default)
|
|
return default
|
|
|
|
def Extend(self, map):
|
|
return Emitter.Frame(map, self)
|