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:
21
python/which/LICENSE.txt
Normal file
21
python/which/LICENSE.txt
Normal 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
3
python/which/MANIFEST.in
Normal 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
21
python/which/Makefile.win
Normal 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
21
python/which/PKG-INFO
Normal 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
229
python/which/README.txt
Normal 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
113
python/which/TODO.txt
Normal 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
442
python/which/build.py
Normal 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
402
python/which/launcher.cpp
Normal 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
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
70
python/which/setup.py
Normal 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) ],
|
||||||
|
)
|
||||||
|
|
||||||
168
python/which/test/test_which.py
Normal file
168
python/which/test/test_which.py
Normal 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()
|
||||||
|
|
||||||
83
python/which/test/testsupport.py
Normal file
83
python/which/test/testsupport.py
Normal 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
335
python/which/which.py
Normal 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) )
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user