Commits

Ronald Oussoren committed 6e9870e

Added bundlebuilder and plistlib from (Mac)Python 2.3. These are not
in the normal Lib tree because we should not normally install these (only
when the user didn't install MacPython)

Comments (0)

Files changed (3)

pyobjc/MPCompat/bundlebuilder.py

+#! /usr/bin/env python
+
+"""\
+bundlebuilder.py -- Tools to assemble MacOS X (application) bundles.
+
+This module contains two classes to build so called "bundles" for
+MacOS X. BundleBuilder is a general tool, AppBuilder is a subclass
+specialized in building application bundles.
+
+[Bundle|App]Builder objects are instantiated with a bunch of keyword
+arguments, and have a build() method that will do all the work. See
+the class doc strings for a description of the constructor arguments.
+
+The module contains a main program that can be used in two ways:
+
+  % python bundlebuilder.py [options] build
+  % python buildapp.py [options] build
+
+Where "buildapp.py" is a user-supplied setup.py-like script following
+this model:
+
+  from bundlebuilder import buildapp
+  buildapp(<lots-of-keyword-args>)
+
+"""
+
+#
+# XXX Todo:
+# - modulefinder support to build standalone apps
+# - consider turning this into a distutils extension
+#
+
+__all__ = ["BundleBuilder", "AppBuilder", "buildapp"]
+
+
+import sys
+import os, errno, shutil
+import getopt
+from plistlib import Plist
+
+
+plistDefaults = Plist(
+	CFBundleDevelopmentRegion = "English",
+	CFBundleInfoDictionaryVersion = "6.0",
+)
+
+
+class BundleBuilder:
+
+	"""BundleBuilder is a barebones class for assembling bundles. It
+	knows nothing about executables or icons, it only copies files
+	and creates the PkgInfo and Info.plist files.
+
+	Constructor arguments:
+
+		name: Name of the bundle, with or without extension.
+		plist: A plistlib.Plist object.
+		type: The type of the bundle. Defaults to "APPL".
+		creator: The creator code of the bundle. Defaults to "????".
+		resources: List of files that have to be copied to
+			<bundle>/Contents/Resources. Defaults to an empty list.
+		files: List of (src, dest) tuples; dest should be a path relative
+			to the bundle (eg. "Contents/Resources/MyStuff/SomeFile.ext.
+			Defaults to an empty list.
+		builddir: Directory where the bundle will be assembled. Defaults
+			to "build" (in the current directory).
+		symlink: Make symlinks instead copying files. This is handy during
+			debugging, but makes the bundle non-distributable. Defaults to
+			False.
+		verbosity: verbosity level, defaults to 1
+	"""
+
+	def __init__(self, name=None, plist=None, type="APPL", creator="????",
+			resources=None, files=None, builddir="build", platform="MacOS",
+			symlink=0, verbosity=1):
+		"""See the class doc string for a description of the arguments."""
+		if plist is None:
+			plist = Plist()
+		if resources is None:
+			resources = []
+		if files is None:
+			files = []
+		self.name = name
+		self.plist = plist
+		self.type = type
+		self.creator = creator
+		self.resources = resources
+		self.files = files
+		self.builddir = builddir
+		self.platform = platform
+		self.symlink = symlink
+		self.verbosity = verbosity
+
+	def setup(self):
+		self.name, ext = os.path.splitext(self.name)
+		if not ext:
+			ext = ".bundle"
+		self.bundleextension = ext
+		# misc (derived) attributes
+		self.bundlepath = pathjoin(self.builddir, self.name + self.bundleextension)
+		self.execdir = pathjoin("Contents", self.platform)
+
+		plist = plistDefaults.copy()
+		plist.CFBundleName = self.name
+		plist.CFBundlePackageType = self.type
+		plist.CFBundleSignature = self.creator
+		plist.update(self.plist)
+		self.plist = plist
+
+	def build(self):
+		"""Build the bundle."""
+		builddir = self.builddir
+		if builddir and not os.path.exists(builddir):
+			os.mkdir(builddir)
+		self.message("Building %s" % repr(self.bundlepath), 1)
+		if os.path.exists(self.bundlepath):
+			shutil.rmtree(self.bundlepath)
+		os.mkdir(self.bundlepath)
+		self.preProcess()
+		self._copyFiles()
+		self._addMetaFiles()
+		self.postProcess()
+
+	def preProcess(self):
+		"""Hook for subclasses."""
+		pass
+	def postProcess(self):
+		"""Hook for subclasses."""
+		pass
+
+	def _addMetaFiles(self):
+		contents = pathjoin(self.bundlepath, "Contents")
+		makedirs(contents)
+		#
+		# Write Contents/PkgInfo
+		assert len(self.type) == len(self.creator) == 4, \
+				"type and creator must be 4-byte strings."
+		pkginfo = pathjoin(contents, "PkgInfo")
+		f = open(pkginfo, "wb")
+		f.write(self.type + self.creator)
+		f.close()
+		#
+		# Write Contents/Info.plist
+		infoplist = pathjoin(contents, "Info.plist")
+		self.plist.write(infoplist)
+
+	def _copyFiles(self):
+		files = self.files[:]
+		for path in self.resources:
+			files.append((path, pathjoin("Contents", "Resources",
+				os.path.basename(path))))
+		if self.symlink:
+			self.message("Making symbolic links", 1)
+			msg = "Making symlink from"
+		else:
+			self.message("Copying files", 1)
+			msg = "Copying"
+		for src, dst in files:
+			if os.path.isdir(src):
+				self.message("%s %s/ to %s/" % (msg, src, dst), 2)
+			else:
+				self.message("%s %s to %s" % (msg, src, dst), 2)
+			dst = pathjoin(self.bundlepath, dst)
+			if self.symlink:
+				symlink(src, dst, mkdirs=1)
+			else:
+				copy(src, dst, mkdirs=1)
+
+	def message(self, msg, level=0):
+		if level <= self.verbosity:
+			indent = ""
+			if level > 1:
+				indent = (level - 1) * "  "
+			sys.stderr.write(indent + msg + "\n")
+
+	def report(self):
+		# XXX something decent
+		import pprint
+		pprint.pprint(self.__dict__)
+
+
+mainWrapperTemplate = """\
+#!/usr/bin/env python
+
+import os
+from sys import argv, executable
+resources = os.path.join(os.path.dirname(os.path.dirname(argv[0])),
+		"Resources")
+mainprogram = os.path.join(resources, "%(mainprogram)s")
+assert os.path.exists(mainprogram)
+argv.insert(1, mainprogram)
+os.environ["PYTHONPATH"] = resources
+%(setpythonhome)s
+%(setexecutable)s
+os.execve(executable, argv, os.environ)
+"""
+
+setExecutableTemplate = """executable = os.path.join(resources, "%s")"""
+pythonhomeSnippet = """os.environ["home"] = resources"""
+
+class AppBuilder(BundleBuilder):
+
+	"""This class extends the BundleBuilder constructor with these
+	arguments:
+
+		mainprogram: A Python main program. If this argument is given,
+			the main executable in the bundle will be a small wrapper
+			that invokes the main program. (XXX Discuss why.)
+		executable: The main executable. If a Python main program is
+			specified the executable will be copied to Resources and
+			be invoked by the wrapper program mentioned above. Else
+			it will simply be used as the main executable.
+		nibname: The name of the main nib, for Cocoa apps. Defaults
+			to None, but must be specified when building a Cocoa app.
+
+	For the other keyword arguments see the BundleBuilder doc string.
+	"""
+
+	def __init__(self, name=None, mainprogram=None, executable=None,
+			nibname=None, **kwargs):
+		"""See the class doc string for a description of the arguments."""
+		self.mainprogram = mainprogram
+		self.executable = executable
+		self.nibname = nibname
+		BundleBuilder.__init__(self, name=name, **kwargs)
+
+	def setup(self):
+		if self.mainprogram is None and self.executable is None:
+			raise TypeError, ("must specify either or both of "
+					"'executable' and 'mainprogram'")
+
+		if self.name is not None:
+			pass
+		elif self.mainprogram is not None:
+			self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
+		elif executable is not None:
+			self.name = os.path.splitext(os.path.basename(self.executable))[0]
+		if self.name[-4:] != ".app":
+			self.name += ".app"
+
+		if self.nibname:
+			self.plist.NSMainNibFile = self.nibname
+			if not hasattr(self.plist, "NSPrincipalClass"):
+				self.plist.NSPrincipalClass = "NSApplication"
+
+		BundleBuilder.setup(self)
+
+		self.plist.CFBundleExecutable = self.name
+
+	def preProcess(self):
+		resdir = pathjoin("Contents", "Resources")
+		if self.executable is not None:
+			if self.mainprogram is None:
+				execpath = pathjoin(self.execdir, self.name)
+			else:
+				execpath = pathjoin(resdir, os.path.basename(self.executable))
+			self.files.append((self.executable, execpath))
+			# For execve wrapper
+			setexecutable = setExecutableTemplate % os.path.basename(self.executable)
+		else:
+			setexecutable = ""  # XXX for locals() call
+
+		if self.mainprogram is not None:
+			setpythonhome = ""  # pythonhomeSnippet if we're making a standalone app
+			mainname = os.path.basename(self.mainprogram)
+			self.files.append((self.mainprogram, pathjoin(resdir, mainname)))
+			# Create execve wrapper
+			mainprogram = self.mainprogram  # XXX for locals() call
+			execdir = pathjoin(self.bundlepath, self.execdir)
+			mainwrapperpath = pathjoin(execdir, self.name)
+			makedirs(execdir)
+			open(mainwrapperpath, "w").write(mainWrapperTemplate % locals())
+			os.chmod(mainwrapperpath, 0777)
+
+
+def copy(src, dst, mkdirs=0):
+	"""Copy a file or a directory."""
+	if mkdirs:
+		makedirs(os.path.dirname(dst))
+	if os.path.isdir(src):
+		shutil.copytree(src, dst)
+	else:
+		shutil.copy2(src, dst)
+
+def copytodir(src, dstdir):
+	"""Copy a file or a directory to an existing directory."""
+	dst = pathjoin(dstdir, os.path.basename(src))
+	copy(src, dst)
+
+def makedirs(dir):
+	"""Make all directories leading up to 'dir' including the leaf
+	directory. Don't moan if any path element already exists."""
+	try:
+		os.makedirs(dir)
+	except OSError, why:
+		if why.errno != errno.EEXIST:
+			raise
+
+def symlink(src, dst, mkdirs=0):
+	"""Copy a file or a directory."""
+	if mkdirs:
+		makedirs(os.path.dirname(dst))
+	os.symlink(os.path.abspath(src), dst)
+
+def pathjoin(*args):
+	"""Safe wrapper for os.path.join: asserts that all but the first
+	argument are relative paths."""
+	for seg in args[1:]:
+		assert seg[0] != "/"
+	return os.path.join(*args)
+
+
+cmdline_doc = """\
+Usage:
+  python bundlebuilder.py [options] command
+  python mybuildscript.py [options] command
+
+Commands:
+  build      build the application
+  report     print a report
+
+Options:
+  -b, --builddir=DIR     the build directory; defaults to "build"
+  -n, --name=NAME        application name
+  -r, --resource=FILE    extra file or folder to be copied to Resources
+  -e, --executable=FILE  the executable to be used
+  -m, --mainprogram=FILE the Python main program
+  -p, --plist=FILE       .plist file (default: generate one)
+      --nib=NAME         main nib name
+  -c, --creator=CCCC     4-char creator code (default: '????')
+  -l, --link             symlink files/folder instead of copying them
+  -v, --verbose          increase verbosity level
+  -q, --quiet            decrease verbosity level
+  -h, --help             print this message
+"""
+
+def usage(msg=None):
+	if msg:
+		print msg
+	print cmdline_doc
+	sys.exit(1)
+
+def main(builder=None):
+	if builder is None:
+		builder = AppBuilder(verbosity=1)
+
+	shortopts = "b:n:r:e:m:c:plhvq"
+	longopts = ("builddir=", "name=", "resource=", "executable=",
+		"mainprogram=", "creator=", "nib=", "plist=", "link", "help",
+		"verbose", "quiet")
+
+	try:
+		options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
+	except getopt.error:
+		usage()
+
+	for opt, arg in options:
+		if opt in ('-b', '--builddir'):
+			builder.builddir = arg
+		elif opt in ('-n', '--name'):
+			builder.name = arg
+		elif opt in ('-r', '--resource'):
+			builder.resources.append(arg)
+		elif opt in ('-e', '--executable'):
+			builder.executable = arg
+		elif opt in ('-m', '--mainprogram'):
+			builder.mainprogram = arg
+		elif opt in ('-c', '--creator'):
+			builder.creator = arg
+		elif opt == "--nib":
+			builder.nibname = arg
+		elif opt in ('-p', '--plist'):
+			builder.plist = Plist.fromFile(arg)
+		elif opt in ('-l', '--link'):
+			builder.symlink = 1
+		elif opt in ('-h', '--help'):
+			usage()
+		elif opt in ('-v', '--verbose'):
+			builder.verbosity += 1
+		elif opt in ('-q', '--quiet'):
+			builder.verbosity -= 1
+
+	if len(args) != 1:
+		usage("Must specify one command ('build', 'report' or 'help')")
+	command = args[0]
+
+	if command == "build":
+		builder.setup()
+		builder.build()
+	elif command == "report":
+		builder.setup()
+		builder.report()
+	elif command == "help":
+		usage()
+	else:
+		usage("Unknown command '%s'" % command)
+
+
+def buildapp(**kwargs):
+	builder = AppBuilder(**kwargs)
+	main(builder)
+
+
+if __name__ == "__main__":
+	main()

pyobjc/MPCompat/plistlib.py

+"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
+
+The main class in this module is Plist. It takes a set of arbitrary
+keyword arguments, which will be the top level elements of the plist
+dictionary. After instantiation you can add more elements by assigning
+new attributes to the Plist instance.
+
+To write out a plist file, call the write() method of the Plist
+instance with a filename or a file object.
+
+To parse a plist from a file, use the Plist.fromFile(pathOrFile)
+classmethod, with a file name or a file object as the only argument.
+(Warning: you need pyexpat installed for this to work, ie. it doesn't
+work with a vanilla Python 2.2 as shipped with MacOS X.2.)
+
+Values can be strings, integers, floats, booleans, tuples, lists,
+dictionaries, Data or Date objects. String values (including dictionary
+keys) may be unicode strings -- they will be written out as UTF-8.
+
+For convenience, this module exports a class named Dict(), which
+allows you to easily construct (nested) dicts using keyword arguments.
+But regular dicts work, too.
+
+To support Boolean values in plists with Python < 2.3, "bool", "True"
+and "False" are exported. Use these symbols from this module if you
+want to be compatible with Python 2.2.x (strongly recommended).
+
+The <data> plist type is supported through the Data class. This is a
+thin wrapper around a Python string.
+
+The <date> plist data has (limited) support through the Date class.
+(Warning: Dates are only supported if the PyXML package is installed.)
+
+Generate Plist example:
+
+	pl = Plist(
+		aString="Doodah",
+		aList=["A", "B", 12, 32.1, [1, 2, 3]],
+		aFloat = 0.1,
+		anInt = 728,
+		aDict=Dict(
+			anotherString="<hello & hi there!>",
+			aUnicodeValue=u'M\xe4ssig, Ma\xdf',
+			aTrueValue=True,
+			aFalseValue=False,
+		),
+		someData = Data("<binary gunk>"),
+		someMoreData = Data("<lots of binary gunk>" * 10),
+		aDate = Date(time.mktime(time.gmtime())),
+	)
+	# unicode keys are possible, but a little awkward to use:
+	pl[u'\xc5benraa'] = "That was a unicode key."
+	pl.write(fileName)
+
+Parse Plist example:
+
+	pl = Plist.fromFile(pathOrFile)
+	print pl.aKey
+
+
+"""
+
+# written by Just van Rossum (just@letterror.com), 2002-11-19
+
+
+__all__ = ["Plist", "Data", "Date", "Dict", "False", "True", "bool"]
+
+
+INDENT = "\t"
+
+
+class DumbXMLWriter:
+
+	def __init__(self, file):
+		self.file = file
+		self.stack = []
+		self.indentLevel = 0
+
+	def beginElement(self, element):
+		self.stack.append(element)
+		self.writeln("<%s>" % element)
+		self.indentLevel += 1
+
+	def endElement(self, element):
+		assert self.indentLevel > 0
+		assert self.stack.pop() == element
+		self.indentLevel -= 1
+		self.writeln("</%s>" % element)
+
+	def simpleElement(self, element, value=None):
+		if value:
+			value = _encode(value)
+			self.writeln("<%s>%s</%s>" % (element, value, element))
+		else:
+			self.writeln("<%s/>" % element)
+
+	def writeln(self, line):
+		if line:
+			self.file.write(self.indentLevel * INDENT + line + "\n")
+		else:
+			self.file.write("\n")
+
+
+def _encode(text):
+	text = text.replace("&", "&amp;")
+	text = text.replace("<", "&lt;")
+	return text.encode("utf-8")
+
+
+PLISTHEADER = """\
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+"""
+
+class PlistWriter(DumbXMLWriter):
+
+	def __init__(self, file):
+		file.write(PLISTHEADER)
+		DumbXMLWriter.__init__(self, file)
+
+	def writeValue(self, value):
+		if isinstance(value, (str, unicode)):
+			self.simpleElement("string", value)
+		elif isinstance(value, bool):
+			# must switch for bool before int, as bool is a
+			# subclass of int...
+			if value:
+				self.simpleElement("true")
+			else:
+				self.simpleElement("false")
+		elif isinstance(value, int):
+			self.simpleElement("integer", str(value))
+		elif isinstance(value, float):
+			# should perhaps use repr() for better precision?
+			self.simpleElement("real", str(value))
+		elif isinstance(value, (dict, Dict)):
+			self.writeDict(value)
+		elif isinstance(value, Data):
+			self.writeData(value)
+		elif isinstance(value, Date):
+			self.simpleElement("date", value.toString())
+		elif isinstance(value, (tuple, list)):
+			self.writeArray(value)
+		else:
+			assert 0, "unsuported type: %s" % type(value)
+
+	def writeData(self, data):
+		self.beginElement("data")
+		for line in data.asBase64().split("\n"):
+			if line:
+				self.writeln(line)
+		self.endElement("data")
+
+	def writeDict(self, d):
+		self.beginElement("dict")
+		items = d.items()
+		items.sort()
+		for key, value in items:
+			assert isinstance(key, (str, unicode)), "keys must be strings"
+			self.simpleElement("key", key)
+			self.writeValue(value)
+		self.endElement("dict")
+
+	def writeArray(self, array):
+		self.beginElement("array")
+		for value in array:
+			self.writeValue(value)
+		self.endElement("array")
+
+
+class Dict:
+
+	"""Dict wrapper for convenient access of values through attributes."""
+
+	def __init__(self, **kwargs):
+		self.__dict__.update(kwargs)
+
+	def __cmp__(self, other):
+		if isinstance(other, self.__class__):
+			return cmp(self.__dict__, other.__dict__)
+		elif isinstance(other, dict):
+			return cmp(self.__dict__, other)
+		else:
+			return cmp(id(self), id(other))
+
+	def __str__(self):
+		return "%s(**%s)" % (self.__class__.__name__, self.__dict__)
+	__repr__ = __str__
+
+	def copy(self):
+		return self.__class__(**self.__dict__)
+
+	def __getattr__(self, attr):
+		"""Delegate everything else to the dict object."""
+		return getattr(self.__dict__, attr)
+
+
+class Plist(Dict):
+
+	"""The main Plist object. Basically a dict (the toplevel object
+	of a plist is a dict) with two additional methods to read from
+	and write to files.
+	"""
+
+	def fromFile(cls, pathOrFile):
+		didOpen = 0
+		if not hasattr(pathOrFile, "write"):
+			pathOrFile = open(pathOrFile)
+			didOpen = 1
+		p = PlistParser()
+		plist = p.parse(pathOrFile)
+		if didOpen:
+			pathOrFile.close()
+		return plist
+	fromFile = classmethod(fromFile)
+
+	def write(self, pathOrFile):
+		if not hasattr(pathOrFile, "write"):
+			pathOrFile = open(pathOrFile, "w")
+			didOpen = 1
+		else:
+			didOpen = 0
+
+		writer = PlistWriter(pathOrFile)
+		writer.writeln("<plist version=\"1.0\">")
+		writer.writeDict(self.__dict__)
+		writer.writeln("</plist>")
+
+		if didOpen:
+			pathOrFile.close()
+
+
+class Data:
+
+	"""Wrapper for binary data."""
+
+	def __init__(self, data):
+		self.data = data
+
+	def fromBase64(cls, data):
+		import base64
+		return cls(base64.decodestring(data))
+	fromBase64 = classmethod(fromBase64)
+
+	def asBase64(self):
+		import base64
+		return base64.encodestring(self.data)
+
+	def __cmp__(self, other):
+		if isinstance(other, self.__class__):
+			return cmp(self.data, other.data)
+		elif isinstance(other, str):
+			return cmp(self.data, other)
+		else:
+			return cmp(id(self), id(other))
+
+	def __repr__(self):
+		return "%s(%s)" % (self.__class__.__name__, repr(self.data))
+
+
+class Date:
+
+	"""Primitive date wrapper, uses time floats internally, is agnostic
+	about time zones.
+	"""
+
+	def __init__(self, date):
+		if isinstance(date, str):
+			from xml.utils.iso8601 import parse
+			date = parse(date)
+		self.date = date
+
+	def toString(self):
+		from xml.utils.iso8601 import tostring
+		return tostring(self.date)
+
+	def __cmp__(self, other):
+		if isinstance(other, self.__class__):
+			return cmp(self.date, other.date)
+		elif isinstance(other, (int, float)):
+			return cmp(self.date, other)
+		else:
+			return cmp(id(self), id(other))
+
+	def __repr__(self):
+		return "%s(%s)" % (self.__class__.__name__, repr(self.toString()))
+
+
+class PlistParser:
+
+	def __init__(self):
+		self.stack = []
+		self.currentKey = None
+		self.root = None
+
+	def parse(self, file):
+		from xml.parsers.expat import ParserCreate
+		parser = ParserCreate()
+		parser.StartElementHandler = self.handleBeginElement
+		parser.EndElementHandler = self.handleEndElement
+		parser.CharacterDataHandler = self.handleData
+		parser.ParseFile(file)
+		return self.root
+
+	def handleBeginElement(self, element, attrs):
+		self.data = []
+		handler = getattr(self, "begin_" + element, None)
+		if handler is not None:
+			handler(attrs)
+
+	def handleEndElement(self, element):
+		handler = getattr(self, "end_" + element, None)
+		if handler is not None:
+			handler()
+
+	def handleData(self, data):
+		self.data.append(data)
+
+	def addObject(self, value):
+		if self.currentKey is not None:
+			self.stack[-1][self.currentKey] = value
+			self.currentKey = None
+		elif not self.stack:
+			# this is the root object
+			assert self.root is value
+		else:
+			self.stack[-1].append(value)
+
+	def getData(self):
+		data = "".join(self.data)
+		try:
+			data = data.encode("ascii")
+		except UnicodeError:
+			pass
+		self.data = []
+		return data
+
+	# element handlers
+
+	def begin_dict(self, attrs):
+		if self.root is None:
+			self.root = d = Plist()
+		else:
+			d = Dict()
+		self.addObject(d)
+		self.stack.append(d)
+	def end_dict(self):
+		self.stack.pop()
+
+	def end_key(self):
+		self.currentKey = self.getData()
+
+	def begin_array(self, attrs):
+		a = []
+		self.addObject(a)
+		self.stack.append(a)
+	def end_array(self):
+		self.stack.pop()
+
+	def end_true(self):
+		self.addObject(True)
+	def end_false(self):
+		self.addObject(False)
+	def end_integer(self):
+		self.addObject(int(self.getData()))
+	def end_real(self):
+		self.addObject(float(self.getData()))
+	def end_string(self):
+		self.addObject(self.getData())
+	def end_data(self):
+		self.addObject(Data.fromBase64(self.getData()))
+	def end_date(self):
+		self.addObject(Date(self.getData()))
+
+
+# cruft to support booleans in Python <= 2.3
+import sys
+if sys.version_info[:2] < (2, 3):
+	# Python 2.2 and earlier: no booleans
+	# Python 2.2.x: booleans are ints
+	class bool(int):
+		"""Imitation of the Python 2.3 bool object."""
+		def __new__(cls, value):
+			return int.__new__(cls, not not value)
+		def __repr__(self):
+			if self:
+				return "True"
+			else:
+				return "False"
+	True = bool(1)
+	False = bool(0)
+else:
+	# Bind the boolean builtins to local names
+	True = True
+	False = False
+	bool = bool
+
+
+if __name__ == "__main__":
+	from StringIO import StringIO
+	import time
+	if len(sys.argv) == 1:
+		pl = Plist(
+			aString="Doodah",
+			aList=["A", "B", 12, 32.1, [1, 2, 3]],
+			aFloat = 0.1,
+			anInt = 728,
+			aDict=Dict(
+				anotherString="<hello & hi there!>",
+				aUnicodeValue=u'M\xe4ssig, Ma\xdf',
+				aTrueValue=True,
+				aFalseValue=False,
+			),
+			someData = Data("<binary gunk>"),
+			someMoreData = Data("<lots of binary gunk>" * 10),
+			aDate = Date(time.mktime(time.gmtime())),
+		)
+	elif len(sys.argv) == 2:
+		pl = Plist.fromFile(sys.argv[1])
+	else:
+		print "Too many arguments: at most 1 plist file can be given."
+		sys.exit(1)
+
+	# unicode keys are possible, but a little awkward to use:
+	pl[u'\xc5benraa'] = "That was a unicode key."
+	f = StringIO()
+	pl.write(f)
+	xml = f.getvalue()
+	print xml
+	f.seek(0)
+	pl2 = Plist.fromFile(f)
+	assert pl == pl2
+	f = StringIO()
+	pl2.write(f)
+	assert xml == f.getvalue()
+	#print repr(pl2)

pyobjc/MPCompat/setup.py

+#!/usr/bin/env python
+"""
+A number of usefull modules from MacPython. These are necessary to build
+.app bundles using python scripts (e.g. Examples/TableModel/buildpyapp.py)
+
+Install this only if you do not have a MacPython installation!
+"""
+
+from distutils.core import setup
+
+setup (name = "pyobjc-macpython",
+          version = '0.1',
+           description = "PyObjC: Modules borrowed from MacPython",
+           author_email = "pyobjc-dev@lists.sourceforge.net",
+	   url = "http://pyobjc.sourceforge.net/",
+           py_modules = [ 'bundlebuilder', 'plistlib' ],
+           package_dir = { '': '' },
+           )
+
+