Preferences is a general module to get (and set) preferences in
resource files with various inheritence rules, parsers, etc.

pythonprefs uses this to create a single uniform interface to all
relevant python preferences.
diff --git a/Mac/Lib/preferences.py b/Mac/Lib/preferences.py
new file mode 100644
index 0000000..810d9cc
--- /dev/null
+++ b/Mac/Lib/preferences.py
@@ -0,0 +1,219 @@
+#
+# General parser/loaders for preferences files and such
+#
+import Res
+import macfs
+import struct
+import MACFS
+
+READ=1
+READWRITE=3
+Error = "Preferences.Error"
+
+debug = 0
+
+class NullLoader:
+	def __init__(self, data=None):
+		self.data = data
+		
+	def load(self):
+		if self.data is None:
+			raise Error, "No default given"
+		return self.data
+		
+	def save(self, data):
+		raise Error, "Cannot save to default value"
+		
+	def delete(self, deep=0):
+		if debug:
+			print 'Attempt to delete default value'
+		raise Error, "Cannot delete default value"
+		
+_defaultdefault = NullLoader()
+		
+class ResLoader:
+	def __init__(self, filename, resid, resnum=None, resname=None, default=_defaultdefault):
+		self.filename = filename
+		self.fss = macfs.FSSpec(self.filename)
+		self.resid = resid
+		self.resnum = resnum
+		self.resname = resname
+		self.default = default
+		self.data = None
+		
+	def load(self):
+		oldrh = Res.CurResFile()
+		try:
+			rh = Res.FSpOpenResFile(self.fss, READ)
+		except Res.Error:
+			self.data = self.default.load()
+			return self.data
+		try:
+			if self.resname:
+				handle = Res.Get1NamedResource(self.resid, self.resname)
+			else:
+				handle = Res.Get1Resource(self.resid, self.resnum)
+		except Res.Error:
+			self.data = self.default.load()
+		else:
+			if debug:
+				print 'Loaded', (self.resid, self.resnum, self.resname), 'from', self.fss.as_pathname()
+			self.data = handle.data
+		Res.CloseResFile(rh)
+		Res.UseResFile(oldrh)
+		return self.data
+		
+	def save(self, data):
+		if self.data is None or self.data != data:
+			oldrh = Res.CurResFile()
+			rh = Res.FSpOpenResFile(self.fss, READWRITE)
+			try:
+				handle = Res.Get1Resource(self.resid, self.resnum)
+			except Res.Error:
+				handle = Res.Resource(data)
+				handle.AddResource(self.resid, self.resnum, '')
+				if debug:
+					print 'Added', (self.resid, self.resnum), 'to', self.fss.as_pathname()
+			else:
+				handle.data = data
+				handle.ChangedResource()
+				if debug:
+					print 'Changed', (self.resid, self.resnum), 'in', self.fss.as_pathname()
+			Res.CloseResFile(rh)
+			Res.UseResFile(oldrh)
+			
+	def delete(self, deep=0):
+		if debug:
+			print 'Deleting in', self.fss.as_pathname(), `self.data`, deep
+		oldrh = Res.CurResFile()
+		rh = Res.FSpOpenResFile(self.fss, READWRITE)
+		try:
+			handle = Res.Get1Resource(self.resid, self.resnum)
+		except Res.Error:
+			if 	deep:
+				if debug: print 'deep in', self.default
+				self.default.delete(1)
+		else:
+			handle.RemoveResource()
+			if debug:
+				print 'Deleted', (self.resid, self.resnum), 'from', self.fss.as_pathname()
+		self.data = None
+		Res.CloseResFile(rh)
+		Res.UseResFile(oldrh)
+
+class AnyResLoader:
+	def __init__(self, resid, resnum=None, resname=None, default=_defaultdefault):
+		self.resid = resid
+		self.resnum = resnum
+		self.resname = resname
+		self.default = default
+		self.data = None
+		
+	def load(self):
+		try:
+			if self.resname:
+				handle = Res.GetNamedResource(self.resid, self.resname)
+			else:
+				handle = Res.GetResource(self.resid, self.resnum)
+		except Res.Error:
+			self.data = self.default.load()
+		else:
+			self.data = handle.data
+		return self.data
+		
+	def save(self, data):
+		raise Error, "Cannot save AnyResLoader preferences"
+					
+	def delete(self, deep=0):
+		raise Error, "Cannot delete AnyResLoader preferences"
+			
+class StructLoader:
+	def __init__(self, format, loader):
+		self.format = format
+		self.loader = loader
+		
+	def load(self):
+		data = self.loader.load()
+		return struct.unpack(self.format, data)
+		
+	def save(self, data):
+		data = apply(struct.pack, (self.format,)+data)
+		self.loader.save(data)
+		
+	def delete(self, deep=0):
+		self.loader.delete(deep)
+		
+class PstringLoader:
+	def __init__(self, loader):
+		self.loader = loader
+		
+	def load(self):
+		data = self.loader.load()
+		len = ord(data[0])
+		return data[1:1+len]
+		
+	def save(self, data):
+		if len(data) > 255:
+			raise Error, "String too big for pascal-style"
+		self.loader.save(chr(len(data))+data)
+		
+	def delete(self, deep=0):
+		self.loader.delete(deep)
+		
+class VersionLoader(StructLoader):
+	def load(self):
+		while 1:
+			data = self.loader.load()
+			if debug:
+				print 'Versionloader:', `data`
+			try:
+				rv = struct.unpack(self.format, data)
+				rv = self.versioncheck(rv)
+				return rv
+			except (struct.error, Error):
+				self.delete(1)
+				
+	def versioncheck(self, data):
+		return data
+		
+class StrListLoader:
+	def __init__(self, loader):
+		self.loader = loader
+		
+	def load(self):
+		data = self.loader.load()
+		num, = struct.unpack('h', data[:2])
+		data = data[2:]
+		rv = []
+		for i in range(num):
+			strlen = ord(data[0])
+			if strlen < 0: strlen = strlen + 256
+			str = data[1:strlen+1]
+			data = data[strlen+1:]
+			rv.append(str)
+		return rv
+
+	def save(self, list):
+		rv = struct.pack('h', len(list))
+		for str in list:
+			rv = rv + chr(len(str)) + str
+		self.loader.save(rv)
+		
+	def delete(self, deep=0):
+		self.loader.delete(deep)
+
+def preferencefile(filename, creator=None, type=None):
+	create = creator != None and type != None
+	vrefnum, dirid = macfs.FindFolder(MACFS.kOnSystemDisk, 'pref', create)
+	fss = macfs.FSSpec((vrefnum, dirid, filename))
+	oldrf = Res.CurResFile()
+	if create:
+		try:
+			rh = Res.FSpOpenResFile(fss, READ)
+		except Res.Error:
+			Res.FSpCreateResFile(fss, creator, type, MACFS.smAllScripts)
+		else:
+			Res.CloseResFile(rh)
+			Res.UseResFile(oldrf)
+	return fss
+	
diff --git a/Mac/Lib/pythonprefs.py b/Mac/Lib/pythonprefs.py
new file mode 100644
index 0000000..1f9fd98
--- /dev/null
+++ b/Mac/Lib/pythonprefs.py
@@ -0,0 +1,122 @@
+from preferences import *
+
+# Names of Python resources
+PREFNAME_NAME="PythonPreferenceFileName"
+
+# Resource IDs in the preferences file
+PATH_ID = 128
+DIR_ID = 128
+POPT_ID = 128
+GUSI_ID = 10240
+
+# Override IDs (in the applet)
+OVERRIDE_PATH_ID = 129
+OVERRIDE_DIR_ID = 129
+OVERRIDE_POPT_ID = 129
+OVERRIDE_GUSI_ID = 10241
+
+# version
+CUR_VERSION=3
+
+preffilename = PstringLoader(AnyResLoader('STR ', resname=PREFNAME_NAME)).load()
+pref_fss = preferencefile(preffilename, 'Pyth', 'pref')
+
+class PoptLoader(VersionLoader):
+	def __init__(self, loader):
+		VersionLoader.__init__(self, "bbbbbbbbbb", loader)
+		
+	def versioncheck(self, data):
+		if data[0] == CUR_VERSION:
+			return data
+		print 'old resource'
+		raise Error, "old resource"
+		
+class GusiLoader:
+	def __init__(self, loader):
+		self.loader = loader
+		self.data = None
+		
+	def load(self):
+		self.data = self.loader.load()
+		while self.data[10:14] != '0181':
+			self.loader.delete(1)
+			self.loader.load()
+		tp = self.data[0:4]
+		cr = self.data[4:8]
+		flags = ord(self.data[9])
+		delay = ((flags & 0x20) == 0x20)
+		return cr, tp, delay
+		
+	def save(self, (cr, tp, delay)):
+		flags = ord(self.data[9])
+		if delay:
+			flags = flags | 0x20
+		else:
+			flags = flags & ~0x20
+		newdata = tp + cr + self.data[8] + chr(flags) + self.data[10:]
+		self.loader.save(newdata)
+		
+popt_default_default = NullLoader(chr(CUR_VERSION) + 8*'\0')
+popt_default = AnyResLoader('Popt', POPT_ID, default=popt_default_default)
+popt_loader = ResLoader(pref_fss, 'Popt', POPT_ID, default=popt_default)
+popt = PoptLoader(popt_loader)
+
+dir_default = AnyResLoader('alis', DIR_ID)
+dir = ResLoader(pref_fss, 'alis', DIR_ID, default=dir_default)
+
+gusi_default = AnyResLoader('GU\267I', GUSI_ID)
+gusi_loader = ResLoader(pref_fss, 'GU\267I', GUSI_ID, default=gusi_default)
+gusi = GusiLoader(gusi_loader)
+
+path_default = AnyResLoader('STR#', PATH_ID)
+path_loader = ResLoader(pref_fss, 'STR#', PATH_ID, default=path_default)
+path = StrListLoader(path_loader)
+
+class PythonOptions:
+	def __init__(self, popt=popt, dir=dir, gusi=gusi, path=path):
+		self.popt = popt
+		self.dir = dir
+		self.gusi = gusi
+		self.path = path
+		
+	def load(self):
+		dict = {}
+		dict['path'] = self.path.load()
+		diralias = self.dir.load()
+		dirfss, dummy = macfs.RawAlias(diralias).Resolve()
+		dict['dir'] = dirfss
+		dict['creator'], dict['type'], dict['delayconsole'] = self.gusi.load()
+		flags = self.popt.load()
+		dict['version'], dict['inspect'], dict['verbose'], dict['optimize'], \
+			dict['unbuffered'], dict['debugging'], dict['keepopen'], dict['keeperror'], \
+			dict['nointopt'], dict['noargs'] = flags
+		return dict
+		
+	def save(self, dict):
+		self.path.save(dict['path'])
+		diralias = macfs.FSSpec(dict['dir']).NewAlias().data
+		self.dir.save(diralias)
+		self.gusi.save((dict['creator'], dict['type'], dict['delayconsole']))
+		flags = dict['version'], dict['inspect'], dict['verbose'], dict['optimize'], \
+			dict['unbuffered'], dict['debugging'], dict['keepopen'], dict['keeperror'], \
+			dict['nointopt'], dict['noargs'] 
+		self.popt.save(flags)
+
+def AppletOptions(file):
+	fss = macfs.FSSpec(file)
+	a_popt = PoptLoader(ResLoader(fss, 'Popt', OVERRIDE_POPT_ID, default=popt_loader))
+	a_dir = ResLoader(fss, 'alis', OVERRIDE_DIR_ID, default=dir)
+	a_gusi = ResLoader(fss, 'GU\267I', OVERRIDE_GUSI_ID, default=gusi_loader)
+	a_path = StrListLoader(fss, 'STR#', OVERRIDE_PATH_ID, default=path_loader)
+	return PythonOptions(a_popt, a_dir, a_gusi, a_path)
+	
+def _test():
+	import preferences
+	preferences.debug = 1
+	dict = PythonOptions().load()
+	for k in dict.keys():
+		print k, '\t', dict[k]
+		
+if __name__ == '__main__':
+	_test()
+