Refactor command-line parsing. (Steve Leblanc)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 6 Nov 2002 12:25:25 +0000 (12:25 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 6 Nov 2002 12:25:25 +0000 (12:25 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@492 fdb21ef1-2011-0410-befe-b5e4ea1792b1

14 files changed:
rpm/scons.spec
src/CHANGES.txt
src/engine/MANIFEST.in
src/engine/SCons/Optik/.aeignore [new file with mode: 0644]
src/engine/SCons/Optik/__init__.py [new file with mode: 0644]
src/engine/SCons/Optik/errors.py [new file with mode: 0644]
src/engine/SCons/Optik/option.py [new file with mode: 0644]
src/engine/SCons/Optik/option_parser.py [new file with mode: 0644]
src/engine/SCons/Script/__init__.py
src/engine/setup.py
src/setup.py
test/SCONSFLAGS.py
test/option--profile.py
test/option-unknown.py

index 8c1cf73c501a90f547e64c2c59568527a189e5e3..76f6d3b19091778b7495a58df47cc54c29b21e72 100644 (file)
@@ -68,6 +68,14 @@ rm -rf $RPM_BUILD_ROOT
 /usr/lib/scons/SCons/Node/FS.pyc
 /usr/lib/scons/SCons/Node/__init__.py
 /usr/lib/scons/SCons/Node/__init__.pyc
+/usr/lib/scons/SCons/Optik/__init__.py
+/usr/lib/scons/SCons/Optik/__init__.pyc
+/usr/lib/scons/SCons/Optik/errors.py
+/usr/lib/scons/SCons/Optik/errors.pyc
+/usr/lib/scons/SCons/Optik/option.py
+/usr/lib/scons/SCons/Optik/option.pyc
+/usr/lib/scons/SCons/Optik/option_parser.py
+/usr/lib/scons/SCons/Optik/option_parser.pyc
 /usr/lib/scons/SCons/Options.py
 /usr/lib/scons/SCons/Options.pyc
 /usr/lib/scons/SCons/Platform/cygwin.py
index c91a59edb6ac3a9eaedc8eaaaed814839379a02f..83c0d08f5cb4505209a99066a03615b696e04bda 100644 (file)
@@ -50,6 +50,11 @@ RELEASE 0.09 -
   - Add separate $SHOBJPREFIX and $SHOBJSUFFIX construction variables
     (by default, the same as $OBJPREFIX and $OBJSUFFIX).
 
+ From Steve LeBlanc:
+
+  - Refactor option processing to use our own version of Greg Ward's
+    Optik module, modified to run under Python 1.5.2.
+
  From Jeff Petkau:
 
   - Fix interpretation of '#/../foo' on Win32 systems.
index bca093f8630506d153d592eb82056f3bd1a22e2e..d50073332ebed323b4d7c0aeda245486ff13b991 100644 (file)
@@ -9,6 +9,10 @@ SCons/exitfuncs.py
 SCons/Node/__init__.py
 SCons/Node/Alias.py
 SCons/Node/FS.py
+SCons/Optik/__init__.py
+SCons/Optik/errors.py
+SCons/Optik/option.py
+SCons/Optik/option_parser.py
 SCons/Options.py
 SCons/Platform/__init__.py
 SCons/Platform/cygwin.py
diff --git a/src/engine/SCons/Optik/.aeignore b/src/engine/SCons/Optik/.aeignore
new file mode 100644 (file)
index 0000000..22ebd62
--- /dev/null
@@ -0,0 +1,5 @@
+*,D
+*.pyc
+.*.swp
+.consign
+.sconsign
diff --git a/src/engine/SCons/Optik/__init__.py b/src/engine/SCons/Optik/__init__.py
new file mode 100644 (file)
index 0000000..8ea41fb
--- /dev/null
@@ -0,0 +1,32 @@
+"""optik
+
+A powerful, extensible, and easy-to-use command-line parser for Python.
+
+By Greg Ward <gward@python.net>
+
+See http://optik.sourceforge.net/
+"""
+
+# Copyright (c) 2001 Gregory P. Ward.  All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+# Original Optik revision this is based on:
+__Optik_revision__ = "__init__.py,v 1.11 2002/04/11 19:17:34 gward Exp"
+
+__version__ = "1.3"
+
+
+# Re-import these for convenience
+from SCons.Optik.option import Option
+from SCons.Optik.option_parser import \
+     OptionParser, SUPPRESS_HELP, SUPPRESS_USAGE
+from SCons.Optik.errors import OptionValueError
+
+
+# Some day, there might be many Option classes.  As of Optik 1.3, the
+# preferred way to instantiate Options is indirectly, via make_option(),
+# which will become a factory function when there are many Option
+# classes.
+make_option = Option
diff --git a/src/engine/SCons/Optik/errors.py b/src/engine/SCons/Optik/errors.py
new file mode 100644 (file)
index 0000000..dca8a69
--- /dev/null
@@ -0,0 +1,55 @@
+"""optik.errors
+
+Exception classes used by Optik.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+# Original Optik revision this is based on:
+__Optik_revision__ = "errors.py,v 1.5 2002/02/13 23:29:47 gward Exp"
+
+# Copyright (c) 2001 Gregory P. Ward.  All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+# created 2001/10/17 GPW (from optik.py)
+
+
+class OptikError (Exception):
+    def __init__ (self, msg):
+        self.msg = msg
+
+    def __str__ (self):
+        return self.msg
+
+
+class OptionError (OptikError):
+    """
+    Raised if an Option instance is created with invalid or
+    inconsistent arguments.
+    """
+
+    def __init__ (self, msg, option):
+        self.msg = msg
+        self.option_id = str(option)
+
+    def __str__ (self):
+        if self.option_id:
+            return "option %s: %s" % (self.option_id, self.msg)
+        else:
+            return self.msg
+
+class OptionConflictError (OptionError):
+    """
+    Raised if conflicting options are added to an OptionParser.
+    """
+
+class OptionValueError (OptikError):
+    """
+    Raised if an invalid option value is encountered on the command
+    line.
+    """
+
+class BadOptionError (OptikError):
+    """
+    Raised if an invalid or ambiguous option is seen on the command-line.
+    """
diff --git a/src/engine/SCons/Optik/option.py b/src/engine/SCons/Optik/option.py
new file mode 100644 (file)
index 0000000..2e1ec33
--- /dev/null
@@ -0,0 +1,388 @@
+"""optik.option
+
+Defines the Option class and some standard value-checking functions.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+# Original Optik revision this is based on:
+__Optik_revision__ = "option.py,v 1.19.2.1 2002/07/23 01:51:14 gward Exp" 
+
+# Copyright (c) 2001 Gregory P. Ward.  All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+# created 2001/10/17, GPW (from optik.py)
+
+import sys
+import string
+from types import TupleType, ListType, DictType
+from SCons.Optik.errors import OptionError, OptionValueError
+
+_builtin_cvt = { "int" : (int, "integer"),
+                 "long" : (long, "long integer"),
+                 "float" : (float, "floating-point"),
+                 "complex" : (complex, "complex") }
+
+def check_builtin (option, opt, value):
+    (cvt, what) = _builtin_cvt[option.type]
+    try:
+        return cvt(value)
+    except ValueError:
+        raise OptionValueError(
+            #"%s: invalid %s argument %s" % (opt, what, repr(value)))
+            "option %s: invalid %s value: %s" % (opt, what, repr(value)))
+
+def check_choice(option, opt, value):
+    if value in option.choices:
+        return value
+    else:
+        choices = string.join(map(repr, option.choices),", ")
+        raise OptionValueError(
+            "option %s: invalid choice: %s (choose from %s)"
+            % (opt, repr(value), choices))
+
+# Not supplying a default is different from a default of None,
+# so we need an explicit "not supplied" value.
+NO_DEFAULT = "NO"+"DEFAULT"
+
+
+class Option:
+    """
+    Instance attributes:
+      _short_opts : [string]
+      _long_opts : [string]
+
+      action : string
+      type : string
+      dest : string
+      default : any
+      nargs : int
+      const : any
+      choices : [string]
+      callback : function
+      callback_args : (any*)
+      callback_kwargs : { string : any }
+      help : string
+      metavar : string
+    """
+
+    # The list of instance attributes that may be set through
+    # keyword args to the constructor.
+    ATTRS = ['action',
+             'type',
+             'dest',
+             'default',
+             'nargs',
+             'const',
+             'choices',
+             'callback',
+             'callback_args',
+             'callback_kwargs',
+             'help',
+             'metavar']
+
+    # The set of actions allowed by option parsers.  Explicitly listed
+    # here so the constructor can validate its arguments.
+    ACTIONS = ("store",
+               "store_const",
+               "store_true",
+               "store_false",
+               "append",
+               "count",
+               "callback",
+               "help",
+               "version")
+
+    # The set of actions that involve storing a value somewhere;
+    # also listed just for constructor argument validation.  (If
+    # the action is one of these, there must be a destination.)
+    STORE_ACTIONS = ("store",
+                     "store_const",
+                     "store_true",
+                     "store_false",
+                     "append",
+                     "count")
+
+    # The set of actions for which it makes sense to supply a value
+    # type, ie. where we expect an argument to this option.
+    TYPED_ACTIONS = ("store",
+                     "append",
+                     "callback")
+
+    # The set of known types for option parsers.  Again, listed here for
+    # constructor argument validation.
+    TYPES = ("string", "int", "long", "float", "complex", "choice")
+
+    # Dictionary of argument checking functions, which convert and
+    # validate option arguments according to the option type.
+    # 
+    # Signature of checking functions is:
+    #   check(option : Option, opt : string, value : string) -> any
+    # where
+    #   option is the Option instance calling the checker
+    #   opt is the actual option seen on the command-line
+    #     (eg. "-a", "--file")
+    #   value is the option argument seen on the command-line
+    #
+    # The return value should be in the appropriate Python type
+    # for option.type -- eg. an integer if option.type == "int".
+    # 
+    # If no checker is defined for a type, arguments will be
+    # unchecked and remain strings.
+    TYPE_CHECKER = { "int"    : check_builtin,
+                     "long"   : check_builtin,
+                     "float"  : check_builtin,
+                     "complex"  : check_builtin,
+                     "choice" : check_choice,
+                   }
+
+
+    # CHECK_METHODS is a list of unbound method objects; they are called
+    # by the constructor, in order, after all attributes are
+    # initialized.  The list is created and filled in later, after all
+    # the methods are actually defined.  (I just put it here because I
+    # like to define and document all class attributes in the same
+    # place.)  Subclasses that add another _check_*() method should
+    # define their own CHECK_METHODS list that adds their check method
+    # to those from this class.
+    CHECK_METHODS = None
+                    
+
+    # -- Constructor/initialization methods ----------------------------
+
+    def __init__ (self, *opts, **attrs):
+        # Set _short_opts, _long_opts attrs from 'opts' tuple
+        opts = self._check_opt_strings(opts)
+        self._set_opt_strings(opts)
+
+        # Set all other attrs (action, type, etc.) from 'attrs' dict
+        self._set_attrs(attrs)
+
+        # Check all the attributes we just set.  There are lots of
+        # complicated interdependencies, but luckily they can be farmed
+        # out to the _check_*() methods listed in CHECK_METHODS -- which
+        # could be handy for subclasses!  The one thing these all share
+        # is that they raise OptionError if they discover a problem.
+        for checker in self.CHECK_METHODS:
+            checker(self)
+
+    def _check_opt_strings (self, opts):
+        # Filter out None because early versions of Optik had exactly
+        # one short option and one long option, either of which
+        # could be None.
+        opts = filter(None, opts)
+        if not opts:
+            raise OptionError("at least one option string must be supplied",
+                              self)
+        return opts
+
+    def _set_opt_strings (self, opts):
+        self._short_opts = []
+        self._long_opts = []
+        for opt in opts:
+            if len(opt) < 2:
+                raise OptionError(
+                    "invalid option string %s: "
+                    "must be at least two characters long" % (`opt`,), self)
+            elif len(opt) == 2:
+                if not (opt[0] == "-" and opt[1] != "-"):
+                    raise OptionError(
+                        "invalid short option string %s: "
+                        "must be of the form -x, (x any non-dash char)" % (`opt`,),
+                        self)
+                self._short_opts.append(opt)
+            else:
+                if not (opt[0:2] == "--" and opt[2] != "-"):
+                    raise OptionError(
+                        "invalid long option string %s: "
+                        "must start with --, followed by non-dash" % (`opt`,),
+                        self)
+                self._long_opts.append(opt)
+
+    def _set_attrs (self, attrs):
+        for attr in self.ATTRS:
+            if attrs.has_key(attr):
+                setattr(self, attr, attrs[attr])
+                del attrs[attr]
+            else:
+                if attr == 'default':
+                    setattr(self, attr, NO_DEFAULT)
+                else:
+                    setattr(self, attr, None)
+        if attrs:
+            raise OptionError(
+                "invalid keyword arguments: %s" % string.join(attrs.keys(),", "),
+                self)
+
+
+    # -- Constructor validation methods --------------------------------
+
+    def _check_action (self):
+        if self.action is None:
+            self.action = "store"
+        elif self.action not in self.ACTIONS:
+            raise OptionError("invalid action: %s" % (`self.action`,), self)
+
+    def _check_type (self):
+        if self.type is None:
+            # XXX should factor out another class attr here: list of
+            # actions that *require* a type
+            if self.action in ("store", "append"):
+                if self.choices is not None:
+                    # The "choices" attribute implies "choice" type.
+                    self.type = "choice"
+                else:
+                    # No type given?  "string" is the most sensible default.
+                    self.type = "string"
+        else:
+            if self.type not in self.TYPES:
+                raise OptionError("invalid option type: %s" % (`self.type`,), self)
+            if self.action not in self.TYPED_ACTIONS:
+                raise OptionError(
+                    "must not supply a type for action %s" % (`self.action`,), self)
+
+    def _check_choice(self):
+        if self.type == "choice":
+            if self.choices is None:
+                raise OptionError(
+                    "must supply a list of choices for type 'choice'", self)
+            elif type(self.choices) not in (TupleType, ListType):
+                raise OptionError(
+                    "choices must be a list of strings ('%s' supplied)"
+                    % string.split(str(type(self.choices)),"'")[1], self)
+        elif self.choices is not None:
+            raise OptionError(
+                "must not supply choices for type %s" % (repr(self.type),), self)
+
+    def _check_dest (self):
+        if self.action in self.STORE_ACTIONS and self.dest is None:
+            # No destination given, and we need one for this action.
+            # Glean a destination from the first long option string,
+            # or from the first short option string if no long options.
+            if self._long_opts:
+                # eg. "--foo-bar" -> "foo_bar"
+                self.dest = string.replace(self._long_opts[0][2:],'-', '_')
+            else:
+                self.dest = self._short_opts[0][1]
+
+    def _check_const (self):
+        if self.action != "store_const" and self.const is not None:
+            raise OptionError(
+                "'const' must not be supplied for action %s" % (repr(self.action),),
+                self)
+        
+    def _check_nargs (self):
+        if self.action in self.TYPED_ACTIONS:
+            if self.nargs is None:
+                self.nargs = 1
+        elif self.nargs is not None:
+            raise OptionError(
+                "'nargs' must not be supplied for action %s" % (repr(self.action),),
+                self)
+
+    def _check_callback (self):
+        if self.action == "callback":
+            if not callable(self.callback):
+                raise OptionError(
+                    "callback not callable: %s" % (repr(self.callback),), self)
+            if (self.callback_args is not None and
+                type(self.callback_args) is not TupleType):
+                raise OptionError(
+                    "callback_args, if supplied, must be a tuple: not %s"
+                    % (repr(self.callback_args),), self)
+            if (self.callback_kwargs is not None and
+                type(self.callback_kwargs) is not DictType):
+                raise OptionError(
+                    "callback_kwargs, if supplied, must be a dict: not %s"
+                    % (repr(self.callback_kwargs),), self)
+        else:
+            if self.callback is not None:
+                raise OptionError(
+                    "callback supplied (%s) for non-callback option"
+                    % (repr(self.callback),), self)
+            if self.callback_args is not None:
+                raise OptionError(
+                    "callback_args supplied for non-callback option", self)
+            if self.callback_kwargs is not None:
+                raise OptionError(
+                    "callback_kwargs supplied for non-callback option", self)
+
+
+    CHECK_METHODS = [_check_action,
+                     _check_type,
+                     _check_choice,
+                     _check_dest,
+                     _check_const,
+                     _check_nargs,
+                     _check_callback]
+        
+
+    # -- Miscellaneous methods -----------------------------------------
+
+    def __str__ (self):
+        if self._short_opts or self._long_opts:
+            return string.join(self._short_opts + self._long_opts,"/")
+        else:
+            raise RuntimeError, "short_opts and long_opts both empty!"
+
+    def takes_value (self):
+        return self.type is not None
+
+
+    # -- Processing methods --------------------------------------------
+
+    def check_value (self, opt, value):
+        checker = self.TYPE_CHECKER.get(self.type)
+        if checker is None:
+            return value
+        else:
+            return checker(self, opt, value)
+
+    def process (self, opt, value, values, parser):
+
+        # First, convert the value(s) to the right type.  Howl if any
+        # value(s) are bogus.
+        if value is not None:
+            if self.nargs == 1:
+                value = self.check_value(opt, value)
+            else:
+               def cv(v,check=self.check_value,o=opt):
+                   return check(o,v)
+
+                value = tuple(map(cv,value))
+
+        # And then take whatever action is expected of us.
+        # This is a separate method to make life easier for
+        # subclasses to add new actions.
+        return self.take_action(
+            self.action, self.dest, opt, value, values, parser)
+
+    def take_action (self, action, dest, opt, value, values, parser):
+        if action == "store":
+            setattr(values, dest, value)
+        elif action == "store_const":
+            setattr(values, dest, self.const)
+        elif action == "store_true":
+            setattr(values, dest, 1)
+        elif action == "store_false":
+            setattr(values, dest, 0)
+        elif action == "append":
+            values.ensure_value(dest, []).append(value)
+        elif action == "count":
+            setattr(values, dest, values.ensure_value(dest, 0) + 1)
+        elif action == "callback":
+            args = self.callback_args or ()
+            kwargs = self.callback_kwargs or {}
+            apply( self.callback, (self, opt, value, parser,)+ args, kwargs)
+        elif action == "help":
+            parser.print_help()
+            sys.exit(0)
+        elif action == "version":
+            parser.print_version()
+            sys.exit(0)
+        else:
+            raise RuntimeError, "unknown action %s" % (repr(self.action),)
+
+        return 1
+
+# class Option
diff --git a/src/engine/SCons/Optik/option_parser.py b/src/engine/SCons/Optik/option_parser.py
new file mode 100644 (file)
index 0000000..ebe8336
--- /dev/null
@@ -0,0 +1,661 @@
+"""optik.option_parser
+
+Provides the OptionParser and Values classes.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+# Original Optik revision this is based on:
+__Optik_revision__ = "option_parser.py,v 1.38.2.1 2002/07/23 01:51:14 gward Exp"
+
+# Copyright (c) 2001 Gregory P. Ward.  All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+# created 2001/10/17, GPW (from optik.py)
+
+import sys, os
+import string
+import types
+from SCons.Optik.option import Option, NO_DEFAULT
+from SCons.Optik.errors import OptionConflictError, OptionValueError, BadOptionError
+
+def get_prog_name ():
+    return os.path.basename(sys.argv[0])
+
+
+SUPPRESS_HELP = "SUPPRESS"+"HELP"
+SUPPRESS_USAGE = "SUPPRESS"+"USAGE"
+
+class Values:
+
+    def __init__ (self, defaults=None):
+        if defaults:
+            for (attr, val) in defaults.items():
+                setattr(self, attr, val)
+
+
+    def _update_careful (self, dict):
+        """
+        Update the option values from an arbitrary dictionary, but only
+        use keys from dict that already have a corresponding attribute
+        in self.  Any keys in dict without a corresponding attribute
+        are silently ignored.
+        """
+        for attr in dir(self):
+            if dict.has_key(attr):
+                dval = dict[attr]
+                if dval is not None:
+                    setattr(self, attr, dval)
+
+    def _update_loose (self, dict):
+        """
+        Update the option values from an arbitrary dictionary,
+        using all keys from the dictionary regardless of whether
+        they have a corresponding attribute in self or not.
+        """
+        self.__dict__.update(dict)
+
+    def _update (self, dict, mode):
+        if mode == "careful":
+            self._update_careful(dict)
+        elif mode == "loose":
+            self._update_loose(dict)
+        else:
+            raise ValueError, "invalid update mode: %s" % (repr(mode),)
+
+    def read_module (self, modname, mode="careful"):
+        __import__(modname)
+        mod = sys.modules[modname]
+        self._update(vars(mod), mode)
+
+    def read_file (self, filename, mode="careful"):
+        vars = {}
+        execfile(filename, vars)
+        self._update(vars, mode)
+
+    def ensure_value (self, attr, value):
+        if not hasattr(self, attr) or getattr(self, attr) is None:
+            setattr(self, attr, value)
+        return getattr(self, attr)
+
+
+class OptionParser:
+    """
+    Class attributes:
+      standard_option_list : [Option]
+        list of standard options that will be accepted by all instances
+        of this parser class (intended to be overridden by subclasses).
+
+    Instance attributes:
+      usage : string
+        a usage string for your program.  Before it is displayed
+        to the user, "%prog" will be expanded to the name of
+        your program (os.path.basename(sys.argv[0])).
+      option_list : [Option]
+        the list of all options accepted on the command-line of
+        this program
+      _short_opt : { string : Option }
+        dictionary mapping short option strings, eg. "-f" or "-X",
+        to the Option instances that implement them.  If an Option
+        has multiple short option strings, it will appears in this
+        dictionary multiple times.
+      _long_opt : { string : Option }
+        dictionary mapping long option strings, eg. "--file" or
+        "--exclude", to the Option instances that implement them.
+        Again, a given Option can occur multiple times in this
+        dictionary.
+      defaults : { string : any }
+        dictionary mapping option destination names to default
+        values for each destination.
+
+      allow_interspersed_args : boolean = true
+        if true, positional arguments may be interspersed with options.
+        Assuming -a and -b each take a single argument, the command-line
+          -ablah foo bar -bboo baz
+        will be interpreted the same as
+          -ablah -bboo -- foo bar baz
+        If this flag were false, that command line would be interpreted as
+          -ablah -- foo bar -bboo baz
+        -- ie. we stop processing options as soon as we see the first
+        non-option argument.  (This is the tradition followed by
+        Python's getopt module, Perl's Getopt::Std, and other argument-
+        parsing libraries, but it is generally annoying to users.)
+
+      rargs : [string]
+        the argument list currently being parsed.  Only set when
+        parse_args() is active, and continually trimmed down as
+        we consume arguments.  Mainly there for the benefit of
+        callback options.
+      largs : [string]
+        the list of leftover arguments that we have skipped while
+        parsing options.  If allow_interspersed_args is false, this
+        list is always empty.
+      values : Values
+        the set of option values currently being accumulated.  Only
+        set when parse_args() is active.  Also mainly for callbacks.
+
+    Because of the 'rargs', 'largs', and 'values' attributes,
+    OptionParser is not thread-safe.  If, for some perverse reason, you
+    need to parse command-line arguments simultaneously in different
+    threads, use different OptionParser instances.
+    
+    """ 
+
+    standard_option_list = []
+
+
+    def __init__ (self,
+                  usage=None,
+                  option_list=None,
+                  option_class=Option,
+                  version=None,
+                  conflict_handler="error"):
+        self.set_usage(usage)
+        self.option_class = option_class
+        self.version = version
+        self.set_conflict_handler(conflict_handler)
+        self.allow_interspersed_args = 1
+
+        # Create the various lists and dicts that constitute the
+        # "option list".  See class docstring for details about
+        # each attribute.
+        self._create_option_list()
+
+        # Populate the option list; initial sources are the
+        # standard_option_list class attribute, the 'option_list'
+        # argument, and the STD_VERSION_OPTION global (if 'version'
+        # supplied).
+        self._populate_option_list(option_list)
+
+        self._init_parsing_state()
+
+    # -- Private methods -----------------------------------------------
+    # (used by the constructor)
+
+    def _create_option_list (self):
+        self.option_list = []
+        self._short_opt = {}            # single letter -> Option instance
+        self._long_opt = {}             # long option -> Option instance
+        self.defaults = {}              # maps option dest -> default value
+
+    def _populate_option_list (self, option_list):
+        if self.standard_option_list:
+            self.add_options(self.standard_option_list)
+        if option_list:
+            self.add_options(option_list)
+        
+    def _init_parsing_state (self):
+        # These are set in parse_args() for the convenience of callbacks.
+        self.rargs = None
+        self.largs = None
+        self.values = None
+
+
+    # -- Simple modifier methods ---------------------------------------
+
+    def set_usage (self, usage):
+        if usage is None:
+            self.usage = "usage: %prog [options]"
+        elif usage is SUPPRESS_USAGE:
+            self.usage = None
+        else:
+            self.usage = usage
+
+    def enable_interspersed_args (self):
+        self.allow_interspersed_args = 1
+
+    def disable_interspersed_args (self):
+        self.allow_interspersed_args = 0
+
+    def set_conflict_handler (self, handler):
+        if handler not in ("ignore", "error", "resolve"):
+            raise ValueError, "invalid conflict_resolution value %s" % (repr(handler),)
+        self.conflict_handler = handler
+
+    def set_default (self, dest, value):
+        self.defaults[dest] = value
+
+    def set_defaults (self, **kwargs):
+        self.defaults.update(kwargs)
+
+    def get_default_values(self):
+        return Values(self.defaults)
+
+
+    # -- Option-adding methods -----------------------------------------
+
+    def _check_conflict (self, option):
+        conflict_opts = []
+        for opt in option._short_opts:
+            if self._short_opt.has_key(opt):
+                conflict_opts.append((opt, self._short_opt[opt]))
+        for opt in option._long_opts:
+            if self._long_opt.has_key(opt):
+                conflict_opts.append((opt, self._long_opt[opt]))
+
+        if conflict_opts:
+            handler = self.conflict_handler
+            if handler == "ignore":     # behaviour for Optik 1.0, 1.1
+                pass
+            elif handler == "error":    # new in 1.2
+                raise OptionConflictError(
+                    "conflicting option string(s): %s"
+                    % string.join( map( lambda x: x[0], conflict_opts),", "),
+                    option)
+            elif handler == "resolve":  # new in 1.2
+                for (opt, c_option) in conflict_opts:
+                    if len(opt)>2 and opt[:2]=="--":
+                        c_option._long_opts.remove(opt)
+                        del self._long_opt[opt]
+                    else:
+                        c_option._short_opts.remove(opt)
+                        del self._short_opt[opt]
+                    if not (c_option._short_opts or c_option._long_opts):
+                        self.option_list.remove(c_option)
+
+
+    def add_option (self, *args, **kwargs):
+        """add_option(Option)
+           add_option(opt_str, ..., kwarg=val, ...)
+        """
+        if type(args[0]) is types.StringType:
+            option = apply(self.option_class,args, kwargs)
+        elif len(args) == 1 and not kwargs:
+            option = args[0]
+            if not isinstance(option, Option):
+                raise TypeError, "not an Option instance: %s" % (repr(option),)
+        else:
+            raise TypeError, "invalid arguments"
+
+        self._check_conflict(option)
+
+        self.option_list.append(option)
+        for opt in option._short_opts:
+            self._short_opt[opt] = option
+        for opt in option._long_opts:
+            self._long_opt[opt] = option
+
+        if option.dest is not None:     # option has a dest, we need a default
+            if option.default is not NO_DEFAULT:
+                self.defaults[option.dest] = option.default
+            elif not self.defaults.has_key(option.dest):
+                self.defaults[option.dest] = None
+
+    def add_options (self, option_list):
+        for option in option_list:
+            self.add_option(option)
+
+
+    # -- Option query/removal methods ----------------------------------
+
+    def get_option (self, opt_str):
+        return (self._short_opt.get(opt_str) or
+                self._long_opt.get(opt_str))
+
+    def has_option (self, opt_str):
+        return (self._short_opt.has_key(opt_str) or
+                self._long_opt.has_key(opt_str))
+
+
+    def remove_option (self, opt_str):
+        option = self._short_opt.get(opt_str)
+        if option is None:
+            option = self._long_opt.get(opt_str)
+        if option is None:
+            raise ValueError("no such option %s" % (repr(opt_str),))
+
+        for opt in option._short_opts:
+            del self._short_opt[opt]
+        for opt in option._long_opts:
+            del self._long_opt[opt]
+        self.option_list.remove(option)
+
+
+    # -- Option-parsing methods ----------------------------------------
+
+    def _get_args (self, args):
+        if args is None:
+            return sys.argv[1:]
+        else:
+            return args[:]              # don't modify caller's list
+
+    def parse_args (self, args=None, values=None):
+        """
+        parse_args(args : [string] = sys.argv[1:],
+                   values : Values = None)
+        -> (values : Values, args : [string])
+
+        Parse the command-line options found in 'args' (default:
+        sys.argv[1:]).  Any errors result in a call to 'error()', which
+        by default prints the usage message to stderr and calls
+        sys.exit() with an error message.  On success returns a pair
+        (values, args) where 'values' is an Values instance (with all
+        your option values) and 'args' is the list of arguments left
+        over after parsing options.
+        """
+        rargs = self._get_args(args)
+        if values is None:
+            values = self.get_default_values()
+
+        # Store the halves of the argument list as attributes for the
+        # convenience of callbacks:
+        #   rargs
+        #     the rest of the command-line (the "r" stands for
+        #     "remaining" or "right-hand")
+        #   largs
+        #     the leftover arguments -- ie. what's left after removing
+        #     options and their arguments (the "l" stands for "leftover"
+        #     or "left-hand")
+        self.rargs = rargs
+        self.largs = largs = []
+        self.values = values
+
+        try:
+            stop = self._process_args(largs, rargs, values)
+        except (BadOptionError, OptionValueError), err:
+            self.error(err.msg)
+
+        args = largs + rargs
+        return self.check_values(values, args)
+
+    def check_values (self, values, args):
+        """
+        check_values(values : Values, args : [string])
+        -> (values : Values, args : [string])
+
+        Check that the supplied option values and leftover arguments are
+        valid.  Returns the option values and leftover arguments
+        (possibly adjusted, possibly completely new -- whatever you
+        like).  Default implementation just returns the passed-in
+        values; subclasses may override as desired.
+        """
+        return (values, args)
+
+    def _process_args (self, largs, rargs, values):
+        """_process_args(largs : [string],
+                         rargs : [string],
+                         values : Values)
+
+        Process command-line arguments and populate 'values', consuming
+        options and arguments from 'rargs'.  If 'allow_interspersed_args' is
+        false, stop at the first non-option argument.  If true, accumulate any
+        interspersed non-option arguments in 'largs'.
+        """
+        while rargs:
+            arg = rargs[0]
+            # We handle bare "--" explicitly, and bare "-" is handled by the
+            # standard arg handler since the short arg case ensures that the
+            # len of the opt string is greater than 1.
+            if arg == "--":
+                del rargs[0]
+                return
+            elif arg[0:2] == "--":
+                # process a single long option (possibly with value(s))
+                self._process_long_opt(rargs, values)
+            elif arg[:1] == "-" and len(arg) > 1:
+                # process a cluster of short options (possibly with
+                # value(s) for the last one only)
+                self._process_short_opts(rargs, values)
+            elif self.allow_interspersed_args:
+                largs.append(arg)
+                del rargs[0]
+            else:
+                return                  # stop now, leave this arg in rargs
+
+        # Say this is the original argument list:
+        # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
+        #                            ^
+        # (we are about to process arg(i)).
+        #
+        # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
+        # [arg0, ..., arg(i-1)] (any options and their arguments will have
+        # been removed from largs).
+        #
+        # The while loop will usually consume 1 or more arguments per pass.
+        # If it consumes 1 (eg. arg is an option that takes no arguments),
+        # then after _process_arg() is done the situation is:
+        #
+        #   largs = subset of [arg0, ..., arg(i)]
+        #   rargs = [arg(i+1), ..., arg(N-1)]
+        #
+        # If allow_interspersed_args is false, largs will always be
+        # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
+        # not a very interesting subset!
+        
+    def _match_long_opt (self, opt):
+        """_match_long_opt(opt : string) -> string
+
+        Determine which long option string 'opt' matches, ie. which one
+        it is an unambiguous abbrevation for.  Raises BadOptionError if
+        'opt' doesn't unambiguously match any long option string.
+        """
+        return _match_abbrev(opt, self._long_opt)
+
+    def _process_long_opt (self, rargs, values):
+        arg = rargs.pop(0)
+
+        # Value explicitly attached to arg?  Pretend it's the next
+        # argument.
+        if "=" in arg:
+            (opt, next_arg) = string.split(arg,"=", 1)
+            rargs.insert(0, next_arg)
+            had_explicit_value = 1
+        else:
+            opt = arg
+            had_explicit_value = 0
+
+        opt = self._match_long_opt(opt)
+        option = self._long_opt[opt]
+        if option.takes_value():
+            nargs = option.nargs
+            if len(rargs) < nargs:
+                if nargs == 1:
+                    self.error("%s option requires a value" % opt)
+                else:
+                    self.error("%s option requires %d values"
+                               % (opt, nargs))
+            elif nargs == 1:
+                value = rargs.pop(0)
+            else:
+                value = tuple(rargs[0:nargs])
+                del rargs[0:nargs]
+
+        elif had_explicit_value:
+            self.error("%s option does not take a value" % opt)
+
+        else:
+            value = None
+
+        option.process(opt, value, values, self)
+
+    def _process_short_opts (self, rargs, values):
+        arg = rargs.pop(0)
+        stop = 0
+        i = 1
+        for ch in arg[1:]:
+            opt = "-" + ch
+            option = self._short_opt.get(opt)
+            i = i+1                      # we have consumed a character
+
+            if not option:
+                self.error("no such option: %s" % opt)
+            if option.takes_value():
+                # Any characters left in arg?  Pretend they're the
+                # next arg, and stop consuming characters of arg.
+                if i < len(arg):
+                    rargs.insert(0, arg[i:])
+                    stop = 1
+
+                nargs = option.nargs
+                if len(rargs) < nargs:
+                    if nargs == 1:
+                        self.error("%s option requires a value" % opt)
+                    else:
+                        self.error("%s option requires %s values"
+                                   % (opt, nargs))
+                elif nargs == 1:
+                    value = rargs.pop(0)
+                else:
+                    value = tuple(rargs[0:nargs])
+                    del rargs[0:nargs]
+
+            else:                       # option doesn't take a value
+                value = None
+
+            option.process(opt, value, values, self)
+
+            if stop:
+                break
+
+
+    # -- Output/error methods ------------------------------------------
+
+    def error (self, msg):
+        """error(msg : string)
+
+        Print a usage message incorporating 'msg' to stderr and exit.
+        If you override this in a subclass, it should not return -- it
+        should either exit or raise an exception.
+        """
+        self.print_usage(sys.stderr)
+        sys.stderr.write("\nSCons error: %s\n" % msg)
+        sys.exit(2)
+
+    def print_usage (self, file=None):
+        """print_usage(file : file = stdout)
+
+        Print the usage message for the current program (self.usage) to
+        'file' (default stdout).  Any occurence of the string "%prog" in
+        self.usage is replaced with the name of the current program
+        (basename of sys.argv[0]).  Does nothing if self.usage is empty
+        or not defined.
+        """
+       if file is None:
+           file = sys.stdout
+        if self.usage:
+            usage = string.replace(self.usage,"%prog", get_prog_name())
+            file.write(usage + "\n")
+
+    def print_version (self, file=None):
+        """print_version(file : file = stdout)
+
+        Print the version message for this program (self.version) to
+        'file' (default stdout).  As with print_usage(), any occurence
+        of "%prog" in self.version is replaced by the current program's
+        name.  Does nothing if self.version is empty or undefined.
+        """
+       if file is None:
+           file = sys.stdout
+        if self.version:
+            version = string.replace(self.version,"%prog", get_prog_name())
+            file.write(version+"\n")
+
+    def print_help (self, file=None):
+        """print_help(file : file = stdout)
+
+        Print an extended help message, listing all options and any
+        help text provided with them, to 'file' (default stdout).
+        """
+        from distutils.fancy_getopt import wrap_text
+        
+        if file is None:
+            file = sys.stdout
+
+        self.print_usage(file)
+
+        # The help for each option consists of two parts:
+        #   * the opt strings and metavars
+        #     eg. ("-x", or "-fFILENAME, --file=FILENAME")
+        #   * the user-supplied help string
+        #     eg. ("turn on expert mode", "read data from FILENAME")
+        #
+        # If possible, we write both of these on the same line:
+        #   -x      turn on expert mode
+        # 
+        # But if the opt string list is too long, we put the help
+        # string on a second line, indented to the same column it would
+        # start in if it fit on the first line.
+        #   -fFILENAME, --file=FILENAME
+        #           read data from FILENAME
+
+        file.write("Options:\n")
+        width = 78                      # assume 80 cols for now
+
+        option_help = []                # list of (string, string) tuples
+        lengths = []
+
+        for option in self.option_list:
+            takes_value = option.takes_value()
+            if takes_value:
+                metavar = option.metavar or string.upper(option.dest)
+
+            opts = []               # list of "-a" or "--foo=FILE" strings
+            if option.help is SUPPRESS_HELP:
+                continue
+
+            if takes_value:
+                for sopt in option._short_opts:
+                    opts.append(sopt + ' ' + metavar)
+                for lopt in option._long_opts:
+                    opts.append(lopt + "=" + metavar)
+            else:
+                for opt in option._short_opts + option._long_opts:
+                    opts.append(opt)
+
+            opts = string.join(opts,", ")
+            option_help.append((opts, option.help))
+            lengths.append(len(opts))
+
+        max_opts = min(max(lengths), 26)
+
+        for (opts, help) in option_help:
+            # how much to indent lines 2 .. N of help text
+            indent_rest = 2 + max_opts + 2 
+            help_width = width - indent_rest
+
+            if len(opts) > max_opts:
+                opts = "  " + opts + "\n"
+                indent_first = indent_rest
+            else:                       # start help on same line as opts
+                opts = "  %-*s  " % (max_opts, opts)
+                indent_first = 0
+
+            file.write(opts)
+
+            if help:
+                help_lines = wrap_text(help, help_width)
+                file.write( "%*s%s\n" % (indent_first, "", help_lines[0]))
+                for line in help_lines[1:]:
+                    file.write("  %*s%s\n" % (indent_rest, "", line))
+            elif opts[-1] != "\n":
+                file.write("\n")
+
+# class OptionParser
+
+
+def _match_abbrev (s, wordmap):
+    """_match_abbrev(s : string, wordmap : {string : Option}) -> string
+
+    Return the string key in 'wordmap' for which 's' is an unambiguous
+    abbreviation.  If 's' is found to be ambiguous or doesn't match any of
+    'words', raise BadOptionError.
+    """
+    # Is there an exact match?
+    if wordmap.has_key(s):
+        return s
+    else:
+        # Isolate all words with s as a prefix.
+        possibilities = []
+       ls = len(s)
+       for word in wordmap.keys():
+            if len(word)>=ls and word[:ls]==s:
+               possibilities.append(word)
+        # No exact match, so there had better be just one possibility.
+        if len(possibilities) == 1:
+            return possibilities[0]
+        elif not possibilities:
+            raise BadOptionError("no such option: %s" % s)
+        else:
+            # More than one possible completion: ambiguous prefix.
+            raise BadOptionError("ambiguous option: %s (%s?)"
+                                 % (s, string.join(possibilities,", ")))
index 1403724f3fcf1e21fd593e5c705d3ff94545303e..bc6451c0e08bfd15dcc9799a301198fd09ebcf75 100644 (file)
@@ -39,7 +39,6 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import time
 start_time = time.time()
 
-import getopt
 import os
 import os.path
 import string
@@ -65,6 +64,7 @@ from SCons.Taskmaster import Taskmaster
 import SCons.Builder
 import SCons.Script.SConscript
 import SCons.Warnings
+from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
 
 
 #
@@ -99,9 +99,9 @@ class BuildTask(SCons.Taskmaster.Task):
                 # don't try to walk the stack, just print the error.
                 sys.stderr.write("\nSCons error: %s\n" % e)
                 raise
-           except:
-               sys.stderr.write("scons: *** %s\n" % sys.exc_value)
-               raise
+            except:
+                sys.stderr.write("scons: *** %s\n" % sys.exc_value)
+                raise
 
     def executed(self):
         SCons.Taskmaster.Task.executed(self)
@@ -110,9 +110,9 @@ class BuildTask(SCons.Taskmaster.Task):
         if print_tree and self.top:
             print
             print SCons.Util.render_tree(self.targets[0], get_all_children)
-       if print_dtree and self.top:
-           print
-           print SCons.Util.render_tree(self.targets[0], get_derived_children)
+        if print_dtree and self.top:
+            print
+            print SCons.Util.render_tree(self.targets[0], get_derived_children)
 
     def failed(self):
         global exit_status
@@ -157,29 +157,18 @@ class QuestionTask(SCons.Taskmaster.Task):
 
 # Global variables
 
-include_dirs = []
-num_jobs = 1
-scripts = []
-task_class = BuildTask # default action is to build targets
-current_func = None
-calc = None
-ignore_errors = 0
 keep_going_on_error = 0
-help_option = None
 print_tree = 0
 print_dtree = 0
 print_time = 0
+ignore_errors = 0
 sconscript_time = 0
 command_time = 0
-climb_up = 0
-target_top = None
 exit_status = 0 # exit status, assume success by default
 profiling = 0
-max_drift = None
 repositories = []
 sig_module = None
 
-#
 def print_it(text):
     print text
 
@@ -264,583 +253,55 @@ def _scons_other_errors():
     traceback.print_exc()
     sys.exit(2)
 
-
-#
-# After options are initialized, the following variables are
-# filled in:
-#
-option_list = []       # list of Option objects
-short_opts = ""                # string of short (single-character) options
-long_opts = []         # array of long (--) options
-opt_func = {}          # mapping of option strings to functions
-
-def options_init():
-    """Initialize command-line options processing.
-
-    This is in a subroutine mainly so we can easily single-step over
-    it in the debugger.
-    """
-
-    class Option:
-       """Class for command-line option information.
-
-       This exists to provide a central location for everything
-       describing a command-line option, so that we can change
-       options without having to update the code to handle the
-       option in one place, the -H help message in another place,
-       etc.  There are no methods here, only attributes.
-
-       You can initialize an Option with the following:
-
-       func    The function that will be called when this
-               option is processed on the command line.
-               Calling sequence is:
-
-                       func(opt, arg)
-
-               If there is no func, then this Option probably
-               stores an optstring to be printed.
-
-       helpline
-               The string to be printed in -H output.  If no
-               helpline is specified but a help string is
-               specified (the usual case), a helpline will be
-               constructed automatically from the short, long,
-               arg, and help attributes.  (In practice, then,
-               setting helpline without setting func allows you
-               to print arbitrary lines of text in the -H
-               output.)
-
-       short   The string for short, single-hyphen
-               command-line options.
-               Do not include the hyphen:
-
-                       'a' for -a, 'xy' for -x and -y, etc.
-
-       long    An array of strings for long, double-hyphen
-               command-line options.  Do not include
-               the initial hyphens:
-
-                       ['my-option', 'verbose']
-
-       arg     If this option takes an argument, this string
-               specifies how you want it to appear in the
-               -H output ('DIRECTORY', 'FILE', etc.).
-
-       help    The help string that will be printed for
-               this option in the -H output.  Must be
-               49 characters or fewer.
-
-               May be an array of strings, which will be
-               printed on successive lines.  The first string
-               must be 49 characters or fewer.  The remaining
-               strings will be indented two spaces and must
-               be 47 characters or fewer.
-
-       future  If non-zero, this indicates that this feature
-               will be supported in a future release, not
-               the currently planned one.  SCons will
-               recognize the option, but it won't show up
-               in the -H output.
-
-       The following attribute is derived from the supplied attributes:
-
-       optstring
-               A string, with hyphens, describing the flags
-               for this option, as constructed from the
-               specified short, long and arg attributes.
-
-       All Option objects are stored in the global option_list list,
-       in the order in which they're created.  This is the list
-       that's used to generate -H output, so the order in which the
-       objects are created is the order in which they're printed.
-
-       The upshot is that specifying a command-line option and having
-       everything work correctly is a matter of defining a function to
-       process its command-line argument (set the right flag, update
-       the right value), and then creating an appropriate Option object
-       at the correct point in the code below.
-       """
-
-       def __init__(self, func = None, helpline = None,
-                short = None, long = None, arg = None,
-                help = None, future = None):
-           self.func = func
-           self.short = short
-           self.long = long
-           self.arg = arg
-           if not SCons.Util.is_List(help):
-               help = [help]
-           self.help = help
-           opts = []
-           if self.short:
-               for c in self.short:
-                   if arg:
-                       c = c + " " + arg
-                   opts = opts + ['-' + c]
-           if self.long:
-               l = self.long
-               if arg:
-                   l = map(lambda x,a=arg: x + "=" + a, self.long)
-               opts = opts + map(lambda x: '--' + x, l)
-           self.optstring = string.join(opts, ', ')
-           if helpline:
-               self.helpline = helpline
-           elif help and not future:
-               if len(self.optstring) <= 26:
-                   sep = " " * (28 - len(self.optstring))
-               else:
-                   sep = self.helpstring = "\n" + " " * 30
-               self.helpline = "  " + self.optstring + sep + \
-                               string.join(self.help, "\n" + " " * 32)
-           else:
-               self.helpline = None
-           global option_list
-           option_list.append(self)
-
-    # Generic routine for to-be-written options, used by multiple
-    # options below.
-
-    def opt_not_yet(opt, arg):
-        sys.stderr.write("Warning:  the %s option is not yet implemented\n"
-                         % opt)
-
-    # In the following instantiations, the help string should be no
-    # longer than 49 characters.  Use the following as a guide:
-    #  help = "1234567890123456789012345678901234567890123456789"
-
-    def opt_ignore(opt, arg):
-       sys.stderr.write("Warning:  ignoring %s option\n" % opt)
-
-    Option(func = opt_ignore,
-       short = 'bmSt', long = ['no-keep-going', 'stop', 'touch'],
-       help = "Ignored for compatibility.")
-
-    def opt_c(opt, arg):
-        global task_class, calc
-        task_class = CleanTask
-        class CleanCalculator:
-            def bsig(self, node):
-                return None
-            def csig(self, node):
-                return None
-            def current(self, node, sig):
-                return 0
-            def write(self):
-                pass
-        calc = CleanCalculator()
-
-    Option(func = opt_c,
-       short = 'c', long = ['clean', 'remove'],
-       help = "Remove specified targets and dependencies.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['cache-disable', 'no-cache'],
-       help = "Do not retrieve built targets from Cache.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['cache-force', 'cache-populate'],
-       help = "Copy already-built targets into the Cache.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['cache-show'],
-       help = "Print what would have built Cached targets.")
-
-    def opt_C(opt, arg):
-       try:
-           os.chdir(arg)
-       except:
-           sys.stderr.write("Could not change directory to 'arg'\n")
-
-    Option(func = opt_C,
-       short = 'C', long = ['directory'], arg = 'DIRECTORY',
-       help = "Change to DIRECTORY before doing anything.")
-
-    Option(func = opt_not_yet,
-       short = 'd',
-       help = "Print file dependency information.")
-
-    def opt_D(opt, arg):
-        global climb_up
-        climb_up = 2
-
-    Option(func = opt_D,
-        short = 'D',
-        help = ["Search up directory tree for SConstruct,",
-                "build all Default() targets."])
-
-    def opt_debug(opt, arg):
-        global print_tree
-        global print_dtree
-        global print_time
-        if arg == "pdb":
-            args = [ sys.executable, "pdb.py" ] + \
-                     filter(lambda x: x != "--debug=pdb", sys.argv)
-            if sys.platform == 'win32':
-                args[1] = os.path.join(sys.prefix, "lib", "pdb.py")
-                sys.exit(os.spawnve(os.P_WAIT, args[0], args, os.environ))
-            else:
-                args[1] = os.path.join(sys.prefix,
-                                       "lib",
-                                       "python" + sys.version[0:3],
-                                      "pdb.py")
-                os.execvpe(args[0], args, os.environ)
-        elif arg == "tree":
-            print_tree = 1
-        elif arg == "dtree":
-            print_dtree = 1
-        elif arg == "time":
-            print_time = 1
-        else:
-            sys.stderr.write("Warning:  %s is not a valid debug type\n"
-                             % arg)
-
-    Option(func = opt_debug,
-           long = ['debug'], arg='TYPE',
-           help = "Print various types of debugging information.")
-
-    Option(func = opt_not_yet, future = 1,
-       short = 'e', long = ['environment-overrides'],
-       help = "Environment variables override makefiles.")
-
-    def opt_f(opt, arg):
-       global scripts
-        scripts.append(arg)
-
-    Option(func = opt_f,
-       short = 'f', long = ['file', 'makefile', 'sconstruct'], arg = 'FILE',
-       help = "Read FILE as the top-level SConstruct file.")
-
-    def opt_help(opt, arg):
-        global help_option
-        help_option = 'h'
-        def raisePrintHelp(text):
-            raise PrintHelp, text
-        SCons.Script.SConscript.HelpFunction = raisePrintHelp
-
-    Option(func = opt_help,
-       short = 'h', long = ['help'],
-       help = "Print defined help message, or this one.")
-
-    def opt_help_options(opt, arg):
-       global help_option
-        help_option = 'H'
-
-    Option(func = opt_help_options,
-       short = 'H', long = ['help-options'],
-       help = "Print this message and exit.")
-
-    def opt_i(opt, arg):
-        global ignore_errors
-        ignore_errors = 1
-
-    Option(func = opt_i,
-       short = 'i', long = ['ignore-errors'],
-       help = "Ignore errors from build actions.")
-
-    def opt_I(opt, arg):
-       global include_dirs
-       include_dirs = include_dirs + [arg]
-
-    Option(func = opt_I,
-       short = 'I', long = ['include-dir'], arg = 'DIRECTORY',
-       help = "Search DIRECTORY for imported Python modules.")
-
-    def opt_implicit_cache(opt, arg):
-        import SCons.Node
-        SCons.Node.implicit_cache = 1
-
-    Option(func = opt_implicit_cache,
-        long = ['implicit-cache'],
-        help = "Cache implicit (scanned) dependencies.")
-
-    def opt_implicit_deps_changed(opt, arg):
-        import SCons.Node
-        SCons.Node.implicit_cache = 1
-        SCons.Node.implicit_deps_changed = 1
-
-    Option(func = opt_implicit_deps_changed,
-        long = ['implicit-deps-changed'],
-        help = "Ignore the cached implicit dependencies.")
-
-    def opt_implicit_deps_unchanged(opt, arg):
-        import SCons.Node
-        SCons.Node.implicit_cache = 1
-        SCons.Node.implicit_deps_unchanged = 1
-
-    Option(func = opt_implicit_deps_unchanged,
-        long = ['implicit-deps-unchanged'],
-        help = "Ignore changes in implicit dependencies.")
-
-    def opt_j(opt, arg):
-       global num_jobs
-       try:
-            num_jobs = int(arg)
-       except:
-            print UsageString()
-            sys.exit(1)
-
-       if num_jobs <= 0:
-            print UsageString()
-            sys.exit(1)
-
-    Option(func = opt_j,
-       short = 'j', long = ['jobs'], arg = 'N',
-       help = "Allow N jobs at once.")
-
-    def opt_k(opt, arg):
-        global keep_going_on_error
-        keep_going_on_error = 1
-
-    Option(func = opt_k,
-       short = 'k', long = ['keep-going'],
-       help = "Keep going when a target can't be made.")
-
-    Option(func = opt_not_yet, future = 1,
-       short = 'l', long = ['load-average', 'max-load'], arg = 'N',
-       help = "Don't start multiple jobs unless load is below N.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['list-derived'],
-       help = "Don't build; list files that would be built.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['list-actions'],
-       help = "Don't build; list files and build actions.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['list-where'],
-       help = "Don't build; list files and where defined.")
-
-    def opt_max_drift(opt, arg):
-        global max_drift
-        try:
-            max_drift = int(arg)
-        except ValueError:
-            raise UserError, "The argument for --max-drift must be an integer."
-
-    Option(func = opt_max_drift,
-        long = ['max-drift'],
-        arg = 'SECONDS',
-        help = "Set the maximum system clock drift to be SECONDS.")
-
-    def opt_n(opt, arg):
-       SCons.Action.execute_actions = None
-       CleanTask.execute = CleanTask.show
-
-    Option(func = opt_n,
-       short = 'n', long = ['no-exec', 'just-print', 'dry-run', 'recon'],
-       help = "Don't build; just print commands.")
-
-    Option(func = opt_not_yet, future = 1,
-       short = 'o', long = ['old-file', 'assume-old'], arg = 'FILE',
-       help = "Consider FILE to be old; don't rebuild it.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['override'], arg = 'FILE',
-       help = "Override variables as specified in FILE.")
-
-    Option(func = opt_not_yet, future = 1,
-       short = 'p',
-       help = "Print internal environments/objects.")
-
-    def opt_profile(opt, arg):
-        global profiling
-        if not profiling:
-            profiling = 1
-            import profile
-            profile.run('SCons.Script.main()', arg)
-            sys.exit(exit_status)
-
-    Option(func = opt_profile,
-       long = ['profile'], arg = 'FILE',
-       help = "Profile SCons and put results in FILE.")
-
-    def opt_Q(opt, arg):
-       global display
-       def dont_print_it(text):
-           pass
-       display = dont_print_it
-
-    Option(func = opt_Q,
-       short = 'Q',
-       help = "Don't print SCons progress messages.")
-
-    def opt_q(opt, arg):
-        global task_class
-        task_class = QuestionTask
-
-    Option(func = opt_q,
-       short = 'q', long = ['question'],
-       help = "Don't build; exit status says if up to date.")
-
-    Option(func = opt_not_yet, future = 1,
-       short = 'rR', long = ['no-builtin-rules', 'no-builtin-variables'],
-       help = "Clear default environments and variables.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['random'],
-       help = "Build dependencies in random order.")
-
-    def opt_s(opt, arg):
-       global display
-       def dont_print_it(text):
-           pass
-       display = dont_print_it
-       SCons.Action.print_actions = None
-
-    Option(func = opt_s,
-       short = 's', long = ['silent', 'quiet'],
-       help = "Don't print commands.")
-
-    def opt_u(opt, arg):
-        global climb_up
-        climb_up = 1
-
-    Option(func = opt_u,
-       short = 'u', long = ['up', 'search-up'],
-        help = ["Search up directory tree for SConstruct,",
-                "build targets at or below current directory."])
-
-    def opt_U(opt, arg):
-        global climb_up
-        climb_up = 3
-
-    Option(func = opt_U,
-        short = 'U',
-        help = ["Search up directory tree for SConstruct,",
-                "build Default() targets from local SConscript."])
-
-    def option_v(opt, arg):
-        import __main__
-        import SCons
-       print "SCons by Steven Knight et al.:"
-       try:
-           print "\tscript: v%s.%s, %s, by %s on %s" % (__main__.__version__,
-                                                        __main__.__build__,
-                                                        __main__.__date__,
-                                                        __main__.__developer__,
-                                                        __main__.__buildsys__)
-       except:
-           # On win32 there is no scons.py, so there is no __main__.__version__,
-           # hence there is no script version.
-           pass 
-       print "\tengine: v%s.%s, %s, by %s on %s" % (SCons.__version__,
-                                                    SCons.__build__,
-                                                    SCons.__date__,
-                                                    SCons.__developer__,
-                                                    SCons.__buildsys__)
-       print "Copyright 2001, 2002 Steven Knight"
-       sys.exit(0)
-
-    Option(func = option_v,
-       short = 'v', long = ['version'],
-       help = "Print the SCons version number and exit.")
-
-    Option(func = opt_not_yet, future = 1,
-       short = 'w', long = ['print-directory'],
-       help = "Print the current directory.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['no-print-directory'],
-       help = "Turn off -w, even if it was turned on implicitly.")
-
-    Option(func = opt_not_yet, future = 1,
-       long = ['write-filenames'], arg = 'FILE',
-       help = "Write all filenames examined into FILE.")
-
-    Option(func = opt_not_yet, future = 1,
-       short = 'W', long = ['what-if', 'new-file', 'assume-new'], arg = 'FILE',
-       help = "Consider FILE to be changed.")
-
-    def opt_warn(opt, arg):
-        """The --warn option.  An argument to this option
-        should be of the form <warning-class> or no-<warning-class>.
-        The warning class is munged in order to get an actual class
-        name from the SCons.Warnings module to enable or disable.
-        The supplied <warning-class> is split on hyphens, each element
-        is captialized, then smushed back together.  Then the string
-        "SCons.Warnings." is added to the front and "Warning" is added
-        to the back to get the fully qualified class name.
-
-        For example, --warn=deprecated will enable the
-        SCons.Warnings.DeprecatedWarning class.
-
-        --warn=no-dependency will disable the
-        SCons.Warnings.DependencyWarning class.
-
-        As a special case, --warn=all and --warn=no-all
-        will enable or disable (respectively) the base
-        class of all warnings, which is SCons.Warning.Warning."""
-        
-        elems = string.split(string.lower(arg), '-')
-        enable = 1
-        if elems[0] == 'no':
-            enable = 0
-            del elems[0]
-
-        if len(elems) == 1 and elems[0] == 'all':
-            class_name = "Warning"
-        else:
-            class_name = string.join(map(string.capitalize, elems), '') + \
-                         "Warning"
-        try:
-            clazz = getattr(SCons.Warnings, class_name)
-        except AttributeError:
-            sys.stderr.write("No warning type: '%s'\n" % arg)
+def _varargs(option, parser):
+    value = None
+    if parser.rargs:
+        arg = parser.rargs[0]
+        if arg[0] != "-":
+            value = arg
+            del parser.rargs[0]
+    return value
+
+def _setup_warn(arg):
+    """The --warn option.  An argument to this option
+    should be of the form <warning-class> or no-<warning-class>.
+    The warning class is munged in order to get an actual class
+    name from the SCons.Warnings module to enable or disable.
+    The supplied <warning-class> is split on hyphens, each element
+    is captialized, then smushed back together.  Then the string
+    "SCons.Warnings." is added to the front and "Warning" is added
+    to the back to get the fully qualified class name.
+
+    For example, --warn=deprecated will enable the
+    SCons.Warnings.DeprecatedWarning class.
+
+    --warn=no-dependency will disable the
+    SCons.Warnings.DependencyWarning class.
+
+    As a special case, --warn=all and --warn=no-all
+    will enable or disable (respectively) the base
+    class of all warnings, which is SCons.Warning.Warning."""
+
+    elems = string.split(string.lower(arg), '-')
+    enable = 1
+    if elems[0] == 'no':
+        enable = 0
+        del elems[0]
+
+    if len(elems) == 1 and elems[0] == 'all':
+        class_name = "Warning"
+    else:
+        class_name = string.join(map(string.capitalize, elems), '') + \
+                     "Warning"
+    try:
+        clazz = getattr(SCons.Warnings, class_name)
+    except AttributeError:
+        sys.stderr.write("No warning type: '%s'\n" % arg)
+    else:
+        if enable:
+            SCons.Warnings.enableWarningClass(clazz)
         else:
-            if enable:
-                SCons.Warnings.enableWarningClass(clazz)
-            else:
-                SCons.Warnings.suppressWarningClass(clazz)
-
-    Option(func = opt_warn,
-           long = [ 'warn', 'warning' ], arg='WARNING-SPEC',
-           help = "Enable or disable warnings.")
-
-
-    # We want to preserve the --warn-undefined-variables option for
-    # compatibility with GNU Make.  Unfortunately, this conflicts with
-    # the --warn=type option that we're using for our own warning
-    # control.  The getopt module reports "--warn not a unique prefix"
-    # when both are defined.  We may be able to support both in the
-    # future with a more robust getopt solution.
-    #
-    #Option(func = opt_not_yet, future = 1,
-    #    long = ['warn-undefined-variables'],
-    #    help = "Warn when an undefined variable is referenced.")
-
-    def opt_Y(opt, arg):
-        global repositories
-        repositories.append(arg)
-
-    Option(func = opt_Y,
-       short = 'Y', long = ['repository'], arg = 'REPOSITORY',
-       help = "Search REPOSITORY for source and target files.")
-
-    global short_opts
-    global long_opts
-    global opt_func
-    for o in option_list:
-       if o.short:
-           if o.func:
-               for c in o.short:
-                   opt_func['-' + c] = o.func
-           short_opts = short_opts + o.short
-           if o.arg:
-               short_opts = short_opts + ":"
-       if o.long:
-           if o.func:
-               for l in o.long:
-                   opt_func['--' + l] = o.func
-           if o.arg:
-               long_opts = long_opts + map(lambda a: a + "=", o.long)
-           else:
-               long_opts = long_opts + o.long
-
-options_init()
-
-
+            SCons.Warnings.suppressWarningClass(clazz)
 
 def _SConstruct_exists(dirname=''):
     """This function checks that an SConstruct file exists in a directory.
@@ -858,60 +319,343 @@ def _SConstruct_exists(dirname=''):
                     return sfile
     return None
 
+def _set_globals(options):
+    global repositories, keep_going_on_error, print_tree, print_dtree
+    global print_time, ignore_errors
+
+    if options.repository:
+        repositories.extend(options.repository)
+    keep_going_on_error = options.keep_going
+    try:
+        if options.debug:
+            if options.debug == "tree":
+                print_tree = 1
+            elif options.debug == "dtree":
+                print_dtree = 1
+            elif options.debug == "time":
+                print_time = 1
+    except AttributeError:
+        pass
+    ignore_errors = options.ignore_errors
+
+def _create_path(plist):
+    path = '.'
+    for d in plist:
+        if os.path.isabs(d):
+            path = d
+        else:
+            path = path + '/' + d
+    return path
 
-def UsageString():
-    help_opts = filter(lambda x: x.helpline, option_list)
-    s = "Usage: scons [OPTION] [TARGET] ...\n" + "Options:\n" + \
-       string.join(map(lambda x: x.helpline, help_opts), "\n") + "\n"
-    return s
 
+class OptParser(OptionParser):
+    def __init__(self):
+        import __main__
+        import SCons
+        parts = ["SCons by Steven Knight et al.:\n"]
+        try:
+            parts.append("\tscript: v%s.%s, %s, by %s on %s\n" % (__main__.__version__,
+                                                                  __main__.__build__,
+                                                                  __main__.__date__,
+                                                                  __main__.__developer__,
+                                                                  __main__.__buildsys__))
+        except:
+            # On win32 there is no scons.py, so there is no __main__.__version__,
+            # hence there is no script version.
+            pass 
+        parts.append("\tengine: v%s.%s, %s, by %s on %s\n" % (SCons.__version__,
+                                                              SCons.__build__,
+                                                              SCons.__date__,
+                                                              SCons.__developer__,
+                                                              SCons.__buildsys__))
+        parts.append("Copyright 2001, 2002 Steven Knight")
+        OptionParser.__init__(self, version=string.join(parts, ''),
+                              usage="usage: scons [OPTION] [TARGET] ...")
+
+        # options ignored for compatibility
+        def opt_ignore(option, opt, value, parser):
+            sys.stderr.write("Warning:  ignoring %s option\n" % opt)
+        self.add_option("-b", "-m", "-S", "-t", "--no-keep-going", "--stop",
+                        "--touch", action="callback", callback=opt_ignore,
+                        help="Ignored for compatibility.")
+
+        def opt_c(option, opt, value, parser):
+            setattr(parser.values, 'clean', 1)
+        self.add_option('-c', '--clean', '--remove', action="callback",
+                        callback=opt_c,
+                        help="Remove specified targets and dependencies.")
+
+        self.add_option('-C', '--directory', type="string", action = "append",
+                        help="Change to DIRECTORY before doing anything.")
+
+        def opt_not_yet(option, opt, value, parser):
+            sys.stderr.write("Warning:  the %s option is not yet implemented\n" % opt)
+            sys.exit(0)
+        self.add_option('-d', action="callback",
+                        callback=opt_not_yet,
+                        help = "Print file dependency information.")
+        
+        self.add_option('-D', action="store_const", const=2, dest="climb_up",
+                        help="Search up directory tree for SConstruct, "
+                             "build all Default() targets.")
+
+        def opt_debug(option, opt, value, parser):
+            if value == "pdb":
+                args = [ sys.executable, "pdb.py" ] + \
+                       filter(lambda x: x != "--debug=pdb", sys.argv)
+                if sys.platform == 'win32':
+                    args[1] = os.path.join(sys.prefix, "lib", "pdb.py")
+                    sys.exit(os.spawnve(os.P_WAIT, args[0], args, os.environ))
+                else:
+                    args[1] = os.path.join(sys.prefix,
+                                           "lib",
+                                           "python" + sys.version[0:3],
+                                           "pdb.py")
+                os.execvpe(args[0], args, os.environ)
+            elif value in ["tree", "dtree", "time"]:
+                setattr(parser.values, 'debug', value)
+            else:
+                raise OptionValueError("Warning:  %s is not a valid debug type" % value)
+        self.add_option('--debug', action="callback", type="string",
+                        callback=opt_debug, nargs=1, dest="debug",
+                        help="Print various types of debugging information.")
+
+        self.add_option('-f', '--file', '--makefile', '--sconstruct',
+                        action="append", nargs=1,
+                        help="Read FILE as the top-level SConstruct file.")
+
+        self.add_option('-h', '--help', action="store_true", default=0,
+                        dest="help_msg",
+                        help="Print defined help message, or this one.")
+
+        self.add_option("-H", "--help-options",
+                        action="help",
+                        help="Print this message and exit.")
+
+        self.add_option('-i', '--ignore-errors', action="store_true",
+                        default=0, dest='ignore_errors',
+                        help="Ignore errors from build actions.")
+
+        self.add_option('-I', '--include-dir', action="append",
+                        dest='include_dir', metavar="DIRECTORY",
+                        help="Search DIRECTORY for imported Python modules.")
+
+        self.add_option('--implicit-cache', action="store_true", default=0,
+                        dest='implicit_cache',
+                        help="Cache implicit dependencies")
+
+        self.add_option('--implicit-deps-changed', action="store_true",
+                        default=0, dest='implicit_deps_changed',
+                        help="Ignore the cached implicit deps.")
+        self.add_option('--implicit-deps-unchanged', action="store_true",
+                        default=0, dest='implicit_deps_unchanged',
+                        help="Ignore changes in implicit deps.")
+
+        def opt_j(option, opt, value, parser):
+            value = int(value)
+            setattr(parser.values, 'num_jobs', value)
+        self.add_option('-j', '--jobs', action="callback", type="int",
+                        callback=opt_j, metavar="N",
+                        help="Allow N jobs at once.")
+
+        self.add_option('-k', '--keep-going', action="store_true", default=0,
+                        dest='keep_going',
+                        help="Keep going when a target can't be made.")
+
+        self.add_option('--max-drift', type="int", action="store",
+                        dest='max_drift',
+                        help="Set the maximum system clock drift to be"
+                             " MAX_DRIFT seconds.")
+
+        self.add_option('-n', '--no-exec', '--just-print', '--dry-run',
+                        '--recon', action="store_true", dest='noexec',
+                        default=0, help="Don't build; just print commands.")
+
+        def opt_profile(option, opt, value, parser):
+            global profiling
+            if not profiling:
+                profiling = 1
+                import profile
+                profile.run('SCons.Script.main()', value)
+                sys.exit(exit_status)
+        self.add_option('--profile', nargs=1, action="callback",
+                        callback=opt_profile, type="string", dest="profile",
+                        help="Profile SCons and put results in PROFILE.")
+
+        self.add_option('-q', '--question', action="store_true", default=0,
+                        help="Don't build; exit status says if up to date.")
+
+        self.add_option('-Q', dest='no_progress', action="store_true",
+                        default=0,
+                        help="Don't print SCons progress messages.")
+
+        self.add_option('-s', '--silent', '--quiet', action="store_true",
+                        default=0, help="Don't print commands.")
+
+        self.add_option('-u', '--up', '--search-up', action="store_const",
+                        dest="climb_up", default=0, const=1,
+                        help="Search up directory tree for SConstruct, "
+                             "build targets at or below current directory.")
+        self.add_option('-U', action="store_const", dest="climb_up",
+                        default=0, const=3,
+                        help="Search up directory tree for SConstruct, "
+                             "build Default() targets from local SConscript.")
+
+        self.add_option("-v", "--version",
+                        action="version",
+                        help="Print the SCons version number and exit.")
+
+        self.add_option('--warn', '--warning', nargs=1, action="store",
+                        metavar="WARNING-SPEC",
+                        help="Enable or disable warnings.")
+
+        self.add_option('-Y', '--repository', nargs=1, action="append",
+                        help="Search REPOSITORY for source and target files.")
+
+        self.add_option('--cache-disable', '--no-cache', action="callback",
+                        callback=opt_not_yet,
+                        # help = "Do not retrieve built targets from Cache."
+                        help=SUPPRESS_HELP)
+        self.add_option('--cache-force', '--cache-populate', action="callback",
+                        callback=opt_not_yet,
+                        # help = "Copy already-built targets into the Cache."
+                        help=SUPPRESS_HELP)
+        self.add_option('--cache-show', action="callback",
+                        callback=opt_not_yet,
+                        # help = "Print what would have built Cached targets.",
+                        help=SUPPRESS_HELP)
+        self.add_option('-e', '--environment-overrides', action="callback",
+                        callback=opt_not_yet,
+                        # help="Environment variables override makefiles."
+                        help=SUPPRESS_HELP)
+        self.add_option('-l', '--load-average', '--max-load', action="callback",
+                        callback=opt_not_yet, type="int", dest="load_average",
+                        # action="store",
+                        # help="Don't start multiple jobs unless load is below "
+                        #      "LOAD-AVERAGE."
+                        # type="int",
+                        help=SUPPRESS_HELP)
+        self.add_option('--list-derived', action="callback",
+                        callback=opt_not_yet,
+                        # help="Don't build; list files that would be built."
+                        help=SUPPRESS_HELP)
+        self.add_option('--list-actions', action="callback",
+                        callback=opt_not_yet,
+                        # help="Don't build; list files and build actions."
+                        help=SUPPRESS_HELP)
+        self.add_option('--list-where', action="callback",
+                        callback=opt_not_yet,
+                        # help="Don't build; list files and where defined."
+                        help=SUPPRESS_HELP)
+        self.add_option('-o', '--old-file', '--assume-old', action="callback",
+                        callback=opt_not_yet, type="string", dest="old_file",
+                        # help = "Consider FILE to be old; don't rebuild it."
+                        help=SUPPRESS_HELP)
+        self.add_option('--override', action="callback", dest="override",
+                        callback=opt_not_yet, type="string",
+                        # help="Override variables as specified in FILE."
+                        help=SUPPRESS_HELP)
+        self.add_option('-p', action="callback",
+                        callback=opt_not_yet,
+                        # help="Print internal environments/objects."
+                        help=SUPPRESS_HELP)
+        self.add_option('-r', '-R', '--no-builtin-rules',
+                        '--no-builtin-variables', action="callback",
+                        callback=opt_not_yet,
+                        # help="Clear default environments and variables."
+                        help=SUPPRESS_HELP)
+        self.add_option('--random', action="callback",
+                        callback=opt_not_yet,
+                        # help="Build dependencies in random order."
+                        help=SUPPRESS_HELP)
+        self.add_option('-w', '--print-directory', action="callback",
+                        callback=opt_not_yet,
+                        # help="Print the current directory."
+                        help=SUPPRESS_HELP)
+        self.add_option('--no-print-directory', action="callback",
+                        callback=opt_not_yet,
+                        # help="Turn off -w, even if it was turned on implicitly."
+                        help=SUPPRESS_HELP)
+        self.add_option('--write-filenames', action="callback",
+                        callback=opt_not_yet, type="string", dest="write_filenames",
+                        # help="Write all filenames examined into FILE."
+                        help=SUPPRESS_HELP)
+        self.add_option('-W', '--what-if', '--new-file', '--assume-new',
+                        dest="new_file",
+                        action="callback", callback=opt_not_yet, type="string",
+                        # help="Consider FILE to be changed."
+                        help=SUPPRESS_HELP)
+        self.add_option('--warn-undefined-variables', action="callback",
+                        callback=opt_not_yet,
+                        # help="Warn when an undefined variable is referenced."
+                        help=SUPPRESS_HELP)
+
+    def parse_args(self, args=None, values=None):
+        opt, arglist = OptionParser.parse_args(self, args, values)
+        if opt.implicit_deps_changed or opt.implicit_deps_unchanged:
+            opt.implicit_cache = 1
+        if not hasattr(opt, "num_jobs"):
+            setattr(opt, "num_jobs", 1)
+        return opt, arglist
 
 
 def _main():
-    global scripts, num_jobs, task_class, calc, target_top
+    import SCons.Node
 
     targets = []
 
-    # It looks like 2.0 changed the name of the exception class
-    # raised by getopt.
-    try:
-       getopt_err = getopt.GetoptError
-    except:
-       getopt_err = getopt.error
-
     # Enable deprecated warnings by default.
     SCons.Warnings._warningOut = _scons_internal_warning
     SCons.Warnings.enableWarningClass(SCons.Warnings.DeprecatedWarning)
     SCons.Warnings.enableWarningClass(SCons.Warnings.CorruptSConsignWarning)
 
+    all_args = sys.argv[1:]
     try:
-       cmd_opts, t = getopt.getopt(string.split(os.environ['SCONSFLAGS']),
-                                         short_opts, long_opts)
+        all_args = string.split(os.environ['SCONSFLAGS']) + all_args
     except KeyError:
-       # It's all right if there's no SCONSFLAGS environment variable.
-       pass
-    except getopt_err, x:
-       _scons_user_warning("SCONSFLAGS " + str(x))
-    else:
-       for opt, arg in cmd_opts:
-           opt_func[opt](opt, arg)
+            # it's OK if there's no SCONSFLAGS
+            pass
+    parser = OptParser()
+    options, args = parser.parse_args(all_args)
 
-    try:
-        cmd_opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
-    except getopt_err, x:
-       _scons_user_error(x)
-    else:
-       for opt, arg in cmd_opts:
-           opt_func[opt](opt, arg)
-        xmit_args = []
-        for a in args:
-            if '=' in a:
-                xmit_args.append(a)
-            else:
-                targets.append(a)
-        SCons.Script.SConscript._scons_add_args(xmit_args)
+    if options.help_msg:
+        def raisePrintHelp(text):
+            raise PrintHelp, text
+        SCons.Script.SConscript.HelpFunction = raisePrintHelp
 
-    if climb_up:
+    _set_globals(options)
+    SCons.Node.implicit_cache = options.implicit_cache
+    SCons.Node.implicit_deps_changed = options.implicit_deps_changed
+    SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
+    if options.warn:
+        _setup_warn(options.warn)
+    if options.noexec:
+        SCons.Action.execute_actions = None
+        CleanTask.execute = CleanTask.show
+    if options.no_progress or options.silent:
+        global display
+        def dont_print_it(text):
+            pass
+        display = dont_print_it
+    if options.silent:
+        SCons.Action.print_actions = None
+    if options.directory:
+        cdir = _create_path(options.directory)
+        try:
+            os.chdir(cdir)
+        except:
+            sys.stderr.write("Could not change directory to %s\n" % cdir)
+
+    xmit_args = []
+    for a in args:
+        if '=' in a:
+            xmit_args.append(a)
+        else:
+            targets.append(a)
+    SCons.Script.SConscript._scons_add_args(xmit_args)
+
+    target_top = None
+    if options.climb_up:
         target_top = '.'  # directory to prepend to targets
         script_dir = os.getcwd()  # location of script
         while script_dir and not _SConstruct_exists(script_dir):
@@ -928,24 +672,25 @@ def _main():
 
     SCons.Node.FS.default_fs.set_toplevel_dir(os.getcwd())
 
+    scripts = []
+    if options.file:
+        scripts.extend(options.file)
     if not scripts:
         sfile = _SConstruct_exists()
         if sfile:
             scripts.append(sfile)
 
-    if help_option == 'H':
-       print UsageString()
-       sys.exit(0)
-
-    if not scripts:
-        if help_option == 'h':
+    if options.help_msg:
+        if not scripts:
             # There's no SConstruct, but they specified -h.
             # Give them the options usage now, before we fail
             # trying to read a non-existent SConstruct file.
-           print UsageString()
-           sys.exit(0)
-       else:
-           raise UserError, "No SConstruct file found."
+            parser.print_help()
+            sys.exit(0)
+        SCons.Script.SConscript.print_help = 1
+
+    if not scripts:
+        raise UserError, "No SConstruct file found."
 
     class Unbuffered:
         def __init__(self, file):
@@ -958,7 +703,8 @@ def _main():
 
     sys.stdout = Unbuffered(sys.stdout)
 
-    sys.path = include_dirs + sys.path
+    if options.include_dir:
+        sys.path = options.include_dir + sys.path
 
     global repositories
     for rep in repositories:
@@ -976,27 +722,26 @@ def _main():
         print text
         print "Use scons -H for help about command-line options."
         sys.exit(0)
-
     display("scons: done reading SConscript files.")
 
     SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Top)
 
-    if help_option == 'h':
-       # They specified -h, but there was no Help() inside the
-       # SConscript files.  Give them the options usage.
-       print UsageString()
-       sys.exit(0)
+    if options.help_msg:
+        # They specified -h, but there was no Help() inside the
+        # SConscript files.  Give them the options usage.
+        parser.print_help(sys.stdout)
+        sys.exit(0)
 
     if target_top:
         target_top = SCons.Node.FS.default_fs.Dir(target_top)
         
-        if climb_up == 2 and not targets:
+        if options.climb_up == 2 and not targets:
             # -D with default targets
             target_top = None
-        elif climb_up == 3 and not targets:
+        elif options.climb_up == 3 and not targets:
             # -U with default targets
             default_targets = SCons.Script.SConscript.default_targets
-            def check_dir(x):
+            def check_dir(x, target_top=target_top):
                 cwd = x.cwd.srcnode()
                 return cwd == target_top
             default_targets = filter(check_dir, default_targets)
@@ -1038,22 +783,42 @@ def _main():
 
     nodes = filter(lambda x: x is not None, map(Entry, targets))
 
+    calc = None
+    task_class = BuildTask     # default action is to build targets
+    if options.question:
+        task_class = QuestionTask
+    try:
+        if options.clean:
+            task_class = CleanTask
+            class CleanCalculator:
+                def bsig(self, node):
+                    return None
+                def csig(self, node):
+                    return None
+                def current(self, node, sig):
+                    return 0
+                def write(self):
+                    pass
+            calc = CleanCalculator()
+    except AttributeError:
+        pass
+
     if not calc:
-        if max_drift is not None:
-            if sig_module is not None:
+        if options.max_drift is not None:
+           if sig_module is not None:
                 SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module,
-                                                              max_drift=max_drift)
-            else:
-                SCons.Sig.default_calc = SCons.Sig.Calculator(max_drift=max_drift)
+                                                              max_drift=options.max_drift)
+           else:
+                SCons.Sig.default_calc = SCons.Sig.Calculator(max_drift=options.max_drift)
         elif sig_module is not None:
             SCons.Sig.default_calc = SCons.Sig.Calculator(module=sig_module)
