# Copyright (c) 2006 The Regents of The University of Michigan # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer; # redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution; # neither the name of the copyright holders nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Authors: Kevin Lim import sys class ternary(object): def __new__(cls, *args): if len(args) > 1: raise TypeError, \ '%s() takes at most 1 argument (%d given)' % \ (cls.__name__, len(args)) if args: if not isinstance(args[0], (bool, ternary)): raise TypeError, \ '%s() argument must be True, False, or Any' % \ cls.__name__ return args[0] return super(ternary, cls).__new__(cls) def __bool__(self): return True def __neg__(self): return self def __eq__(self, other): return True def __ne__(self, other): return False def __str__(self): return 'Any' def __repr__(self): return 'Any' Any = ternary() class Flags(dict): def __init__(self, *args, **kwargs): super(Flags, self).__init__() self.update(*args, **kwargs) def __getattr__(self, attr): return self[attr] def __setattr__(self, attr, value): self[attr] = value def __setitem__(self, item, value): return super(Flags, self).__setitem__(item, ternary(value)) def __getitem__(self, item): if item not in self: return False return super(Flags, self).__getitem__(item) def update(self, *args, **kwargs): for arg in args: if isinstance(arg, Flags): super(Flags, self).update(arg) elif isinstance(arg, dict): for key,val in kwargs.iteritems(): self[key] = val else: raise AttributeError, \ 'flags not of type %s or %s, but %s' % \ (Flags, dict, type(arg)) for key,val in kwargs.iteritems(): self[key] = val def match(self, *args, **kwargs): match = Flags(*args, **kwargs) for key,value in match.iteritems(): if self[key] != value: return False return True def crossproduct(items): if not isinstance(items, (list, tuple)): raise AttributeError, 'crossproduct works only on sequences' if not items: yield None return current = items[0] remainder = items[1:] if not hasattr(current, '__iter__'): current = [ current ] for item in current: for rem in crossproduct(remainder): data = [ item ] if rem: data += rem yield data def flatten(items): if not isinstance(items, (list, tuple)): yield items return for item in items: for flat in flatten(item): yield flat class Data(object): def __init__(self, name, desc, **kwargs): self.name = name self.desc = desc self.system = None self.flags = Flags() self.env = {} for k,v in kwargs.iteritems(): setattr(self, k, v) def update(self, obj): if not isinstance(obj, Data): raise AttributeError, "can only update from Data object" self.env.update(obj.env) self.flags.update(obj.flags) if obj.system: if self.system and self.system != obj.system: raise AttributeError, \ "conflicting values for system: '%s'/'%s'" % \ (self.system, obj.system) self.system = obj.system def printinfo(self): if self.name: print 'name: %s' % self.name if self.desc: print 'desc: %s' % self.desc if self.system: print 'system: %s' % self.system def printverbose(self): print 'flags:' keys = self.flags.keys() keys.sort() for key in keys: print ' %s = %s' % (key, self.flags[key]) print 'env:' keys = self.env.keys() keys.sort() for key in keys: print ' %s = %s' % (key, self.env[key]) print def __str__(self): return self.name class Job(Data): def __init__(self, options): super(Job, self).__init__('', '') self.setoptions(options) self.checkpoint = False opts = [] for opt in options: cpt = opt.group.checkpoint if not cpt: self.checkpoint = True continue if isinstance(cpt, Option): opt = cpt.clone(suboptions=False) else: opt = opt.clone(suboptions=False) opts.append(opt) if not opts: self.checkpoint = False if self.checkpoint: self.checkpoint = Job(opts) def clone(self): return Job(self.options) def __getattribute__(self, attr): if attr == 'name': names = [ ] for opt in self.options: if opt.name: names.append(opt.name) return ':'.join(names) if attr == 'desc': descs = [ ] for opt in self.options: if opt.desc: descs.append(opt.desc) return ', '.join(descs) return super(Job, self).__getattribute__(attr) def setoptions(self, options): config = options[0].config for opt in options: if opt.config != config: raise AttributeError, \ "All options are not from the same Configuration" self.config = config self.groups = [ opt.group for opt in options ] self.options = options self.update(self.config) for group in self.groups: self.update(group) for option in self.options: self.update(option) if option._suboption: self.update(option._suboption) def printinfo(self): super(Job, self).printinfo() if self.checkpoint: print 'checkpoint: %s' % self.checkpoint.name print 'config: %s' % self.config.name print 'groups: %s' % [ g.name for g in self.groups ] print 'options: %s' % [ o.name for o in self.options ] super(Job, self).printverbose() class SubOption(Data): def __init__(self, name, desc, **kwargs): super(SubOption, self).__init__(name, desc, **kwargs) self.number = None class Option(Data): def __init__(self, name, desc, **kwargs): super(Option, self).__init__(name, desc, **kwargs) self._suboptions = [] self._suboption = None self.number = None def __getattribute__(self, attr): if attr == 'name': name = self.__dict__[attr] if self._suboption is not None: name = '%s:%s' % (name, self._suboption.name) return name if attr == 'desc': desc = [ self.__dict__[attr] ] if self._suboption is not None and self._suboption.desc: desc.append(self._suboption.desc) return ', '.join(desc) return super(Option, self).__getattribute__(attr) def suboption(self, name, desc, **kwargs): subo = SubOption(name, desc, **kwargs) subo.config = self.config subo.group = self.group subo.option = self subo.number = len(self._suboptions) self._suboptions.append(subo) return subo def clone(self, suboptions=True): option = Option(self.__dict__['name'], self.__dict__['desc']) option.update(self) option.group = self.group option.config = self.config option.number = self.number if suboptions: option._suboptions.extend(self._suboptions) option._suboption = self._suboption return option def subopts(self): if not self._suboptions: return [ self ] subopts = [] for subo in self._suboptions: option = self.clone() option._suboption = subo subopts.append(option) return subopts def printinfo(self): super(Option, self).printinfo() print 'config: %s' % self.config.name super(Option, self).printverbose() class Group(Data): def __init__(self, name, desc, **kwargs): super(Group, self).__init__(name, desc, **kwargs) self._options = [] self.checkpoint = False self.number = None def option(self, name, desc, **kwargs): opt = Option(name, desc, **kwargs) opt.config = self.config opt.group = self opt.number = len(self._options) self._options.append(opt) return opt def options(self): return self._options def subopts(self): subopts = [] for opt in self._options: for subo in opt.subopts(): subopts.append(subo) return subopts def printinfo(self): super(Group, self).printinfo() print 'config: %s' % self.config.name print 'options: %s' % [ o.name for o in self._options ] super(Group, self).printverbose() class Configuration(Data): def __init__(self, name, desc, **kwargs): super(Configuration, self).__init__(name, desc, **kwargs) self._groups = [] self._posfilters = [] self._negfilters = [] def group(self, name, desc, **kwargs): grp = Group(name, desc, **kwargs) grp.config = self grp.number = len(self._groups) self._groups.append(grp) return grp def groups(self, flags=Flags(), sign=True): if not flags: return self._groups return [ grp for grp in self._groups if sign ^ grp.flags.match(flags) ] def checkchildren(self, kids): for kid in kids: if kid.config != self: raise AttributeError, "child from the wrong configuration" def sortgroups(self, groups): groups = [ (grp.number, grp) for grp in groups ] groups.sort() return [ grp[1] for grp in groups ] def options(self, groups = None, checkpoint = False): if groups is None: groups = self._groups self.checkchildren(groups) groups = self.sortgroups(groups) if checkpoint: groups = [ grp for grp in groups if grp.checkpoint ] optgroups = [ g.options() for g in groups ] else: optgroups = [ g.subopts() for g in groups ] for options in crossproduct(optgroups): for opt in options: cpt = opt.group.checkpoint if not isinstance(cpt, bool) and cpt != opt: if checkpoint: break else: yield options else: if checkpoint: yield options def addfilter(self, filt, pos=True): import re filt = re.compile(filt) if pos: self._posfilters.append(filt) else: self._negfilters.append(filt) def jobfilter(self, job): for filt in self._negfilters: if filt.match(job.name): return False if not self._posfilters: return True for filt in self._posfilters: if filt.match(job.name): return True return False def checkpoints(self, groups = None): for options in self.options(groups, True): job = Job(options) if self.jobfilter(job): yield job def jobs(self, groups = None): for options in self.options(groups, False): job = Job(options) if self.jobfilter(job): yield job def alljobs(self, groups = None): for options in self.options(groups, True): yield Job(options) for options in self.options(groups, False): yield Job(options) def find(self, jobname): for job in self.alljobs(): if job.name == jobname: return job else: raise AttributeError, "job '%s' not found" % jobname def job(self, options): self.checkchildren(options) options = [ (opt.group.number, opt) for opt in options ] options.sort() options = [ opt[1] for opt in options ] job = Job(options) return job def printinfo(self): super(Configuration, self).printinfo() print 'groups: %s' % [ g.name for g in self._grouips ] super(Configuration, self).printverbose() def JobFile(jobfile): from os.path import expanduser, isfile, join as joinpath filename = expanduser(jobfile) # Can't find filename in the current path, search sys.path if not isfile(filename): for path in sys.path: testname = joinpath(path, filename) if isfile(testname): filename = testname break else: raise AttributeError, \ "Could not find file '%s'" % jobfile data = {} execfile(filename, data) if 'conf' not in data: raise ImportError, 'cannot import name conf from %s' % jobfile conf = data['conf'] import jobfile if not isinstance(conf, Configuration): raise AttributeError, \ 'conf in jobfile: %s (%s) is not type %s' % \ (jobfile, type(conf), Configuration) return conf if __name__ == '__main__': from jobfile import * import sys usage = 'Usage: %s [-b] [-c] [-v] ' % sys.argv[0] try: import getopt opts, args = getopt.getopt(sys.argv[1:], '-bcv') except getopt.GetoptError: sys.exit(usage) if len(args) != 1: raise AttributeError, usage both = False checkpoint = False verbose = False for opt,arg in opts: if opt == '-b': both = True checkpoint = True if opt == '-c': checkpoint = True if opt == '-v': verbose = True jobfile = args[0] conf = JobFile(jobfile) if both: gen = conf.alljobs() elif checkpoint: gen = conf.checkpoints() else: gen = conf.jobs() for job in gen: if not verbose: cpt = '' if job.checkpoint: cpt = job.checkpoint.name print job.name, cpt else: job.printinfo()