1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16  import re 
 17  import weakref 
 18  from buildbot import util 
 19  from buildbot.interfaces import IRenderable, IProperties 
 20  from twisted.python.components import registerAdapter 
 21  from zope.interface import implements 
 22   
 24      """ 
 25      I represent a set of properties that can be interpolated into various 
 26      strings in buildsteps. 
 27   
 28      @ivar properties: dictionary mapping property values to tuples  
 29          (value, source), where source is a string identifing the source 
 30          of the property. 
 31   
 32      Objects of this class can be read like a dictionary -- in this case, 
 33      only the property value is returned. 
 34   
 35      As a special case, a property value of None is returned as an empty  
 36      string when used as a mapping. 
 37      """ 
 38   
 39      compare_attrs = ('properties',) 
 40      implements(IProperties) 
 41   
 43          """ 
 44          @param kwargs: initial property values (for testing) 
 45          """ 
 46          self.properties = {} 
 47           
 48           
 49          self.runtime = set() 
 50          self.pmap = PropertyMap(self) 
 51          self.build = None  
 52          if kwargs: self.update(kwargs, "TEST") 
  53   
 55          d = self.__dict__.copy() 
 56          del d['pmap'] 
 57          d['build'] = None 
 58          return d 
  59   
 61          self.__dict__ = d 
 62          self.pmap = PropertyMap(self) 
 63          if not hasattr(self, 'runtime'): 
 64              self.runtime = set() 
  65   
 68   
 70          """Just get the value for this property.""" 
 71          rv = self.properties[name][0] 
 72          return rv 
  73   
 76   
 79   
 81          """Return the properties as a sorted list of (name, value, source)""" 
 82          l = [ (k, v[0], v[1]) for k,v in self.properties.iteritems() ] 
 83          l.sort() 
 84          return l 
  85   
 87          """Return the properties as a simple key:value dictionary""" 
 88          return dict(self.properties) 
  89   
 91          return ('Properties(**' + 
 92                  repr(dict((k,v[0]) for k,v in self.properties.iteritems())) + 
 93                  ')') 
  94   
 95 -    def update(self, dict, source, runtime=False): 
  96          """Update this object from a dictionary, with an explicit source specified.""" 
 97          for k, v in dict.items(): 
 98              self.properties[k] = (v, source) 
 99              if runtime: 
100                  self.runtime.add(k) 
 101   
106   
108          """Update this object based on another object, but don't 
109          include properties that were marked as runtime.""" 
110          for k,v in other.properties.iteritems(): 
111              if k not in other.runtime: 
112                  self.properties[k] = v 
 113   
114       
115   
118   
121   
122      has_key = hasProperty 
123   
124 -    def setProperty(self, name, value, source, runtime=False): 
 128   
131   
134   
 138   
139   
141      """ 
142      A mixin to add L{IProperties} methods to a class which does not implement 
143      the interface, but which can be coerced to the interface via an adapter. 
144   
145      This is useful because L{IProperties} methods are often called on L{Build} 
146      and L{BuildStatus} objects without first coercing them. 
147   
148      @ivar set_runtime_properties: the default value for the C{runtime} 
149      parameter of L{setProperty}. 
150      """ 
151   
152      set_runtime_properties = False 
153   
157   
161   
162      has_key = hasProperty 
163   
164 -    def setProperty(self, propname, value, source, runtime=None): 
 169   
172   
 176   
177   
178   
180      """ 
181      Privately-used mapping object to implement WithProperties' substitutions, 
182      including the rendering of None as ''. 
183      """ 
184      colon_minus_re = re.compile(r"(.*):-(.*)") 
185      colon_tilde_re = re.compile(r"(.*):~(.*)") 
186      colon_plus_re = re.compile(r"(.*):\+(.*)") 
191   
193          properties = self.properties() 
194          assert properties is not None 
195   
196          def colon_minus(mo): 
197               
198               
199              prop, repl = mo.group(1,2) 
200              if prop in self.temp_vals: 
201                  return self.temp_vals[prop] 
202              elif properties.has_key(prop): 
203                  return properties[prop] 
204              else: 
205                  return repl 
 206   
