OLD | NEW |
| (Empty) |
1 import re | |
2 import weakref | |
3 from buildbot import util | |
4 | |
5 class Properties(util.ComparableMixin): | |
6 """ | |
7 I represent a set of properties that can be interpolated into various | |
8 strings in buildsteps. | |
9 | |
10 @ivar properties: dictionary mapping property values to tuples | |
11 (value, source), where source is a string identifing the source | |
12 of the property. | |
13 | |
14 Objects of this class can be read like a dictionary -- in this case, | |
15 only the property value is returned. | |
16 | |
17 As a special case, a property value of None is returned as an empty | |
18 string when used as a mapping. | |
19 """ | |
20 | |
21 compare_attrs = ('properties',) | |
22 | |
23 def __init__(self, **kwargs): | |
24 """ | |
25 @param kwargs: initial property values (for testing) | |
26 """ | |
27 self.properties = {} | |
28 self.pmap = PropertyMap(self) | |
29 if kwargs: self.update(kwargs, "TEST") | |
30 | |
31 def __getstate__(self): | |
32 d = self.__dict__.copy() | |
33 del d['pmap'] | |
34 return d | |
35 | |
36 def __setstate__(self, d): | |
37 self.__dict__ = d | |
38 self.pmap = PropertyMap(self) | |
39 | |
40 def __contains__(self, name): | |
41 return name in self.properties | |
42 | |
43 def __getitem__(self, name): | |
44 """Just get the value for this property.""" | |
45 rv = self.properties[name][0] | |
46 return rv | |
47 | |
48 def has_key(self, name): | |
49 return self.properties.has_key(name) | |
50 | |
51 def getProperty(self, name, default=None): | |
52 """Get the value for the given property.""" | |
53 return self.properties.get(name, (default,))[0] | |
54 | |
55 def getPropertySource(self, name): | |
56 return self.properties[name][1] | |
57 | |
58 def asList(self): | |
59 """Return the properties as a sorted list of (name, value, source)""" | |
60 l = [ (k, v[0], v[1]) for k,v in self.properties.items() ] | |
61 l.sort() | |
62 return l | |
63 | |
64 def __repr__(self): | |
65 return repr(dict([ (k,v[0]) for k,v in self.properties.iteritems() ])) | |
66 | |
67 def setProperty(self, name, value, source): | |
68 self.properties[name] = (value, source) | |
69 | |
70 def update(self, dict, source): | |
71 """Update this object from a dictionary, with an explicit source specifi
ed.""" | |
72 for k, v in dict.items(): | |
73 self.properties[k] = (v, source) | |
74 | |
75 def updateFromProperties(self, other): | |
76 """Update this object based on another object; the other object's """ | |
77 self.properties.update(other.properties) | |
78 | |
79 def render(self, value): | |
80 """ | |
81 Return a variant of value that has any WithProperties objects | |
82 substituted. This recurses into Python's compound data types. | |
83 """ | |
84 # we use isinstance to detect Python's standard data types, and call | |
85 # this function recursively for the values in those types | |
86 if isinstance(value, (str, unicode)): | |
87 return value | |
88 elif isinstance(value, WithProperties): | |
89 return value.render(self.pmap) | |
90 elif isinstance(value, list): | |
91 return [ self.render(e) for e in value ] | |
92 elif isinstance(value, tuple): | |
93 return tuple([ self.render(e) for e in value ]) | |
94 elif isinstance(value, dict): | |
95 return dict([ (self.render(k), self.render(v)) for k,v in value.iter
items() ]) | |
96 else: | |
97 return value | |
98 | |
99 class PropertyMap: | |
100 """ | |
101 Privately-used mapping object to implement WithProperties' substitutions, | |
102 including the rendering of None as ''. | |
103 """ | |
104 colon_minus_re = re.compile(r"(.*):-(.*)") | |
105 colon_plus_re = re.compile(r"(.*):\+(.*)") | |
106 def __init__(self, properties): | |
107 # use weakref here to avoid a reference loop | |
108 self.properties = weakref.ref(properties) | |
109 | |
110 def __getitem__(self, key): | |
111 properties = self.properties() | |
112 assert properties is not None | |
113 | |
114 # %(prop:-repl)s | |
115 # if prop exists, use it; otherwise, use repl | |
116 mo = self.colon_minus_re.match(key) | |
117 if mo: | |
118 prop, repl = mo.group(1,2) | |
119 if properties.has_key(prop): | |
120 rv = properties[prop] | |
121 else: | |
122 rv = repl | |
123 else: | |
124 # %(prop:+repl)s | |
125 # if prop exists, use repl; otherwise, an empty string | |
126 mo = self.colon_plus_re.match(key) | |
127 if mo: | |
128 prop, repl = mo.group(1,2) | |
129 if properties.has_key(prop): | |
130 rv = repl | |
131 else: | |
132 rv = '' | |
133 else: | |
134 rv = properties[key] | |
135 | |
136 # translate 'None' to an empty string | |
137 if rv is None: rv = '' | |
138 return rv | |
139 | |
140 class WithProperties(util.ComparableMixin): | |
141 """ | |
142 This is a marker class, used fairly widely to indicate that we | |
143 want to interpolate build properties. | |
144 """ | |
145 | |
146 compare_attrs = ('fmtstring', 'args') | |
147 | |
148 def __init__(self, fmtstring, *args): | |
149 self.fmtstring = fmtstring | |
150 self.args = args | |
151 | |
152 def render(self, pmap): | |
153 if self.args: | |
154 strings = [] | |
155 for name in self.args: | |
156 strings.append(pmap[name]) | |
157 s = self.fmtstring % tuple(strings) | |
158 else: | |
159 s = self.fmtstring % pmap | |
160 return s | |
OLD | NEW |