#! /usr/bin/env python """ A Python replacement for java.util.Properties class This is modelled as closely as possible to the Java original. Created - Anand B Pillai SEE: https://bitbucket.org/jnoller/pyjavaproperties/src This is forked from an original at LICENSE It would appear that the authors license the code under the PSF license: Quoth: ''' License ------------------ As with all ASPN recipes not otherwise licensed prior to July 15, 2008 on aspn.activestate.com, the original recipe is under PSF License. For more information, see the ASPN terms of service here: While the licensing under the PSF license is sub-optimal, it is what it is. See for more information about the PSF license. ''' """ import sys,os import re import time class IllegalArgumentException(Exception): def __init__(self, lineno, msg): self.lineno = lineno self.msg = msg def __str__(self): s='Exception at line number %d => %s' % (self.lineno, self.msg) return s class Properties(object): """ A Python replacement for java.util.Properties """ def __init__(self, props=None): # Note: We don't take a default properties object # as argument yet # Dictionary of properties. self._props = {} # Dictionary of properties with 'pristine' keys # This is used for dumping the properties to a file # using the 'store' method self._origprops = {} self._keyorder = [] # Dictionary mapping keys from property # dictionary to pristine dictionary self._keymap = {} self.othercharre = re.compile(r'(?',line # Means we need to split by space. first, last = m2.span() sepidx = first elif m: # print 'Other match=>',line # No matching wspace char found, need # to split by either '=' or ':' first, last = m.span() sepidx = last - 1 # print line[sepidx] # If the last character is a backslash # it has to be preceded by a space in which # case the next line is read as part of the # same property while line[-1] == '\\': # Read next line nextline = i.next() nextline = nextline.strip() lineno += 1 # This line will become part of the value line = line[:-1] + nextline # Now split to key,value according to separation char if sepidx != -1: key, value = line[:sepidx], line[sepidx+1:] else: key,value = line,'' self._keyorder.append(key) self.processPair(key, value) def processPair(self, key, value): """ Process a (key, value) pair """ oldkey = key oldvalue = value # Create key intelligently keyparts = self.bspacere.split(key) # print keyparts strippable = False lastpart = keyparts[-1] if lastpart.find('\\ ') != -1: keyparts[-1] = lastpart.replace('\\','') # If no backspace is found at the end, but empty # space is found, strip it elif lastpart and lastpart[-1] == ' ': strippable = True key = ''.join(keyparts) if strippable: key = key.strip() oldkey = oldkey.strip() oldvalue = self.unescape(oldvalue) value = self.unescape(value) # Patch from N B @ ActiveState curlies = re.compile("{.+?}") found = curlies.findall(value) for f in found: srcKey = f[1:-1] if self._props.has_key(srcKey): value = value.replace(f, self._props[srcKey], 1) self._props[key] = value.strip() # Check if an entry exists in pristine keys if self._keymap.has_key(key): oldkey = self._keymap.get(key) self._origprops[oldkey] = oldvalue.strip() else: self._origprops[oldkey] = oldvalue.strip() # Store entry in keymap self._keymap[key] = oldkey if key not in self._keyorder: self._keyorder.append(key) def escape(self, value): # Java escapes the '=' and ':' in the value # string with backslashes in the store method. # So let us do the same. newvalue = value.replace(':','\:') newvalue = newvalue.replace('=','\=') return newvalue def unescape(self, value): # Reverse of escape newvalue = value.replace('\:',':') newvalue = newvalue.replace('\=','=') return newvalue def load(self, stream): """ Load properties from an open file stream """ # For the time being only accept file input streams if type(stream) is not file: raise TypeError,'Argument should be a file object!' # Check for the opened mode if stream.mode != 'r': raise ValueError,'Stream should be opened in read-only mode!' try: lines = stream.readlines() self.__parse(lines) except IOError, e: raise def getProperty(self, key): """ Return a property for the given key """ return self._props.get(key,'') def setProperty(self, key, value): """ Set the property for the given key """ if type(key) is str and type(value) is str: self.processPair(key, value) else: raise TypeError,'both key and value should be strings!' def propertyNames(self): """ Return an iterator over all the keys of the property dictionary, i.e the names of the properties """ return self._props.keys() def list(self, out=sys.stdout): """ Prints a listing of the properties to the stream 'out' which defaults to the standard output """ out.write('-- listing properties --\n') for key,value in self._props.items(): out.write(''.join((key,'=',value,'\n'))) def store(self, out, header=""): """ Write the properties list to the stream 'out' along with the optional 'header' """ if out.mode[0] != 'w': raise ValueError,'Steam should be opened in write mode!' try: out.write(''.join(('#',header,'\n'))) # Write timestamp tstamp = time.strftime('%a %b %d %H:%M:%S %Z %Y', time.localtime()) out.write(''.join(('#',tstamp,'\n'))) # Write properties from the pristine dictionary for prop in self._keyorder: if prop in self._origprops: val = self._origprops[prop] out.write(''.join((prop,'=',self.escape(val),'\n'))) out.close() except IOError, e: raise def getPropertyDict(self): return self._props def __getitem__(self, name): """ To support direct dictionary like access """ return self.getProperty(name) def __setitem__(self, name, value): """ To support direct dictionary like access """ self.setProperty(name, value) def __getattr__(self, name): """ For attributes not found in self, redirect to the properties dictionary """ try: return self.__dict__[name] except KeyError: if hasattr(self._props,name): return getattr(self._props, name) if __name__=="__main__": p = Properties() p.load(open('test2.properties')) p.list() print p print p.items() print p['name3'] p['name3'] = 'changed = value' print p['name3'] p['new key'] = 'new value' p.store(open('test2.properties','w'))