| # persist.py | 
 | # | 
 | # Implement limited persistence. | 
 | # | 
 | # Simple interface: | 
 | #	persist.save()		save __main__ module on file (overwrite) | 
 | #	persist.load()		load __main__ module from file (merge) | 
 | # | 
 | # These use the filename persist.defaultfile, initialized to 'wsrestore.py'. | 
 | # | 
 | # A raw interface also exists: | 
 | #	persist.writedict(dict, fp)	save dictionary to open file | 
 | #	persist.readdict(dict, fp)	read (merge) dictionary from open file | 
 | # | 
 | # Internally, the function dump() and a whole bunch of support of functions | 
 | # traverse a graph of objects and print them in a restorable form | 
 | # (which happens to be a Python module). | 
 | # | 
 | # XXX Limitations: | 
 | # - Volatile objects are dumped as strings: | 
 | #   - open files, windows etc. | 
 | # - Other 'obscure' objects are dumped as strings: | 
 | #   - classes, instances and methods | 
 | #   - compiled regular expressions | 
 | #   - anything else reasonably obscure (e.g., capabilities) | 
 | #   - type objects for obscure objects | 
 | # - It's slow when there are many of lists or dictionaries | 
 | #   (This could be fixed if there were a quick way to compute a hash | 
 | #   function of any object, even if recursive) | 
 |  | 
 | defaultfile = 'wsrestore.py' | 
 |  | 
 | def save(): | 
 | 	import __main__ | 
 | 	import os | 
 | 	# XXX On SYSV, if len(defaultfile) >= 14, this is wrong! | 
 | 	backup = defaultfile + '~' | 
 | 	try: | 
 | 		os.unlink(backup) | 
 | 	except os.error: | 
 | 		pass | 
 | 	try: | 
 | 		os.rename(defaultfile, backup) | 
 | 	except os.error: | 
 | 		pass | 
 | 	fp = open(defaultfile, 'w') | 
 | 	writedict(__main__.__dict__, fp) | 
 | 	fp.close() | 
 |  | 
 | def load(): | 
 | 	import __main__ | 
 | 	fp = open(defaultfile, 'r') | 
 | 	readdict(__main__.__dict__, fp) | 
 |  | 
 | def writedict(dict, fp): | 
 | 	import sys | 
 | 	savestdout = sys.stdout | 
 | 	try: | 
 | 		sys.stdout = fp | 
 | 		dump(dict)	# Writes to sys.stdout | 
 | 	finally: | 
 | 		sys.stdout = savestdout | 
 |  | 
 | def readdict(dict, fp): | 
 | 	contents = fp.read() | 
 | 	globals = {} | 
 | 	exec(contents, globals) | 
 | 	top = globals['top'] | 
 | 	for key in top.keys(): | 
 | 		if dict.has_key(key): | 
 | 			print 'warning:', key, 'not overwritten' | 
 | 		else: | 
 | 			dict[key] = top[key] | 
 |  | 
 |  | 
 | # Function dump(x) prints (on sys.stdout!) a sequence of Python statements | 
 | # that, when executed in an empty environment, will reconstruct the | 
 | # contents of an arbitrary dictionary. | 
 |  | 
 | import sys | 
 |  | 
 | # Name used for objects dict on output. | 
 | # | 
 | FUNNYNAME = FN = 'A' | 
 |  | 
 | # Top-level function.  Call with the object you want to dump. | 
 | # | 
 | def dump(x): | 
 | 	types = {} | 
 | 	stack = []			# Used by test for recursive objects | 
 | 	print FN, '= {}' | 
 | 	topuid = dumpobject(x, types, stack) | 
 | 	print 'top =', FN, '[', `topuid`, ']' | 
 |  | 
 | # Generic function to dump any object. | 
 | # | 
 | dumpswitch = {} | 
 | # | 
 | def dumpobject(x, types, stack): | 
 | 	typerepr = `type(x)` | 
 | 	if not types.has_key(typerepr): | 
 | 		types[typerepr] = {} | 
 | 	typedict = types[typerepr] | 
 | 	if dumpswitch.has_key(typerepr): | 
 | 		return dumpswitch[typerepr](x, typedict, types, stack) | 
 | 	else: | 
 | 		return dumpbadvalue(x, typedict, types, stack) | 
 |  | 
 | # Generic function to dump unknown values. | 
 | # This assumes that the Python interpreter prints such values as | 
 | # <foo object at xxxxxxxx>. | 
 | # The object will be read back as a string: '<foo object at xxxxxxxx>'. | 
 | # In some cases it may be possible to fix the dump manually; | 
 | # to ease the editing, these cases are labeled with an XXX comment. | 
 | # | 
 | def dumpbadvalue(x, typedict, types, stack): | 
 | 	xrepr = `x` | 
 | 	if typedict.has_key(xrepr): | 
 | 		return typedict[xrepr] | 
 | 	uid = genuid() | 
 | 	typedict[xrepr] = uid | 
 | 	print FN, '[', `uid`, '] =', `xrepr`, '# XXX' | 
 | 	return uid | 
 |  | 
 | # Generic function to dump pure, simple values, except strings | 
 | # | 
 | def dumpvalue(x, typedict, types, stack): | 
 | 	xrepr = `x` | 
 | 	if typedict.has_key(xrepr): | 
 | 		return typedict[xrepr] | 
 | 	uid = genuid() | 
 | 	typedict[xrepr] = uid | 
 | 	print FN, '[', `uid`, '] =', `x` | 
 | 	return uid | 
 |  | 
 | # Functions to dump string objects | 
 | # | 
 | def dumpstring(x, typedict, types, stack): | 
 | 	# XXX This can break if strings have embedded '\0' bytes | 
 | 	# XXX because of a bug in the dictionary module | 
 | 	if typedict.has_key(x): | 
 | 		return typedict[x] | 
 | 	uid = genuid() | 
 | 	typedict[x] = uid | 
 | 	print FN, '[', `uid`, '] =', `x` | 
 | 	return uid | 
 |  | 
 | # Function to dump type objects | 
 | # | 
 | typeswitch = {} | 
 | class some_class: | 
 | 	def method(self): pass | 
 | some_instance = some_class() | 
 | # | 
 | def dumptype(x, typedict, types, stack): | 
 | 	xrepr = `x` | 
 | 	if typedict.has_key(xrepr): | 
 | 		return typedict[xrepr] | 
 | 	uid = genuid() | 
 | 	typedict[xrepr] = uid | 
 | 	if typeswitch.has_key(xrepr): | 
 | 		print FN, '[', `uid`, '] =', typeswitch[xrepr] | 
 | 	elif x == type(sys): | 
 | 		print 'import sys' | 
 | 		print FN, '[', `uid`, '] = type(sys)' | 
 | 	elif x == type(sys.stderr): | 
 | 		print 'import sys' | 
 | 		print FN, '[', `uid`, '] = type(sys.stderr)' | 
 | 	elif x == type(dumptype): | 
 | 		print 'def some_function(): pass' | 
 | 		print FN, '[', `uid`, '] = type(some_function)' | 
 | 	elif x == type(some_class): | 
 | 		print 'class some_class: pass' | 
 | 		print FN, '[', `uid`, '] = type(some_class)' | 
 | 	elif x == type(some_instance): | 
 | 		print 'class another_class: pass' | 
 | 		print 'some_instance = another_class()' | 
 | 		print FN, '[', `uid`, '] = type(some_instance)' | 
 | 	elif x == type(some_instance.method): | 
 | 		print 'class yet_another_class:' | 
 | 		print '    def method(): pass' | 
 | 		print 'another_instance = yet_another_class()' | 
 | 		print FN, '[', `uid`, '] = type(another_instance.method)' | 
 | 	else: | 
 | 		# Unknown type | 
 | 		print FN, '[', `uid`, '] =', `xrepr`, '# XXX' | 
 | 	return uid | 
 |  | 
 | # Initialize the typeswitch | 
 | # | 
 | for x in None, 0, 0.0, '', (), [], {}: | 
 | 	typeswitch[`type(x)`] = 'type(' + `x` + ')' | 
 | for s in 'type(0)', 'abs', '[].append': | 
 | 	typeswitch[`type(eval(s))`] = 'type(' + s + ')' | 
 |  | 
 | # Dump a tuple object | 
 | # | 
 | def dumptuple(x, typedict, types, stack): | 
 | 	item_uids = [] | 
 | 	xrepr = '' | 
 | 	for item in x: | 
 | 		item_uid = dumpobject(item, types, stack) | 
 | 		item_uids.append(item_uid) | 
 | 		xrepr = xrepr + ' ' + item_uid | 
 | 	del stack[-1:] | 
 | 	if typedict.has_key(xrepr): | 
 | 		return typedict[xrepr] | 
 | 	uid = genuid() | 
 | 	typedict[xrepr] = uid | 
 | 	print FN, '[', `uid`, '] = (', | 
 | 	for item_uid in item_uids: | 
 | 		print FN, '[', `item_uid`, '],', | 
 | 	print ')' | 
 | 	return uid | 
 |  | 
 | # Dump a list object | 
 | # | 
 | def dumplist(x, typedict, types, stack): | 
 | 	# Check for recursion | 
 | 	for x1, uid1 in stack: | 
 | 		if x is x1: return uid1 | 
 | 	# Check for occurrence elsewhere in the typedict | 
 | 	for uid1 in typedict.keys(): | 
 | 		if x is typedict[uid1]: return uid1 | 
 | 	# This uses typedict differently! | 
 | 	uid = genuid() | 
 | 	typedict[uid] = x | 
 | 	print FN, '[', `uid`, '] = []' | 
 | 	stack.append(x, uid) | 
 | 	item_uids = [] | 
 | 	for item in x: | 
 | 		item_uid = dumpobject(item, types, stack) | 
 | 		item_uids.append(item_uid) | 
 | 	del stack[-1:] | 
 | 	for item_uid in item_uids: | 
 | 		print FN, '[', `uid`, '].append(', FN, '[', `item_uid`, '])' | 
 | 	return uid | 
 |  | 
 | # Dump a dictionary object | 
 | # | 
 | def dumpdict(x, typedict, types, stack): | 
 | 	# Check for recursion | 
 | 	for x1, uid1 in stack: | 
 | 		if x is x1: return uid1 | 
 | 	# Check for occurrence elsewhere in the typedict | 
 | 	for uid1 in typedict.keys(): | 
 | 		if x is typedict[uid1]: return uid1 | 
 | 	# This uses typedict differently! | 
 | 	uid = genuid() | 
 | 	typedict[uid] = x | 
 | 	print FN, '[', `uid`, '] = {}' | 
 | 	stack.append(x, uid) | 
 | 	item_uids = [] | 
 | 	for key in x.keys(): | 
 | 		val_uid = dumpobject(x[key], types, stack) | 
 | 		item_uids.append(key, val_uid) | 
 | 	del stack[-1:] | 
 | 	for key, val_uid in item_uids: | 
 | 		print FN, '[', `uid`, '][', `key`, '] =', | 
 | 		print FN, '[', `val_uid`, ']' | 
 | 	return uid | 
 |  | 
 | # Dump a module object | 
 | # | 
 | def dumpmodule(x, typedict, types, stack): | 
 | 	xrepr = `x` | 
 | 	if typedict.has_key(xrepr): | 
 | 		return typedict[xrepr] | 
 | 	from string import split | 
 | 	# `x` has the form <module 'foo'> | 
 | 	name = xrepr[9:-2] | 
 | 	uid = genuid() | 
 | 	typedict[xrepr] = uid | 
 | 	print 'import', name | 
 | 	print FN, '[', `uid`, '] =', name | 
 | 	return uid | 
 |  | 
 |  | 
 | # Initialize dumpswitch, a table of functions to dump various objects, | 
 | # indexed by `type(x)`. | 
 | # | 
 | for x in None, 0, 0.0: | 
 | 	dumpswitch[`type(x)`] = dumpvalue | 
 | for x, f in ('', dumpstring), (type(0), dumptype), ((), dumptuple), \ | 
 | 		([], dumplist), ({}, dumpdict), (sys, dumpmodule): | 
 | 	dumpswitch[`type(x)`] = f | 
 |  | 
 |  | 
 | # Generate the next unique id; a string consisting of digits. | 
 | # The seed is stored as seed[0]. | 
 | # | 
 | seed = [0] | 
 | # | 
 | def genuid(): | 
 | 	x = seed[0] | 
 | 	seed[0] = seed[0] + 1 | 
 | 	return `x` |