diff options
Diffstat (limited to 'buildbot/buildbot/process/properties.py')
-rw-r--r-- | buildbot/buildbot/process/properties.py | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/buildbot/buildbot/process/properties.py b/buildbot/buildbot/process/properties.py new file mode 100644 index 0000000..2d07db9 --- /dev/null +++ b/buildbot/buildbot/process/properties.py @@ -0,0 +1,157 @@ +import re +import weakref +from buildbot import util + +class Properties(util.ComparableMixin): + """ + I represent a set of properties that can be interpolated into various + strings in buildsteps. + + @ivar properties: dictionary mapping property values to tuples + (value, source), where source is a string identifing the source + of the property. + + Objects of this class can be read like a dictionary -- in this case, + only the property value is returned. + + As a special case, a property value of None is returned as an empty + string when used as a mapping. + """ + + compare_attrs = ('properties',) + + def __init__(self, **kwargs): + """ + @param kwargs: initial property values (for testing) + """ + self.properties = {} + self.pmap = PropertyMap(self) + if kwargs: self.update(kwargs, "TEST") + + def __getstate__(self): + d = self.__dict__.copy() + del d['pmap'] + return d + + def __setstate__(self, d): + self.__dict__ = d + self.pmap = PropertyMap(self) + + def __getitem__(self, name): + """Just get the value for this property.""" + rv = self.properties[name][0] + return rv + + def has_key(self, name): + return self.properties.has_key(name) + + def getProperty(self, name, default=None): + """Get the value for the given property.""" + return self.properties.get(name, (default,))[0] + + def getPropertySource(self, name): + return self.properties[name][1] + + def asList(self): + """Return the properties as a sorted list of (name, value, source)""" + l = [ (k, v[0], v[1]) for k,v in self.properties.items() ] + l.sort() + return l + + def __repr__(self): + return repr(dict([ (k,v[0]) for k,v in self.properties.iteritems() ])) + + def setProperty(self, name, value, source): + self.properties[name] = (value, source) + + def update(self, dict, source): + """Update this object from a dictionary, with an explicit source specified.""" + for k, v in dict.items(): + self.properties[k] = (v, source) + + def updateFromProperties(self, other): + """Update this object based on another object; the other object's """ + self.properties.update(other.properties) + + def render(self, value): + """ + Return a variant of value that has any WithProperties objects + substituted. This recurses into Python's compound data types. + """ + # we use isinstance to detect Python's standard data types, and call + # this function recursively for the values in those types + if isinstance(value, (str, unicode)): + return value + elif isinstance(value, WithProperties): + return value.render(self.pmap) + elif isinstance(value, list): + return [ self.render(e) for e in value ] + elif isinstance(value, tuple): + return tuple([ self.render(e) for e in value ]) + elif isinstance(value, dict): + return dict([ (self.render(k), self.render(v)) for k,v in value.iteritems() ]) + else: + return value + +class PropertyMap: + """ + Privately-used mapping object to implement WithProperties' substitutions, + including the rendering of None as ''. + """ + colon_minus_re = re.compile(r"(.*):-(.*)") + colon_plus_re = re.compile(r"(.*):\+(.*)") + def __init__(self, properties): + # use weakref here to avoid a reference loop + self.properties = weakref.ref(properties) + + def __getitem__(self, key): + properties = self.properties() + assert properties is not None + + # %(prop:-repl)s + # if prop exists, use it; otherwise, use repl + mo = self.colon_minus_re.match(key) + if mo: + prop, repl = mo.group(1,2) + if properties.has_key(prop): + rv = properties[prop] + else: + rv = repl + else: + # %(prop:+repl)s + # if prop exists, use repl; otherwise, an empty string + mo = self.colon_plus_re.match(key) + if mo: + prop, repl = mo.group(1,2) + if properties.has_key(prop): + rv = repl + else: + rv = '' + else: + rv = properties[key] + + # translate 'None' to an empty string + if rv is None: rv = '' + return rv + +class WithProperties(util.ComparableMixin): + """ + This is a marker class, used fairly widely to indicate that we + want to interpolate build properties. + """ + + compare_attrs = ('fmtstring', 'args') + + def __init__(self, fmtstring, *args): + self.fmtstring = fmtstring + self.args = args + + def render(self, pmap): + if self.args: + strings = [] + for name in self.args: + strings.append(pmap[name]) + s = self.fmtstring % tuple(strings) + else: + s = self.fmtstring % pmap + return s |