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.
This commit is contained in:
Gregory Szorc 2012-09-20 20:54:46 -07:00
parent 126f5faa53
commit 4c76dc81e6
13 changed files with 1908 additions and 0 deletions

21
python/which/LICENSE.txt Normal file
View File

@ -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.

3
python/which/MANIFEST.in Normal file
View File

@ -0,0 +1,3 @@
include *.py *.cpp *.in which.exe Makefile* *.txt logo.jpg
exclude *~
recursive-include test *.txt *.py

21
python/which/Makefile.win Normal file
View File

@ -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

21
python/which/PKG-INFO Normal file
View File

@ -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

229
python/which/README.txt Normal file
View File

@ -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

113
python/which/TODO.txt Normal file
View File

@ -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

442
python/which/build.py Normal file
View File

@ -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) )

402
python/which/launcher.cpp Normal file
View File

@ -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

BIN
python/which/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

70
python/which/setup.py Normal file
View File

@ -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) ],
)

View File

@ -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()

View File

@ -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)

335
python/which/which.py Normal file
View File

@ -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) )