-            
+
         calc = SCons.Sig.default_calc
 
     display("scons: Building targets ...")
     taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, calc)
 
-    jobs = SCons.Job.Jobs(num_jobs, taskmaster)
+    jobs = SCons.Job.Jobs(options.num_jobs, taskmaster)
 
     try:
         jobs.run()
index d1ef81ba2900da407b744c4a8b76da34734100f0..115bced974b7be6b06632488f86406767ee1163a 100644 (file)
@@ -63,6 +63,7 @@ software.""",
       keywords = "scons, cons, make, build tool, make tool",
       packages = ["SCons",
                  "SCons.Node",
+                 "SCons.Optik",
                  "SCons.Scanner",
                  "SCons.Sig",
                  "SCons.Script"])
index ceb4440e2fd972992ac1c1f3af36a0d7b0f53fba..50c1491ac19c4f421be7e9fc8ed07455ad781a74 100644 (file)
@@ -76,6 +76,7 @@ arguments = {
     'version'          : "__VERSION__",
     'packages'         : ["SCons",
                           "SCons.Node",
+                          "SCons.Optik",
                           "SCons.Platform",
                           "SCons.Scanner",
                           "SCons.Script",
index f8d757ad1dfe8a5e354cd39b51ce67d5c9b6fa85..2d0ac8da3c2b33f1a309dac65a94fff236118ac7 100644 (file)
@@ -29,7 +29,7 @@ import TestSCons
 import os
 import string
 
-test = TestSCons.TestSCons(match = TestCmd.match_re)
+test = TestSCons.TestSCons()
 
 wpath = test.workpath()
 
@@ -59,11 +59,10 @@ test.fail_test(string.find(test.stdout(), '-H, --help-options') == -1)
 
 os.environ['SCONSFLAGS'] = '-Z'
 
-test.run(arguments = "-H", stderr = r"""
-SCons warning: SCONSFLAGS option -Z not recognized
-File "[^"]*", line \d+, in \S+
-""")
+test.run(arguments = "-H", status = 2,
+         stderr = r"""usage: scons [OPTION] [TARGET] ...
 
