summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorGreg Ward <gward@python.net>1999-03-22 14:52:19 (GMT)
committerGreg Ward <gward@python.net>1999-03-22 14:52:19 (GMT)
commit2689e3ddce70e8acc5bc231a80221980d5bdfec3 (patch)
tree5c60133b1b4c8732503d4ecbe625091321be7217 /Lib
parent481ac8811e41f509e3a4ca6ef0151ce69671b43e (diff)
downloadcpython-2689e3ddce70e8acc5bc231a80221980d5bdfec3.zip
cpython-2689e3ddce70e8acc5bc231a80221980d5bdfec3.tar.gz
cpython-2689e3ddce70e8acc5bc231a80221980d5bdfec3.tar.bz2
First checkin of real Distutils code.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/distutils/core.py597
-rw-r--r--Lib/distutils/errors.py63
-rw-r--r--Lib/distutils/fancy_getopt.py115
-rw-r--r--Lib/distutils/options.py111
-rw-r--r--Lib/distutils/util.py245
5 files changed, 1131 insertions, 0 deletions
diff --git a/Lib/distutils/core.py b/Lib/distutils/core.py
new file mode 100644
index 0000000..3a7443c
--- /dev/null
+++ b/Lib/distutils/core.py
@@ -0,0 +1,597 @@
+"""distutils.core
+
+The only module that needs to be imported to use the Distutils; provides
+the 'setup' function (which must be called); the 'Distribution' class
+(which may be subclassed if additional functionality is desired), and
+the 'Command' class (which is used both internally by Distutils, and
+may be subclassed by clients for still more flexibility)."""
+
+# created 1999/03/01, Greg Ward
+
+__rcsid__ = "$Id$"
+
+import sys
+import string, re
+from distutils.errors import *
+from distutils.fancy_getopt import fancy_getopt
+
+# This is not *quite* the same as a Python NAME; I don't allow leading
+# underscores. The fact that they're very similar is no coincidence...
+command_re = re.compile (r'^[a-zA-Z]([a-zA-Z0-9_]*)$')
+
+# Defining this as a global is probably inadequate -- what about
+# listing the available options (or even commands, which can vary
+# quite late as well)
+usage = '%s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]' % sys.argv[0]
+
+
+
+def setup (**attrs):
+ """The gateway to the Distutils: do everything your setup script
+ needs to do, in a highly flexible and user-driven way. Briefly:
+ create a Distribution instance; parse the command-line, creating
+ and customizing instances of the command class for each command
+ found on the command-line; run each of those commands.
+
+ The Distribution instance might be an instance of a class
+ supplied via the 'distclass' keyword argument to 'setup'; if no
+ such class is supplied, then the 'Distribution' class (also in
+ this module) is instantiated. All other arguments to 'setup'
+ (except for 'cmdclass') are used to set attributes of the
+ Distribution instance.
+
+ The 'cmdclass' argument, if supplied, is a dictionary mapping
+ command names to command classes. Each command encountered on the
+ command line will be turned into a command class, which is in turn
+ instantiated; any class found in 'cmdclass' is used in place of the
+ default, which is (for command 'foo_bar') class 'FooBar' in module
+ 'distutils.command.foo_bar'. The command object must provide an
+ 'options' attribute which is a list of option specifiers for
+ 'distutils.fancy_getopt'. Any command-line options between the
+ current and the next command are used to set attributes in the
+ current command object.
+
+ When the entire command-line has been successfully parsed, calls the
+ 'run' method on each command object in turn. This method will be
+ driven entirely by the Distribution object (which each command
+ object has a reference to, thanks to its constructor), and the
+ command-specific options that became attributes of each command
+ object."""
+
+ # Determine the distribution class -- either caller-supplied or
+ # our Distribution (see below).
+ klass = attrs.get ('distclass')
+ if klass:
+ del attrs['distclass']
+ else:
+ klass = Distribution
+
+ # Create the Distribution instance, using the remaining arguments
+ # (ie. everything except distclass) to initialize it
+ dist = klass (attrs)
+
+ # Get it to parse the command line; any command-line errors are
+ # the end-users fault, so turn them into SystemExit to suppress
+ # tracebacks.
+ try:
+ dist.parse_command_line (sys.argv[1:])
+ except DistutilsArgError, msg:
+ raise SystemExit, msg
+
+ # And finally, run all the commands found on the command line.
+ dist.run_commands ()
+
+# setup ()
+
+
+class Distribution:
+ """The core of the Distutils. Most of the work hiding behind
+ 'setup' is really done within a Distribution instance, which
+ farms the work out to the Distutils commands specified on the
+ command line.
+
+ Clients will almost never instantiate Distribution directly,
+ unless the 'setup' function is totally inadequate to their needs.
+ However, it is conceivable that a client might wish to subclass
+ Distribution for some specialized purpose, and then pass the
+ subclass to 'setup' as the 'distclass' keyword argument. If so,
+ it is necessary to respect the expectations that 'setup' has of
+ Distribution: it must have a constructor and methods
+ 'parse_command_line()' and 'run_commands()' with signatures like
+ those described below."""
+
+
+ # 'global_options' describes the command-line options that may
+ # be supplied to the client (setup.py) prior to any actual
+ # commands. Eg. "./setup.py -nv" or "./setup.py --verbose"
+ # both take advantage of these global options.
+ global_options = [('verbose', 'v', "run verbosely"),
+ ('dry-run', 'n', "don't actually do anything"),
+ ]
+
+
+ # -- Creation/initialization methods -------------------------------
+
+ def __init__ (self, attrs=None):
+ """Construct a new Distribution instance: initialize all the
+ attributes of a Distribution, and then uses 'attrs' (a
+ dictionary mapping attribute names to values) to assign
+ some of those attributes their "real" values. (Any attributes
+ not mentioned in 'attrs' will be assigned to some null
+ value: 0, None, an empty list or dictionary, etc.) Most
+ importantly, initialize the 'command_obj' attribute
+ to the empty dictionary; this will be filled in with real
+ command objects by 'parse_command_line()'."""
+
+ # Default values for our command-line options
+ self.verbose = 0
+ self.dry_run = 0
+
+ # And for all other attributes (stuff that might be passed in
+ # from setup.py, rather than from the end-user)
+ self.name = None
+ self.version = None
+ self.author = None
+ self.licence = None
+ self.description = None
+
+ self.cmdclass = {}
+
+ # The rest of these are really the business of various commands,
+ # rather than of the Distribution itself. However, they have
+ # to be here as a conduit to the relevant command class.
+ self.py_modules = None
+ self.ext_modules = None
+ self.package = None
+
+ # Now we'll use the attrs dictionary to possibly override
+ # any or all of these distribution options
+ if attrs:
+ for k in attrs.keys():
+ setattr (self, k, attrs[k])
+
+ # And now initialize bookkeeping stuff that can't be supplied by
+ # the caller at all
+ self.command_obj = {}
+
+ # __init__ ()
+
+
+ def parse_command_line (self, args):
+ """Parse the client's command line: set any Distribution
+ attributes tied to command-line options, create all command
+ objects, and set their options from the command-line. 'args'
+ must be a list of command-line arguments, most likely
+ 'sys.argv[1:]' (see the 'setup()' function). This list is
+ first processed for "global options" -- options that set
+ attributes of the Distribution instance. Then, it is
+ alternately scanned for Distutils command and options for
+ that command. Each new command terminates the options for
+ the previous command. The allowed options for a command are
+ determined by the 'options' attribute of the command object
+ -- thus, we instantiate (and cache) every command object
+ here, in order to access its 'options' attribute. Any error
+ in that 'options' attribute raises DistutilsGetoptError; any
+ error on the command-line raises DistutilsArgError. If no
+ Distutils commands were found on the command line, raises
+ DistutilsArgError."""
+
+ # We have to parse the command line a bit at a time -- global
+ # options, then the first command, then its options, and so on --
+ # because each command will be handled by a different class, and
+ # the options that are valid for a particular class aren't
+ # known until we instantiate the command class, which doesn't
+ # happen until we know what the command is.
+
+ self.commands = []
+ args = fancy_getopt (self.global_options, self, sys.argv[1:])
+
+ while args:
+ # Pull the current command from the head of the command line
+ command = args[0]
+ if not command_re.match (command):
+ raise SystemExit, "invalid command name '%s'" % command
+ self.commands.append (command)
+
+ # Have to instantiate the command class now, so we have a
+ # way to get its valid options and somewhere to put the
+ # results of parsing its share of the command-line
+ cmd_obj = self.create_command_obj (command)
+
+ # Require that the command class be derived from Command --
+ # that way, we can be sure that we at least have the 'run'
+ # and 'get_option' methods.
+ if not isinstance (cmd_obj, Command):
+ raise DistutilsClassError, \
+ "command class %s must subclass Command" % \
+ cmd_obj.__class__
+
+ # XXX this assumes that cmd_obj provides an 'options'
+ # attribute, but we're not enforcing that anywhere!
+ args = fancy_getopt (cmd_obj.options, cmd_obj, args[1:])
+ self.command_obj[command] = cmd_obj
+
+ # while args
+
+ # Oops, no commands found -- an end-user error
+ if not self.commands:
+ sys.stderr.write (usage + "\n")
+ raise DistutilsArgError, "no commands supplied"
+
+ # parse_command_line()
+
+
+ # -- Command class/object methods ----------------------------------
+
+ # This is a method just so it can be overridden if desired; it doesn't
+ # actually use or change any attributes of the Distribution instance.
+ def find_command_class (self, command):
+ """Given a command, derives the names of the module and class
+ expected to implement the command: eg. 'foo_bar' becomes
+ 'distutils.command.foo_bar' (the module) and 'FooBar' (the
+ class within that module). Loads the module, extracts the
+ class from it, and returns the class object.
+
+ Raises DistutilsModuleError with a semi-user-targeted error
+ message if the expected module could not be loaded, or the
+ expected class was not found in it."""
+
+ module_name = 'distutils.command.' + command
+ klass_name = string.join \
+ (map (string.capitalize, string.split (command, '_')), '')
+
+ try:
+ __import__ (module_name)
+ module = sys.modules[module_name]
+ except ImportError:
+ raise DistutilsModuleError, \
+ "invalid command '%s' (no module named %s)" % \
+ (command, module_name)
+
+ try:
+ klass = vars(module)[klass_name]
+ except KeyError:
+ raise DistutilsModuleError, \
+ "invalid command '%s' (no class '%s' in module '%s')" \
+ % (command, klass_name, module_name)
+
+ return klass
+
+ # find_command_class ()
+
+
+ def create_command_obj (self, command):
+ """Figure out the class that should implement a command,
+ instantiate it, cache and return the new "command object".
+ The "command class" is determined either by looking it up in
+ the 'cmdclass' attribute (this is the mechanism whereby
+ clients may override default Distutils commands or add their
+ own), or by calling the 'find_command_class()' method (if the
+ command name is not in 'cmdclass'."""
+
+ # Determine the command class -- either it's in the command_class
+ # dictionary, or we have to divine the module and class name
+ klass = self.cmdclass.get(command)
+ if not klass:
+ klass = self.find_command_class (command)
+ self.cmdclass[command] = klass
+
+ # Found the class OK -- instantiate it
+ cmd_obj = klass (self)
+ return cmd_obj
+
+
+ def find_command_obj (self, command, create=1):
+ """Look up and return a command object in the cache maintained by
+ 'create_command_obj()'. If none found, the action taken
+ depends on 'create': if true (the default), create a new
+ command object by calling 'create_command_obj()' and return
+ it; otherwise, return None."""
+
+ cmd_obj = self.command_obj.get (command)
+ if not cmd_obj and create:
+ cmd_obj = self.create_command_obj (command)
+ self.command_obj[command] = cmd_obj
+
+ return cmd_obj
+
+
+ # -- Methods that operate on the Distribution ----------------------
+
+ def announce (self, msg, level=1):
+ """Print 'msg' if 'level' is greater than or equal to the verbosity
+ level recorded in the 'verbose' attribute (which, currently,
+ can be only 0 or 1)."""
+
+ if self.verbose >= level:
+ print msg
+
+
+ def run_commands (self):
+ """Run each command that was seen on the client command line.
+ Uses the list of commands found and cache of command objects
+ created by 'create_command_obj()'."""
+
+ for cmd in self.commands:
+ self.run_command (cmd)
+
+
+ def get_option (self, option):
+ """Return the value of a distribution option. Raise
+ DistutilsOptionError if 'option' is not known."""
+
+ try:
+ return getattr (self, opt)
+ except AttributeError:
+ raise DistutilsOptionError, \
+ "unknown distribution option %s" % option
+
+
+ def get_options (self, *options):
+ """Return (as a tuple) the values of several distribution
+ options. Raise DistutilsOptionError if any element of
+ 'options' is not known."""
+
+ values = []
+ try:
+ for opt in options:
+ values.append (getattr (self, opt))
+ except AttributeError, name:
+ raise DistutilsOptionError, \
+ "unknown distribution option %s" % name
+
+ return tuple (values)
+
+
+ # -- Methods that operate on its Commands --------------------------
+
+ def run_command (self, command):
+ """Create a command object for 'command' if necessary, and
+ run the command by invoking its 'run()' method."""
+
+ self.announce ("running " + command)
+ cmd_obj = self.find_command_obj (command)
+ cmd_obj.run ()
+
+
+ def get_command_option (self, command, option):
+ """Create a command object for 'command' if necessary, finalize
+ its option values by invoking its 'set_final_options()'
+ method, and return the value of its 'option' option. Raise
+ DistutilsOptionError if 'option' is not known for
+ that 'command'."""
+
+ cmd_obj = self.find_command_obj (command)
+ cmd_obj.set_final_options ()
+ return cmd_obj.get_option (option)
+ try:
+ return getattr (cmd_obj, option)
+ except AttributeError:
+ raise DistutilsOptionError, \
+ "command %s: no such option %s" % (command, option)
+
+
+ def get_command_options (self, command, *options):
+ """Create a command object for 'command' if necessary, finalize
+ its option values by invoking its 'set_final_options()'
+ method, and return the values of all the options listed in
+ 'options' for that command. Raise DistutilsOptionError if
+ 'option' is not known for that 'command'."""
+
+ cmd_obj = self.find_command_obj (command)
+ cmd_obj.set_final_options ()
+ values = []
+ try:
+ for opt in options:
+ values.append (getattr (cmd_obj, option))
+ except AttributeError, name:
+ raise DistutilsOptionError, \
+ "command %s: no such option %s" % (command, name)
+
+ return tuple (values)
+
+# end class Distribution
+
+
+class Command:
+ """Abstract base class for defining command classes, the "worker bees"
+ of the Distutils. A useful analogy for command classes is to
+ think of them as subroutines with local variables called
+ "options". The options are "declared" in 'set_initial_options()'
+ and "initialized" (given their real values) in
+ 'set_final_options()', both of which must be defined by every
+ command class. The distinction between the two is necessary
+ because option values might come from the outside world (command
+ line, option file, ...), and any options dependent on other
+ options must be computed *after* these outside influences have
+ been processed -- hence 'set_final_values()'. The "body" of the
+ subroutine, where it does all its work based on the values of its
+ options, is the 'run()' method, which must also be implemented by
+ every command class."""
+
+ # -- Creation/initialization methods -------------------------------
+
+ def __init__ (self, dist):
+ """Create and initialize a new Command object. Most importantly,
+ invokes the 'set_default_options()' method, which is the
+ real initializer and depends on the actual command being
+ instantiated."""
+
+ if not isinstance (dist, Distribution):
+ raise TypeError, "dist must be a Distribution instance"
+ if self.__class__ is Command:
+ raise RuntimeError, "Command is an abstract class"
+
+ self.distribution = dist
+ self.set_default_options ()
+
+ # end __init__ ()
+
+ # Subclasses must define:
+ # set_default_options()
+ # provide default values for all options; may be overridden
+ # by Distutils client, by command-line options, or by options
+ # from option file
+ # set_final_options()
+ # decide on the final values for all options; this is called
+ # after all possible intervention from the outside world
+ # (command-line, option file, etc.) has been processed
+ # run()
+ # run the command: do whatever it is we're here to do,
+ # controlled by the command's various option values
+
+ def set_default_options (self):
+ """Set default values for all the options that this command
+ supports. Note that these defaults may be overridden
+ by the command-line supplied by the user; thus, this is
+ not the place to code dependencies between options; generally,
+ 'set_default_options()' implementations are just a bunch
+ of "self.foo = None" assignments.
+
+ This method must be implemented by all command classes."""
+
+ raise RuntimeError, \
+ "abstract method -- subclass %s must override" % self.__class__
+
+ def set_final_options (self):
+ """Set final values for all the options that this command
+ supports. This is always called as late as possible, ie.
+ after any option assignments from the command-line or from
+ other commands have been done. Thus, this is the place to to
+ code option dependencies: if 'foo' depends on 'bar', then it
+ is safe to set 'foo' from 'bar' as long as 'foo' still has
+ the same value it was assigned in 'set_default_options()'.
+
+ This method must be implemented by all command classes."""
+
+ raise RuntimeError, \
+ "abstract method -- subclass %s must override" % self.__class__
+
+ def run (self):
+ """A command's raison d'etre: carry out the action it exists
+ to perform, controlled by the options initialized in
+ 'set_initial_options()', customized by the user and other
+ commands, and finalized in 'set_final_options()'. All
+ terminal output and filesystem interaction should be done by
+ 'run()'.
+
+ This method must be implemented by all command classes."""
+
+ raise RuntimeError, \
+ "abstract method -- subclass %s must override" % self.__class__
+
+ def announce (self, msg, level=1):
+ """If the Distribution instance to which this command belongs
+ has a verbosity level of greater than or equal to 'level'
+ print 'msg' to stdout."""
+ if self.distribution.verbose >= level:
+ print msg
+
+
+ # -- Option query/set methods --------------------------------------
+
+ def get_option (self, option):
+ """Return the value of a single option for this command. Raise
+ DistutilsOptionError if 'option' is not known."""
+ try:
+ return getattr (self, option)
+ except AttributeError:
+ raise DistutilsOptionError, \
+ "command %s: no such option %s" % \
+ (self.command_name(), option)
+
+
+ def get_options (self, *options):
+ """Return (as a tuple) the values of several options for this
+ command. Raise DistutilsOptionError if any of the options in
+ 'options' are not known."""
+
+ values = []
+ try:
+ for opt in options:
+ values.append (getattr (self, opt))
+ except AttributeError, name:
+ raise DistutilsOptionError, \
+ "command %s: no such option %s" % \
+ (self.command_name(), name)
+
+ return tuple (values)
+
+
+ def set_option (self, option, value):
+ """Set the value of a single option for this command. Raise
+ DistutilsOptionError if 'option' is not known."""
+
+ if not hasattr (self, option):
+ raise DistutilsOptionError, \
+ "command %s: no such option %s" % \
+ (self.command_name(), option)
+ if value is not None:
+ setattr (self, option, value)
+
+ def set_options (self, **optval):
+ """Set the values of several options for this command. Raise
+ DistutilsOptionError if any of the options specified as
+ keyword arguments are not known."""
+
+ for k in optval.keys():
+ if optval[k] is not None:
+ self.set_option (k, optval[k])
+
+
+ # -- Convenience methods for commands ------------------------------
+
+ def set_undefined_options (self, src_cmd, *option_pairs):
+ """Set the values of any "undefined" options from corresponding
+ option values in some other command object. "Undefined" here
+ means "is None", which is the convention used to indicate
+ that an option has not been changed between
+ 'set_initial_values()' and 'set_final_values()'. Usually
+ called from 'set_final_values()' for options that depend on
+ some other command rather than another option of the same
+ command. 'src_cmd' is the other command from which option
+ values will be taken (a command object will be created for it
+ if necessary); the remaining arguments are
+ '(src_option,dst_option)' tuples which mean "take the value
+ of 'src_option' in the 'src_cmd' command object, and copy it
+ to 'dst_option' in the current command object"."""
+
+ # Option_pairs: list of (src_option, dst_option) tuples
+
+ src_cmd_obj = self.distribution.find_command_obj (src_cmd)
+ src_cmd_obj.set_final_options ()
+ try:
+ for (src_option, dst_option) in option_pairs:
+ if getattr (self, dst_option) is None:
+ self.set_option (dst_option,
+ src_cmd_obj.get_option (src_option))
+ except AttributeError, name:
+ # duh, which command?
+ raise DistutilsOptionError, "unknown option %s" % name
+
+
+ def set_peer_option (self, command, option, value):
+ """Attempt to simulate a command-line override of some option
+ value in another command. Creates a command object for
+ 'command' if necessary, sets 'option' to 'value', and invokes
+ 'set_final_options()' on that command object. This will only
+ have the desired effect if the command object for 'command'
+ has not previously been created. Generally this is used to
+ ensure that the options in 'command' dependent on 'option'
+ are computed, hopefully (but not necessarily) deriving from
+ 'value'. It might be more accurate to call this method
+ 'influence_dependent_peer_options()'."""
+
+ cmd_obj = self.distribution.find_command_obj (command)
+ cmd_obj.set_option (option, value)
+ cmd_obj.set_final_options ()
+
+
+ def run_peer (self, command):
+ """Run some other command: uses the 'run_command()' method of
+ Distribution, which creates the command object if necessary
+ and then invokes its 'run()' method."""
+
+ self.distribution.run_command (command)
+
+# end class Command
diff --git a/Lib/distutils/errors.py b/Lib/distutils/errors.py
new file mode 100644
index 0000000..6605ad2
--- /dev/null
+++ b/Lib/distutils/errors.py
@@ -0,0 +1,63 @@
+"""distutils.errors
+
+Provides exceptions used by the Distutils modules. Note that Distutils
+modules may raise standard exceptions; in particular, SystemExit is
+usually raised for errors that are obviously the end-user's fault
+(eg. bad command-line arguments).
+
+This module safe to use in "from ... import *" mode; it only exports
+symbols whose names start with "Distutils" and end with "Error"."""
+
+# created 1999/03/03, Greg Ward
+
+__rcsid__ = "$Id$"
+
+from types import *
+
+if type (RuntimeError) is ClassType:
+
+ # DistutilsError is the root of all Distutils evil.
+ class DistutilsError (Exception):
+ pass
+
+ # DistutilsModuleError is raised if we are unable to load an expected
+ # module, or find an expected class within some module
+ class DistutilsModuleError (DistutilsError):
+ pass
+
+ # DistutilsClassError is raised if we encounter a distribution or command
+ # class that's not holding up its end of the bargain.
+ class DistutilsClassError (DistutilsError):
+ pass
+
+ # DistutilsGetoptError (help me -- I have JavaProgrammersDisease!) is
+ # raised if the option table provided to fancy_getopt is bogus.
+ class DistutilsGetoptError (DistutilsError):
+ pass
+
+ # DistutilsArgError is raised by fancy_getopt in response to getopt.error;
+ # distutils.core then turns around and raises SystemExit from that. (Thus
+ # client code should never see DistutilsArgError.)
+ class DistutilsArgError (DistutilsError):
+ pass
+
+ # DistutilsFileError is raised for any problems in the filesystem:
+ # expected file not found, etc.
+ class DistutilsFileError (DistutilsError):
+ pass
+
+ # DistutilsOptionError is raised anytime an attempt is made to access
+ # (get or set) an option that does not exist for a particular command
+ # (or for the distribution itself).
+ class DistutilsOptionError (DistutilsError):
+ pass
+
+# String-based exceptions
+else:
+ DistutilsError = 'DistutilsError'
+ DistutilsModuleError = 'DistutilsModuleError'
+ DistutilsClassError = 'DistutilsClassError'
+ DistutilsGetoptError = 'DistutilsGetoptError'
+ DistutilsArgError = 'DistutilsArgError'
+ DistutilsFileError = 'DistutilsFileError'
+ DistutilsOptionError = 'DistutilsOptionError'
diff --git a/Lib/distutils/fancy_getopt.py b/Lib/distutils/fancy_getopt.py
new file mode 100644
index 0000000..c63ce61
--- /dev/null
+++ b/Lib/distutils/fancy_getopt.py
@@ -0,0 +1,115 @@
+"""distutils.fancy_getopt
+
+Wrapper around the standard getopt module that provides the following
+additional features:
+ * short and long options are tied together
+ * options have help strings, so fancy_getopt could potentially
+ create a complete usage summary
+ * options set attributes of a passed-in object
+"""
+
+# created 1999/03/03, Greg Ward
+
+__rcsid__ = "$Id$"
+
+import string, re
+from types import *
+import getopt
+from distutils.errors import *
+
+# Much like command_re in distutils.core, this is close to but not quite
+# the same as a Python NAME -- except, in the spirit of most GNU
+# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!)
+# The similarities to NAME are again not a coincidence...
+longopt_re = re.compile (r'^[a-zA-Z]([a-zA-Z0-9-]*)$')
+
+# This is used to translate long options to legitimate Python identifiers
+# (for use as attributes of some object).
+longopt_xlate = string.maketrans ('-', '_')
+
+
+def fancy_getopt (options, object, args):
+
+ # The 'options' table is a list of 3-tuples:
+ # (long_option, short_option, help_string)
+ # if an option takes an argument, its long_option should have '='
+ # appended; short_option should just be a single character, no ':' in
+ # any case. If a long_option doesn't have a corresponding
+ # short_option, short_option should be None. All option tuples must
+ # have long options.
+
+ # Build the short_opts string and long_opts list, remembering how
+ # the two are tied together
+
+ short_opts = [] # we'll join 'em when done
+ long_opts = []
+ short2long = {}
+ attr_name = {}
+ takes_arg = {}
+
+ for (long, short, help) in options:
+ # Type-check the option names
+ if type (long) is not StringType or len (long) < 2:
+ raise DistutilsGetoptError, \
+ "long option must be a string of length >= 2"
+
+ if (not ((short is None) or
+ (type (short) is StringType and len (short) == 1))):
+ raise DistutilsGetoptError, \
+ "short option must be None or string of length 1"
+
+ long_opts.append (long)
+
+ if long[-1] == '=': # option takes an argument?
+ if short: short = short + ':'
+ long = long[0:-1]
+ takes_arg[long] = 1
+ else:
+ takes_arg[long] = 0
+
+ # Now enforce some bondage on the long option name, so we can later
+ # translate it to an attribute name in 'object'. Have to do this a
+ # bit late to make sure we've removed any trailing '='.
+ if not longopt_re.match (long):
+ raise DistutilsGetoptError, \
+ ("invalid long option name '%s' " +
+ "(must be letters, numbers, hyphens only") % long
+
+ attr_name[long] = string.translate (long, longopt_xlate)
+ if short:
+ short_opts.append (short)
+ short2long[short[0]] = long
+
+ # end loop over 'options'
+
+ short_opts = string.join (short_opts)
+ try:
+ (opts, args) = getopt.getopt (args, short_opts, long_opts)
+ except getopt.error, msg:
+ raise DistutilsArgError, msg
+
+ for (opt, val) in opts:
+ if len (opt) == 2 and opt[0] == '-': # it's a short option
+ opt = short2long[opt[1]]
+
+ elif len (opt) > 2 and opt[0:2] == '--':
+ opt = opt[2:]
+
+ else:
+ raise RuntimeError, "getopt lies! (bad option string '%s')" % \
+ opt
+
+ attr = attr_name[opt]
+ if takes_arg[opt]:
+ setattr (object, attr, val)
+ else:
+ if val == '':
+ setattr (object, attr, 1)
+ else:
+ raise RuntimeError, "getopt lies! (bad value '%s')" % value
+
+ # end loop over options found in 'args'
+
+ return args
+
+# end fancy_getopt()
diff --git a/Lib/distutils/options.py b/Lib/distutils/options.py
new file mode 100644
index 0000000..f6cae82
--- /dev/null
+++ b/Lib/distutils/options.py
@@ -0,0 +1,111 @@
+# XXX this is ridiculous! if commands need to pass options around,
+# they can just pass them via the 'run' method... what we REALLY need
+# is a way for commands to get at each other, via the Distribution!
+
+class Options:
+ """Used by Distribution and Command to encapsulate distribution
+ and command options -- parsing them from command-line arguments,
+ passing them between the distribution and command objects, etc."""
+
+ # -- Creation/initialization methods -------------------------------
+
+ def __init__ (self, owner):
+
+ # 'owner' is the object (presumably either a Distribution
+ # or Command instance) to which this set of options applies.
+ self.owner = owner
+
+ # The option table: maps option names to dictionaries, which
+ # look something like:
+ # { 'longopt': long command-line option string (optional)
+ # 'shortopt': short option (1 char) (optional)
+ # 'type': 'string', 'boolean', or 'list'
+ # 'description': text description (eg. for help strings)
+ # 'default': default value for the option
+ # 'send': list of (cmd,option) tuples: send option down the line
+ # 'receive': (cmd,option) tuple: pull option from upstream
+ # }
+ self.table = {}
+
+
+ def set_basic_options (self, *options):
+ """Add very basic options: no separate longopt, no fancy typing, no
+ send targets or receive destination. The arguments should just
+ be {1..4}-tuples of
+ (name [, shortopt [, description [, default]]])
+ If name ends with '=', the option takes a string argument;
+ otherwise it's boolean."""
+
+ for opt in options:
+ if not (type (opt) is TupleType and 1 <= len (opt) <= 4):
+ raise ValueError, \
+ ("invalid basic option record '%s': " + \
+ "must be tuple of length 1 .. 4") % opt
+
+ elements = ('name', 'shortopt', 'description', 'default')
+ name = opt[0]
+ self.table[name] = {}
+ for i in range (1,4):
+ if len (opt) >= i:
+ self.table[name][elements[i]] = opt[i]
+ else:
+ break
+
+ # set_basic_options ()
+
+
+ def add_option (self, name, **args):
+
+ # XXX should probably sanity-check the keys of args
+ self.table[name] = args
+
+
+ # ------------------------------------------------------------------
+
+ # These are in the order that they will execute in to ensure proper
+ # prioritizing of option sources -- the default value is the most
+ # basic; it can be overridden by "client options" (the keyword args
+ # passed from setup.py to the 'setup' function); they in turn lose to
+ # options passed in "from above" (ie. from the Distribution, or from
+ # higher-level Commands); these in turn may be overridden by
+ # command-line arguments (which come from the end-user, the runner of
+ # setup.py). Only when all this is done can we pass options down to
+ # other Commands.
+
+ # Hmmm, it also matters in which order Commands are processed: should a
+ # command-line option to 'make_blib' take precedence over the
+ # corresponding value passed down from its boss, 'build'?
+
+ def set_defaults (self):
+ pass
+
+ def set_client_options (self, options):
+ # 'self' should be a Distribution instance for this one --
+ # this is to process the kw args passed to 'setup'
+ pass
+
+ def receive_option (self, option, value):
+ # do we need to know the identity of the sender? don't
+ # think we should -- too much B&D
+
+ # oh, 'self' should be anything *but* a Distribution (ie.
+ # a Command instance) -- only Commands take orders from above!
+ # (ironically enough)
+ pass
+
+ def parse_command_line (self, args):
+ # here, 'self' can usefully be either a Distribution (for parsing
+ # "global" command-line options) or a Command (for "command-specific"
+ # options)
+ pass
+
+
+ def send_option (self, option, dest):
+ # perhaps this should not take a dest, but send the option
+ # to all possible receivers?
+ pass
+
+
+ # ------------------------------------------------------------------
+
+# class Options
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py
new file mode 100644
index 0000000..7c13abe
--- /dev/null
+++ b/Lib/distutils/util.py
@@ -0,0 +1,245 @@
+"""distutils.util
+
+General-purpose utility functions used throughout the Distutils
+(especially in command classes). Mostly filesystem manipulation, but
+not limited to that. The functions in this module generally raise
+DistutilsFileError when they have problems with the filesystem, because
+os.error in pre-1.5.2 Python only gives the error message and not the
+file causing it."""
+
+# created 1999/03/08, Greg Ward
+
+__rcsid__ = "$Id$"
+
+import os
+from distutils.errors import *
+
+
+# I don't use os.makedirs because a) it's new to Python 1.5.2, and
+# b) it blows up if the directory already exists (I want to silently
+# succeed in that case).
+def mkpath (name, mode=0777, verbose=0):
+ """Create a directory and any missing ancestor directories. If the
+ directory already exists, return silently. Raise
+ DistutilsFileError if unable to create some directory along the
+ way (eg. some sub-path exists, but is a file rather than a
+ directory). If 'verbose' is true, print a one-line summary of
+ each mkdir to stdout."""
+
+ # XXX what's the better way to handle verbosity? print as we create
+ # each directory in the path (the current behaviour), or only announce
+ # the creation of the whole path, and force verbose=0 on all sub-calls?
+
+ if os.path.isdir (name):
+ return
+
+ (head, tail) = os.path.split (name)
+ tails = [tail] # stack of lone dirs to create
+
+ while head and tail and not os.path.isdir (head):
+ #print "splitting '%s': " % head,
+ (head, tail) = os.path.split (head)
+ #print "to ('%s','%s')" % (head, tail)
+ tails.insert (0, tail) # push next higher dir onto stack
+
+ #print "stack of tails:", tails
+
+ # now 'head' contains the highest directory that already exists
+ for d in tails:
+ #print "head = %s, d = %s: " % (head, d),
+ head = os.path.join (head, d)
+ if verbose:
+ print "creating", head
+ try:
+ os.mkdir (head)
+ except os.error, (errno, errstr):
+ raise DistutilsFileError, "%s: %s" % (head, errstr)
+
+# mkpath ()
+
+
+def newer (file1, file2):
+ """Return true if file1 exists and is more recently modified than
+ file2, or if file1 exists and file2 doesn't. Return false if both
+ exist and file2 is the same age or younger than file1. Raises
+ DistutilsFileError if file1 does not exist."""
+
+ if not os.path.exists (file1):
+ raise DistutilsFileError, "file '%s' does not exist" % file1
+ if not os.path.exists (file2):
+ return 1
+
+ from stat import *
+ mtime1 = os.stat(file1)[ST_MTIME]
+ mtime2 = os.stat(file2)[ST_MTIME]
+
+ return mtime1 > mtime2
+
+# newer ()
+
+
+def make_file (src, dst, func, args,
+ verbose=0, update_message=None, noupdate_message=None):
+ """Makes 'dst' from 'src' (both filenames) by calling 'func' with
+ 'args', but only if it needs to: i.e. if 'dst' does not exist or
+ 'src' is newer than 'dst'."""
+
+ if newer (src, dst):
+ if verbose and update_message:
+ print update_message
+ apply (func, args)
+ else:
+ if verbose and noupdate_message:
+ print noupdate_message
+
+# make_file ()
+
+
+def _copy_file_contents (src, dst, buffer_size=16*1024):
+ """Copy the file 'src' to 'dst'; both must be filenames. Any error
+ opening either file, reading from 'src', or writing to 'dst',
+ raises DistutilsFileError. Data is read/written in chunks of
+ 'buffer_size' bytes (default 16k). No attempt is made to handle
+ anything apart from regular files."""
+
+ # Stolen from shutil module in the standard library, but with
+ # custom error-handling added.
+
+ fsrc = None
+ fdst = None
+ try:
+ try:
+ fsrc = open(src, 'rb')
+ except os.error, (errno, errstr):
+ raise DistutilsFileError, "could not open %s: %s" % (src, errstr)
+
+ try:
+ fdst = open(dst, 'wb')
+ except os.error, (errno, errstr):
+ raise DistutilsFileError, "could not create %s: %s" % (dst, errstr)
+
+ while 1:
+ try:
+ buf = fsrc.read (buffer_size)
+ except os.error, (errno, errstr):
+ raise DistutilsFileError, \
+ "could not read from %s: %s" % (src, errstr)
+
+ if not buf:
+ break
+
+ try:
+ fdst.write(buf)
+ except os.error, (errno, errstr):
+ raise DistutilsFileError, \
+ "could not write to %s: %s" % (dst, errstr)
+
+ finally:
+ if fdst:
+ fdst.close()
+ if fsrc:
+ fsrc.close()
+
+# _copy_file_contents()
+
+
+def copy_file (src, dst,
+ preserve_mode=1,
+ preserve_times=1,
+ update=0,
+ verbose=0):
+
+ """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src'
+ is copied there with the same name; otherwise, it must be a
+ filename. (If the file exists, it will be ruthlessly clobbered.)
+ If 'preserve_mode' is true (the default), the file's mode (type
+ and permission bits, or whatever is analogous on the current
+ platform) is copied. If 'preserve_times' is true (the default),
+ the last-modified and last-access times are copied as well. If
+ 'update' is true, 'src' will only be copied if 'dst' does not
+ exist, or if 'dst' does exist but is older than 'src'. If
+ 'verbose' is true, then a one-line summary of the copy will be
+ printed to stdout."""
+
+ # XXX doesn't copy Mac-specific metadata
+
+ from shutil import copyfile
+ from stat import *
+
+ if not os.path.isfile (src):
+ raise DistutilsFileError, \
+ "can't copy %s:not a regular file" % src
+
+ if os.path.isdir (dst):
+ dir = dst
+ dst = os.path.join (dst, os.path.basename (src))
+ else:
+ dir = os.path.dirname (dst)
+
+ if update and not newer (src, dst):
+ return
+
+ if verbose:
+ print "copying %s -> %s" % (src, dir)
+
+ copyfile (src, dst)
+ if preserve_mode or preserve_times:
+ st = os.stat (src)
+ if preserve_mode:
+ os.chmod (dst, S_IMODE (st[ST_MODE]))
+ if preserve_times:
+ os.utime (dst, (st[ST_ATIME], st[ST_MTIME]))
+
+# copy_file ()
+
+
+def copy_tree (src, dst,
+ preserve_mode=1,
+ preserve_times=1,
+ preserve_symlinks=0,
+ update=0,
+ verbose=0):
+
+ """Copy an entire directory tree 'src' to a new location 'dst'. Both
+ 'src' and 'dst' must be directory names. If 'src' is not a
+ directory, raise DistutilsFileError. If 'dst' does not exist, it
+ is created with 'mkpath'. The endresult of the copy is that
+ every file in 'src' is copied to 'dst', and directories under
+ 'src' are recursively copied to 'dst'.
+
+ 'preserve_mode' and 'preserve_times' are the same as for
+ 'copy_file'; note that they only apply to regular files, not to
+ directories. If 'preserve_symlinks' is true, symlinks will be
+ copied as symlinks (on platforms that support them!); otherwise
+ (the default), the destination of the symlink will be copied.
+ 'update' and 'verbose' are the same as for 'copy_file'."""
+
+ if not os.path.isdir (src):
+ raise DistutilsFileError, \
+ "cannot copy tree %s: not a directory" % src
+ try:
+ names = os.listdir (src)
+ except os.error, (errno, errstr):
+ raise DistutilsFileError, \
+ "error listing files in %s: %s" % (src, errstr)
+
+
+ mkpath (dst, verbose=verbose)
+
+ for n in names:
+ src_name = os.path.join (src, n)
+ dst_name = os.path.join (dst, n)
+
+ if preserve_symlinks and os.path.islink (src_name):
+ link_dest = os.readlink (src_name)
+ os.symlink (link_dest, dst_name)
+ elif os.path.isdir (src_name):
+ copy_tree (src_name, dst_name,
+ preserve_mode, preserve_times, preserve_symlinks,
+ update, verbose)
+ else:
+ copy_file (src_name, dst_name,
+ preserve_mode, preserve_times,
+ update, verbose)
+
+# copy_tree ()