mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
126f5faa53
commit
4c76dc81e6
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) )
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user