Bug 792135 - Part 1: Add which Python package; r=glandium
authorGregory Szorc <gps@mozilla.com>
Thu, 20 Sep 2012 20:54:46 -0700
changeset 107708 88a1e8e2df95185d41e414ff6b95b61e07105cc0
parent 107707 f33e3b2c9cac0d69a699ab2d73725f796f2e89ee
child 107709 9b81d80f94920413226a3b6106426c30b9c46d5b
push id23507
push userryanvm@gmail.com
push dateSat, 22 Sep 2012 02:38:57 +0000
treeherdermozilla-central@8f359a9d1f19 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs792135
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 792135 - Part 1: Add which Python package; r=glandium Version 1.1.0 obtained from https://code.google.com/p/which/ and added to tree without modifications aside from the removal of which.exe, which has no reason to be in the tree.
python/which/LICENSE.txt
python/which/MANIFEST.in
python/which/Makefile.win
python/which/PKG-INFO
python/which/README.txt
python/which/TODO.txt
python/which/build.py
python/which/launcher.cpp
python/which/logo.jpg
python/which/setup.py
python/which/test/test_which.py
python/which/test/testsupport.py
python/which/which.py
new file mode 100644
--- /dev/null
+++ b/python/which/LICENSE.txt
@@ -0,0 +1,21 @@
+Copyright (c) 2002-2005 ActiveState Corp.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
new file mode 100644
--- /dev/null
+++ b/python/which/MANIFEST.in
@@ -0,0 +1,3 @@
+include *.py *.cpp *.in which.exe Makefile* *.txt logo.jpg
+exclude *~
+recursive-include test *.txt *.py
new file mode 100644
--- /dev/null
+++ b/python/which/Makefile.win
@@ -0,0 +1,21 @@
+# Copyright (c) 2002-2003 ActiveState Corp.
+# Author: Trent Mick (TrentM@ActiveState.com)
+#
+# A Makefile to do this: launcher.cpp -> foo.exe
+
+APPNAME=which
+
+# for release:
+CFLAGS=-D_CONSOLE -D_MBCS -DWIN32 -W3 -Ox -DNDEBUG -D_NDEBUG -MD 
+LDFLAGS=/subsystem:console kernel32.lib user32.lib gdi32.lib advapi32.lib shlwapi.lib
+# for debug:
+#  CFLAGS = -D_CONSOLE -D_MBCS /DWIN32 /Zi /Od /DDEBUG /D_DEBUG /MDd
+#  LDFLAGS += /DEBUG
+
+$(APPNAME).exe: launcher.cpp
+	cl -nologo $(CFLAGS) -c launcher.cpp
+	link -nologo $(LDFLAGS) launcher.obj -out:$(APPNAME).exe
+
+clean:
+	if exist launcher.obj; del launcher.obj
+    if exist $(APPNAME).exe; del $(APPNAME).exe
new file mode 100644
--- /dev/null
+++ b/python/which/PKG-INFO
@@ -0,0 +1,21 @@
+Metadata-Version: 1.0
+Name: which
+Version: 1.1.0
+Summary: a portable GNU which replacement
+Home-page: http://trentm.com/projects/which/
+Author: Trent Mick
+Author-email: TrentM@ActiveState.com
+License: MIT License
+Description: This is a GNU which replacement with the following features:
+        - it is portable (Windows, Linux);
+        - it understands PATHEXT on Windows;
+        - it can print <em>all</em> matches on the PATH;
+        - it can note "near misses" on the PATH (e.g. files that match but
+        may not, say, have execute permissions; and
+        - it can be used as a Python module.
+        
+Keywords: which,find,path,where
+Platform: Windows
+Platform: Linux
+Platform: Mac OS X
+Platform: Unix
new file mode 100644
--- /dev/null
+++ b/python/which/README.txt
@@ -0,0 +1,229 @@
+which.py -- a portable GNU which replacement
+============================================
+
+Download the latest which.py packages from here:
+    (source) http://trentm.com/downloads/which/1.1.0/which-1.1.0.zip
+
+
+Home            : http://trentm.com/projects/which/
+License         : MIT (see LICENSE.txt)
+Platforms       : Windows, Linux, Mac OS X, Unix
+Current Version : 1.1
+Dev Status      : mature, has been heavily used in a commercial product for
+                  over 2 years
+Requirements    : Python >= 2.3 (http://www.activestate.com/ActivePython/)
+
+
+What's new?
+-----------
+
+I have moved hosting of `which.py` from my old [Starship
+pages](http://starship.python.net/~tmick/) to this site. These starter
+docs have been improved a little bit. See the [Change Log](#changelog)
+below for more.
+
+**WARNING**: If you are upgrading your `which.py` and you also use my
+[process.py](../process/) module, you must upgrade `process.py` as well
+because of the `_version_/__version__` change in v1.1.0.
+
+
+Why which.py?
+-------------
+
+`which.py` is a small GNU-which replacement. It has the following
+features:
+
+- it is portable (Windows, Linux, Mac OS X, Un*x); 
+- it understands PATHEXT and "App Paths" registration on Windows 
+  (i.e. it will find everything that `start` does from the command shell); 
+- it can print all matches on the PATH; 
+- it can note "near misses" on the PATH (e.g. files that match but may
+  not, say, have execute permissions); and 
+- it can be used as a Python module. 
+
+I also would be happy to have this be a replacement for the `which.py` in the
+Python CVS tree at `dist/src/Tools/scripts/which.py` which is
+Unix-specific and not usable as a module; and perhaps for inclusion in
+the stdlib.
+
+Please send any feedback to [Trent Mick](mailto:TrentM@ActiveState.com).
+
+
+Install Notes
+-------------
+
+Download the latest `which.py` source package, unzip it, and run
+`python setup.py install`:
+
+    unzip which-1.1.0.zip
+    cd which-1.1.0
+    python setup.py install
+
+If your install fails then please visit [the Troubleshooting
+FAQ](http://trentm.com/faq.html#troubleshooting-python-package-installation).
+
+`which.py` can be used both as a module and as a script. By default,
+`which.py` will be installed into your Python's `site-packages`
+directory so it can be used as a module. On *Windows only*, `which.py`
+(and the launcher stub `which.exe`) will be installed in the Python
+install dir to (hopefully) put `which` on your PATH.
+
+On Un*x platforms (including Linux and Mac OS X) there is often a
+`which` executable already on your PATH. To use this `which` instead of
+your system's on those platforms you can manually do one of the
+following:
+
+- Copy `which.py` to `which` somewhere on your PATH ahead of the system
+  `which`. This can be a symlink, as well:
+
+        ln -s /PATH/TO/site-packages/which.py /usr/local/bin/which
+
+- Python 2.4 users might want to use Python's new '-m' switch and setup
+  and alias:
+  
+        alias which='python -m which'
+  
+  or stub script like this:
+
+        #!/bin/sh
+        python -m which $@
+
+
+Getting Started
+---------------
+
+Currently the best intro to using `which.py` as a module is its module
+documentation.  Either install `which.py` and run:
+
+    pydoc which
+    
+take a look at `which.py` in your editor or [here](which.py), or read
+on. Most commonly you'll use the `which()` method to find an
+executable:
+
+    >>> import which
+    >>> which.which("perl")
+    '/usr/local/bin/perl'
+
+Or you might want to know if you have multiple versions on your path:
+
+    >>> which.whichall("perl")
+    ['/usr/local/bin/perl', '/usr/bin/perl']
+
+Use `verbose` to see where your executable is being found. (On Windows
+this might not always be so obvious as your PATH environment variable.
+There is an "App Paths" area of the registry where the `start` command
+will find "registered" executables -- `which.py` mimics this.)
+
+    >>> which.whichall("perl", verbose=True)
+    [('/usr/local/bin/perl', 'from PATH element 10'), 
+     ('/usr/bin/perl', 'from PATH element 15')]
+
+You can restrict the searched path:
+
+    >>> which.whichall("perl", path=["/usr/bin"])
+    ['/usr/bin/perl']
+
+There is a generator interface:
+
+    >>> for perl in which.whichgen("perl"):
+    ...     print "found a perl here:", perl
+    ... 
+    found a perl here: /usr/local/bin/perl
+    found a perl here: /usr/bin/perl
+
+An exception is raised if your executable is not found:
+
+    >>> which.which("fuzzywuzzy")
+    Traceback (most recent call last):
+      ...
+    which.WhichError: Could not find 'fuzzywuzzy' on the path.
+    >>> 
+
+There are some other options too:
+    
+    >>> help(which.which)
+    ...
+
+Run `which --help` to see command-line usage:
+
+    $ which --help
+    Show the full path of commands.
+
+    Usage:
+        which [<options>...] [<command-name>...]
+
+    Options:
+        -h, --help      Print this help and exit.
+        -V, --version   Print the version info and exit.
+
+        -a, --all       Print *all* matching paths.
+        -v, --verbose   Print out how matches were located and
+                        show near misses on stderr.
+        -q, --quiet     Just print out matches. I.e., do not print out
+                        near misses.
+
+        -p <altpath>, --path=<altpath>
+                        An alternative path (list of directories) may
+                        be specified for searching.
+        -e <exts>, --exts=<exts>
+                        Specify a list of extensions to consider instead
+                        of the usual list (';'-separate list, Windows
+                        only).
+
+    Show the full path to the program that would be run for each given
+    command name, if any. Which, like GNU's which, returns the number of
+    failed arguments, or -1 when no <command-name> was given.
+
+    Near misses include duplicates, non-regular files and (on Un*x)
+    files without executable access.
+
+
+Change Log
+----------
+
+### v1.1.0
+- Change version attributes and semantics. Before: had a _version_
+  tuple. After: __version__ is a string, __version_info__ is a tuple.
+
+### v1.0.3
+- Move hosting of which.py to trentm.com. Tweaks to associated bits
+  (README.txt, etc.)
+
+### v1.0.2:
+- Rename mainline handler function from _main() to main(). I can
+  conceive of it being called from externally.
+
+### v1.0.1:
+- Add an optimization for Windows to allow the optional
+  specification of a list of exts to consider when searching the
+  path.
+
+### v1.0.0:
+- Simpler interface: What was which() is now called whichgen() -- it
+  is a generator of matches. The simpler which() and whichall()
+  non-generator interfaces were added.
+
+### v0.8.1:
+- API change: 0.8.0's API change making "verbose" output the default
+  was a mistake -- it breaks backward compatibility for existing
+  uses of which in scripts. This makes verbose, once again, optional
+  but NOT the default.
+
+### v0.8.0:
+- bug fix: "App Paths" lookup had been crippled in 0.7.0. Restore that.
+- feature/module API change: Now print out (and return for the module
+  interface) from where a match was found, e.g. "(from PATH element 3)".
+  The module interfaces now returns (match, from-where) tuples.
+- bug fix: --path argument was broken (-p shortform was fine)
+
+### v0.7.0:
+- bug fix: Handle "App Paths" registered executable that does not
+  exist.
+- feature: Allow an alternate PATH to be specified via 'path'
+  optional argument to which.which() and via -p|--path command line
+  option.
+
+### v0.6.1:
+- first public release
+
new file mode 100644
--- /dev/null
+++ b/python/which/TODO.txt
@@ -0,0 +1,113 @@
+# High Priority
+
+- Figure out the script story on the various platforms. On Windows, look into
+  the launcher thing that effbot has. Unix, don't install the script my
+  default. They can always do "python -m which ..." with Python >= 2.4.
+  Suggest an alias that some folks might want to use for that.
+
+
+# Medium Priority
+
+- define __all__?
+- improve test suite
+- test with other versions of Python
+- get the PATHEXT attached extension to reflect the actual canonical
+  case of file matches on Windows, currently the extension from PATHEXT
+  is always uppercase
+- What to do with Change 145624 by shanec. It is a bit of a
+  bastardization. Maybe allow this with a special option to allow the change
+  in semantics.
+
+    > Change 145624 by shanec@shanec-ocelotl on 2005/05/24 16:51:55
+    > 
+    >     make which work better on OSX
+    >     - add support for searching /Applications and /Network/Applications
+    >     - add support for .app bundles
+    > 
+    > Affected files ...
+    > 
+    > ... //depot/main/Apps/Komodo-devel/src/python-sitelib/which.py#7 edit
+    > 
+    > Differences ...
+    > 
+    > ==== //depot/main/Apps/Komodo-devel/src/python-sitelib/which.py#7 (text) ====
+    > 
+    > @@ -126,10 +126,11 @@
+    >                  sys.stderr.write("duplicate: %s (%s)\n" % potential)
+    >              return None
+    >      else:
+    > -        if not stat.S_ISREG(os.stat(potential[0]).st_mode):
+    > +        darwinApp = sys.platform == 'darwin' and potential[0][-4:]=='.app'
+    > +        if not darwinApp and not stat.S_ISREG(os.stat(potential[0]).st_mode):
+    >              if verbose:
+    >                  sys.stderr.write("not a regular file: %s (%s)\n" % potential)
+    > -        elif not os.access(potential[0], os.X_OK):
+    > +        elif not darwinApp and not os.access(potential[0], os.X_OK):
+    >              if verbose:
+    >                  sys.stderr.write("no executable access: %s (%s)\n"\
+    >                                   % potential)
+    > @@ -166,6 +167,9 @@
+    >          path = os.environ.get("PATH", "").split(os.pathsep)
+    >          if sys.platform.startswith("win"):
+    >              path.insert(0, os.curdir)  # implied by Windows shell
+    > +        if sys.platform == 'darwin':
+    > +            path.insert(0, '/Network/Applications')
+    > +            path.insert(0, '/Applications')
+    >      else:
+    >          usingGivenPath = 1
+    >  
+    > @@ -182,6 +186,9 @@
+    >                  exts = ['.COM', '.EXE', '.BAT']
+    >          elif not isinstance(exts, list):
+    >              raise TypeError("'exts' argument must be a list or None")
+    > +    elif sys.platform == 'darwin':
+    > +        if exts is None:
+    > +            exts = ['.app']
+    >      else:
+    >          if exts is not None:
+    >              raise WhichError("'exts' argument is not supported on "\
+    > @@ -202,7 +209,8 @@
+    >              for ext in ['']+exts:
+    >                  absName = os.path.abspath(
+    >                      os.path.normpath(os.path.join(dirName, command+ext)))
+    > -                if os.path.isfile(absName):
+    > +                if os.path.isfile(absName) or (sys.platform == 'darwin' and \
+    > +                    absName[-4:]=='.app' and os.path.isdir(absName)):
+    >                      if usingGivenPath:
+    >                          fromWhere = "from given path element %d" % i
+    >                      elif not sys.platform.startswith("win"):
+
+  Here is a start with slight improvements:
+
+    > Index: which.py
+    > ===================================================================
+    > --- which.py	(revision 270)
+    > +++ which.py	(working copy)
+    > @@ -126,9 +126,18 @@
+    >                  sys.stderr.write("duplicate: %s (%s)\n" % potential)
+    >              return None
+    >      else:
+    > -        if not stat.S_ISREG(os.stat(potential[0]).st_mode):
+    > +        st_mode = os.stat(potential[0]).st_mode
+    > +        isMacAppBundle = sys.platform == "darwin" \
+    > +                         and potential[0].endswith(".app") \
+    > +                         and stat.S_ISDIR(st_mode)
+    > +        if not isMacAppBundle and not stat.S_ISREG(st_mode):
+    >              if verbose:
+    > -                sys.stderr.write("not a regular file: %s (%s)\n" % potential)
+    > +                if sys.platform == "darwin":
+    > +                    sys.stderr.write("not a regular file or .app bundle: "
+    > +                                     "%s (%s)\n" % potential)
+    > +                else:
+    > +                    sys.stderr.write("not a regular file: %s (%s)\n"
+    > +                                     % potential)
+    >          elif not os.access(potential[0], os.X_OK):
+    >              if verbose:
+    >                  sys.stderr.write("no executable access: %s (%s)\n"\
+
+
+# Low Priority
+
+- have a version for pre-generators (i.e. Python 2.1)
+- add a "logging" interface
+
new file mode 100644
--- /dev/null
+++ b/python/which/build.py
@@ -0,0 +1,442 @@
+#!/usr/bin/env python
+# Copyright (c) 2002-2005 ActiveState
+# See LICENSE.txt for license details.
+
+"""
+    which.py dev build script
+
+    Usage:
+        python build.py [<options>...] [<targets>...]
+
+    Options:
+        --help, -h      Print this help and exit.
+        --targets, -t   List all available targets.
+
+    This is the primary build script for the which.py project. It exists
+    to assist in building, maintaining, and distributing this project.
+    
+    It is intended to have Makefile semantics. I.e. 'python build.py'
+    will build execute the default target, 'python build.py foo' will
+    build target foo, etc. However, there is no intelligent target
+    interdependency tracking (I suppose I could do that with function
+    attributes).
+"""
+
+import os
+from os.path import basename, dirname, splitext, isfile, isdir, exists, \
+                    join, abspath, normpath
+import sys
+import getopt
+import types
+import getpass
+import shutil
+import glob
+import logging
+import re
+
+
+
+#---- exceptions
+
+class Error(Exception):
+    pass
+
+
+
+#---- globals
+
+log = logging.getLogger("build")
+
+
+
+
+#---- globals
+
+_project_name_ = "which"
+
+
+
+#---- internal support routines
+
+def _get_trentm_com_dir():
+    """Return the path to the local trentm.com source tree."""
+    d = normpath(join(dirname(__file__), os.pardir, "trentm.com"))
+    if not isdir(d):
+        raise Error("could not find 'trentm.com' src dir at '%s'" % d)
+    return d
+
+def _get_local_bits_dir():
+    import imp
+    info = imp.find_module("tmconfig", [_get_trentm_com_dir()])
+    tmconfig = imp.load_module("tmconfig", *info)
+    return tmconfig.bitsDir
+
+def _get_project_bits_dir():
+    d = normpath(join(dirname(__file__), "bits"))
+    return d
+
+def _get_project_version():
+    import imp, os
+    data = imp.find_module(_project_name_, [os.path.dirname(__file__)])
+    mod = imp.load_module(_project_name_, *data)
+    return mod.__version__
+
+
+# Recipe: run (0.5.1) in /Users/trentm/tm/recipes/cookbook
+_RUN_DEFAULT_LOGSTREAM = ("RUN", "DEFAULT", "LOGSTREAM")
+def __run_log(logstream, msg, *args, **kwargs):
+    if not logstream:
+        pass
+    elif logstream is _RUN_DEFAULT_LOGSTREAM:
+        try:
+            log.debug(msg, *args, **kwargs)
+        except NameError:
+            pass
+    else:
+        logstream(msg, *args, **kwargs)
+
+def _run(cmd, logstream=_RUN_DEFAULT_LOGSTREAM):
+    """Run the given command.
+
+        "cmd" is the command to run
+        "logstream" is an optional logging stream on which to log the command.
+            If None, no logging is done. If unspecifed, this looks for a Logger
+            instance named 'log' and logs the command on log.debug().
+
+    Raises OSError is the command returns a non-zero exit status.
+    """
+    __run_log(logstream, "running '%s'", cmd)
+    retval = os.system(cmd)
+    if hasattr(os, "WEXITSTATUS"):
+        status = os.WEXITSTATUS(retval)
+    else:
+        status = retval
+    if status:
+        #TODO: add std OSError attributes or pick more approp. exception
+        raise OSError("error running '%s': %r" % (cmd, status))
+
+def _run_in_dir(cmd, cwd, logstream=_RUN_DEFAULT_LOGSTREAM):
+    old_dir = os.getcwd()
+    try:
+        os.chdir(cwd)
+        __run_log(logstream, "running '%s' in '%s'", cmd, cwd)
+        _run(cmd, logstream=None)
+    finally:
+        os.chdir(old_dir)
+
+
+# Recipe: rmtree (0.5) in /Users/trentm/tm/recipes/cookbook
+def _rmtree_OnError(rmFunction, filePath, excInfo):
+    if excInfo[0] == OSError:
+        # presuming because file is read-only
+        os.chmod(filePath, 0777)
+        rmFunction(filePath)
+def _rmtree(dirname):
+    import shutil
+    shutil.rmtree(dirname, 0, _rmtree_OnError)
+
+
+# Recipe: pretty_logging (0.1) in /Users/trentm/tm/recipes/cookbook
+class _PerLevelFormatter(logging.Formatter):
+    """Allow multiple format string -- depending on the log level.
+    
+    A "fmtFromLevel" optional arg is added to the constructor. It can be
+    a dictionary mapping a log record level to a format string. The
+    usual "fmt" argument acts as the default.
+    """
+    def __init__(self, fmt=None, datefmt=None, fmtFromLevel=None):
+        logging.Formatter.__init__(self, fmt, datefmt)
+        if fmtFromLevel is None:
+            self.fmtFromLevel = {}
+        else:
+            self.fmtFromLevel = fmtFromLevel
+    def format(self, record):
+        record.levelname = record.levelname.lower()
+        if record.levelno in self.fmtFromLevel:
+            #XXX This is a non-threadsafe HACK. Really the base Formatter
+            #    class should provide a hook accessor for the _fmt
+            #    attribute. *Could* add a lock guard here (overkill?).
+            _saved_fmt = self._fmt
+            self._fmt = self.fmtFromLevel[record.levelno]
+            try:
+                return logging.Formatter.format(self, record)
+            finally:
+                self._fmt = _saved_fmt
+        else:
+            return logging.Formatter.format(self, record)
+
+def _setup_logging():
+    hdlr = logging.StreamHandler()
+    defaultFmt = "%(name)s: %(levelname)s: %(message)s"
+    infoFmt = "%(name)s: %(message)s"
+    fmtr = _PerLevelFormatter(fmt=defaultFmt,
+                              fmtFromLevel={logging.INFO: infoFmt})
+    hdlr.setFormatter(fmtr)
+    logging.root.addHandler(hdlr)
+    log.setLevel(logging.INFO)
+
+
+def _getTargets():
+    """Find all targets and return a dict of targetName:targetFunc items."""
+    targets = {}
+    for name, attr in sys.modules[__name__].__dict__.items():
+        if name.startswith('target_'):
+            targets[ name[len('target_'):] ] = attr
+    return targets
+
+def _listTargets(targets):
+    """Pretty print a list of targets."""
+    width = 77
+    nameWidth = 15 # min width
+    for name in targets.keys():
+        nameWidth = max(nameWidth, len(name))
+    nameWidth += 2  # space btwn name and doc
+    format = "%%-%ds%%s" % nameWidth
+    print format % ("TARGET", "DESCRIPTION")
+    for name, func in sorted(targets.items()):
+        doc = _first_paragraph(func.__doc__ or "", True)
+        if len(doc) > (width - nameWidth):
+            doc = doc[:(width-nameWidth-3)] + "..."
+        print format % (name, doc)
+
+
+# Recipe: first_paragraph (1.0.1) in /Users/trentm/tm/recipes/cookbook
+def _first_paragraph(text, join_lines=False):
+    """Return the first paragraph of the given text."""
+    para = text.lstrip().split('\n\n', 1)[0]
+    if join_lines:
+        lines = [line.strip() for line in  para.splitlines(0)]
+        para = ' '.join(lines)
+    return para
+
+
+
+#---- build targets
+
+def target_default():
+    target_all()
+
+def target_all():
+    """Build all release packages."""
+    log.info("target: default")
+    if sys.platform == "win32":
+        target_launcher()
+    target_sdist()
+    target_webdist()
+
+
+def target_clean():
+    """remove all build/generated bits"""
+    log.info("target: clean")
+    if sys.platform == "win32":
+        _run("nmake -f Makefile.win clean")
+
+    ver = _get_project_version()
+    dirs = ["dist", "build", "%s-%s" % (_project_name_, ver)]
+    for d in dirs:
+        print "removing '%s'" % d
+        if os.path.isdir(d): _rmtree(d)
+
+    patterns = ["*.pyc", "*~", "MANIFEST",
+                os.path.join("test", "*~"),
+                os.path.join("test", "*.pyc"),
+               ]
+    for pattern in patterns:
+        for file in glob.glob(pattern):
+            print "removing '%s'" % file
+            os.unlink(file)
+
+
+def target_launcher():
+    """Build the Windows launcher executable."""
+    log.info("target: launcher")
+    assert sys.platform == "win32", "'launcher' target only supported on Windows"
+    _run("nmake -f Makefile.win")
+
+
+def target_docs():
+    """Regenerate some doc bits from project-info.xml."""
+    log.info("target: docs")
+    _run("projinfo -f project-info.xml -R -o README.txt --force")
+    _run("projinfo -f project-info.xml --index-markdown -o index.markdown --force")
+
+
+def target_sdist():
+    """Build a source distribution."""
+    log.info("target: sdist")
+    target_docs()
+    bitsDir = _get_project_bits_dir()
+    _run("python setup.py sdist -f --formats zip -d %s" % bitsDir,
+         log.info)
+
+
+def target_webdist():
+    """Build a web dist package.
+    
+    "Web dist" packages are zip files with '.web' package. All files in
+    the zip must be under a dir named after the project. There must be a
+    webinfo.xml file at <projname>/webinfo.xml. This file is "defined"
+    by the parsing in trentm.com/build.py.
+    """ 
+    assert sys.platform != "win32", "'webdist' not implemented for win32"
+    log.info("target: webdist")
+    bitsDir = _get_project_bits_dir()
+    buildDir = join("build", "webdist")
+    distDir = join(buildDir, _project_name_)
+    if exists(buildDir):
+        _rmtree(buildDir)
+    os.makedirs(distDir)
+
+    target_docs()
+    
+    # Copy the webdist bits to the build tree.
+    manifest = [
+        "project-info.xml",
+        "index.markdown",
+        "LICENSE.txt",
+        "which.py",
+        "logo.jpg",
+    ]
+    for src in manifest:
+        if dirname(src):
+            dst = join(distDir, dirname(src))
+            os.makedirs(dst)
+        else:
+            dst = distDir
+        _run("cp %s %s" % (src, dst))
+
+    # Zip up the webdist contents.
+    ver = _get_project_version()
+    bit = abspath(join(bitsDir, "%s-%s.web" % (_project_name_, ver)))
+    if exists(bit):
+        os.remove(bit)
+    _run_in_dir("zip -r %s %s" % (bit, _project_name_), buildDir, log.info)
+
+
+def target_install():
+    """Use the setup.py script to install."""
+    log.info("target: install")
+    _run("python setup.py install")
+
+
+def target_upload_local():
+    """Update release bits to *local* trentm.com bits-dir location.
+    
+    This is different from the "upload" target, which uploads release
+    bits remotely to trentm.com.
+    """
+    log.info("target: upload_local")
+    assert sys.platform != "win32", "'upload_local' not implemented for win32"
+
+    ver = _get_project_version()
+    localBitsDir = _get_local_bits_dir()
+    uploadDir = join(localBitsDir, _project_name_, ver)
+
+    bitsPattern = join(_get_project_bits_dir(),
+                       "%s-*%s*" % (_project_name_, ver))
+    bits = glob.glob(bitsPattern)
+    if not bits:
+        log.info("no bits matching '%s' to upload", bitsPattern)
+    else:
+        if not exists(uploadDir):
+            os.makedirs(uploadDir)
+        for bit in bits:
+            _run("cp %s %s" % (bit, uploadDir), log.info)
+
+
+def target_upload():
+    """Upload binary and source distribution to trentm.com bits
+    directory.
+    """
+    log.info("target: upload")
+
+    ver = _get_project_version()
+    bitsDir = _get_project_bits_dir()
+    bitsPattern = join(bitsDir, "%s-*%s*" % (_project_name_, ver))
+    bits = glob.glob(bitsPattern)
+    if not bits:
+        log.info("no bits matching '%s' to upload", bitsPattern)
+        return
+
+    # Ensure have all the expected bits.
+    expectedBits = [
+        re.compile("%s-.*\.zip$" % _project_name_),
+        re.compile("%s-.*\.web$" % _project_name_)
+    ]
+    for expectedBit in expectedBits:
+        for bit in bits:
+            if expectedBit.search(bit):
+                break
+        else:
+            raise Error("can't find expected bit matching '%s' in '%s' dir"
+                        % (expectedBit.pattern, bitsDir))
+
+    # Upload the bits.
+    user = "trentm"
+    host = "trentm.com"
+    remoteBitsBaseDir = "~/data/bits"
+    remoteBitsDir = join(remoteBitsBaseDir, _project_name_, ver)
+    if sys.platform == "win32":
+        ssh = "plink"
+        scp = "pscp -unsafe"
+    else:
+        ssh = "ssh"
+        scp = "scp"
+    _run("%s %s@%s 'mkdir -p %s'" % (ssh, user, host, remoteBitsDir), log.info)
+    for bit in bits:
+        _run("%s %s %s@%s:%s" % (scp, bit, user, host, remoteBitsDir),
+             log.info)
+
+
+def target_check_version():
+    """grep for version strings in source code
+    
+    List all things that look like version strings in the source code.
+    Used for checking that versioning is updated across the board.  
+    """
+    sources = [
+        "which.py",
+        "project-info.xml",
+    ]
+    pattern = r'[0-9]\+\(\.\|, \)[0-9]\+\(\.\|, \)[0-9]\+'
+    _run('grep -n "%s" %s' % (pattern, ' '.join(sources)), None)
+
+
+
+#---- mainline
+
+def build(targets=[]):
+    log.debug("build(targets=%r)" % targets)
+    available = _getTargets()
+    if not targets:
+        if available.has_key('default'):
+            return available['default']()
+        else:   
+            log.warn("No default target available. Doing nothing.")
+    else:
+        for target in targets:
+            if available.has_key(target):
+                retval = available[target]()
+                if retval:
+                    raise Error("Error running '%s' target: retval=%s"\
+                                % (target, retval))
+            else:
+                raise Error("Unknown target: '%s'" % target)
+
+def main(argv):
+    _setup_logging()
+
+    # Process options.
+    optlist, targets = getopt.getopt(argv[1:], 'ht', ['help', 'targets'])
+    for opt, optarg in optlist:
+        if opt in ('-h', '--help'):
+            sys.stdout.write(__doc__ + '\n')
+            return 0
+        elif opt in ('-t', '--targets'):
+            return _listTargets(_getTargets())
+
+    return build(targets)
+
+if __name__ == "__main__":
+    sys.exit( main(sys.argv) )
+
new file mode 100644
--- /dev/null
+++ b/python/which/launcher.cpp
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2002-2003 ActiveState Corp.
+ * Author: Trent Mick (TrentM@ActiveState.com)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* Console launch executable.  
+ * 
+ * This program exists solely to launch:
+ * 		python <installdir>/<exename>.py <argv>
+ * on Windows. "<exename>.py" must be in the same directory.
+ *
+ * Rationale:
+ *    - On some Windows flavours .py *can* be put on the PATHEXT to be
+ *      able to find "<exename>.py" if it is on the PATH. This is fine
+ *      until you need shell redirection to work. It does NOT for
+ *      extensions to PATHEXT.  Redirection *does* work for "python
+ *      <script>.py" so we will try to do that.
+ */
+
+#ifdef WIN32
+    #include <windows.h>
+    #include <process.h>
+    #include <direct.h>
+    #include <shlwapi.h>
+#else /* linux */
+    #include <unistd.h>
+#endif /* WIN32 */
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+//---- constants
+
+#define BUF_LENGTH 2048
+#define MAX_PYTHON_ARGS 50
+#define MAX_FILES 50
+#define MAXPATHLEN 1024
+#ifdef WIN32
+    #define SEP '\\'
+    #define ALTSEP '/'
+    // path list element separator
+    #define DELIM ';'
+#else /* linux */
+    #define SEP '/'
+    // path list element separator
+    #define DELIM ':'
+#endif
+
+#ifdef WIN32
+    #define spawnvp _spawnvp
+    #define snprintf _snprintf
+    #define vsnprintf _vsnprintf
+    //NOTE: this is for the stat *call* and the stat *struct*
+    #define stat _stat
+#endif
+
+
+//---- globals
+
+char* programName = NULL;
+char* programPath = NULL;
+#ifndef WIN32 /* i.e. linux */
+    extern char **environ;   // the user environment
+#endif /* linux */
+
+//---- error logging functions
+
+void _LogError(const char* format ...)
+{
+    va_list ap;
+    va_start(ap, format);
+#if defined(WIN32) && defined(_WINDOWS)
+    // put up a MessageBox
+    char caption[BUF_LENGTH+1];
+    snprintf(caption, BUF_LENGTH, "Error in %s", programName);
+    char msg[BUF_LENGTH+1];
+    vsnprintf(msg, BUF_LENGTH, format, ap);
+    va_end(ap);
+    MessageBox(NULL, msg, caption, MB_OK | MB_ICONEXCLAMATION);
+#else
+    fprintf(stderr, "%s: error: ", programName);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+#endif /* WIN32 && _WINDOWS */
+}
+
+
+void _LogWarning(const char* format ...)
+{
+    va_list ap;
+    va_start(ap, format);
+#if defined(WIN32) && defined(_WINDOWS)
+    // put up a MessageBox
+    char caption[BUF_LENGTH+1];
+    snprintf(caption, BUF_LENGTH, "Warning in %s", programName);
+    char msg[BUF_LENGTH+1];
+    vsnprintf(msg, BUF_LENGTH, format, ap);
+    va_end(ap);
+    MessageBox(NULL, msg, caption, MB_OK | MB_ICONWARNING);
+#else
+    fprintf(stderr, "%s: warning: ", programName);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+#endif /* WIN32 && _WINDOWS */
+}
+
+
+
+//---- utilities functions
+
+/* _IsDir: Is the given dirname an existing directory */
+static int _IsDir(char *dirname)
+{
+#ifdef WIN32
+    DWORD dwAttrib;
+    dwAttrib = GetFileAttributes(dirname);
+    if (dwAttrib == -1) {
+        return 0;
+    }
+    if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
+        return 1;
+    }
+    return 0;
+#else /* i.e. linux */
+    struct stat buf;
+    if (stat(dirname, &buf) != 0)
+        return 0;
+    if (!S_ISDIR(buf.st_mode))
+        return 0;
+    return 1;
+#endif
+}
+
+
+/* _IsLink: Is the given filename a symbolic link */
+static int _IsLink(char *filename)
+{
+#ifdef WIN32
+    return 0;
+#else /* i.e. linux */
+    struct stat buf;
+    if (lstat(filename, &buf) != 0)
+        return 0;
+    if (!S_ISLNK(buf.st_mode))
+        return 0;
+    return 1;
+#endif
+}
+
+
+/* Is executable file
+ * On Linux: check 'x' permission. On Windows: just check existence.
+ */
+static int _IsExecutableFile(char *filename)
+{
+#ifdef WIN32
+    return (int)PathFileExists(filename);
+#else /* i.e. linux */
+    struct stat buf;
+    if (stat(filename, &buf) != 0)
+        return 0;
+    if (!S_ISREG(buf.st_mode))
+        return 0;
+    if ((buf.st_mode & 0111) == 0)
+        return 0;
+    return 1;
+#endif /* WIN32 */
+}
+
+
+/* _GetProgramPath: Determine the absolute path to the given program name.
+ *
+ *      Takes into account the current working directory, etc.
+ *      The implementations require the global 'programName' to be set.
+ */
+#ifdef WIN32
+    static char* _GetProgramPath(void)
+    {
+        //XXX this is ugly but I didn't want to use malloc, no reason
+        static char progPath[MAXPATHLEN+1];
+        // get absolute path to module
+        if (!GetModuleFileName(NULL, progPath, MAXPATHLEN)) {
+            _LogError("could not get absolute program name from "\
+                "GetModuleFileName\n");
+            exit(1);
+        }
+        // just need dirname
+        for (char* p = progPath+strlen(progPath);
+             *p != SEP && *p != ALTSEP;
+             --p)
+        {
+            *p = '\0';
+        }
+        *p = '\0';  // remove the trailing SEP as well
+
+        return progPath;
+    }
+#else
+
+    /* _JoinPath requires that any buffer argument passed to it has at
+       least MAXPATHLEN + 1 bytes allocated.  If this requirement is met,
+       it guarantees that it will never overflow the buffer.  If stuff
+       is too long, buffer will contain a truncated copy of stuff.
+    */
+    static void
+    _JoinPath(char *buffer, char *stuff)
+    {
+        size_t n, k;
+        if (stuff[0] == SEP)
+            n = 0;
+        else {
+            n = strlen(buffer);
+            if (n > 0 && buffer[n-1] != SEP && n < MAXPATHLEN)
+                buffer[n++] = SEP;
+        }
+        k = strlen(stuff);
+        if (n + k > MAXPATHLEN)
+            k = MAXPATHLEN - n;
+        strncpy(buffer+n, stuff, k);
+        buffer[n+k] = '\0';
+    }
+
+
+    static char*
+    _GetProgramPath(void)
+    {
+        /* XXX this routine does *no* error checking */
+        char* path = getenv("PATH");
+        static char progPath[MAXPATHLEN+1];
+
+        /* If there is no slash in the argv0 path, then we have to
+         * assume the program is on the user's $PATH, since there's no
+         * other way to find a directory to start the search from.  If
+         * $PATH isn't exported, you lose.
+         */
+        if (strchr(programName, SEP)) {
+            strncpy(progPath, programName, MAXPATHLEN);
+        }
+        else if (path) {
+            int bufspace = MAXPATHLEN;
+            while (1) {
+                char *delim = strchr(path, DELIM);
+
+                if (delim) {
+                    size_t len = delim - path;
+                    if (len > bufspace) {
+                        len = bufspace;
+                    }
+                    strncpy(progPath, path, len);
+                    *(progPath + len) = '\0';
+                    bufspace -= len;
+                }
+                else {
+                    strncpy(progPath, path, bufspace);
+                }
+
+                _JoinPath(progPath, programName);
+                if (_IsExecutableFile(progPath)) {
+                    break;
+                }
+
+                if (!delim) {
+                    progPath[0] = '\0';
+                    break;
+                }
+                path = delim + 1;
+            }
+        }
+        else {
+            progPath[0] = '\0';
+        }
+
+        // now we have to resolve a string of possible symlinks
+        //   - we'll just handle the simple case of a single level of
+        //     indirection
+        //
+        // XXX note this does not handle multiple levels of symlinks
+        //     here is pseudo-code for that (please implement it :):
+        // while 1:
+        //     if islink(progPath):
+        //         linkText = readlink(progPath)
+        //         if isabsolute(linkText):
+        //             progPath = os.path.join(dirname(progPath), linkText)
+        //         else:
+        //             progPath = linkText
+        //     else:
+        //         break
+        if (_IsLink(progPath)) {
+            char newProgPath[MAXPATHLEN+1];
+            readlink(progPath, newProgPath, MAXPATHLEN);
+            strncpy(progPath, newProgPath, MAXPATHLEN);
+        }
+
+        
+        // prefix with the current working directory if the path is
+        // relative to conform with the Windows version of this
+        if (strlen(progPath) != 0 && progPath[0] != SEP) {
+            char cwd[MAXPATHLEN+1];
+            char tmp[MAXPATHLEN+1];
+            //XXX should check for failure retvals
+            getcwd(cwd, MAXPATHLEN);
+            snprintf(tmp, MAXPATHLEN, "%s%c%s", cwd, SEP, progPath);
+            strncpy(progPath, tmp, MAXPATHLEN);
+        }
+        
+        // 'progPath' now contains the full path to the program *and* the program
+        // name. The latter is not desire.
+        char* pLetter = progPath + strlen(progPath);
+        for (;pLetter != progPath && *pLetter != SEP; --pLetter) {
+            /* do nothing */
+        }
+        *pLetter = '\0';
+
+        return progPath;
+    }
+#endif  /* WIN32 */
+
+
+//---- mainline
+
+int main(int argc, char** argv)
+{
+    programName = argv[0];
+    programPath = _GetProgramPath();
+
+    // Determine the extension-less program basename.
+    // XXX Will not always handle app names with '.' in them (other than
+    //     the '.' for the extension.
+    char programNameNoExt[MAXPATHLEN+1];
+    char *pStart, *pEnd;
+    pStart = pEnd = programName + strlen(programName) - 1;
+    while (pStart != programName && *(pStart-1) != SEP) {
+	pStart--;
+    }
+    while (1) {
+	if (pEnd == pStart) {
+            pEnd = programName + strlen(programName) - 1;
+	    break;
+	}
+	pEnd--;
+	if (*(pEnd+1) == '.') {
+	    break;
+	}
+    }
+    strncpy(programNameNoExt, pStart, pEnd-pStart+1);
+    *(programNameNoExt+(pEnd-pStart+1)) = '\0';
+
+    // determine the full path to "<exename>.py"
+    char pyFile[MAXPATHLEN+1];
+    snprintf(pyFile, MAXPATHLEN, "%s%c%s.py", programPath, SEP,
+	     programNameNoExt);
+    
+    // Build the argument array for launching.
+    char* pythonArgs[MAX_PYTHON_ARGS+1];
+    int nPythonArgs = 0;
+    pythonArgs[nPythonArgs++] = "python";
+    pythonArgs[nPythonArgs++] = "-tt";
+    pythonArgs[nPythonArgs++] = pyFile;
+    for (int i = 1; i < argc; ++i) {
+        pythonArgs[nPythonArgs++] = argv[i];
+    }
+    pythonArgs[nPythonArgs++] = NULL;
+
+    return _spawnvp(_P_WAIT, pythonArgs[0], pythonArgs);
+}
+
+
+//---- mainline for win32 subsystem:windows app
+#ifdef WIN32
+    int WINAPI WinMain(
+        HINSTANCE hInstance,      /* handle to current instance */
+        HINSTANCE hPrevInstance,  /* handle to previous instance */
+        LPSTR lpCmdLine,          /* pointer to command line */
+        int nCmdShow              /* show state of window */
+    )
+    {
+        return main(__argc, __argv);
+    }
+#endif
+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..300c23f1464e7337f9ea9071c7be13021ac8ca68
GIT binary patch
literal 3635
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
W>a0EtZPckpjZ^68(tiB$<o^J+#B>+{
new file mode 100644
--- /dev/null
+++ b/python/which/setup.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# Copyright (c) 2002-2005 ActiveState Corp.
+# Author: Trent Mick (TrentM@ActiveState.com)
+
+"""Distutils setup script for 'which'."""
+
+import sys
+import os
+import shutil
+from distutils.core import setup
+
+
+#---- support routines
+
+def _getVersion():
+    import which
+    return which.__version__
+
+def _getBinDir():
+    """Return the current Python's bindir."""
+    if sys.platform.startswith("win"):
+        bindir = sys.prefix
+    else:
+        bindir = os.path.join(sys.prefix, "bin")
+    return bindir
+
+
+#---- setup mainline
+
+if sys.platform == "win32":
+    scripts = []
+    binFiles = ["which.exe", "which.py"]
+else:
+    #XXX Disable installing which as a script on non-Windows platforms.
+    #    It can get in the way of the system which.
+    #
+    #if os.path.exists("which"):
+    #    os.remove("which")
+    #shutil.copy2("which.py", "which")
+    #scripts = ["which"]
+    binFiles = []
+    scripts = []
+
+setup(name="which",
+      version=_getVersion(),
+      description="a portable GNU which replacement",
+      author="Trent Mick",
+      author_email="TrentM@ActiveState.com",
+      url="http://trentm.com/projects/which/",
+      license="MIT License",
+      platforms=["Windows", "Linux", "Mac OS X", "Unix"],
+      long_description="""\
+This is a GNU which replacement with the following features:
+    - it is portable (Windows, Linux);
+    - it understands PATHEXT on Windows;
+    - it can print <em>all</em> matches on the PATH;
+    - it can note "near misses" on the PATH (e.g. files that match but
+      may not, say, have execute permissions; and
+    - it can be used as a Python module.
+""",
+      keywords=["which", "find", "path", "where"],
+
+      py_modules=['which'],
+      scripts=scripts,
+      # Install the Windows script/executable bits as data files with
+      # distutils chosen scripts install dir on Windows,
+      # "<prefix>/Scripts", is just wrong.
+      data_files=[ (_getBinDir(), binFiles) ],
+     )
+
new file mode 100644
--- /dev/null
+++ b/python/which/test/test_which.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+# Copyright (c) 2002-2003 ActiveState Corp.
+# Author: Trent Mick (TrentM@ActiveState.com)
+
+"""Test suite for which.py."""
+
+import sys
+import os
+import re
+import tempfile
+import unittest
+
+import testsupport
+
+#XXX:TODO
+#   - def test_registry_success(self): ...App Paths setting
+#   - def test_registry_noexist(self):
+#   - test all the other options
+#   - test on linux
+#   - test the module API
+
+class WhichTestCase(unittest.TestCase):
+    def setUp(self):
+        """Create a temp directory with a couple test "commands".
+        The temp dir can be added to the PATH, etc, for testing purposes.
+        """
+        # Find the which.py to call.
+        whichPy = os.path.join(os.path.dirname(__file__),
+                               os.pardir, "which.py")
+        self.which = sys.executable + " " + whichPy
+
+        # Setup the test environment.
+        self.tmpdir = tempfile.mktemp()
+        os.makedirs(self.tmpdir)
+        if sys.platform.startswith("win"):
+            self.testapps = ['whichtestapp1.exe',
+                             'whichtestapp2.exe',
+                             'whichtestapp3.wta']
+        else:
+            self.testapps = ['whichtestapp1', 'whichtestapp2']
+        for app in self.testapps:
+            path = os.path.join(self.tmpdir, app)
+            open(path, 'wb').write('\n')
+            os.chmod(path, 0755)
+
+    def tearDown(self):
+        testsupport.rmtree(self.tmpdir)
+
+    def test_opt_h(self):
+        output, error, retval = testsupport.run(self.which+' --h')
+        token = 'Usage:'
+        self.failUnless(output.find(token) != -1,
+                        "'%s' was not found in 'which -h' output: '%s' "\
+                        % (token, output))
+        self.failUnless(retval == 0,
+                        "'which -h' did not return 0: retval=%d" % retval)
+
+    def test_opt_help(self):
+        output, error, retval = testsupport.run(self.which+' --help')
+        token = 'Usage:'
+        self.failUnless(output.find(token) != -1,
+                        "'%s' was not found in 'which --help' output: '%s' "\
+                        % (token, output))
+        self.failUnless(retval == 0,
+                        "'which --help' did not return 0: retval=%d" % retval)
+
+    def test_opt_version(self):
+        output, error, retval = testsupport.run(self.which+' --version')
+        versionRe = re.compile("^which \d+\.\d+\.\d+$")
+        versionMatch = versionRe.search(output.strip())
+        self.failUnless(versionMatch,
+                        "Version, '%s', from 'which --version' does not "\
+                        "match pattern, '%s'."\
+                        % (output.strip(), versionRe.pattern))
+        self.failUnless(retval == 0,
+                        "'which --version' did not return 0: retval=%d"\
+                        % retval)
+
+    def test_no_args(self):
+        output, error, retval = testsupport.run(self.which)
+        self.failUnless(retval == -1,
+                        "'which' with no args should return -1: retval=%d"\
+                        % retval)
+
+    def test_one_failure(self):
+        output, error, retval = testsupport.run(
+            self.which+' whichtestapp1')
+        self.failUnless(retval == 1,
+            "One failure did not return 1: retval=%d" % retval)
+
+    def test_two_failures(self):
+        output, error, retval = testsupport.run(
+            self.which+' whichtestapp1 whichtestapp2')
+        self.failUnless(retval == 2,
+            "Two failures did not return 2: retval=%d" % retval)
+
+    def _match(self, path1, path2):
+        #print "_match: %r =?= %r" % (path1, path2)
+        if sys.platform.startswith('win'):
+            path1 = os.path.normpath(os.path.normcase(path1))
+            path2 = os.path.normpath(os.path.normcase(path2))
+            path1 = os.path.splitext(path1)[0]
+            path2 = os.path.splitext(path2)[0]
+            return path1 == path2
+        else:
+            return os.path.samefile(path1, path2)
+
+    def test_one_success(self):
+        os.environ["PATH"] += os.pathsep + self.tmpdir 
+        output, error, retval = testsupport.run(self.which+' -q whichtestapp1')
+        expectedOutput = os.path.join(self.tmpdir, "whichtestapp1")
+        self.failUnless(self._match(output.strip(), expectedOutput),
+            "Output, %r, and expected output, %r, do not match."\
+            % (output.strip(), expectedOutput))
+        self.failUnless(retval == 0,
+            "'which ...' should have returned 0: retval=%d" % retval)
+
+    def test_two_successes(self):
+        os.environ["PATH"] += os.pathsep + self.tmpdir 
+        apps = ['whichtestapp1', 'whichtestapp2']
+        output, error, retval = testsupport.run(
+            self.which + ' -q ' + ' '.join(apps))
+        lines = output.strip().split("\n")
+        for app, line in zip(apps, lines):
+            expected = os.path.join(self.tmpdir, app)
+            self.failUnless(self._match(line, expected),
+                "Output, %r, and expected output, %r, do not match."\
+                % (line, expected))
+        self.failUnless(retval == 0,
+            "'which ...' should have returned 0: retval=%d" % retval)
+
+    if sys.platform.startswith("win"):
+        def test_PATHEXT_failure(self):
+            os.environ["PATH"] += os.pathsep + self.tmpdir 
+            output, error, retval = testsupport.run(self.which+' whichtestapp3')
+            self.failUnless(retval == 1,
+                "'which ...' should have returned 1: retval=%d" % retval)
+
+        def test_PATHEXT_success(self):
+            os.environ["PATH"] += os.pathsep + self.tmpdir 
+            os.environ["PATHEXT"] += os.pathsep + '.wta'
+            output, error, retval = testsupport.run(self.which+' whichtestapp3')
+            expectedOutput = os.path.join(self.tmpdir, "whichtestapp3")
+            self.failUnless(self._match(output.strip(), expectedOutput),
+                "Output, %r, and expected output, %r, do not match."\
+                % (output.strip(), expectedOutput))
+            self.failUnless(retval == 0,
+                "'which ...' should have returned 0: retval=%d" % retval)
+
+        def test_exts(self):
+            os.environ["PATH"] += os.pathsep + self.tmpdir 
+            output, error, retval = testsupport.run(self.which+' -e .wta whichtestapp3')
+            expectedOutput = os.path.join(self.tmpdir, "whichtestapp3")
+            self.failUnless(self._match(output.strip(), expectedOutput),
+                "Output, %r, and expected output, %r, do not match."\
+                % (output.strip(), expectedOutput))
+            self.failUnless(retval == 0,
+                "'which ...' should have returned 0: retval=%d" % retval)
+
+
+
+def suite():
+    """Return a unittest.TestSuite to be used by test.py."""
+    return unittest.makeSuite(WhichTestCase)
+
+if __name__ == "__main__":
+    unittest.main()
+
new file mode 100644
--- /dev/null
+++ b/python/which/test/testsupport.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# Copyright (c) 2002-2003 ActiveState Corp.
+# Author: Trent Mick (TrentM@ActiveState.com)
+
+import os
+import sys
+import types
+
+
+#---- Support routines
+
+def _escapeArg(arg):
+    """Escape the given command line argument for the shell."""
+    #XXX There is a *lot* more that we should escape here.
+    return arg.replace('"', r'\"')
+
+
+def _joinArgv(argv):
+    r"""Join an arglist to a string appropriate for running.
+        >>> import os
+        >>> _joinArgv(['foo', 'bar "baz'])
+        'foo "bar \\"baz"'
+    """
+    cmdstr = ""
+    for arg in argv:
+        if ' ' in arg:
+            cmdstr += '"%s"' % _escapeArg(arg)
+        else:
+            cmdstr += _escapeArg(arg)
+        cmdstr += ' '
+    if cmdstr.endswith(' '): cmdstr = cmdstr[:-1]  # strip trailing space
+    return cmdstr
+
+
+def run(argv):
+    """Prepare and run the given arg vector, 'argv', and return the
+    results.  Returns (<stdout lines>, <stderr lines>, <return value>).
+    Note: 'argv' may also just be the command string.
+    """
+    if type(argv) in (types.ListType, types.TupleType):
+        cmd = _joinArgv(argv)
+    else:
+        cmd = argv
+    if sys.platform.startswith('win'):
+        i, o, e = os.popen3(cmd)
+        output = o.read()
+        error = e.read()
+        i.close()
+        e.close()
+        try:
+            retval = o.close()
+        except IOError:
+            # IOError is raised iff the spawned app returns -1. Go
+            # figure.
+            retval = -1 
+        if retval is None:
+            retval = 0
+    else:
+        import popen2
+        p = popen2.Popen3(cmd, 1)
+        i, o, e = p.tochild, p.fromchild, p.childerr
+        output = o.read()
+        error = e.read()
+        i.close()
+        o.close()
+        e.close()
+        retval = (p.wait() & 0xFF00) >> 8
+        if retval > 2**7: # 8-bit signed 1's-complement conversion
+            retval -= 2**8
+    return output, error, retval
+
+
+def _rmtreeOnError(rmFunction, filePath, excInfo):
+    if excInfo[0] == OSError:
+        # presuming because file is read-only
+        os.chmod(filePath, 0777)
+        rmFunction(filePath)
+
+def rmtree(dirname):
+    import shutil
+    shutil.rmtree(dirname, 0, _rmtreeOnError)
+
+
new file mode 100644
--- /dev/null
+++ b/python/which/which.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+# Copyright (c) 2002-2005 ActiveState Corp.
+# See LICENSE.txt for license details.
+# Author:
+#   Trent Mick (TrentM@ActiveState.com)
+# Home:
+#   http://trentm.com/projects/which/
+
+r"""Find the full path to commands.
+
+which(command, path=None, verbose=0, exts=None)
+    Return the full path to the first match of the given command on the
+    path.
+
+whichall(command, path=None, verbose=0, exts=None)
+    Return a list of full paths to all matches of the given command on
+    the path.
+
+whichgen(command, path=None, verbose=0, exts=None)
+    Return a generator which will yield full paths to all matches of the
+    given command on the path.
+    
+By default the PATH environment variable is searched (as well as, on
+Windows, the AppPaths key in the registry), but a specific 'path' list
+to search may be specified as well.  On Windows, the PATHEXT environment
+variable is applied as appropriate.
+
+If "verbose" is true then a tuple of the form
+    (<fullpath>, <matched-where-description>)
+is returned for each match. The latter element is a textual description
+of where the match was found. For example:
+    from PATH element 0
+    from HKLM\SOFTWARE\...\perl.exe
+"""
+
+_cmdlnUsage = """
+    Show the full path of commands.
+
+    Usage:
+        which [<options>...] [<command-name>...]
+
+    Options:
+        -h, --help      Print this help and exit.
+        -V, --version   Print the version info and exit.
+
+        -a, --all       Print *all* matching paths.
+        -v, --verbose   Print out how matches were located and
+                        show near misses on stderr.
+        -q, --quiet     Just print out matches. I.e., do not print out
+                        near misses.
+
+        -p <altpath>, --path=<altpath>
+                        An alternative path (list of directories) may
+                        be specified for searching.
+        -e <exts>, --exts=<exts>
+                        Specify a list of extensions to consider instead
+                        of the usual list (';'-separate list, Windows
+                        only).
+
+    Show the full path to the program that would be run for each given
+    command name, if any. Which, like GNU's which, returns the number of
+    failed arguments, or -1 when no <command-name> was given.
+
+    Near misses include duplicates, non-regular files and (on Un*x)
+    files without executable access.
+"""
+
+__revision__ = "$Id: which.py 430 2005-08-20 03:11:58Z trentm $"
+__version_info__ = (1, 1, 0)
+__version__ = '.'.join(map(str, __version_info__))
+
+import os
+import sys
+import getopt
+import stat
+
+
+#---- exceptions
+
+class WhichError(Exception):
+    pass
+
+
+
+#---- internal support stuff
+
+def _getRegisteredExecutable(exeName):
+    """Windows allow application paths to be registered in the registry."""
+    registered = None
+    if sys.platform.startswith('win'):
+        if os.path.splitext(exeName)[1].lower() != '.exe':
+            exeName += '.exe'
+        import _winreg
+        try:
+            key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
+                  exeName
+            value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key)
+            registered = (value, "from HKLM\\"+key)
+        except _winreg.error:
+            pass
+        if registered and not os.path.exists(registered[0]):
+            registered = None
+    return registered
+
+def _samefile(fname1, fname2):
+    if sys.platform.startswith('win'):
+        return ( os.path.normpath(os.path.normcase(fname1)) ==\
+            os.path.normpath(os.path.normcase(fname2)) )
+    else:
+        return os.path.samefile(fname1, fname2)
+
+def _cull(potential, matches, verbose=0):
+    """Cull inappropriate matches. Possible reasons:
+        - a duplicate of a previous match
+        - not a disk file
+        - not executable (non-Windows)
+    If 'potential' is approved it is returned and added to 'matches'.
+    Otherwise, None is returned.
+    """
+    for match in matches:  # don't yield duplicates
+        if _samefile(potential[0], match[0]):
+            if verbose:
+                sys.stderr.write("duplicate: %s (%s)\n" % potential)
+            return None
+    else:
+        if not stat.S_ISREG(os.stat(potential[0]).st_mode):
+            if verbose:
+                sys.stderr.write("not a regular file: %s (%s)\n" % potential)
+        elif not os.access(potential[0], os.X_OK):
+            if verbose:
+                sys.stderr.write("no executable access: %s (%s)\n"\
+                                 % potential)
+        else:
+            matches.append(potential)
+            return potential
+
+        
+#---- module API
+
+def whichgen(command, path=None, verbose=0, exts=None):
+    """Return a generator of full paths to the given command.
+    
+    "command" is a the name of the executable to search for.
+    "path" is an optional alternate path list to search. The default it
+        to use the PATH environment variable.
+    "verbose", if true, will cause a 2-tuple to be returned for each
+        match. The second element is a textual description of where the
+        match was found.
+    "exts" optionally allows one to specify a list of extensions to use
+        instead of the standard list for this system. This can
+        effectively be used as an optimization to, for example, avoid
+        stat's of "foo.vbs" when searching for "foo" and you know it is
+        not a VisualBasic script but ".vbs" is on PATHEXT. This option
+        is only supported on Windows.
+
+    This method returns a generator which yields either full paths to
+    the given command or, if verbose, tuples of the form (<path to
+    command>, <where path found>).
+    """
+    matches = []
+    if path is None:
+        usingGivenPath = 0
+        path = os.environ.get("PATH", "").split(os.pathsep)
+        if sys.platform.startswith("win"):
+            path.insert(0, os.curdir)  # implied by Windows shell
+    else:
+        usingGivenPath = 1
+
+    # Windows has the concept of a list of extensions (PATHEXT env var).
+    if sys.platform.startswith("win"):
+        if exts is None:
+            exts = os.environ.get("PATHEXT", "").split(os.pathsep)
+            # If '.exe' is not in exts then obviously this is Win9x and
+            # or a bogus PATHEXT, then use a reasonable default.
+            for ext in exts:
+                if ext.lower() == ".exe":
+                    break
+            else:
+                exts = ['.COM', '.EXE', '.BAT']
+        elif not isinstance(exts, list):
+            raise TypeError("'exts' argument must be a list or None")
+    else:
+        if exts is not None:
+            raise WhichError("'exts' argument is not supported on "\
+                             "platform '%s'" % sys.platform)
+        exts = []
+
+    # File name cannot have path separators because PATH lookup does not
+    # work that way.
+    if os.sep in command or os.altsep and os.altsep in command:
+        pass
+    else:
+        for i in range(len(path)):
+            dirName = path[i]
+            # On windows the dirName *could* be quoted, drop the quotes
+            if sys.platform.startswith("win") and len(dirName) >= 2\
+               and dirName[0] == '"' and dirName[-1] == '"':
+                dirName = dirName[1:-1]
+            for ext in ['']+exts:
+                absName = os.path.abspath(
+                    os.path.normpath(os.path.join(dirName, command+ext)))
+                if os.path.isfile(absName):
+                    if usingGivenPath:
+                        fromWhere = "from given path element %d" % i
+                    elif not sys.platform.startswith("win"):
+                        fromWhere = "from PATH element %d" % i
+                    elif i == 0:
+                        fromWhere = "from current directory"
+                    else:
+                        fromWhere = "from PATH element %d" % (i-1)
+                    match = _cull((absName, fromWhere), matches, verbose)
+                    if match:
+                        if verbose:
+                            yield match
+                        else:
+                            yield match[0]
+        match = _getRegisteredExecutable(command)
+        if match is not None:
+            match = _cull(match, matches, verbose)
+            if match:
+                if verbose:
+                    yield match
+                else:
+                    yield match[0]
+
+
+def which(command, path=None, verbose=0, exts=None):
+    """Return the full path to the first match of the given command on
+    the path.
+    
+    "command" is a the name of the executable to search for.
+    "path" is an optional alternate path list to search. The default it
+        to use the PATH environment variable.
+    "verbose", if true, will cause a 2-tuple to be returned. The second
+        element is a textual description of where the match was found.
+    "exts" optionally allows one to specify a list of extensions to use
+        instead of the standard list for this system. This can
+        effectively be used as an optimization to, for example, avoid
+        stat's of "foo.vbs" when searching for "foo" and you know it is
+        not a VisualBasic script but ".vbs" is on PATHEXT. This option
+        is only supported on Windows.
+
+    If no match is found for the command, a WhichError is raised.
+    """
+    try:
+        match = whichgen(command, path, verbose, exts).next()
+    except StopIteration:
+        raise WhichError("Could not find '%s' on the path." % command)
+    return match
+
+
+def whichall(command, path=None, verbose=0, exts=None):
+    """Return a list of full paths to all matches of the given command
+    on the path.  
+
+    "command" is a the name of the executable to search for.
+    "path" is an optional alternate path list to search. The default it
+        to use the PATH environment variable.
+    "verbose", if true, will cause a 2-tuple to be returned for each
+        match. The second element is a textual description of where the
+        match was found.
+    "exts" optionally allows one to specify a list of extensions to use
+        instead of the standard list for this system. This can
+        effectively be used as an optimization to, for example, avoid
+        stat's of "foo.vbs" when searching for "foo" and you know it is
+        not a VisualBasic script but ".vbs" is on PATHEXT. This option
+        is only supported on Windows.
+    """
+    return list( whichgen(command, path, verbose, exts) )
+
+
+
+#---- mainline
+
+def main(argv):
+    all = 0
+    verbose = 0
+    altpath = None
+    exts = None
+    try:
+        optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:',
+            ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts='])
+    except getopt.GetoptError, msg:
+        sys.stderr.write("which: error: %s. Your invocation was: %s\n"\
+                         % (msg, argv))
+        sys.stderr.write("Try 'which --help'.\n")
+        return 1
+    for opt, optarg in optlist:
+        if opt in ('-h', '--help'):
+            print _cmdlnUsage
+            return 0
+        elif opt in ('-V', '--version'):
+            print "which %s" % __version__
+            return 0
+        elif opt in ('-a', '--all'):
+            all = 1
+        elif opt in ('-v', '--verbose'):
+            verbose = 1
+        elif opt in ('-q', '--quiet'):
+            verbose = 0
+        elif opt in ('-p', '--path'):
+            if optarg:
+                altpath = optarg.split(os.pathsep)
+            else:
+                altpath = []
+        elif opt in ('-e', '--exts'):
+            if optarg:
+                exts = optarg.split(os.pathsep)
+            else:
+                exts = []
+
+    if len(args) == 0:
+        return -1
+
+    failures = 0
+    for arg in args:
+        #print "debug: search for %r" % arg
+        nmatches = 0
+        for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts):
+            if verbose:
+                print "%s (%s)" % match
+            else:
+                print match
+            nmatches += 1
+            if not all:
+                break
+        if not nmatches:
+            failures += 1
+    return failures
+
+
+if __name__ == "__main__":
+    sys.exit( main(sys.argv) )
+
+