#!/usr/bin/env python # -*- coding: utf-8 -*- """manages preferences files that are hierarchical and nice for both humans and python""" # Copyright 2002, 2003 St James Software # # This file is part of jToolkit. # # jToolkit is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # jToolkit is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with jToolkit; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from jToolkit import sparse import os import sys class indent(str): def __init__(self, value): self.level = 0 def setlevel(self, level): self.level = level def __repr__(self): return "indent(%d)" % self.level def evaluateboolean(value, default=False): """evaluates the pref value as a boolean""" if value is None: return default if isinstance(value, (str, unicode)): if value.isdigit(): value = int(value) else: value = value.lower() == 'true' return bool(value) class PrefNode(object): def __init__(self, parent, keypart): if keypart is None: self.__dict__["_parser"] = parent self.__dict__["__root__"] = self self.__dict__["__key__"] = "" self.__dict__["_setvalue"] = getattr(parent, "setvalue") self.__dict__["_removekey"] = getattr(parent, "removekey") self.__dict__["_resolve"] = getattr(parent, "resolveconfigobject") self.__dict__["_assignments"] = {} else: self.__dict__["__root__"] = parent.__root__ if parent.__key__ == "": self.__dict__["__key__"] = keypart else: self.__dict__["__key__"] = parent.__key__ + "." + keypart def __setattr__(self, keypart, value): parentpart = self.__key__ if len(parentpart) > 0: parentpart += "." self.__root__._setvalue(parentpart + keypart, value) def __delattr__(self, keypart): if not keypart in self.__dict__: object.__delattr__(self, keypart) elif self.__key__ == "": self.__root__._removekey(keypart) else: self.__root__._removekey(self.__key__ + "." + keypart) def __getattr__(self, attr): """tries to find the attribute, handling unicode if given""" # TODO: add default support if "." in attr: if self.__root__ == self: return self._parser.getvalue(attr) return self.__root__.__getattr__(self.__key__ + "." + attr) if isinstance(attr, basestring) and attr in self.__dict__: return self.__dict__[attr] if not (isinstance(attr, unicode) or "." in attr): return getattr(super(PrefNode, self), attr) else: raise AttributeError("%r PrefNode object has no attribute %r" % (self.__class__.__name__, attr)) def __hasattr__(self, attr): """tries to find if attribute is present, handling unicode if given""" if "." in attr: if self.__root__ == self: return self._parser.hasvalue(attr) return self.__root__.__hasattr__(self.__key__ + "." + attr) if isinstance(attr, basestring): if attr in self.__dict__: return True if not (isinstance(attr, unicode) or "." in attr): return hasattr(super(PrefNode, self), attr) return False def __repr__(self): return self.__key__ def __str__(self): return self.__key__ def __getstate__(self): return (self.__root__._resolve.im_self.__getstate__(), self.__key__) def __setstate__(self, state): parserText, key = state parser = PrefsParser() parser.parse(parserText) # Now we need some way of making self the object denoted by state[1] keys = key.split(".") oldobject = parser.__root__ parent = None if not len(keys) == 1 and keys[0] == "": for keypart in keys: parent = oldobject oldobject = oldobject.__getattr__(keypart) self.__dict__["__key__"] = key if oldobject.__dict__.has_key("__root__"): if key == "": self.__dict__["__root__"] = self else: self.__dict__["__root__"] = oldobject.__root__ if oldobject.__dict__.has_key("__base__"): self.__dict__["__base__"] = oldobject.__base__ PrefNode.copyattrs(oldobject, self) if parent == None: parser.__root__ = self else: parent.__setattr__(keys[-1], self) def getlabel(self): return getattr(self, "__base__", self.__key__) def copy(oldobject, parent, keypart): """ Returns a PrefNode identical to this one, but with a different name and parent Assignments are duplicated in __root__._assignments so they will be noticed by getsource """ newobject = PrefNode(parent, keypart) PrefNode.copyattrs(oldobject, newobject) return newobject def copyattrs(oldobject, newobject): for childkeypart in oldobject.__dict__: if childkeypart in ("__root__", "__key__", "__base__"): continue oldchild = oldobject.__dict__[childkeypart] if isinstance(oldchild, PrefNode): newobject.__dict__[childkeypart] = oldchild.copy(newobject, childkeypart) else: newobject.__dict__[childkeypart] = oldchild label = newobject.getlabel() + '.' + childkeypart newobject.__root__._assignments[label] = oldchild def relocate(self, newkey): self.__dict__["__base__"] = self.__key__ self.__dict__["__key__"] = newkey for childkeypart in self.__dict__: if childkeypart in ("__root__", "__key__", "__base__"): continue child = self.__dict__[childkeypart] if isinstance(child, PrefNode): child.relocate(newkey + "." + childkeypart) def renamepref(self,name,newname): """ Rename a pref by removing it and adding a new one name is the pref to rename newname is what that pref will be renamed to """ value = getattr(self,name) setattr(self,newname,value) delattr(self,name) def iteritems(self, sorted=False): """iterate through the items, sort them by key if sorted""" if sorted: childitems = self.__dict__.items() childitems.sort() else: childitems = self.__dict__.iteritems() for childkeypart, child in childitems: if childkeypart in ("__root__", "__key__", "__base__"): continue if childkeypart in ("_parser", "_setvalue", "_removekey", "_resolve", "_assignments"): continue yield childkeypart, child def getparser(self): """finds the parser object this belongs to""" return self.__root__._parser # TODO: allow UnresolvedPref to resolve prefs using Python imports class UnresolvedPref: def __init__(self, root, label): self.__dict__["__root__"] = root self.__dict__["__key__"] = label def __repr__(self): return self.__key__ def __str__(self): return self.__key__ def resolve(self): return self.__root__._resolve(self.__key__) # TODO: improve handling of object values - strings, integers, classes, modules etc class PrefsParser(sparse.SimpleParser, object): def __init__(self, filename=None): """sets up the PrefsParser""" sparse.SimpleParser.__init__(self, includewhitespacetokens = 1) self.unicodeprefix = "u" self.standardtokenizers = [self.commenttokenize, self.stringtokenize, \ self.removewhitespace, self.splitatnewline, self.separatetokens] self.__root__ = PrefNode(self, None) # Initialise all the token holders if filename is None: self.parse("") else: self.parsefile(filename) self.__initialized__ = True def __setattr__(self, keypart, value): """we need to be able to act as the root node in case setattr is called on us directly""" if getattr(self, "__initialized__", False) and not self.__hasattr__(keypart) and keypart not in ("changes", "filename"): self.setvalue(keypart, value) else: super(PrefsParser, self).__setattr__(keypart, value) def __getstate__(self): """Prepares the object for pickling This preserves the only thing we actually need to preserve - the text returned from self.getsource()""" return self.getsource() def __setstate__(self, state): """Unpickles the object This takes a prefsfile source, created by another parser, and creates a new parser""" self.__init__() # In case it hasn't been called self.parse(state) def keeptogether(self, input): """checks whether a token should be kept together""" # don't retokenize strings return sparse.SimpleParser.keeptogether(self, input) or self.iscommenttoken(input) def stringtokenize(self, input): """makes strings in input into tokens... but keeps comment tokens together""" if self.iscommenttoken(input): return [input] return sparse.SimpleParser.stringtokenize(self, input) def iscommenttoken(self, input): """checks whether a token is a comment token""" return input[:1] == "#" def commenttokenize(self, input): """makes comments in input into tokens...""" if sparse.SimpleParser.keeptogether(self, input): return [input] tokens = [] incomment = False laststart = 0 startcommentchar, endcommentchar = '#', '\n' for pos in range(len(input)): char = input[pos] if incomment: if char == endcommentchar: if pos > laststart: tokens.append(input[laststart:pos]) incomment, laststart = False, pos else: if char == startcommentchar: if pos > laststart: tokens.append(input[laststart:pos]) incomment, laststart = True, pos if laststart < len(input): tokens.append(input[laststart:]) return tokens def splitatnewline(self, input): """splits whitespace tokens at newline, putting the newline at the beginning of the split strings whitespace tokens not containing newlines are discarded""" if self.keeptogether(input): return [input] if input.isspace(): lastnewline = input.rfind("\n") if lastnewline != -1: return [input[lastnewline:]] return [] return [input] def handleindents(self): """finds indents in tokens and replaces them with indent objects""" indentedtokens = [] indentstack = [0] for tokennum, token in enumerate(self.tokens): if token[:1] == "\n": if tokennum+1 < len(self.tokens) and self.iscommenttoken(self.tokens[tokennum+1]): # treat indents before comments as whitespace, by removing them continue tokenlength = len(token[1:]) if tokenlength <= indentstack[-1]: if tokenlength not in indentstack: self.raiseerror("invalid indentation", tokennum) indentstack = indentstack[:indentstack.index(tokenlength)+1] else: indentstack.append(tokenlength) token = indent(token) token.setlevel(indentstack.index(tokenlength)) indentedtokens.append(token) if len(indentedtokens) > 0 and not isinstance(indentedtokens[0], indent): indentedtokens = [indent("")] + indentedtokens self.tokens = indentedtokens def parseassignments(self): """parses all the assignments from the tokenized preferences""" self.__dict__.setdefault('removals',{}) self.__dict__.setdefault('valuepos',{}) self.__dict__.setdefault('commentpos',{}) self.__dict__.setdefault('sectionstart',{}) self.__dict__.setdefault('sectionend',{}) assignvar = None operation = None lastcomment = None lastvalue = None lastsection = None lastindent = indent("") context = {} indentlevels = {} self.refreshposcache() for tokennum, token in enumerate(self.tokens): if isinstance(token, indent): if token.level < lastindent.level: parentcontext = ".".join([context[level] for level in range(token.level)]) for level in range(token.level, lastindent.level): if level in context: if not parentcontext: childcontext = context[level] else: childcontext = parentcontext + "." + context[level] self.sectionend[childcontext] = (tokennum, indentlevels[level+1]) parentcontext = childcontext del context[level] elif token.level > lastindent.level: if operation == ':': operation = None else: self.raiseerror("indent without preceding :", tokennum) lastindent = token indentlevels[lastindent.level] = token elif self.iscommenttoken(token): # if the last value or section found is on the same line # as this comment then this comment refers to that pref lastcomment = (tokennum,token) if (lastvalue is not None) and (lastsection is not None): commentline,commentcharpos = self.getlinepos(self.findtokenpos(tokennum)) valuenum,value = lastvalue vline,vpos = self.getlinepos(self.findtokenpos(valuenum)) sectionnum,section = lastsection sectionline,sectionpos = self.getlinepos(self.findtokenpos(sectionnum)) if commentline == vline: self.commentpos[value] = tokennum lastcomment = None elif commentline == sectionline: self.commentpos[section] = tokennum lastcomment = None elif token == '=': operation = token elif token == ':': context[lastindent.level] = assignvar operation = token prefixes = [context[level] for level in range(0, lastindent.level)] key = ".".join(prefixes+[assignvar]) self.sectionstart[key] = tokennum lastsection = (tokennum,key) elif operation == '=': prefixes = [context[level] for level in range(0, lastindent.level)] key = ".".join(prefixes+[assignvar]) realvalue = self.parsevalue(token) self.setvalue(key, realvalue) self.valuepos[key] = tokennum operation = None lastvalue = (tokennum,key) elif operation is None: if self.isstringtoken(token): if token.startswith(self.unicodeprefix): assignvar = sparse.stringeval(token.replace(self.unicodeprefix, "", 1)).decode("utf-8") else: assignvar = sparse.stringeval(token) else: assignvar = token else: self.raiseerror("I don't know how to parse that here", tokennum) # handle comments if operation == '=' or token == ':': # if the last comment found is on the line before this one then it refers to this pref/section if lastcomment is not None: commentnum,comment = lastcomment commentline,commentcharpos = self.getlinepos(self.findtokenpos(commentnum)) myline,mycharpos = self.getlinepos(self.findtokenpos(tokennum)) if myline == (commentline+1): if token == ':' and lastsection is not None: self.commentpos[lastsection[1]] = commentnum elif operation == '=' and lastvalue is not None: self.commentpos[lastvalue[1]] = commentnum def parse(self, prefsstring): """this parses a string and returns a base preferences object""" self.tokenize(prefsstring) self.handleindents() self.parseassignments() self.resolveinternalassignments() def parsefile(self, filename): """this opens a file and parses the contents""" self.filename = filename prefsfile = open(filename, 'r') contents = prefsfile.read() prefsfile.close() self.parse(contents) def savefile(self, filename=None, safesave=False): """this saves the source to the given filename""" if filename is None: filename = getattr(self, "filename") else: self.filename = filename contents = self.getsource() if safesave: dirpart = os.path.abspath(os.path.dirname(filename)) filepart = os.path.basename(filename) tempfilename = os.tempnam(dirpart, filepart) prefsfile = open(tempfilename, 'w') prefsfile.write(contents) prefsfile.close() try: if os.name == 'nt' and os.path.exists(filename): os.remove(filename) os.rename(tempfilename, filename) except OSError, e: os.remove(tempfilename) raise e else: prefsfile = open(filename, 'w') prefsfile.write(contents) prefsfile.close() def hasvalue(self, key): """returns whether the given key is present""" keyparts = key.split(".") parent = self.__root__ for keypart in keyparts: if not parent.__dict__.has_key(keypart): return False parent = parent.__dict__[keypart] return True def getvalue(self, key): """gets the value of the given key""" keyparts = key.split(".") parent = self.__root__ for keypart in keyparts: if not parent.__dict__.has_key(keypart): raise IndexError("parent does not have child %r when trying to find %r" % (keypart, key)) parent = parent.__dict__[keypart] return parent def setvalue(self, key, value): """set the given key to the given value""" if isinstance(value, PrefNode): value.relocate(key) # we don't store PrefNodes in assignments if not isinstance(value, PrefNode): self.__root__._assignments[key] = value keyparts = key.split(".") parent = self.__root__ for keypart in keyparts[:-1]: if not parent.__dict__.has_key(keypart): parent.__dict__[keypart] = PrefNode(parent, keypart) child = parent.__dict__[keypart] # it is possible that this is overriding a value with a prefnode if not isinstance(child, PrefNode): child = PrefNode(parent, keypart) parent.__dict__[keypart] = child parent = child parent.__dict__[keyparts[-1]] = value def removekey(self, key): """remove the given key from the prefs tree. no node removal yet""" if key in self.__root__._assignments: del self.__root__._assignments[key] parent = self.__root__ keyparts = key.split(".") for keypart in keyparts[:-1]: if not parent.__dict__.has_key(keypart): raise ValueError("key %s not found: %s has no child %s" % (key, parent.__key__, keypart)) parent = parent.__dict__[keypart] attribname = keyparts[-1] if attribname in parent.__dict__: deadnode = parent.__dict__[attribname] if isinstance(deadnode, PrefNode): for childkey in deadnode.__dict__.keys(): if childkey not in ("__root__", "__key__", "__base__"): self.removekey(key + "." + childkey) del parent.__dict__[attribname] else: raise ValueError("key %s not found: %s has no child %s" % (key, parent.__key__, attribname)) self.removals[key] = True def parsevalue(self, value): """Parses a value set in a config file, and returns the correct object type""" if not isinstance(value, (str, unicode)): return value # If it's a string, try to parse it if (value.isdigit()): return int(value) elif (value[0] in ['"',"'"] and value[-1] == value[0]): return sparse.stringeval(value) elif (value[0] == 'u' and value[1] in ['"',"'"] and value[-1] == value[1]): return sparse.stringeval(value[1:]).decode("utf-8") else: return self.resolveconfigobject(value) def quotevalue(self, value): """converts a realvalue from parsevalue back to a string that can be stored in the prefs file""" if isinstance(value, int) or isinstance(value, long): return str(value) elif isinstance(value, PrefNode): return value.getlabel() elif isinstance(value, UnresolvedPref): return repr(value) elif isinstance(value, str): return sparse.stringquote(value) elif isinstance(value, unicode): return "u" + sparse.stringquote(value.encode("utf-8")) elif value is None: return "" else: raise ValueError("don't know how to quote %r value: %r" % (type(value), value)) def resolveconfigobject(self, value): """Tries to find the object specified by "value" as a member of self Should be overridden if more types of objects are available""" valueparts = value.split(".") parent = self.__root__ if hasattr(self.__root__, 'importmodules') and hasattr(self.__root__.importmodules,valueparts[0])\ and not hasattr(self.__root__,valueparts[0]): parent = self.__root__.importmodules found = True for valuepart in valueparts: if isinstance(parent, PrefNode): # handle resolving for standard nodes... if hasattr(parent,valuepart): parent = parent.__dict__[valuepart] else: found = False break else: # handle resolving for other objects... if hasattr(parent, valuepart): parent = getattr(parent, valuepart) else: found = False break if found: if isinstance(parent, PrefNode): return parent.copy(self.__root__, value) else: # other objects can't be copied, so just return them... return parent elif value in self.__root__._assignments: return self.__root__._assignments[value] else: return UnresolvedPref(self.__root__, value) def resolveinternalassignments(self): """resolves any unresolved assignments that are internal (i.e. don't rely on imports)""" unresolved = len(self.__root__._assignments) lastunresolved = unresolved+1 while unresolved < lastunresolved: lastunresolved = unresolved unresolved = 0 for key in self.__root__._assignments: currentvalue = self.__root__._assignments[key] if isinstance(currentvalue, UnresolvedPref): unresolved += 1 newvalue = currentvalue.resolve() if newvalue != currentvalue: self.setvalue(key, newvalue) def resolveimportmodules(self, mapmodulename=None): """actually imports modules specified in importmodules. not used unless explicitly called""" # import any required modules for refname, modulename in self.importmodules.iteritems(): if mapmodulename: modulename = mapmodulename(modulename) try: module = __import__(modulename, globals(), locals()) except ImportError, importmessage: errormessage = "Error importing module %r: %s\nPython path is %r" \ % (modulename, importmessage, sys.path) raise ImportError(errormessage) components = modulename.split('.') for component in components[1:]: module = getattr(module, component) importmodulekey = self.importmodules.__key__ + "." + refname self.setvalue(importmodulekey, module) # TODO: this is a hack. add this resolving into prefs so we don't have to delete the assignment # we currently delete the assignment so that the prefsfile can be safely saved del self.__root__._assignments[importmodulekey] modulevalue = getattr(self.importmodules, refname) def addKeyToDict(self, keyparts, value, dictionary): if len(keyparts) == 1: dictionary[keyparts[0]] = value else: dictionary.setdefault(keyparts[0],{}) self.addKeyToDict(keyparts[1:],value,dictionary[keyparts[0]]) def writeDict(self, dictionary, indent): result = "" sortedKeys = dictionary.keys() sortedKeys.sort() for key in sortedKeys: quotedkey = key if isinstance(quotedkey, unicode): try: quotedkey = str(quotedkey) except UnicodeError: pass if isinstance(quotedkey, unicode): quotedkey = "u" + sparse.stringquote(quotedkey.encode("utf-8")) else: testalphakey = quotedkey.replace("_", "a").replace(".", "0") if not (testalphakey[:1].isalpha() and testalphakey.isalnum()): quotedkey = sparse.stringquote(quotedkey) if isinstance(dictionary[key], dict): result += indent + quotedkey + ":\n" result += self.writeDict(dictionary[key], indent+" ") else: result += indent + "%s = %s\n" % (quotedkey, dictionary[key]) return result def findimmediateparent(self, key): """finds the most immediate indented parent of the given key""" keyparts = key.split(".") for keynum in range(len(keyparts),-1,-1): parentkey = ".".join(keyparts[:keynum]) if parentkey in self.sectionend: return parentkey return None def addchange(self, tokennum, tokenschanged, newtokens, parentstart=None): """adds a change to self.changes""" if tokennum in self.changes: tokenchanges = self.changes[tokennum] else: tokenchanges = {} self.changes[tokennum] = tokenchanges if parentstart in tokenchanges: tokenchanges[parentstart].append((tokenschanged, newtokens)) else: tokenchanges[parentstart] = [(tokenschanged, newtokens)] def getsource(self): """reconstructs the original prefs string with any changes that have been made...""" # changes is a dictionary, key is the position of the change # each value is a list of changes at that position # each change in the list is a tuple of the number of tokens to remove, and the new string self.changes = {} extradict = {} for key in self.__root__._assignments: currentvalue = self.__root__._assignments[key] keyfound = key in self.valuepos # try and handle key being encoded or not sensibly... if not keyfound: try: if isinstance(key, str): otherkey = key.decode("utf-8") elif isinstance(key, unicode): otherkey = key.encode("utf-8") keyfound = otherkey in self.valuepos except: pass if keyfound: # the key exists. change the value tokennum = self.valuepos[key] if currentvalue != self.parsevalue(self.tokens[tokennum]): self.addchange(tokennum, 1, self.quotevalue(currentvalue)) else: # the key doesn't yet exist... keyparts = key.split(".") parentkey = self.findimmediateparent(key) if parentkey is not None: nodetokennum, nodeindent = self.sectionend[parentkey] parentstart = self.sectionstart[parentkey] keynum = parentkey.count(".") + 1 childkey = ".".join(keyparts[keynum:]) testalphakey = childkey.replace("_", "a").replace(".", "0") needsquoting = False if isinstance(testalphakey, unicode): try: testalphakey = testalphakey.encode("ascii") except UnicodeEncodeError: needsquoting = True if not (testalphakey[:1].isalpha() and testalphakey.isalnum()): needsquoting = True if needsquoting: childkey = self.quotevalue(childkey) quotedvalue = nodeindent + "%s = %s" % (childkey, self.quotevalue(currentvalue)) self.addchange(nodetokennum, 0, quotedvalue, parentstart=parentstart) else: self.addKeyToDict(keyparts, self.quotevalue(currentvalue), extradict) for key in self.removals: if key in self.valuepos: tokennum = self.valuepos[key] elif key in self.sectionstart: tokennum = self.sectionstart[key] else: # then we don't know how to remove it # extras.append("# tried to remove key but could not find it: %s" % key) continue # to remove something, we search backwards from the value for an indent # and slice out the section from the indent to the value starttokennum = tokennum while starttokennum >= 0: if isinstance(self.tokens[starttokennum], indent): if starttokennum == 0: starttokennum += 1 break starttokennum -= 1 if starttokennum > 0: self.addchange(starttokennum, tokennum+1-starttokennum, "") else: # if we didn't find it, leave a note... self.addchange(tokennum, 1, "none") # now regenerate the source including the changes... tokennums = self.changes.keys() tokennums.sort() lastpos = 0 newsource = "" for tokennum in tokennums: tokenpos = self.findtokenpos(tokennum) newsource += self.source[lastpos:tokenpos] totalremovetokens = 0 parentstarts = self.changes[tokennum].keys() parentstarts.sort() parentstarts.reverse() for parentstart in parentstarts: for removetokens, newtext in self.changes[tokennum][parentstart]: if isinstance(newtext, unicode): newtext = newtext.encode("utf-8") newsource += newtext totalremovetokens += removetokens # work out the position of the last token, and put lastpos at the end of this token lasttokenpos = tokennum + totalremovetokens - 1 if len(self.tokens) > lasttokenpos: lastpos = self.findtokenpos(lasttokenpos) + len(self.tokens[lasttokenpos]) newsource += self.source[lastpos:] if extradict: if newsource and newsource[-1] != "\n": newsource += "\n" newsource += self.writeDict(extradict, "") return newsource def getcomment(self,key): """ returns the comment associated with this the key given, or none if there is no such key """ tokennum = self.commentpos.get(key,None) if tokennum: return self.tokens[tokennum] else: return None def __getattr__(self, attr): if attr in self.__dict__: return self.__dict__[attr] elif '__root__' in self.__dict__: if isinstance(attr, unicode): return self.__root__.__getattr__(attr) return getattr(self.__root__, attr) raise AttributeError("'PrefsParser' object has no attribute %s" % attr) def __hasattr__(self, attr): if attr in self.__dict__: return True elif '__root__' in self.__dict__: if isinstance(attr, unicode): return self.__root__.__hasattr__(attr) return hasattr(self.__root__, attr) if __name__ == "__main__": import sys parser = PrefsParser() originalsource = sys.stdin.read() parser.parse(originalsource) import pickle pickled = pickle.dumps(parser) recreatedsource = parser.getsource() if recreatedsource != originalsource: print >>sys.stderr, "recreatedsource != originalsource" sys.stdout.write(recreatedsource) unpickled = pickle.loads(pickled) sys.stdout.write("===========================================\n") sys.stdout.write(unpickled.getsource()) if unpickled.getsource() != recreatedsource: print >>sys.stderr, "unpickledsource != recreatedsource"