207          def colon_tilde(mo): 
208               
209               
210              prop, repl = mo.group(1,2) 
211              if prop in self.temp_vals and self.temp_vals[prop]: 
212                  return self.temp_vals[prop] 
213              elif properties.has_key(prop) and properties[prop]: 
214                  return properties[prop] 
215              else: 
216                  return repl 
 217   
218          def colon_plus(mo): 
219               
220               
221              prop, repl = mo.group(1,2) 
222              if properties.has_key(prop) or prop in self.temp_vals: 
223                  return repl 
224              else: 
225                  return '' 
226   
227          for regexp, fn in [ 
228              ( self.colon_minus_re, colon_minus ), 
229              ( self.colon_tilde_re, colon_tilde ), 
230              ( self.colon_plus_re, colon_plus ), 
231              ]: 
232              mo = regexp.match(key) 
233              if mo: 
234                  rv = fn(mo) 
235                  break 
236          else: 
237               
238               
239              if key in self.temp_vals: 
240                  rv = self.temp_vals[key] 
241              else: 
242                  rv = properties[key] 
243   
244           
245          if rv is None: rv = '' 
246          return rv 
247   
249          'Add a temporary value (to support keyword arguments to WithProperties)' 
250          self.temp_vals[key] = val 
 251   
254   
256      """ 
257      This is a marker class, used fairly widely to indicate that we 
258      want to interpolate build properties. 
259      """ 
260   
261      implements(IRenderable) 
262      compare_attrs = ('fmtstring', 'args') 
263   
264 -    def __init__(self, fmtstring, *args, **lambda_subs): 
 265          self.fmtstring = fmtstring 
266          self.args = args 
267          if not self.args: 
268              self.lambda_subs = lambda_subs 
269              for key, val in self.lambda_subs.iteritems(): 
270                  if not callable(val): 
271                      raise ValueError('Value for lambda substitution "%s" must be callable.' % key) 
272          elif lambda_subs: 
273              raise ValueError('WithProperties takes either positional or keyword substitutions, not both.') 
 274   
 288   
289   
291      """ 
292      An instance of this class renders a property of a build. 
293      """ 
294   
295      implements(IRenderable) 
296   
297      compare_attrs = ('key','default', 'defaultWhenFalse') 
298   
299 -    def __init__(self, key, default=None, defaultWhenFalse=True): 
 300          """ 
301          @param key: Property to render. 
302          @param default: Value to use if property isn't set. 
303          @param defaultWhenFalse: When true (default), use default value 
304              if property evaluates to False. Otherwise, use default value 
305              only when property isn't set. 
306          """ 
307          self.key = key 
308          self.default = default 
309          self.defaultWhenFalse = defaultWhenFalse 
 310   
 316   
317   
319      """ 
320      Default IRenderable adaptor. Calls .getRenderingFor if availble, otherwise 
321      returns argument unchanged. 
322      """ 
323   
324      implements(IRenderable) 
325   
327          try: 
328              self.renderer = value.getRenderingFor 
329          except AttributeError: 
330              self.renderer = lambda _: value 
 331   
333          return self.renderer(build) 
  334   
335  registerAdapter(_DefaultRenderer, object, IRenderable) 
336   
337   
339      """ 
340      List IRenderable adaptor. Maps Build.render over the list. 
341      """ 
342   
343      implements(IRenderable) 
344   
347   
 350   
351  registerAdapter(_ListRenderer, list, IRenderable) 
352   
353   
355      """ 
356      Tuple IRenderable adaptor. Maps Build.render over the tuple. 
357      """ 
358   
359      implements(IRenderable) 
360   
363   
365          return tuple([ build.render(e) for e in self.value ]) 
  366   
367  registerAdapter(_TupleRenderer, tuple, IRenderable) 
368   
369   
371      """ 
372      Dict IRenderable adaptor. Maps Build.render over the keya and values in the dict. 
373      """ 
374   
375      implements(IRenderable) 
376   
379   
 382   
383   
384  registerAdapter(_DictRenderer, dict, IRenderable) 
385