587
|
1 |
#============================================================================
|
|
2 |
#Name : configuration.py
|
|
3 |
#Part of : Helium
|
|
4 |
#
|
|
5 |
#Partly Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
|
6 |
#All rights reserved.
|
|
7 |
#This component and the accompanying materials are made available
|
|
8 |
#under the terms of the License "Eclipse Public License v1.0"
|
|
9 |
#which accompanies this distribution, and is available
|
|
10 |
#at the URL "http://www.eclipse.org/legal/epl-v10.html".
|
|
11 |
#
|
|
12 |
#Initial Contributors:
|
|
13 |
#Nokia Corporation - initial contribution.
|
|
14 |
#
|
|
15 |
#Contributors:
|
|
16 |
#
|
|
17 |
#Description:
|
|
18 |
#
|
|
19 |
#==============================================================================
|
|
20 |
|
|
21 |
"""Defines an interface for accessing configurations, typically for SW builds.
|
|
22 |
|
|
23 |
This interface is generally based on the Jakarta Commons Configuration API. A
|
|
24 |
configuration is a collection of properties. Builders create Configuration
|
|
25 |
objects from various source inputs.
|
|
26 |
|
|
27 |
"""
|
|
28 |
|
|
29 |
|
|
30 |
import copy
|
|
31 |
import logging
|
|
32 |
import re
|
|
33 |
import time
|
|
34 |
import types
|
|
35 |
import UserDict
|
|
36 |
import xml.dom.minidom
|
|
37 |
|
|
38 |
|
|
39 |
|
|
40 |
_logger = logging.getLogger('configuration')
|
|
41 |
logging.basicConfig(level=logging.INFO)
|
|
42 |
|
|
43 |
class Configuration(object, UserDict.DictMixin):
|
|
44 |
""" Base Configuration object. """
|
|
45 |
|
|
46 |
key_re = re.compile(r'\${(?P<name>[._a-zA-Z0-9]+)}', re.M)
|
|
47 |
|
|
48 |
def __init__(self, data=None):
|
|
49 |
""" Initialization. """
|
|
50 |
#super(UserDict.DictMixin, self).__init__(data)
|
|
51 |
self.name = None
|
|
52 |
self.data = {}
|
|
53 |
if data is not None:
|
|
54 |
self.data.update(data)
|
|
55 |
|
|
56 |
def __contains__(self, key):
|
|
57 |
""" Check if a keys is defined in the dict. """
|
|
58 |
return self.data.__contains__(key)
|
|
59 |
|
|
60 |
def __getitem__(self, key, interpolate=True):
|
|
61 |
""" Get an item from the configuration via dictionary interface. """
|
|
62 |
if interpolate:
|
|
63 |
return self.interpolate(self.data[key])
|
|
64 |
return self.data[key]
|
|
65 |
|
|
66 |
def __setitem__(self, key, item):
|
|
67 |
""" Set an item from the configuration via dictionary interface. """
|
|
68 |
self.data[key] = item
|
|
69 |
|
|
70 |
def __delitem__(self, key):
|
|
71 |
""" Remove an item from the configuration via dictionary interface. """
|
|
72 |
del self.data[key]
|
|
73 |
|
|
74 |
def keys(self):
|
|
75 |
""" Get the list of item keys. """
|
|
76 |
return self.data.keys()
|
|
77 |
|
|
78 |
def has_key(self, key):
|
|
79 |
""" Check if key exists. """
|
|
80 |
return self.data.has_key(key)
|
|
81 |
|
|
82 |
def match_name(self, name):
|
|
83 |
""" See if the given name matches the name of this configuration. """
|
|
84 |
return self.name == name
|
|
85 |
|
|
86 |
def get(self, key, default_value):
|
|
87 |
""" Get an item from the configuration. """
|
|
88 |
try:
|
|
89 |
return self.__getitem__(key)
|
|
90 |
except KeyError:
|
|
91 |
return default_value
|
|
92 |
|
|
93 |
def get_list(self, key, default_value):
|
|
94 |
""" Get a value as a list. """
|
|
95 |
try:
|
|
96 |
itemlist = self.__getitem__(key)
|
|
97 |
if not isinstance(itemlist, types.ListType):
|
|
98 |
itemlist = [itemlist]
|
|
99 |
return itemlist
|
|
100 |
except KeyError:
|
|
101 |
return default_value
|
|
102 |
|
|
103 |
def get_int(self, key, default_value):
|
|
104 |
""" Get a value as an int. """
|
|
105 |
try:
|
|
106 |
value = self.__getitem__(key)
|
|
107 |
return int(value)
|
|
108 |
except KeyError:
|
|
109 |
return default_value
|
|
110 |
|
|
111 |
def get_boolean(self, key, default_value):
|
|
112 |
""" Get a value as a boolean. """
|
|
113 |
try:
|
|
114 |
value = self.__getitem__(key)
|
|
115 |
return value == "true" or value == "yes" or value == "1"
|
|
116 |
except KeyError:
|
|
117 |
return default_value
|
|
118 |
|
|
119 |
def interpolate(self, value):
|
|
120 |
""" Search for patterns of the form '${..}' and insert matching values. """
|
|
121 |
if isinstance(value, types.ListType):
|
|
122 |
value = [self.interpolate(elem) for elem in value]
|
|
123 |
else:
|
|
124 |
if isinstance(value, types.StringType) or \
|
|
125 |
isinstance(value, types.UnicodeType) or \
|
|
126 |
isinstance(value, types.ListType):
|
|
127 |
for match in self.key_re.finditer(value):
|
|
128 |
for property_name in match.groups():
|
|
129 |
if self.has_key(property_name):
|
|
130 |
# See if interpolation may cause infinite recursion
|
|
131 |
raw_property_value = self.__getitem__(property_name, False)
|
|
132 |
#print 'raw_property_value: ' + raw_property_value
|
|
133 |
if raw_property_value == None:
|
|
134 |
raw_property_value = ''
|
|
135 |
if isinstance(raw_property_value, types.ListType):
|
|
136 |
for prop in raw_property_value:
|
|
137 |
if re.search('\${' + property_name + '}', prop) != None:
|
|
138 |
raise Exception("Key '%s' will cause recursive interpolation with value %s" % (property_name, raw_property_value))
|
|
139 |
else:
|
|
140 |
if re.search('\${' + property_name + '}', raw_property_value) != None:
|
|
141 |
raise Exception("Key '%s' will cause recursive interpolation with value %s" % (property_name, raw_property_value))
|
|
142 |
|
|
143 |
# Get the property value
|
|
144 |
property_value = self.__getitem__(property_name)
|
|
145 |
if isinstance(property_value, types.ListType):
|
|
146 |
property_value = ",".join(property_value)
|
|
147 |
else:
|
|
148 |
property_value = re.sub(r'\\', r'\\\\', property_value, re.M)
|
|
149 |
value = re.sub('\${' + property_name + '}', property_value, value, re.M)
|
|
150 |
return value
|
|
151 |
|
|
152 |
def __str__(self):
|
|
153 |
""" A string representation. """
|
|
154 |
return self.__class__.__name__ + '[' + str(self.name) + ']'
|
|
155 |
|
|
156 |
def __cmp__(self, other):
|
|
157 |
""" Compare with another object. """
|
|
158 |
return cmp(self.__str__, other.__str__)
|
|
159 |
|
|
160 |
|
|
161 |
class PropertiesConfiguration(Configuration):
|
|
162 |
""" A Configuration that parses a plain text properties file.
|
|
163 |
|
|
164 |
This typically follows the java.util.Properties format.
|
|
165 |
|
|
166 |
Note: This code is mostly based on this recipe
|
|
167 |
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496795.
|
|
168 |
Copyright (c) Anand Balachandran Pillai
|
|
169 |
"""
|
|
170 |
def __init__(self, stream=None, data=None):
|
|
171 |
Configuration.__init__(self, data)
|
|
172 |
|
|
173 |
self.othercharre = re.compile(r'(?<!\\)(\s*\=)|(?<!\\)(\s*\:)')
|
|
174 |
self.othercharre2 = re.compile(r'(\s*\=)|(\s*\:)')
|
|
175 |
self.bspacere = re.compile(r'\\(?!\s$)')
|
|
176 |
|
|
177 |
if stream is not None:
|
|
178 |
self.load(stream)
|
|
179 |
|
|
180 |
# def __str__(self):
|
|
181 |
# s='{'
|
|
182 |
# for key,value in self.data.items():
|
|
183 |
# s = ''.join((s,key,'=',value,', '))
|
|
184 |
#
|
|
185 |
# s=''.join((s[:-2],'}'))
|
|
186 |
# return s
|
|
187 |
|
|
188 |
def __parse(self, lines):
|
|
189 |
""" Parse a list of lines and create
|
|
190 |
an internal property dictionary """
|
|
191 |
|
|
192 |
# Every line in the file must consist of either a comment
|
|
193 |
# or a key-value pair. A key-value pair is a line consisting
|
|
194 |
# of a key which is a combination of non-white space characters
|
|
195 |
# The separator character between key-value pairs is a '=',
|
|
196 |
# ':' or a whitespace character not including the newline.
|
|
197 |
# If the '=' or ':' characters are found, in the line, even
|
|
198 |
# keys containing whitespace chars are allowed.
|
|
199 |
|
|
200 |
# A line with only a key according to the rules above is also
|
|
201 |
# fine. In such case, the value is considered as the empty string.
|
|
202 |
# In order to include characters '=' or ':' in a key or value,
|
|
203 |
# they have to be properly escaped using the backslash character.
|
|
204 |
|
|
205 |
# Some examples of valid key-value pairs:
|
|
206 |
#
|
|
207 |
# key value
|
|
208 |
# key=value
|
|
209 |
# key:value
|
|
210 |
# key value1,value2,value3
|
|
211 |
# key value1,value2,value3 \
|
|
212 |
# value4, value5
|
|
213 |
# key
|
|
214 |
# This key= this value
|
|
215 |
# key = value1 value2 value3
|
|
216 |
|
|
217 |
# Any line that starts with a '#' is considerered a comment
|
|
218 |
# and skipped. Also any trailing or preceding whitespaces
|
|
219 |
# are removed from the key/value.
|
|
220 |
|
|
221 |
# This is a line parser. It parses the
|
|
222 |
# contents like by line.
|
|
223 |
|
|
224 |
lineno = 0
|
|
225 |
i = iter(lines)
|
|
226 |
|
|
227 |
for line in i:
|
|
228 |
lineno += 1
|
|
229 |
line = line.strip()
|
|
230 |
# Skip null lines
|
|
231 |
if not line:
|
|
232 |
continue
|
|
233 |
# Skip lines which are comments
|
|
234 |
if line[0] == '#':
|
|
235 |
continue
|
|
236 |
|
|
237 |
# Position of first separation char
|
|
238 |
sepidx = -1
|
|
239 |
# A flag for performing wspace re check
|
|
240 |
#flag = 0
|
|
241 |
# Check for valid space separation
|
|
242 |
# First obtain the max index to which we
|
|
243 |
# can search.
|
|
244 |
m_index = self.othercharre.search(line)
|
|
245 |
if m_index:
|
|
246 |
first, last = m_index.span()
|
|
247 |
start, end = 0, first
|
|
248 |
#flag = 1
|
|
249 |
wspacere = re.compile(r'(?<![\\\=\:])(\s)')
|
|
250 |
else:
|
|
251 |
if self.othercharre2.search(line):
|
|
252 |
# Check if either '=' or ':' is present
|
|
253 |
# in the line. If they are then it means
|
|
254 |
# they are preceded by a backslash.
|
|
255 |
|
|
256 |
# This means, we need to modify the
|
|
257 |
# wspacere a bit, not to look for
|
|
258 |
# : or = characters.
|
|
259 |
wspacere = re.compile(r'(?<![\\])(\s)')
|
|
260 |
start, end = 0, len(line)
|
|
261 |
|
|
262 |
m2_index = wspacere.search(line, start, end)
|
|
263 |
if m2_index:
|
|
264 |
# print 'Space match=>',line
|
|
265 |
# Means we need to split by space.
|
|
266 |
first, last = m2_index.span()
|
|
267 |
sepidx = first
|
|
268 |
elif m_index:
|
|
269 |
# print 'Other match=>',line
|
|
270 |
# No matching wspace char found, need
|
|
271 |
# to split by either '=' or ':'
|
|
272 |
first, last = m_index.span()
|
|
273 |
sepidx = last - 1
|
|
274 |
# print line[sepidx]
|
|
275 |
|
|
276 |
|
|
277 |
# If the last character is a backslash
|
|
278 |
# it has to be preceded by a space in which
|
|
279 |
# case the next line is read as part of the
|
|
280 |
# same property
|
|
281 |
while line[-1] == '\\':
|
|
282 |
# Read next line
|
|
283 |
try:
|
|
284 |
nextline = i.next()
|
|
285 |
nextline = nextline.strip()
|
|
286 |
lineno += 1
|
|
287 |
# This line will become part of the value
|
|
288 |
line = line[:-1] + nextline
|
|
289 |
except StopIteration:
|
|
290 |
break
|
|
291 |
|
|
292 |
# Now split to key,value according to separation char
|
|
293 |
if sepidx != -1:
|
|
294 |
key, value = line[:sepidx], line[sepidx+1:]
|
|
295 |
else:
|
|
296 |
key, value = line,''
|
|
297 |
|
|
298 |
self.processPair(key, value)
|
|
299 |
|
|
300 |
def processPair(self, key, value):
|
|
301 |
""" Process a (key, value) pair """
|
|
302 |
|
|
303 |
oldkey = key
|
|
304 |
oldvalue = value
|
|
305 |
|
|
306 |
# Create key intelligently
|
|
307 |
keyparts = self.bspacere.split(key)
|
|
308 |
# print keyparts
|
|
309 |
|
|
310 |
strippable = False
|
|
311 |
lastpart = keyparts[-1]
|
|
312 |
|
|
313 |
if lastpart.find('\\ ') != -1:
|
|
314 |
keyparts[-1] = lastpart.replace('\\','')
|
|
315 |
|
|
316 |
# If no backspace is found at the end, but empty
|
|
317 |
# space is found, strip it
|
|
318 |
elif lastpart and lastpart[-1] == ' ':
|
|
319 |
strippable = True
|
|
320 |
|
|
321 |
key = ''.join(keyparts)
|
|
322 |
if strippable:
|
|
323 |
key = key.strip()
|
|
324 |
oldkey = oldkey.strip()
|
|
325 |
|
|
326 |
oldvalue = self.unescape(oldvalue)
|
|
327 |
value = self.unescape(value)
|
|
328 |
|
|
329 |
self.data[key] = value.strip()
|
|
330 |
|
|
331 |
# # Check if an entry exists in pristine keys
|
|
332 |
# if self._keymap.has_key(key):
|
|
333 |
# oldkey = self._keymap.get(key)
|
|
334 |
# self._origprops[oldkey] = oldvalue.strip()
|
|
335 |
# else:
|
|
336 |
# self._origprops[oldkey] = oldvalue.strip()
|
|
337 |
# # Store entry in keymap
|
|
338 |
# self._keymap[key] = oldkey
|
|
339 |
|
|
340 |
def escape(self, value):
|
|
341 |
"""Java escapes the '=' and ':' in the value
|
|
342 |
string with backslashes in the store method.
|
|
343 |
So let us do the same."""
|
|
344 |
newvalue = value.replace(':','\:')
|
|
345 |
newvalue = newvalue.replace('=','\=')
|
|
346 |
|
|
347 |
return newvalue
|
|
348 |
|
|
349 |
def unescape(self, value):
|
|
350 |
""" Reverse of escape"""
|
|
351 |
newvalue = value.replace('\:',':')
|
|
352 |
newvalue = newvalue.replace('\=','=')
|
|
353 |
|
|
354 |
return newvalue
|
|
355 |
|
|
356 |
def load(self, stream):
|
|
357 |
""" Load properties from an open file stream """
|
|
358 |
|
|
359 |
# For the time being only accept file input streams
|
|
360 |
if not(hasattr(stream, 'readlines') and callable(stream.readlines)):
|
|
361 |
raise TypeError,'Argument should be a file object!'
|
|
362 |
# Check for the opened mode
|
|
363 |
if hasattr(stream, 'mode') and stream.mode != 'r':
|
|
364 |
raise ValueError,'Stream should be opened in read-only mode!'
|
|
365 |
|
|
366 |
try:
|
|
367 |
lines = stream.readlines()
|
|
368 |
self.__parse(lines)
|
|
369 |
except IOError:
|
|
370 |
raise
|
|
371 |
|
|
372 |
def store(self, out):
|
|
373 |
""" Serialize the properties back to a file. """
|
|
374 |
|
|
375 |
if out.mode[0] != 'w':
|
|
376 |
raise ValueError, 'Stream should be opened in write mode!'
|
|
377 |
|
|
378 |
try:
|
|
379 |
# Write timestamp
|
|
380 |
out.write(''.join(('# ', time.strftime('%a %b %d %H:%M:%S %Z %Y', time.localtime()), '\n')))
|
|
381 |
|
|
382 |
# Write properties from the dictionary
|
|
383 |
for key in self.data.keys():
|
|
384 |
value = self.data[key]
|
|
385 |
out.write(''.join((key, '=', self.escape(value), '\n')))
|
|
386 |
|
|
387 |
out.close()
|
|
388 |
except IOError:
|
|
389 |
raise
|
|
390 |
|
|
391 |
|
|
392 |
class NestedConfiguration(Configuration):
|
|
393 |
""" A nested configuration that may have a parent or child configurations. """
|
|
394 |
def __init__(self):
|
|
395 |
""" Initialization. """
|
|
396 |
Configuration.__init__(self, None)
|
|
397 |
self.parent = None
|
|
398 |
self.type = None
|
|
399 |
self.abstract = None
|
|
400 |
|
|
401 |
def isBuildable(self):
|
|
402 |
""" Is this a buildable configuration? """
|
|
403 |
return self.abstract == None
|
|
404 |
|
|
405 |
def _addPropertyValue(self, key, value, parseList=True):
|
|
406 |
"""Adds a property value to the configuration.
|
|
407 |
|
|
408 |
If the property does not exist, it is added without modification.
|
|
409 |
If there is already a single value matching the key, the value is replaced by a list
|
|
410 |
containing the original and new values.
|
|
411 |
If there is already a list, the new value is added to the list.
|
|
412 |
|
|
413 |
The value is first processed in case it also represents a list of values,
|
|
414 |
e.g. comma-separated values.
|
|
415 |
"""
|
|
416 |
if parseList and value.find(',') != -1:
|
|
417 |
value = value.split(',')
|
|
418 |
# Remove all whitespace
|
|
419 |
value = [v.strip() for v in value]
|
|
420 |
|
|
421 |
if key in self.data:
|
|
422 |
currentValue = self.data[key]
|
|
423 |
|
|
424 |
# Make sure current value is a list
|
|
425 |
if not isinstance(currentValue, types.ListType):
|
|
426 |
currentValue = [currentValue]
|
|
427 |
|
|
428 |
# Add new value(s)
|
|
429 |
if isinstance(value, types.ListType):
|
|
430 |
currentValue.extend(value)
|
|
431 |
else:
|
|
432 |
currentValue.append(value)
|
|
433 |
self.data[key] = currentValue
|
|
434 |
else:
|
|
435 |
self.data[key] = value
|
|
436 |
|
|
437 |
def __getitem__(self, key, interpolate=True):
|
|
438 |
""" Get an item. """
|
|
439 |
#print "__getitem__(%s, %s)" % (self.name, key)
|
|
440 |
if self.data.has_key(key):
|
|
441 |
value = super(NestedConfiguration, self).__getitem__(key, False)
|
|
442 |
if interpolate:
|
|
443 |
return self.interpolate(value)
|
|
444 |
return value
|
|
445 |
elif self.parent != None:
|
|
446 |
value = self.parent.__getitem__(key, False)
|
|
447 |
if interpolate:
|
|
448 |
return self.interpolate(value)
|
|
449 |
return value
|
|
450 |
raise KeyError('Cannot find key: ' + key)
|
|
451 |
|
|
452 |
def __setitem__(self, key, item):
|
|
453 |
""" Set the value of an item. """
|
|
454 |
self.data[key] = item
|
|
455 |
|
|
456 |
def __delitem__(self, key):
|
|
457 |
""" Remove an item. """
|
|
458 |
del self.data[key]
|
|
459 |
|
|
460 |
def __contains__(self, key):
|
|
461 |
""" Check if a keys is defined in the dict. """
|
|
462 |
if self.data.__contains__(key):
|
|
463 |
return True
|
|
464 |
elif self.parent:
|
|
465 |
return self.parent.__contains__(key)
|
|
466 |
return False
|
|
467 |
|
|
468 |
def keys(self):
|
|
469 |
""" Get the list of item keys. """
|
|
470 |
myKeys = self.data.keys()
|
|
471 |
if self.parent != None:
|
|
472 |
parentKeys = self.parent.keys()
|
|
473 |
for key in parentKeys:
|
|
474 |
if not key in myKeys:
|
|
475 |
myKeys.append(key)
|
|
476 |
return myKeys
|
|
477 |
|
|
478 |
def has_key(self, key):
|
|
479 |
""" Check if key exists. """
|
|
480 |
if self.data.has_key(key):
|
|
481 |
return True
|
|
482 |
if self.parent != None:
|
|
483 |
return self.parent.has_key(key)
|
|
484 |
return False
|
|
485 |
|
|
486 |
def match_name(self, name):
|
|
487 |
""" See if the configuration name matches the argument. """
|
|
488 |
if self.name == name:
|
|
489 |
return True
|
|
490 |
if self.parent != None:
|
|
491 |
return self.parent.match_name(name)
|
|
492 |
return False
|
|
493 |
|
|
494 |
|
|
495 |
class Specification(NestedConfiguration):
|
|
496 |
""" Deprecated. This should be removed in future, it adds no value. """
|
|
497 |
|
|
498 |
def __init__(self):
|
|
499 |
""" Initialization. """
|
|
500 |
NestedConfiguration.__init__(self)
|
|
501 |
|
|
502 |
|
|
503 |
class ConfigurationSet(Configuration):
|
|
504 |
"""A ConfigurationSet represents a set of configurations.
|
|
505 |
|
|
506 |
Each configuration should be processed separately. This is matching
|
|
507 |
the Raptor model where a single XML file can contain definitions
|
|
508 |
of multiple specifications and configurations.
|
|
509 |
|
|
510 |
It is however somewhat different from the Commons Configuration classes
|
|
511 |
that combine configurations, e.g. CombinedConfiguration,
|
|
512 |
CompositeConfiguration. These act to combine configurations in a way
|
|
513 |
such that a single configuration interface is still presented to the
|
|
514 |
client.
|
|
515 |
"""
|
|
516 |
|
|
517 |
def __init__(self, configs):
|
|
518 |
""" Initialization. """
|
|
519 |
Configuration.__init__(self)
|
|
520 |
self._configs = configs
|
|
521 |
|
|
522 |
def getConfigurations(self, name=None, type=None):
|
|
523 |
""" Return a list of configs that matches the name and type specified.
|
|
524 |
|
|
525 |
This can be queried multiple times to retrieve different named configurations.
|
|
526 |
"""
|
|
527 |
result = []
|
|
528 |
for conf in self._configs:
|
|
529 |
if ((name != None and conf.match_name(name)) or name == None) and ((type != None and conf.type == type) or type == None):
|
|
530 |
result.append(conf)
|
|
531 |
return result
|
|
532 |
|
|
533 |
|
|
534 |
class ConfigurationBuilder(object):
|
|
535 |
""" Base class for builders that can create Configuration objects. """
|
|
536 |
|
|
537 |
def getConfiguration(self):
|
|
538 |
"""Returns a Configuration object."""
|
|
539 |
raise NotImplementedError
|
|
540 |
|
|
541 |
|
|
542 |
class NestedConfigurationBuilder(ConfigurationBuilder):
|
|
543 |
""" Builder for building Configuration objects from nested configurations. """
|
|
544 |
|
|
545 |
_constructors = {'spec':Specification, 'config':NestedConfiguration}
|
|
546 |
|
|
547 |
def __init__(self, inputfile, configname=''):
|
|
548 |
""" Initialization. """
|
|
549 |
self.inputfile = inputfile
|
|
550 |
self.configname = configname
|
|
551 |
self._warn_on_deprecated_spec = False
|
|
552 |
|
|
553 |
def getConfiguration(self):
|
|
554 |
""" Returns a ConfigurationSet object.
|
|
555 |
|
|
556 |
A ConfigurationSet represents a number of Configuration objects
|
|
557 |
that all may need to be processed.
|
|
558 |
"""
|
|
559 |
try:
|
|
560 |
dom = xml.dom.minidom.parse(self.inputfile)
|
|
561 |
except Exception, exc:
|
|
562 |
raise Exception("XML file '%s' cannot be parsed properly: %s" % (self.inputfile, exc))
|
|
563 |
|
|
564 |
# The root element is typically <build> but can be anything
|
|
565 |
self.rootNode = dom.documentElement
|
|
566 |
configs = []
|
|
567 |
|
|
568 |
# Create a flat list of buildable configurations
|
|
569 |
for child in self.rootNode.childNodes:
|
|
570 |
if child.nodeType == xml.dom.Node.ELEMENT_NODE:
|
|
571 |
_logger.debug('Parsing children')
|
|
572 |
self.parseConfiguration(child, configs)
|
|
573 |
|
|
574 |
# Add configuration references
|
|
575 |
references = []
|
|
576 |
for reference in self.getReferences():
|
|
577 |
for conf in configs:
|
|
578 |
if conf.match_name(reference[1]):
|
|
579 |
newConf = copy.deepcopy(conf)
|
|
580 |
newConf.name = reference[0]
|
|
581 |
references.append(newConf)
|
|
582 |
|
|
583 |
configs = configs + references
|
|
584 |
|
|
585 |
dom.unlink()
|
|
586 |
_logger.debug('Set of configs: ' + str(configs))
|
|
587 |
|
|
588 |
if self._warn_on_deprecated_spec:
|
|
589 |
_logger.warning("Use of deprecated 'spec' element name in this configuration. Please rename to config")
|
|
590 |
return ConfigurationSet(configs)
|
|
591 |
|
|
592 |
def writeToXML(self, output, config_list, config_name=None):
|
|
593 |
"""write XML"""
|
|
594 |
document = """
|
|
595 |
<build>
|
|
596 |
</build>"""
|
|
597 |
doc = xml.dom.minidom.parseString(document)
|
|
598 |
docRootNode = doc.documentElement
|
|
599 |
configNode = doc.createElement( 'config')
|
|
600 |
docRootNode.appendChild(configNode)
|
|
601 |
if config_name is not None:
|
|
602 |
configNode.setAttribute( 'name', config_name)
|
|
603 |
configNode.setAttribute( 'abstract', 'true')
|
|
604 |
|
|
605 |
for config in config_list:
|
|
606 |
configSubNode = doc.createElement( 'config')
|
|
607 |
configNode.appendChild(configSubNode)
|
|
608 |
if config.name is not None:
|
|
609 |
configSubNode.setAttribute( 'name', config.name)
|
|
610 |
|
|
611 |
for key in config.keys():
|
|
612 |
if type(config.__getitem__(key)) == types.ListType:
|
|
613 |
for i in range(len(config.__getitem__(key))):
|
|
614 |
setNode = doc.createElement( 'set')
|
|
615 |
configSubNode.appendChild(setNode)
|
|
616 |
setNode.setAttribute( 'name', key)
|
|
617 |
setNode.setAttribute( 'value', config.__getitem__(key)[i])
|
|
618 |
else:
|
|
619 |
setNode = doc.createElement( 'set')
|
|
620 |
configSubNode.appendChild(setNode)
|
|
621 |
setNode.setAttribute( 'name', key)
|
|
622 |
setNode.setAttribute( 'value', config.__getitem__(key))
|
|
623 |
out = open(output, 'w+')
|
|
624 |
out.write(doc.toprettyxml())
|
|
625 |
out.close()
|
|
626 |
|
|
627 |
|
|
628 |
def getConfigurations(self, name=None, type=None):
|
|
629 |
""" Get a list of the individual configurations.
|
|
630 |
|
|
631 |
Once read a new builder must be opened to retrieve a differently filtered set of configurations.
|
|
632 |
"""
|
|
633 |
config_set = self.getConfiguration()
|
|
634 |
return config_set.getConfigurations(name, type)
|
|
635 |
|
|
636 |
def getReferences(self):
|
|
637 |
"""get references"""
|
|
638 |
references = []
|
|
639 |
for rootNode in self.rootNode.childNodes:
|
|
640 |
if rootNode.nodeType == xml.dom.Node.ELEMENT_NODE:
|
|
641 |
for child in rootNode.childNodes:
|
|
642 |
if child.nodeType == xml.dom.Node.ELEMENT_NODE:
|
|
643 |
for conf in child.childNodes:
|
|
644 |
if conf.nodeName == 'specRef':
|
|
645 |
for ref in conf.getAttribute('ref').split(','):
|
|
646 |
if not ( child.getAttribute('abstract') and str(self.configname) == '' ):
|
|
647 |
references.append([child.getAttribute('name'), ref])
|
|
648 |
return references
|
|
649 |
|
|
650 |
def parseConfiguration(self, configNode, configs, parentConfig=None):
|
|
651 |
""" Parse an individual nested configuration. """
|
|
652 |
# Create appropriate config object
|
|
653 |
if configNode.nodeName == 'spec':
|
|
654 |
self._warn_on_deprecated_spec = True
|
|
655 |
constructor = self._constructors[configNode.nodeName]
|
|
656 |
config = constructor()
|
|
657 |
_logger.debug('Configuration created: ' + str(config))
|
|
658 |
if parentConfig != None:
|
|
659 |
config.parent = parentConfig
|
|
660 |
#config.data.update(parentConfig.data)
|
|
661 |
|
|
662 |
# Add any attribute properties
|
|
663 |
for i in range(configNode.attributes.length):
|
|
664 |
attribute = configNode.attributes.item(i)
|
|
665 |
if hasattr(config, attribute.name):
|
|
666 |
_logger.debug('Adding config attribute: ' + str(attribute.name))
|
|
667 |
setattr(config, str(attribute.name), attribute.nodeValue)
|
|
668 |
else:
|
|
669 |
raise Exception('Invalid attribute for configuration: ' + attribute.name)
|
|
670 |
|
|
671 |
# Process the config element's children
|
|
672 |
configChildNodes = []
|
|
673 |
|
|
674 |
for child in configNode.childNodes:
|
|
675 |
if child.nodeType == xml.dom.Node.ELEMENT_NODE:
|
|
676 |
# <append> directives should add to parent values. In
|
|
677 |
# this case initially set the value to the parent value.
|
|
678 |
if child.nodeName == 'append':
|
|
679 |
name = child.getAttribute('name')
|
|
680 |
if parentConfig != None and parentConfig.has_key(name):
|
|
681 |
parent_value = parentConfig.__getitem__(name, False)
|
|
682 |
if not isinstance(parent_value, types.ListType):
|
|
683 |
parent_value = [parent_value]
|
|
684 |
for value in parent_value:
|
|
685 |
config._addPropertyValue(name, value)
|
|
686 |
|
|
687 |
if child.nodeName == 'set' or child.nodeName == 'append':
|
|
688 |
name = child.getAttribute('name')
|
|
689 |
if child.hasAttribute('value'):
|
|
690 |
value = child.getAttribute('value')
|
|
691 |
config._addPropertyValue(name, value)
|
|
692 |
elif child.hasChildNodes():
|
|
693 |
value = ""
|
|
694 |
for textchild in child.childNodes:
|
|
695 |
value += textchild.data
|
|
696 |
config._addPropertyValue(name, value, False)
|
|
697 |
elif child.nodeName == 'specRef':
|
|
698 |
for ref in child.getAttribute('ref').split(','):
|
|
699 |
node = self.getNodeByReference(ref)
|
|
700 |
if not node:
|
|
701 |
raise Exception('Referenced spec not found: ' + ref)
|
|
702 |
elif self._constructors.has_key(child.nodeName):
|
|
703 |
configChildNodes.append(child)
|
|
704 |
else:
|
|
705 |
raise Exception('Bad configuration xml element: ' + child.nodeName)
|
|
706 |
|
|
707 |
# Only save the buildable configurations
|
|
708 |
if config.isBuildable():
|
|
709 |
_logger.debug('Adding config to buildable set: ' + str(config))
|
|
710 |
configs.append(config)
|
|
711 |
|
|
712 |
for childConfigNode in configChildNodes:
|
|
713 |
self.parseConfiguration(childConfigNode, configs, config)
|
|
714 |
|
|
715 |
def getNodeByReference(self, refName):
|
|
716 |
""" Find a node based on a reference to it. """
|
|
717 |
for child in self.rootNode.childNodes:
|
|
718 |
if child.nodeType == xml.dom.Node.ELEMENT_NODE:
|
|
719 |
for conf in child.childNodes:
|
|
720 |
if conf.nodeName == 'spec':
|
|
721 |
if refName == conf.getAttribute('name'):
|
|
722 |
return conf
|
|
723 |
|
|
724 |
|
|
725 |
class HierarchicalConfiguration(Configuration):
|
|
726 |
""" Represents hierarchical configurations such as XML documents. """
|
|
727 |
|
|
728 |
def __init__(self):
|
|
729 |
""" Initialization. """
|
|
730 |
Configuration.__init__(self, None)
|
|
731 |
self._root = None
|
|
732 |
|
|
733 |
def __getitem__(self, key, interpolate=True):
|
|
734 |
""" Get an item as a dict. """
|
|
735 |
elements = self._root.xpath(_Key(key).to_xpath())
|
|
736 |
values = [element.text for element in elements]
|
|
737 |
value = ','.join(values)
|
|
738 |
if interpolate:
|
|
739 |
value = self.interpolate(value)
|
|
740 |
return value
|
|
741 |
|
|
742 |
def has_key(self, key):
|
|
743 |
""" Check if key exists. """
|
|
744 |
elements = self._root.xpath(_Key(key).to_xpath())
|
|
745 |
if len(elements) > 0:
|
|
746 |
return True
|
|
747 |
return False
|
|
748 |
|
|
749 |
|
|
750 |
class _Key(object):
|
|
751 |
""" A hierarchical configuration key. """
|
|
752 |
|
|
753 |
def __init__(self, string):
|
|
754 |
""" Initialization. """
|
|
755 |
self.string = string
|
|
756 |
|
|
757 |
def to_xpath(self):
|
|
758 |
""" Convert the key to XPath syntax. """
|
|
759 |
return self.string.replace('.', '/')
|
|
760 |
|
|
761 |
|
|
762 |
class XMLConfiguration(HierarchicalConfiguration):
|
|
763 |
""" A XML-based hierarchical configuration. """
|
|
764 |
|
|
765 |
def __init__(self, file_):
|
|
766 |
""" Initialization. """
|
|
767 |
from lxml import etree
|
|
768 |
HierarchicalConfiguration.__init__(self)
|
|
769 |
|
|
770 |
self._root = etree.parse(file_)
|