-test.fail_test(string.find(test.stdout(), '-H, --help-options') == -1)
+SCons error: no such option: -Z
+""")
 
 test.pass_test()
index 6d7efe4b2b157cbc0d238e1a0b3f995efae59e78..5ba9c467e865812267452815c3d457b980d1bd56 100644 (file)
@@ -49,9 +49,9 @@ stats.strip_dirs().print_stats()
 s = sys.stdout.getvalue()
 
 test.fail_test(string.find(s, '__init__.py') == -1)
-test.fail_test(string.find(s, 'option_v') == -1)
+test.fail_test(string.find(s, 'print_version') == -1)
 test.fail_test(string.find(s, 'SCons.Script.main()') == -1)
-test.fail_test(string.find(s, 'getopt.py') == -1)
+test.fail_test(string.find(s, 'option_parser.py') == -1)
 
 
 scons_prof = test.workpath('scons2.prof')
@@ -70,9 +70,9 @@ stats.strip_dirs().print_stats()
 s = sys.stdout.getvalue()
 
 test.fail_test(string.find(s, '__init__.py') == -1)
-test.fail_test(string.find(s, 'option_v') == -1)
+test.fail_test(string.find(s, 'print_version') == -1)
 test.fail_test(string.find(s, 'SCons.Script.main()') == -1)
-test.fail_test(string.find(s, 'getopt.py') == -1)
+test.fail_test(string.find(s, 'option_parser.py') == -1)
  
 
 test.pass_test()
index 496a7e02ceac0f8030bef5c297b1f672bfca38ea..7ddad944c4e18390d147c4d0055814c498bb7572 100644 (file)
@@ -29,21 +29,21 @@ import TestSCons
 import string
 import sys
 
-test = TestSCons.TestSCons(match = TestCmd.match_re)
+test = TestSCons.TestSCons()
 
 test.write('SConstruct', "")
 
 test.run(arguments = '-Z',
-         stderr = """
-SCons error: option -Z not recognized
-File "\S+", line \d+, in short_has_arg
+         stderr = """usage: scons [OPTION] [TARGET] ...
+
+SCons error: no such option: -Z
 """,
          status = 2)
 
 test.run(arguments = '--ZizzerZazzerZuzz',
-         stderr = """
-SCons error: option --ZizzerZazzerZuzz not recognized
-File "\S+", line \d+, in long_has_args
+         stderr = """usage: scons [OPTION] [TARGET] ...
+
+SCons error: no such option: --ZizzerZazzerZuzz
 """,
          status = 2)