diff --git a/python/which/LICENSE.txt b/python/which/LICENSE.txt
new file mode 100644
index 0000000000000..de85cd53a856e
--- /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.
+
diff --git a/python/which/MANIFEST.in b/python/which/MANIFEST.in
new file mode 100644
index 0000000000000..3b8970ab829c6
--- /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
diff --git a/python/which/Makefile.win b/python/which/Makefile.win
new file mode 100644
index 0000000000000..c400aafd4b855
--- /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
diff --git a/python/which/PKG-INFO b/python/which/PKG-INFO
new file mode 100644
index 0000000000000..bfa8baa280728
--- /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 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.
+
+Keywords: which,find,path,where
+Platform: Windows
+Platform: Linux
+Platform: Mac OS X
+Platform: Unix
diff --git a/python/which/README.txt b/python/which/README.txt
new file mode 100644
index 0000000000000..6ece7f6de2737
--- /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:
+ -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 , --path=
+ An alternative path (list of directories) may
+ be specified for searching.
+ -e , --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 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
+
diff --git a/python/which/TODO.txt b/python/which/TODO.txt
new file mode 100644
index 0000000000000..6df2de7f70ac5
--- /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
+
diff --git a/python/which/build.py b/python/which/build.py
new file mode 100644
index 0000000000000..3c8f09d39e1af
--- /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:
+ --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 /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) )
+
diff --git a/python/which/launcher.cpp b/python/which/launcher.cpp
new file mode 100644
index 0000000000000..ae1d30708b2ef
--- /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 /.py
+ * on Windows. ".py" must be in the same directory.
+ *
+ * Rationale:
+ * - On some Windows flavours .py *can* be put on the PATHEXT to be
+ * able to find ".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
+ *