Synopsis - Synopsis/Processor.py

File: Synopsis/Processor.py

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

Generated on Thu Apr 16 16:27:14 2009 by
synopsis (version devel)