Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Unified Diff: tools/telemetry/third_party/rope/rope/contrib/codeassist.py

Issue 1132103009: Example of refactoring using rope library. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/third_party/rope/rope/contrib/codeassist.py
diff --git a/tools/telemetry/third_party/rope/rope/contrib/codeassist.py b/tools/telemetry/third_party/rope/rope/contrib/codeassist.py
new file mode 100644
index 0000000000000000000000000000000000000000..92c1bfc275c77c0b2985545426f736a5ed37dfde
--- /dev/null
+++ b/tools/telemetry/third_party/rope/rope/contrib/codeassist.py
@@ -0,0 +1,695 @@
+import keyword
+import sys
+import warnings
+
+import rope.base.codeanalyze
+import rope.base.evaluate
+from rope.base import builtins
+from rope.base import exceptions
+from rope.base import libutils
+from rope.base import pynames
+from rope.base import pynamesdef
+from rope.base import pyobjects
+from rope.base import pyobjectsdef
+from rope.base import pyscopes
+from rope.base import worder
+from rope.contrib import fixsyntax
+from rope.refactor import functionutils
+
+
+def code_assist(project, source_code, offset, resource=None,
+ templates=None, maxfixes=1, later_locals=True):
+ """Return python code completions as a list of `CodeAssistProposal`\s
+
+ `resource` is a `rope.base.resources.Resource` object. If
+ provided, relative imports are handled.
+
+ `maxfixes` is the maximum number of errors to fix if the code has
+ errors in it.
+
+ If `later_locals` is `False` names defined in this scope and after
+ this line is ignored.
+
+ """
+ if templates is not None:
+ warnings.warn('Codeassist no longer supports templates',
+ DeprecationWarning, stacklevel=2)
+ assist = _PythonCodeAssist(
+ project, source_code, offset, resource=resource,
+ maxfixes=maxfixes, later_locals=later_locals)
+ return assist()
+
+
+def starting_offset(source_code, offset):
+ """Return the offset in which the completion should be inserted
+
+ Usually code assist proposals should be inserted like::
+
+ completion = proposal.name
+ result = (source_code[:starting_offset] +
+ completion + source_code[offset:])
+
+ Where starting_offset is the offset returned by this function.
+
+ """
+ word_finder = worder.Worder(source_code, True)
+ expression, starting, starting_offset = \
+ word_finder.get_splitted_primary_before(offset)
+ return starting_offset
+
+
+def get_doc(project, source_code, offset, resource=None, maxfixes=1):
+ """Get the pydoc"""
+ fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes)
+ pyname = fixer.pyname_at(offset)
+ if pyname is None:
+ return None
+ pyobject = pyname.get_object()
+ return PyDocExtractor().get_doc(pyobject)
+
+
+def get_calltip(project, source_code, offset, resource=None,
+ maxfixes=1, ignore_unknown=False, remove_self=False):
+ """Get the calltip of a function
+
+ The format of the returned string is
+ ``module_name.holding_scope_names.function_name(arguments)``. For
+ classes `__init__()` and for normal objects `__call__()` function
+ is used.
+
+ Note that the offset is on the function itself *not* after the its
+ open parenthesis. (Actually it used to be the other way but it
+ was easily confused when string literals were involved. So I
+ decided it is better for it not to try to be too clever when it
+ cannot be clever enough). You can use a simple search like::
+
+ offset = source_code.rindex('(', 0, offset) - 1
+
+ to handle simple situations.
+
+ If `ignore_unknown` is `True`, `None` is returned for functions
+ without source-code like builtins and extensions.
+
+ If `remove_self` is `True`, the first parameter whose name is self
+ will be removed for methods.
+ """
+ fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes)
+ pyname = fixer.pyname_at(offset)
+ if pyname is None:
+ return None
+ pyobject = pyname.get_object()
+ return PyDocExtractor().get_calltip(pyobject, ignore_unknown, remove_self)
+
+
+def get_definition_location(project, source_code, offset,
+ resource=None, maxfixes=1):
+ """Return the definition location of the python name at `offset`
+
+ Return a (`rope.base.resources.Resource`, lineno) tuple. If no
+ `resource` is given and the definition is inside the same module,
+ the first element of the returned tuple would be `None`. If the
+ location cannot be determined ``(None, None)`` is returned.
+
+ """
+ fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes)
+ pyname = fixer.pyname_at(offset)
+ if pyname is not None:
+ module, lineno = pyname.get_definition_location()
+ if module is not None:
+ return module.get_module().get_resource(), lineno
+ return (None, None)
+
+
+def find_occurrences(*args, **kwds):
+ import rope.contrib.findit
+ warnings.warn('Use `rope.contrib.findit.find_occurrences()` instead',
+ DeprecationWarning, stacklevel=2)
+ return rope.contrib.findit.find_occurrences(*args, **kwds)
+
+
+def get_canonical_path(project, resource, offset):
+ """Get the canonical path to an object.
+
+ Given the offset of the object, this returns a list of
+ (name, name_type) tuples representing the canonical path to the
+ object. For example, the 'x' in the following code:
+
+ class Foo(object):
+ def bar(self):
+ class Qux(object):
+ def mux(self, x):
+ pass
+
+ we will return:
+
+ [('Foo', 'CLASS'), ('bar', 'FUNCTION'), ('Qux', 'CLASS'),
+ ('mux', 'FUNCTION'), ('x', 'PARAMETER')]
+
+ `resource` is a `rope.base.resources.Resource` object.
+
+ `offset` is the offset of the pyname you want the path to.
+
+ """
+ # Retrieve the PyName.
+ pymod = project.get_pymodule(resource)
+ pyname = rope.base.evaluate.eval_location(pymod, offset)
+
+ # Now get the location of the definition and its containing scope.
+ defmod, lineno = pyname.get_definition_location()
+ if not defmod:
+ return None
+ scope = defmod.get_scope().get_inner_scope_for_line(lineno)
+
+ # Start with the name of the object we're interested in.
+ names = []
+ if isinstance(pyname, pynamesdef.ParameterName):
+ names = [(worder.get_name_at(pymod.get_resource(), offset),
+ 'PARAMETER') ]
+ elif isinstance(pyname, pynamesdef.AssignedName):
+ names = [(worder.get_name_at(pymod.get_resource(), offset),
+ 'VARIABLE')]
+
+ # Collect scope names.
+ while scope.parent:
+ if isinstance(scope, pyscopes.FunctionScope):
+ scope_type = 'FUNCTION'
+ elif isinstance(scope, pyscopes.ClassScope):
+ scope_type = 'CLASS'
+ else:
+ scope_type = None
+ names.append((scope.pyobject.get_name(), scope_type))
+ scope = scope.parent
+
+ names.append((defmod.get_resource().real_path, 'MODULE'))
+ names.reverse()
+ return names
+
+
+class CompletionProposal(object):
+ """A completion proposal
+
+ The `scope` instance variable shows where proposed name came from
+ and can be 'global', 'local', 'builtin', 'attribute', 'keyword',
+ 'imported', 'parameter_keyword'.
+
+ The `type` instance variable shows the approximate type of the
+ proposed object and can be 'instance', 'class', 'function', 'module',
+ and `None`.
+
+ All possible relations between proposal's `scope` and `type` are shown
+ in the table below (different scopes in rows and types in columns):
+
+ | instance | class | function | module | None
+ local | + | + | + | + |
+ global | + | + | + | + |
+ builtin | + | + | + | |
+ attribute | + | + | + | + |
+ imported | + | + | + | + |
+ keyword | | | | | +
+ parameter_keyword | | | | | +
+
+ """
+
+ def __init__(self, name, scope, pyname=None):
+ self.name = name
+ self.pyname = pyname
+ self.scope = self._get_scope(scope)
+
+ def __str__(self):
+ return '%s (%s, %s)' % (self.name, self.scope, self.type)
+
+ def __repr__(self):
+ return str(self)
+
+ @property
+ def parameters(self):
+ """The names of the parameters the function takes.
+
+ Returns None if this completion is not a function.
+ """
+ pyname = self.pyname
+ if isinstance(pyname, pynames.ImportedName):
+ pyname = pyname._get_imported_pyname()
+ if isinstance(pyname, pynames.DefinedName):
+ pyobject = pyname.get_object()
+ if isinstance(pyobject, pyobjects.AbstractFunction):
+ return pyobject.get_param_names()
+
+ @property
+ def type(self):
+ pyname = self.pyname
+ if isinstance(pyname, builtins.BuiltinName):
+ pyobject = pyname.get_object()
+ if isinstance(pyobject, builtins.BuiltinFunction):
+ return 'function'
+ elif isinstance(pyobject, builtins.BuiltinClass):
+ return 'class'
+ elif isinstance(pyobject, builtins.BuiltinObject) or \
+ isinstance(pyobject, builtins.BuiltinName):
+ return 'instance'
+ elif isinstance(pyname, pynames.ImportedModule):
+ return 'module'
+ elif isinstance(pyname, pynames.ImportedName) or \
+ isinstance(pyname, pynames.DefinedName):
+ pyobject = pyname.get_object()
+ if isinstance(pyobject, pyobjects.AbstractFunction):
+ return 'function'
+ if isinstance(pyobject, pyobjects.AbstractClass):
+ return 'class'
+ return 'instance'
+
+ def _get_scope(self, scope):
+ if isinstance(self.pyname, builtins.BuiltinName):
+ return 'builtin'
+ if isinstance(self.pyname, pynames.ImportedModule) or \
+ isinstance(self.pyname, pynames.ImportedName):
+ return 'imported'
+ return scope
+
+ def get_doc(self):
+ """Get the proposed object's docstring.
+
+ Returns None if it can not be get.
+ """
+ if not self.pyname:
+ return None
+ pyobject = self.pyname.get_object()
+ if not hasattr(pyobject, 'get_doc'):
+ return None
+ return self.pyname.get_object().get_doc()
+
+ @property
+ def kind(self):
+ warnings.warn("the proposal's `kind` property is deprecated, "
+ "use `scope` instead")
+ return self.scope
+
+
+# leaved for backward compatibility
+CodeAssistProposal = CompletionProposal
+
+
+class NamedParamProposal(CompletionProposal):
+ """A parameter keyword completion proposal
+
+ Holds reference to ``_function`` -- the function which
+ parameter ``name`` belongs to. This allows to determine
+ default value for this parameter.
+ """
+ def __init__(self, name, function):
+ self.argname = name
+ name = '%s=' % name
+ super(NamedParamProposal, self).__init__(name, 'parameter_keyword')
+ self._function = function
+
+ def get_default(self):
+ """Get a string representation of a param's default value.
+
+ Returns None if there is no default value for this param.
+ """
+ definfo = functionutils.DefinitionInfo.read(self._function)
+ for arg, default in definfo.args_with_defaults:
+ if self.argname == arg:
+ return default
+ return None
+
+
+def sorted_proposals(proposals, scopepref=None, typepref=None):
+ """Sort a list of proposals
+
+ Return a sorted list of the given `CodeAssistProposal`\s.
+
+ `scopepref` can be a list of proposal scopes. Defaults to
+ ``['parameter_keyword', 'local', 'global', 'imported',
+ 'attribute', 'builtin', 'keyword']``.
+
+ `typepref` can be a list of proposal types. Defaults to
+ ``['class', 'function', 'instance', 'module', None]``.
+ (`None` stands for completions with no type like keywords.)
+ """
+ sorter = _ProposalSorter(proposals, scopepref, typepref)
+ return sorter.get_sorted_proposal_list()
+
+
+def starting_expression(source_code, offset):
+ """Return the expression to complete"""
+ word_finder = worder.Worder(source_code, True)
+ expression, starting, starting_offset = \
+ word_finder.get_splitted_primary_before(offset)
+ if expression:
+ return expression + '.' + starting
+ return starting
+
+
+def default_templates():
+ warnings.warn('default_templates() is deprecated.',
+ DeprecationWarning, stacklevel=2)
+ return {}
+
+
+class _PythonCodeAssist(object):
+
+ def __init__(self, project, source_code, offset, resource=None,
+ maxfixes=1, later_locals=True):
+ self.project = project
+ self.code = source_code
+ self.resource = resource
+ self.maxfixes = maxfixes
+ self.later_locals = later_locals
+ self.word_finder = worder.Worder(source_code, True)
+ self.expression, self.starting, self.offset = \
+ self.word_finder.get_splitted_primary_before(offset)
+
+ keywords = keyword.kwlist
+
+ def _find_starting_offset(self, source_code, offset):
+ current_offset = offset - 1
+ while current_offset >= 0 and (source_code[current_offset].isalnum() or
+ source_code[current_offset] in '_'):
+ current_offset -= 1
+ return current_offset + 1
+
+ def _matching_keywords(self, starting):
+ result = []
+ for kw in self.keywords:
+ if kw.startswith(starting):
+ result.append(CompletionProposal(kw, 'keyword'))
+ return result
+
+ def __call__(self):
+ if self.offset > len(self.code):
+ return []
+ completions = list(self._code_completions().values())
+ if self.expression.strip() == '' and self.starting.strip() != '':
+ completions.extend(self._matching_keywords(self.starting))
+ return completions
+
+ def _dotted_completions(self, module_scope, holding_scope):
+ result = {}
+ found_pyname = rope.base.evaluate.eval_str(holding_scope,
+ self.expression)
+ if found_pyname is not None:
+ element = found_pyname.get_object()
+ compl_scope = 'attribute'
+ if isinstance(element, (pyobjectsdef.PyModule,
+ pyobjectsdef.PyPackage)):
+ compl_scope = 'imported'
+ for name, pyname in element.get_attributes().items():
+ if name.startswith(self.starting):
+ result[name] = CompletionProposal(name, compl_scope,
+ pyname)
+ return result
+
+ def _undotted_completions(self, scope, result, lineno=None):
+ if scope.parent is not None:
+ self._undotted_completions(scope.parent, result)
+ if lineno is None:
+ names = scope.get_propagated_names()
+ else:
+ names = scope.get_names()
+ for name, pyname in names.items():
+ if name.startswith(self.starting):
+ compl_scope = 'local'
+ if scope.get_kind() == 'Module':
+ compl_scope = 'global'
+ if lineno is None or self.later_locals or \
+ not self._is_defined_after(scope, pyname, lineno):
+ result[name] = CompletionProposal(name, compl_scope,
+ pyname)
+
+ def _from_import_completions(self, pymodule):
+ module_name = self.word_finder.get_from_module(self.offset)
+ if module_name is None:
+ return {}
+ pymodule = self._find_module(pymodule, module_name)
+ result = {}
+ for name in pymodule:
+ if name.startswith(self.starting):
+ result[name] = CompletionProposal(name, scope='global',
+ pyname=pymodule[name])
+ return result
+
+ def _find_module(self, pymodule, module_name):
+ dots = 0
+ while module_name[dots] == '.':
+ dots += 1
+ pyname = pynames.ImportedModule(pymodule,
+ module_name[dots:], dots)
+ return pyname.get_object()
+
+ def _is_defined_after(self, scope, pyname, lineno):
+ location = pyname.get_definition_location()
+ if location is not None and location[1] is not None:
+ if location[0] == scope.pyobject.get_module() and \
+ lineno <= location[1] <= scope.get_end():
+ return True
+
+ def _code_completions(self):
+ lineno = self.code.count('\n', 0, self.offset) + 1
+ fixer = fixsyntax.FixSyntax(self.project, self.code,
+ self.resource, self.maxfixes)
+ pymodule = fixer.get_pymodule()
+ module_scope = pymodule.get_scope()
+ code = pymodule.source_code
+ lines = code.split('\n')
+ result = {}
+ start = fixsyntax._logical_start(lines, lineno)
+ indents = fixsyntax._get_line_indents(lines[start - 1])
+ inner_scope = module_scope.get_inner_scope_for_line(start, indents)
+ if self.word_finder.is_a_name_after_from_import(self.offset):
+ return self._from_import_completions(pymodule)
+ if self.expression.strip() != '':
+ result.update(self._dotted_completions(module_scope, inner_scope))
+ else:
+ result.update(self._keyword_parameters(module_scope.pyobject,
+ inner_scope))
+ self._undotted_completions(inner_scope, result, lineno=lineno)
+ return result
+
+ def _keyword_parameters(self, pymodule, scope):
+ offset = self.offset
+ if offset == 0:
+ return {}
+ word_finder = worder.Worder(self.code, True)
+ if word_finder.is_on_function_call_keyword(offset - 1):
+ function_parens = word_finder.\
+ find_parens_start_from_inside(offset - 1)
+ primary = word_finder.get_primary_at(function_parens - 1)
+ try:
+ function_pyname = rope.base.evaluate.\
+ eval_str(scope, primary)
+ except exceptions.BadIdentifierError:
+ return {}
+ if function_pyname is not None:
+ pyobject = function_pyname.get_object()
+ if isinstance(pyobject, pyobjects.AbstractFunction):
+ pass
+ elif isinstance(pyobject, pyobjects.AbstractClass) and \
+ '__init__' in pyobject:
+ pyobject = pyobject['__init__'].get_object()
+ elif '__call__' in pyobject:
+ pyobject = pyobject['__call__'].get_object()
+ if isinstance(pyobject, pyobjects.AbstractFunction):
+ param_names = []
+ param_names.extend(
+ pyobject.get_param_names(special_args=False))
+ result = {}
+ for name in param_names:
+ if name.startswith(self.starting):
+ result[name + '='] = NamedParamProposal(
+ name, pyobject
+ )
+ return result
+ return {}
+
+
+class _ProposalSorter(object):
+ """Sort a list of code assist proposals"""
+
+ def __init__(self, code_assist_proposals, scopepref=None, typepref=None):
+ self.proposals = code_assist_proposals
+ if scopepref is None:
+ scopepref = ['parameter_keyword', 'local', 'global', 'imported',
+ 'attribute', 'builtin', 'keyword']
+ self.scopepref = scopepref
+ if typepref is None:
+ typepref = ['class', 'function', 'instance', 'module', None]
+ self.typerank = dict((type, index)
+ for index, type in enumerate(typepref))
+
+ def get_sorted_proposal_list(self):
+ """Return a list of `CodeAssistProposal`"""
+ proposals = {}
+ for proposal in self.proposals:
+ proposals.setdefault(proposal.scope, []).append(proposal)
+ result = []
+ for scope in self.scopepref:
+ scope_proposals = proposals.get(scope, [])
+ scope_proposals = [proposal for proposal in scope_proposals
+ if proposal.type in self.typerank]
+ scope_proposals.sort(key=self._proposal_key)
+ result.extend(scope_proposals)
+ return result
+
+ def _proposal_key(self, proposal1):
+ def _underline_count(name):
+ return sum(1 for c in name if c == "_")
+ return (self.typerank.get(proposal1.type, 100),
+ _underline_count(proposal1.name),
+ proposal1.name)
+ #if proposal1.type != proposal2.type:
+ # return cmp(self.typerank.get(proposal1.type, 100),
+ # self.typerank.get(proposal2.type, 100))
+ #return self._compare_underlined_names(proposal1.name,
+ # proposal2.name)
+
+
+class PyDocExtractor(object):
+
+ def get_doc(self, pyobject):
+ if isinstance(pyobject, pyobjects.AbstractFunction):
+ return self._get_function_docstring(pyobject)
+ elif isinstance(pyobject, pyobjects.AbstractClass):
+ return self._get_class_docstring(pyobject)
+ elif isinstance(pyobject, pyobjects.AbstractModule):
+ return self._trim_docstring(pyobject.get_doc())
+ return None
+
+ def get_calltip(self, pyobject, ignore_unknown=False, remove_self=False):
+ try:
+ if isinstance(pyobject, pyobjects.AbstractClass):
+ pyobject = pyobject['__init__'].get_object()
+ if not isinstance(pyobject, pyobjects.AbstractFunction):
+ pyobject = pyobject['__call__'].get_object()
+ except exceptions.AttributeNotFoundError:
+ return None
+ if ignore_unknown and not isinstance(pyobject, pyobjects.PyFunction):
+ return
+ if isinstance(pyobject, pyobjects.AbstractFunction):
+ result = self._get_function_signature(pyobject, add_module=True)
+ if remove_self and self._is_method(pyobject):
+ return result.replace('(self)', '()').replace('(self, ', '(')
+ return result
+
+ def _get_class_docstring(self, pyclass):
+ contents = self._trim_docstring(pyclass.get_doc(), 2)
+ supers = [super.get_name() for super in pyclass.get_superclasses()]
+ doc = 'class %s(%s):\n\n' % (pyclass.get_name(), ', '.join(supers)) \
+ + contents
+
+ if '__init__' in pyclass:
+ init = pyclass['__init__'].get_object()
+ if isinstance(init, pyobjects.AbstractFunction):
+ doc += '\n\n' + self._get_single_function_docstring(init)
+ return doc
+
+ def _get_function_docstring(self, pyfunction):
+ functions = [pyfunction]
+ if self._is_method(pyfunction):
+ functions.extend(self._get_super_methods(pyfunction.parent,
+ pyfunction.get_name()))
+ return '\n\n'.join([self._get_single_function_docstring(function)
+ for function in functions])
+
+ def _is_method(self, pyfunction):
+ return isinstance(pyfunction, pyobjects.PyFunction) and \
+ isinstance(pyfunction.parent, pyobjects.PyClass)
+
+ def _get_single_function_docstring(self, pyfunction):
+ signature = self._get_function_signature(pyfunction)
+ docs = self._trim_docstring(pyfunction.get_doc(), indents=2)
+ return signature + ':\n\n' + docs
+
+ def _get_super_methods(self, pyclass, name):
+ result = []
+ for super_class in pyclass.get_superclasses():
+ if name in super_class:
+ function = super_class[name].get_object()
+ if isinstance(function, pyobjects.AbstractFunction):
+ result.append(function)
+ result.extend(self._get_super_methods(super_class, name))
+ return result
+
+ def _get_function_signature(self, pyfunction, add_module=False):
+ location = self._location(pyfunction, add_module)
+ if isinstance(pyfunction, pyobjects.PyFunction):
+ info = functionutils.DefinitionInfo.read(pyfunction)
+ return location + info.to_string()
+ else:
+ return '%s(%s)' % (location + pyfunction.get_name(),
+ ', '.join(pyfunction.get_param_names()))
+
+ def _location(self, pyobject, add_module=False):
+ location = []
+ parent = pyobject.parent
+ while parent and not isinstance(parent, pyobjects.AbstractModule):
+ location.append(parent.get_name())
+ location.append('.')
+ parent = parent.parent
+ if add_module:
+ if isinstance(pyobject, pyobjects.PyFunction):
+ location.insert(0, self._get_module(pyobject))
+ if isinstance(parent, builtins.BuiltinModule):
+ location.insert(0, parent.get_name() + '.')
+ return ''.join(location)
+
+ def _get_module(self, pyfunction):
+ module = pyfunction.get_module()
+ if module is not None:
+ resource = module.get_resource()
+ if resource is not None:
+ return libutils.modname(resource) + '.'
+ return ''
+
+ def _trim_docstring(self, docstring, indents=0):
+ """The sample code from :PEP:`257`"""
+ if not docstring:
+ return ''
+ # Convert tabs to spaces (following normal Python rules)
+ # and split into a list of lines:
+ lines = docstring.expandtabs().splitlines()
+ # Determine minimum indentation (first line doesn't count):
+ indent = sys.maxsize
+ for line in lines[1:]:
+ stripped = line.lstrip()
+ if stripped:
+ indent = min(indent, len(line) - len(stripped))
+ # Remove indentation (first line is special):
+ trimmed = [lines[0].strip()]
+ if indent < sys.maxsize:
+ for line in lines[1:]:
+ trimmed.append(line[indent:].rstrip())
+ # Strip off trailing and leading blank lines:
+ while trimmed and not trimmed[-1]:
+ trimmed.pop()
+ while trimmed and not trimmed[0]:
+ trimmed.pop(0)
+ # Return a single string:
+ return '\n'.join((' ' * indents + line for line in trimmed))
+
+
+# Deprecated classes
+
+class TemplateProposal(CodeAssistProposal):
+ def __init__(self, name, template):
+ warnings.warn('TemplateProposal is deprecated.',
+ DeprecationWarning, stacklevel=2)
+ super(TemplateProposal, self).__init__(name, 'template')
+ self.template = template
+
+
+class Template(object):
+
+ def __init__(self, template):
+ self.template = template
+ warnings.warn('Template is deprecated.',
+ DeprecationWarning, stacklevel=2)
+
+ def variables(self):
+ return []
+
+ def substitute(self, mapping):
+ return self.template
+
+ def get_cursor_location(self, mapping):
+ return len(self.template)

Powered by Google App Engine
This is Rietveld 408576698