+ 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
+ 23This interface is generally based on the Jakarta Commons Configuration API. A
+ 24configuration is a collection of properties. Builders create Configuration
+ 25objects from various source inputs.
+ 26
+ 27"""
+ 28
+ 29
+ 30importcopy
+ 31importlogging
+ 32importos
+ 33importre
+ 34importsys
+ 35importtime
+ 36importtypes
+ 37importUserDict
+ 38importxml.dom.minidom
+ 39
+ 40
+ 41_logger=logging.getLogger('configuration')
+ 42logging.basicConfig(level=logging.INFO)
+ 43
+
57""" Get an item from the configuration via dictionary interface. """
+ 58ifinterpolate:
+ 59returnself.interpolate(self.data[key])
+ 60returnself.data[key]
+
132""" A Configuration that parses a plain text properties file.
+133
+134 This typically follows the java.util.Properties format.
+135
+136 Note: This code is mostly based on this recipe
+137 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496795.
+138 Copyright (c) Anand Balachandran Pillai
+139 """
+
159""" Parse a list of lines and create
+160 an internal property dictionary """
+161
+162# Every line in the file must consist of either a comment
+163# or a key-value pair. A key-value pair is a line consisting
+164# of a key which is a combination of non-white space characters
+165# The separator character between key-value pairs is a '=',
+166# ':' or a whitespace character not including the newline.
+167# If the '=' or ':' characters are found, in the line, even
+168# keys containing whitespace chars are allowed.
+169
+170# A line with only a key according to the rules above is also
+171# fine. In such case, the value is considered as the empty string.
+172# In order to include characters '=' or ':' in a key or value,
+173# they have to be properly escaped using the backslash character.
+174
+175# Some examples of valid key-value pairs:
+176#
+177# key value
+178# key=value
+179# key:value
+180# key value1,value2,value3
+181# key value1,value2,value3 \
+182# value4, value5
+183# key
+184# This key= this value
+185# key = value1 value2 value3
+186
+187# Any line that starts with a '#' is considerered a comment
+188# and skipped. Also any trailing or preceding whitespaces
+189# are removed from the key/value.
+190
+191# This is a line parser. It parses the
+192# contents like by line.
+193
+194lineno=0
+195i=iter(lines)
+196
+197forlineini:
+198lineno+=1
+199line=line.strip()
+200# Skip null lines
+201ifnotline:continue
+202# Skip lines which are comments
+203ifline[0]=='#':continue
+204
+205# Position of first separation char
+206sepidx=-1
+207# A flag for performing wspace re check
+208#flag = 0
+209# Check for valid space separation
+210# First obtain the max index to which we
+211# can search.
+212m=self.othercharre.search(line)
+213ifm:
+214first,last=m.span()
+215start,end=0,first
+216#flag = 1
+217wspacere=re.compile(r'(?<![\\\=\:])(\s)')
+218else:
+219ifself.othercharre2.search(line):
+220# Check if either '=' or ':' is present
+221# in the line. If they are then it means
+222# they are preceded by a backslash.
+223
+224# This means, we need to modify the
+225# wspacere a bit, not to look for
+226# : or = characters.
+227wspacere=re.compile(r'(?<![\\])(\s)')
+228start,end=0,len(line)
+229
+230m2=wspacere.search(line,start,end)
+231ifm2:
+232# print 'Space match=>',line
+233# Means we need to split by space.
+234first,last=m2.span()
+235sepidx=first
+236elifm:
+237# print 'Other match=>',line
+238# No matching wspace char found, need
+239# to split by either '=' or ':'
+240first,last=m.span()
+241sepidx=last-1
+242# print line[sepidx]
+243
+244
+245# If the last character is a backslash
+246# it has to be preceded by a space in which
+247# case the next line is read as part of the
+248# same property
+249whileline[-1]=='\\':
+250# Read next line
+251try:
+252nextline=i.next()
+253nextline=nextline.strip()
+254lineno+=1
+255# This line will become part of the value
+256line=line[:-1]+nextline
+257exceptStopIteration:
+258break
+259
+260# Now split to key,value according to separation char
+261ifsepidx!=-1:
+262key,value=line[:sepidx],line[sepidx+1:]
+263else:
+264key,value=line,''
+265
+266self.processPair(key,value)
+
269""" Process a (key, value) pair """
+270
+271oldkey=key
+272oldvalue=value
+273
+274# Create key intelligently
+275keyparts=self.bspacere.split(key)
+276# print keyparts
+277
+278strippable=False
+279lastpart=keyparts[-1]
+280
+281iflastpart.find('\\ ')!=-1:
+282keyparts[-1]=lastpart.replace('\\','')
+283
+284# If no backspace is found at the end, but empty
+285# space is found, strip it
+286eliflastpartandlastpart[-1]==' ':
+287strippable=True
+288
+289key=''.join(keyparts)
+290ifstrippable:
+291key=key.strip()
+292oldkey=oldkey.strip()
+293
+294oldvalue=self.unescape(oldvalue)
+295value=self.unescape(value)
+296
+297self.data[key]=value.strip()
+
298
+299# # Check if an entry exists in pristine keys
+300# if self._keymap.has_key(key):
+301# oldkey = self._keymap.get(key)
+302# self._origprops[oldkey] = oldvalue.strip()
+303# else:
+304# self._origprops[oldkey] = oldvalue.strip()
+305# # Store entry in keymap
+306# self._keymap[key] = oldkey
+307
+
309
+310# Java escapes the '=' and ':' in the value
+311# string with backslashes in the store method.
+312# So let us do the same.
+313newvalue=value.replace(':','\:')
+314newvalue=newvalue.replace('=','\=')
+315
+316returnnewvalue
+
327""" Load properties from an open file stream """
+328
+329# For the time being only accept file input streams
+330ifnot(hasattr(stream,'readlines')andcallable(stream.readlines)):
+331raiseTypeError,'Argument should be a file object!'
+332# Check for the opened mode
+333ifhasattr(stream,'mode')andstream.mode!='r':
+334raiseValueError,'Stream should be opened in read-only mode!'
+335
+336try:
+337lines=stream.readlines()
+338self.__parse(lines)
+339exceptIOError:
+340raise
+
343""" Serialize the properties back to a file. """
+344
+345ifout.mode[0]!='w':
+346raiseValueError,'Stream should be opened in write mode!'
+347
+348try:
+349# Write timestamp
+350out.write(''.join(('# ',time.strftime('%a %b %d %H:%M:%S %Z %Y',time.localtime()),'\n')))
+351
+352# Write properties from the dictionary
+353forkeyinself.data.keys():
+354value=self.data[key]
+355out.write(''.join((key,'=',self.escape(value),'\n')))
+356
+357out.close()
+358exceptIOError,e:
+359raise
+
374"""Adds a property value to the configuration.
+375
+376 If the property does not exist, it is added without modification.
+377 If there is already a single value matching the key, the value is replaced by a list
+378 containing the original and new values.
+379 If there is already a list, the new value is added to the list.
+380
+381 The value is first processed in case it also represents a list of values,
+382 e.g. comma-separated values.
+383 """
+384ifparseListandvalue.find(',')!=-1:
+385value=value.split(',')
+386# Remove all whitespace
+387value=[v.strip()forvinvalue]
+388
+389ifkeyinself.data:
+390currentValue=self.data[key]
+391
+392# Make sure current value is a list
+393ifnotisinstance(currentValue,types.ListType):
+394currentValue=[currentValue]
+395
+396# Add new value(s)
+397ifisinstance(value,types.ListType):
+398currentValue.extend(value)
+399else:
+400currentValue.append(value)
+401self.data[key]=currentValue
+402else:
+403self.data[key]=value
+
451"""A ConfigurationSet represents a set of configurations.
+452
+453 Each configuration should be processed separately. This is matching
+454 the Raptor model where a single XML file can contain definitions
+455 of multiple specifications and configurations.
+456
+457 It is however somewhat different from the Commons Configuration classes
+458 that combine configurations, e.g. CombinedConfiguration,
+459 CompositeConfiguration. These act to combine configurations in a way
+460 such that a single configuration interface is still presented to the
+461 client.
+462 """
+463
+
470""" Return a list of configs that matches the name and type specified.
+471
+472 This can be queried multiple times to retrieve different named configurations.
+473 """
+474result=[]
+475forcinself._configs:
+476if((name!=Noneandc.match_name(name))orname==None)and((type!=Noneandc.type==type)ortype==None):
+477result.append(c)
+478returnresult
+
501""" Returns a ConfigurationSet object.
+502
+503 A ConfigurationSet represents a number of Configuration objects
+504 that all may need to be processed.
+505 """
+506try:
+507dom=xml.dom.minidom.parse(self.inputfile)
+508exceptException,exc:
+509raiseException("XML file '%s' cannot be parsed properly: %s"%(self.inputfile,exc))
+510
+511# The root element is typically <build> but can be anything
+512self.rootNode=dom.documentElement
+513configs=[]
+514
+515# Create a flat list of buildable configurations
+516forchildinself.rootNode.childNodes:
+517ifchild.nodeType==xml.dom.Node.ELEMENT_NODE:
+518_logger.debug('Parsing children')
+519self.parseConfiguration(child,configs)
+520
+521# Add configuration references
+522references=[]
+523forreferenceinself.getReferences():
+524forconfinconfigs:
+525ifconf.match_name(reference[1]):
+526newConf=copy.deepcopy(conf)
+527newConf.name=reference[0]
+528references.append(newConf)
+529
+530configs=configs+references
+531
+532dom.unlink()
+533_logger.debug('Set of configs: '+str(configs))
+534
+535ifself._warn_on_deprecated_spec:
+536_logger.warning("Use of deprecated 'spec' element name in this configuration. Please rename to config")
+537returnConfigurationSet(configs)
+
540""" Get a list of the individual configurations.
+541
+542 Once read a new builder must be opened to retrieve a differently filtered set of configurations.
+543 """
+544config_set=self.getConfiguration()
+545returnconfig_set.getConfigurations(name,type)
+
626""" Find a node based on a reference to it. """
+627forchildinself.rootNode.childNodes:
+628ifchild.nodeType==xml.dom.Node.ELEMENT_NODE:
+629forconfinchild.childNodes:
+630ifconf.nodeName=='spec':
+631ifrefName==conf.getAttribute('name'):
+632returnconf
+
644""" Get an item as a dict. """
+645elements=self._root.xpath(_Key(key).to_xpath())
+646values=[element.textforelementinelements]
+647value=','.join(values)
+648ifinterpolate:
+649value=self.interpolate(value)
+650returnvalue
+