added command line interface; refactored a bit; little things.
diff --git a/Mac/Lib/bundlebuilder.py b/Mac/Lib/bundlebuilder.py
index 319522b..ffcda5b 100755
--- a/Mac/Lib/bundlebuilder.py
+++ b/Mac/Lib/bundlebuilder.py
@@ -3,31 +3,39 @@
 """\
 bundlebuilder.py -- Tools to assemble MacOS X (application) bundles.
 
-This module contains three classes to build so called "bundles" for
+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. CocoaAppBuilder is a
-further specialization of AppBuilder.
+specialized in building application bundles.
 
-[Bundle|App|CocoaApp]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.
+[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:
-# - a command line interface, also for use with the buildapp() and
-#   buildcocoaapp() convenience functions.
 # - modulefinder support to build standalone apps
+# - consider turning this into a distutils extension
 #
 
-__all__ = ["BundleBuilder", "AppBuilder", "CocoaAppBuilder",
-		"buildapp", "buildcocoaapp"]
+__all__ = ["BundleBuilder", "AppBuilder", "buildapp"]
 
 
 import sys
 import os, errno, shutil
+import getopt
 from plistlib import Plist
 
 
@@ -62,34 +70,43 @@
 		verbosity: verbosity level, defaults to 1
 	"""
 
-	def __init__(self, name, plist=None, type="APPL", creator="????",
+	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."""
-		self.name, ext = os.path.splitext(name)
-		if not ext:
-			ext = ".bundle"
-		self.bundleextension = ext
 		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
-		if files is None:
-			files = []
-		if resources is None:
-			resources = []
 		self.resources = resources
 		self.files = files
 		self.builddir = builddir
 		self.platform = platform
 		self.symlink = symlink
-		# misc (derived) attributes
-		self.bundlepath = pathjoin(builddir, self.name + self.bundleextension)
-		self.execdir = pathjoin("Contents", platform)
-		self.resdir = pathjoin("Contents", "Resources")
 		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
@@ -124,13 +141,8 @@
 		f.close()
 		#
 		# Write Contents/Info.plist
-		plist = plistDefaults.copy()
-		plist.CFBundleName = self.name
-		plist.CFBundlePackageType = self.type
-		plist.CFBundleSignature = self.creator
-		plist.update(self.plist)
 		infoplist = pathjoin(contents, "Info.plist")
-		plist.write(infoplist)
+		self.plist.write(infoplist)
 
 	def _copyFiles(self):
 		files = self.files[:]
@@ -144,7 +156,10 @@
 			self.message("Copying files", 1)
 			msg = "Copying"
 		for src, dst in files:
-			self.message("%s %s to %s" % (msg, src, dst), 2)
+			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)
@@ -153,7 +168,15 @@
 
 	def message(self, msg, level=0):
 		if level <= self.verbosity:
-			sys.stderr.write(msg + "\n")
+			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 = """\
@@ -166,18 +189,20 @@
 mainprogram = os.path.join(resources, "%(mainprogram)s")
 assert os.path.exists(mainprogram)
 argv.insert(1, mainprogram)
-%(executable)s
+os.environ["PYTHONPATH"] = resources
+%(setpythonhome)s
+%(setexecutable)s
 os.execve(executable, argv, os.environ)
 """
 
-executableTemplate = "executable = os.path.join(resources, \"%s\")"
-
+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.)
@@ -185,46 +210,59 @@
 			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,
-			**kwargs):
+			nibname=None, **kwargs):
 		"""See the class doc string for a description of the arguments."""
-		if mainprogram is None and executable is None:
-			raise TypeError, ("must specify either or both of "
-					"'executable' and 'mainprogram'")
-		if name is not None:
-			pass
-		elif mainprogram is not None:
-			name = os.path.splitext(os.path.basename(mainprogram))[0]
-		elif executable is not None:
-			name = os.path.splitext(os.path.basename(executable))[0]
-		if name[-4:] != ".app":
-			name += ".app"
-
 		self.mainprogram = mainprogram
 		self.executable = executable
-
+		self.nibname = nibname
 		BundleBuilder.__init__(self, name=name, **kwargs)
 
-	def preProcess(self):
+	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"
 		self.plist.CFBundleExecutable = self.name
+
+		if self.nibname:
+			self.plist.NSMainNibFile = self.nibname
+			if not hasattr(self.plist, "NSPrincipalClass"):
+				self.plist.NSPrincipalClass = "NSApplication"
+
+		BundleBuilder.setup(self)
+
+	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(self.resdir, os.path.basename(self.executable))
+				execpath = pathjoin(resdir, os.path.basename(self.executable))
 			self.files.append((self.executable, execpath))
 			# For execve wrapper
-			executable = executableTemplate % os.path.basename(self.executable)
+			setexecutable = setExecutableTemplate % os.path.basename(self.executable)
 		else:
-			executable = ""  # XXX for locals() call
+			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(self.resdir, mainname)))
+			self.files.append((self.mainprogram, pathjoin(resdir, mainname)))
 			# Create execve wrapper
 			mainprogram = self.mainprogram  # XXX for locals() call
 			execdir = pathjoin(self.bundlepath, self.execdir)
@@ -234,22 +272,6 @@
 			os.chmod(mainwrapperpath, 0777)
 
 
-class CocoaAppBuilder(AppBuilder):
-
-	"""Tiny specialization of AppBuilder. It has an extra constructor
-	argument called 'nibname' which defaults to 'MainMenu'. It will
-	set the appropriate fields in the plist.
-	"""
-
-	def __init__(self, nibname="MainMenu", **kwargs):
-		"""See the class doc string for a description of the arguments."""
-		self.nibname = nibname
-		AppBuilder.__init__(self, **kwargs)
-		self.plist.NSMainNibFile = self.nibname
-		if not hasattr(self.plist, "NSPrincipalClass"):
-			self.plist.NSPrincipalClass = "NSApplication"
-
-
 def copy(src, dst, mkdirs=0):
 	"""Copy a file or a directory."""
 	if mkdirs:
@@ -287,21 +309,96 @@
 	return os.path.join(*args)
 
 
+cmdline_doc = """\
+Usage:
+  python [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):
-	# XXX cmd line argument parsing
 	builder = AppBuilder(**kwargs)
-	builder.build()
-
-
-def buildcocoaapp(**kwargs):
-	# XXX cmd line argument parsing
-	builder = CocoaAppBuilder(**kwargs)
-	builder.build()
+	main(builder)
 
 
 if __name__ == "__main__":
-	# XXX This test is meant to be run in the Examples/TableModel/ folder
-	# of the pyobj project... It will go as soon as I've written a proper
-	# main program.
-	buildcocoaapp(mainprogram="TableModel.py",
-		resources=["English.lproj", "nibwrapper.py"], verbosity=4)
+	main()