1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
#
# Copyright (C) 2003 Stefan Seefeld
# All rights reserved.
# Licensed to the public under the terms of the GNU LGPL (>= 2),
# see the file COPYING for details.
#
import AST
class Parameter(object):
"""A Parameter is a documented value, kept inside a Processor."""
def __init__(self, value, doc):
self.value = value
self.doc = doc
class Type(type):
"""Type is the Processor's __metaclass__."""
def __init__(cls, name, bases, dict):
"""Generate a '_parameters' dictionary holding all the 'Parameter' objects.
Then replace 'Parameter' objects by their values for convenient use inside
the code."""
parameters = {}
for i in dict:
if isinstance(dict[i], Parameter):
parameters[i] = dict[i]
for i in parameters:
setattr(cls, i, dict[i].value)
setattr(cls, '_parameters', parameters)
class Parametrized(object):
"""Parametrized implements handling of Parameter attributes."""
__metaclass__ = Type
def __new__(cls, *args, **kwds):
"""merge all parameter catalogs for easy access to documentation,
then use keyword arguments to override default values."""
instance = object.__new__(cls)
# iterate over all base classes, starting at the 'Parametrized' base class
# i.e. remove mixin classes
hierarchy = list(filter(lambda i:issubclass(i, Parametrized), cls.__mro__))
hierarchy.reverse()
parameters = {}
for c in hierarchy:
parameters.update(c._parameters)
setattr(instance, '_parameters', parameters)
for p in kwds:
if not p in instance._parameters:
raise KeyError, "'%s' processor doesn't have '%s' parameter"%(cls.__name__, p)
else:
setattr(instance, p, kwds[p])
return instance
def __init__(self, **kwds):
"""The constructor uses the keywords to update the parameter list."""
self.set_parameters(kwds)
def get_parameters(self):
return self._parameters
def set_parameters(self, kwds):
"""Sets the given parameters to override the default values."""
for i in kwds:
if i in self._parameters:
setattr(self, i, kwds[i])
else:
raise KeyError, "No parameter '%s' in '%s'"%(i, self.__class__.__name__)
class Error:
"""An exception a processor may raise during processing."""
def __init__(self, what):
self.__what = what
def __str__(self):
return "%s: %s"%(self.__class__.__name__, self.__what)
class Processor(Parametrized):
"""Processor documentation..."""
verbose = Parameter(False, "operate verbosely")
debug = Parameter(False, "generate debug traces")
input = Parameter([], "input files to process")
output = Parameter('', "output file to save the ast to")
def merge_input(self, ast):
"""Join the given ast with a set of asts to be read from 'input' parameter"""
input = getattr(self, 'input', [])
for file in input:
ast.merge(AST.load(file))
return ast
def output_and_return_ast(self):
"""writes output if the 'output' attribute is set, then returns"""
output = getattr(self, 'output', None)
if output:
AST.save(output, self.ast)
return self.ast
def process(self, ast, **kwds):
"""The process method provides the interface to be implemented by subclasses.
Commonly used arguments are 'input' and 'output'. If 'input' is defined,
it is interpreted as one or more input file names. If 'output' is defined, it
is interpreted as an output file (or directory) name.
This implementation may serve as a template for real processors."""
# override default parameter values
self.set_parameters(kwds)
# merge in ast from 'input' parameter if given
self.ast = self.merge_input(ast)
# do the real work here...
# write to output (if given) and return ast
return self.output_and_return_ast()
class Composite(Processor):
"""A Composite processor."""
processors = Parameter([], 'the list of processors this is composed of')
def __init__(self, *processors, **kwds):
"""This __init__ is a convenience constructor that takes a var list
to list the desired processors. If the named values contain 'processors',
they override the var list."""
if processors: self.processors = processors
self.set_parameters(kwds)
def process(self, ast, **kwds):
"""apply a list of processors. The 'input' value is passed to the first
processor only, the 'output' to the last. 'verbose' and 'debug' are
passed down if explicitely given as named values.
All other keywords are ignored."""
if not self.processors:
return super(Composite, self).process(ast, **kwds)
self.set_parameters(kwds)
if len(self.processors) == 1:
my_kwds = {}
if self.input: my_kwds['input'] = self.input
if self.output: my_kwds['output'] = self.output
if self.verbose: my_kwds['verbose'] = self.verbose
if self.debug: my_kwds['debug'] = self.debug
return self.processors[0].process(ast, **my_kwds)
# more than one processor...
# call the first, passing the 'input' parameter, if present
my_kwds = {}
if self.input: my_kwds['input'] = self.input
if self.verbose: my_kwds['verbose'] = self.verbose
if self.debug: my_kwds['debug'] = self.debug
ast = self.processors[0].process(ast, **my_kwds)
# deal with all between the first and the last;
# they only get 'verbose' and 'debug' flags
my_kwds = {}
if kwds.has_key('verbose'): my_kwds['verbose'] = kwds['verbose']
if kwds.has_key('debug'): my_kwds['debug'] = kwds['debug']
if len(self.processors) > 2:
for p in self.processors[1:-1]:
ast = p.process(ast, **my_kwds)
# call the last, passing the 'output' parameter, if present
if self.output: my_kwds['output'] = self.output
ast = self.processors[-1].process(ast, **my_kwds)
return ast
class Store(Processor):
"""Store is a convenience class useful to write out the intermediate
state of the ast within a pipeline such as represented by the 'Composite'"""
def process(self, ast, **kwds):
"""Simply store the current ast in the 'output' file."""
self.set_parameters(kwds)
self.ast = self.merge_input(ast)
return self.output_and_return_ast()
|