[Python-checkins] r51481 - in sandbox/branches/jim-fix-setuptools-cli/setuptools: launcher.c setuptools/cli.exe setuptools/gui.exe setuptools/tests/__init__.py setuptools/tests/win_script_wrapper.txt
jim.fulton
python-checkins at python.org
Tue Aug 22 20:44:33 CEST 2006
Author: jim.fulton
Date: Tue Aug 22 20:44:33 2006
New Revision: 51481
Added:
sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/tests/win_script_wrapper.txt
Modified:
sandbox/branches/jim-fix-setuptools-cli/setuptools/launcher.c
sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/cli.exe
sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/gui.exe
sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/tests/__init__.py
Log:
Added quoting of script arguments and extended the quoting logic to
handle embedded quotes.
Added support for passing a single argument on the shebang line to
pass things like -O and -i.
Fixed bug in handling trailing whitespace in Python command.
Modified: sandbox/branches/jim-fix-setuptools-cli/setuptools/launcher.c
==============================================================================
--- sandbox/branches/jim-fix-setuptools-cli/setuptools/launcher.c (original)
+++ sandbox/branches/jim-fix-setuptools-cli/setuptools/launcher.c Tue Aug 22 20:44:33 2006
@@ -33,22 +33,80 @@
fprintf(stderr, format, data);
return 2;
}
+
char *quoted(char *data) {
- char *result = calloc(strlen(data)+3,sizeof(char));
- strcat(result,"\""); strcat(result,data); strcat(result,"\"");
+ int i, l = strlen(data), nb;
+ /* We allocate twice as much space as needed to deal with worse-case
+ of having to escape everything. */
+ char *result = calloc(l*2+3, sizeof(char));
+ char *presult = result;
+
+ *presult++ = '"';
+ for (nb=0, i=0; i < l; i++)
+ {
+ if (data[i] == '\\')
+ nb += 1;
+ else if (data[i] == '"')
+ {
+ for (; nb > 0; nb--)
+ *presult++ = '\\';
+ *presult++ = '\\';
+ }
+ else
+ nb = 0;
+ *presult++ = data[i];
+ }
+ for (; nb > 0; nb--) /* Deal w trailing slashes */
+ *presult++ = '\\';
+
+ *presult++ = '"';
+ *presult++ = 0;
return result;
}
+char *getpyopt(char *python)
+{
+ /* Search a Python command string, read from a #! line for an
+ option. An option must be separated from an executable name by
+ one or more spaces. An option consistes of a hyphen followed by
+ one or more letters.
+ */
+ static char *letters =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ ;
+ char *p = python + strlen(python) - 1;
+ if (strchr(letters, *p) == NULL)
+ return NULL; /* Path doen't end with a letter. Odd. */
+ while (p > python && strchr(letters, *p) != NULL)
+ p--;
+ if (p == python || *p != '-')
+ return NULL; /* Can't be an option */
+ p--;
+ if (p > python && isspace(*p))
+ { /* BINGO, we have an option */
+ char *pyopt = p+1;
+ /* strip trailing spaces from remainder of python command */
+ while (p > python && isspace(*p))
+ *p-- = '\0';
+ return pyopt;
+ }
+ else
+ return NULL;
+}
+
int run(int argc, char **argv, int is_gui) {
char python[256]; /* python executable's filename*/
+ char *pyopt; /* Python option */
char script[256]; /* the script's filename */
HINSTANCE hPython; /* DLL handle for python executable */
int scriptf; /* file descriptor for script file */
- char **newargs; /* argument array for exec */
+ char **newargs, **newargsp; /* argument array for exec */
char *ptr, *end; /* working pointers for string manipulation */
+ int i; /* loop counter */
/* compute script name from our .exe name*/
GetModuleFileName(NULL, script, sizeof(script));
@@ -73,13 +131,16 @@
*ptr='\\'; /* convert slashes to avoid LoadLibrary crashes... */
}
- *ptr = '\0';
+ *ptr-- = '\0';
while (ptr>python && isspace(*ptr)) *ptr-- = '\0'; /* strip trailing sp */
if (strncmp(python, "#!", 2)) {
/* default to python.exe if no #! header */
strcpy(python, "#!python.exe");
}
+ /* Check for Python options */
+ pyopt = getpyopt(python);
+
/* At this point, the python buffer contains "#!pythonfilename" */
/* Using spawnv() can fail strangely if you e.g. find the Cygwin
@@ -94,12 +155,19 @@
/* printf("Python executable: %s\n", python); */
- /* Argument array needs to be argc+1 for args, plus 1 for null sentinel */
- newargs = (char **)calloc(argc+2, sizeof(char *));
- newargs[0] = quoted(python);
- newargs[1] = quoted(script);
- memcpy(newargs+2, argv+1, (argc-1)*sizeof(char *));
- newargs[argc+1] = NULL;
+ /* Argument array needs to be
+ argc+1 for python executable,
+ plus 1 for possible python opts,
+ plus 1 for null sentinel */
+ newargs = (char **)calloc(argc+3, sizeof(char *));
+ newargsp = newargs;
+ *newargsp++ = quoted(python);
+ if (pyopt)
+ *newargsp++ = pyopt;
+ *newargsp++ = quoted(script);
+ for (i = 1; i < argc; i++)
+ *newargsp++ = quoted(argv[i]);
+ *newargsp++ = NULL;
/* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
if (is_gui) {
Modified: sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/cli.exe
==============================================================================
Binary files. No diff available.
Modified: sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/gui.exe
==============================================================================
Binary files. No diff available.
Modified: sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/tests/__init__.py
==============================================================================
--- sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/tests/__init__.py (original)
+++ sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/tests/__init__.py Tue Aug 22 20:44:33 2006
@@ -14,11 +14,15 @@
import sys, os.path
def additional_tests():
- import doctest
- return doctest.DocFileSuite(
- 'api_tests.txt', optionflags=doctest.ELLIPSIS, package=__name__,
- )
-
+ import doctest, unittest
+ suite = unittest.TestSuite((
+ doctest.DocFileSuite('api_tests.txt',
+ optionflags=doctest.ELLIPSIS, package=__name__,
+ ),
+ ))
+ if sys.platform == 'win32':
+ suite.addTest(doctest.DocFileSuite('win_script_wrapper.txt'))
+ return suite
def makeSetup(**args):
"""Return distribution from 'setup(**args)', without executing commands"""
Added: sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/tests/win_script_wrapper.txt
==============================================================================
--- (empty file)
+++ sandbox/branches/jim-fix-setuptools-cli/setuptools/setuptools/tests/win_script_wrapper.txt Tue Aug 22 20:44:33 2006
@@ -0,0 +1,103 @@
+Python Script Wrapper for Windows
+=================================
+
+setuptools includes wrappers for Python scripts that allows them to be
+executed like regular windows programs. There are 2 wrappers, once
+for command-line programs, cli.exe, and one for graphica programs,
+gui.exe. These programs are almost identical, function pretty much
+the same way, and are generated from the same source file. In this
+document, we'll demonstrate use of the command-line program only. The
+wrapper programs are used by copying them to the directory containing
+the script they are to wrap and with the same name as the script they
+are to wrap. In the rest of this document, we'll give an example that
+will illustrate this.
+
+Let's create a simple script, foo-script.py:
+
+ >>> import os, sys, tempfile
+ >>> sample_directory = tempfile.mkdtemp()
+ >>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').write(
+ ... """#!%(python_exe)s
+ ... import sys
+ ... input = repr(sys.stdin.read())
+ ... print sys.argv[0][-14:]
+ ... print sys.argv[1:]
+ ... print input
+ ... if __debug__:
+ ... print 'non-optimized'
+ ... """ % dict(python_exe=sys.executable))
+
+Note that the script starts with a Unix-style '#!' line saying which
+Python executable to run. The wrapper will use this to find the
+correct Python executable.
+
+We'll also copy cli.exe to the sample-directory with the name foo.exe:
+
+ >>> import pkg_resources
+ >>> open(os.path.join(sample_directory, 'foo.exe'), 'wb').write(
+ ... pkg_resources.resource_string('setuptools', 'cli.exe')
+ ... )
+
+When the copy of cli.exe, foo.exe in this example, runs, it examines
+the path name it was run with and computes a Python script path name
+by removing the '.exe' suffic and adding the '-script.py' suffix. (For
+GUI programs, the suffix '-script-pyw' is added.) This is why we
+named out script the way we did. Now we can run out script by running
+the wrapper:
+
+ >>> import os
+ >>> input, output = os.popen4(os.path.join(sample_directory, 'foo.exe')
+ ... + r' arg1 "arg 2" "arg \"2\\\"" "arg 4\\" "arg5 a\\b')
+ >>> input.write('hello\nworld\n')
+ >>> input.close()
+ >>> print output.read(),
+ \foo-script.py
+ ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
+ 'hello\nworld\n'
+ non-optimized
+
+This example was a little pathological in that it exercised windows
+(MS C runtime) quoting rules:
+
+- Strings containing spaces are surrounded by double quotes.
+
+- Double quotes in strings need to be escaped by preceding them with
+ back slashes.
+
+- One or more backslashes preceding double quotes quotes need to be
+ escaped by preceding each of them them with back slashes.
+
+Specifying Python Command-line Options
+--------------------------------------
+
+You can specify a single argument on the '#!' line. This can be used
+to specify Python options like -O, to run in optimized mode or -i
+to start the interactive interpreter. You can combine multiple
+options as usual. For example, to run in optimized mode and
+enter the interpreter after running the script, you could use -Oi:
+
+ >>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').write(
+ ... """#!%(python_exe)s -Oi
+ ... import sys
+ ... input = repr(sys.stdin.read())
+ ... print sys.argv[0][-14:]
+ ... print sys.argv[1:]
+ ... print input
+ ... if __debug__:
+ ... print 'non-optimized'
+ ... sys.ps1 = '---'
+ ... """ % dict(python_exe=sys.executable))
+
+ >>> input, output = os.popen4(os.path.join(sample_directory, 'foo.exe'))
+ >>> input.close()
+ >>> print output.read(),
+ \foo-script.py
+ []
+ ''
+ ---
+
+
+We're done with the sample_directory:
+
+ >>> import shutil
+ >>> shutil.rmtree(sample_directory)
More information about the Python-checkins
mailing list