Skip to content

Commit 20f3d21

Browse files
authored
Merge pull request #10 from manthey/refactor-args
Refactor some argument parsing.
2 parents a77cbcc + b17d47e commit 20f3d21

File tree

7 files changed

+237
-64
lines changed

7 files changed

+237
-64
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,5 @@ ENV/
101101
.mypy_cache/
102102

103103
# Our list
104+
.pytest_cache/
104105
modules_pyexe_list.py

CHANGELOG.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Release Notes
2+
3+
## Version 12
4+
5+
### Changes
6+
7+
- Removed some modules that can't be imported without Scintilla.DLL.
8+
- Ignore standard Python command line options that aren't implemented.
9+
- The banner in interactive mode better matches native Python.
10+
11+
### Bug Fixes
12+
13+
- Fixed raw input on Python 3.
14+
- Fixed handling - on the command line.
15+
- Fixed combining -i with -c or -m.
16+
- Unbuffered interactive mode fixed on Python 3.
17+
18+
### Build
19+
20+
- Added tests to show that pyexe's behavior is the same as native Python.
21+
- Added tests for help, reading from stdin, reading in tty mode, hyphen mode, and the `--all` flag.
22+
23+
## Version 11
24+
25+
### Changes
26+
27+
- Explicitly include `six`, `pip`, and `setuptools`
28+
- Update to the Python 2.7.14, 3.5.3, 3.6.4.
29+
- Update to pywin32 223, pip 10.0.1, psutil 5.4.5, setuptools 39.0.1, six 1.11.0
30+
- Improved version reporting (PR #9)
31+
- More of pywin32 is included thanks to PyInstaller
32+
33+
### Build
34+
35+
- Moved to GitHub
36+
- Built for Python 2.7, 3.5, 3.6 in 32-bit and 64-bit variants
37+
- Built on appveyor using PyInstaller
38+
- Add CI tests for installing and importing pip libraries, testing multiprocessing.
39+
40+
## Version 10
41+
42+
- Upgraded to Python 2.7.9
43+
- Added psutil 2.1.3
44+
- Added support for the `-m` option.
45+
- Turned off the optimization flag when building. Having it on interferes with some modules (such as sympy) which rely on docstring manipulation.
46+
47+
## Version 9:
48+
- Revert changes to global dictionaries to fix multiprocessing forking.
49+
50+
## Version 8
51+
52+
- Fixed a bug introduced in version 7 when renaming the variable `loc`.
53+
54+
## Version 7:
55+
56+
- Added support for `-E`, `-x`, and `--version` options.
57+
- Changed how the globals / locals dictionaries are used for greater consistency in different execution modes.
58+
- Accept multiple single letter command line options grouped together.
59+
60+
## Version 6
61+
62+
- Added support for multiprocessing forking
63+
- Added support for non-tty direct usage (input and output pipes, for instance)
64+
- Added support for `-i` option and `PYTHONINSPECT` environment variable.
65+
- Turned off "frozen" flag in the executable.
66+
- Upgraded pywin32 to build 219 (was 218).
67+
- Upgraded to Python 2.7.8
68+
- Added `import site` to interactive prompts to get help and other commands added to the builtins.
69+
- Added support for unbuffered `-u` option and `PYTHONUNBUFFERED` environment variable.
70+
71+
## Version 5
72+
73+
- Imported submodules, such as logging.handlers, since they weren't included implicitly.
74+
75+
## Version 4
76+
77+
- Upgraded to Python 2.7.5
78+
79+
## Version 3
80+
81+
- Added the program path to `sys.path` when running a program, and `''` to `sys.path` when running direct or interpreted.
82+
83+
## Version 2
84+
85+
- fixed an issue with __file__ and __name__
86+
87+
## Version 1
88+
89+
- initial release
90+
91+

appveyor.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@ test_script:
5151
- SET PATH=%ORIGPATH%
5252
- pip install pytest
5353
- cd tests
54-
- "python -m pytest -l --exe=..\\%OUTPUT%"
54+
# Make sure we only have original files here.
55+
- git clean -fxd .
56+
- "python -m pytest -l --cache-clear --exe=..\\%OUTPUT%"
57+
# We can test the tests against the master python. We have to exclude a few
58+
# tests that only work on the stand-alone build.
59+
# Make sure we only have original files here.
60+
- git clean -fxd .
61+
- 'python -m pytest -l --cache-clear --exe=%PYTHON%\python -k "not Version and not Help and not AllFlag"'
5562

5663
deploy:
5764
- provider: GitHub

modules_pyexe.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@
2727
'turtledemo', 'tcl', 'tk', 'Tkinter',
2828
# These cause test importation fail
2929
'idlelib', 'win32traceutil',
30+
# These cause --all flag to fail
31+
'pywin.debugger', 'pywin.dialogs.ideoptions',
32+
'pywin.framework.dbgcommands', 'pywin.framework.editor',
33+
'pywin.framework.interact', 'pywin.framework.intpyapp',
34+
'pywin.framework.startup', 'pywin.framework.stdin',
35+
'pywin.framework.winout', 'pywin.scintilla', 'pywin.tools.TraceCollector',
36+
'win32com.axdebug.debugger', 'win32com.axdebug.dump',
37+
'win32com.axscript.client.debug',
3038
]
3139
# Exclude examples, tests, and Tk. Some of these won't exist in the default
3240
# installation

py_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
import sys
44

5-
Version = sys.version.split()[0] + '.11'
5+
Version = sys.version.split()[0] + '.12'
66
Description = 'Stand-Alone Python Interpreter'

pyexe.py

Lines changed: 64 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import modules_pyexe_list # noqa, this is the output of modules_pyexe
1616
print(dir(modules_pyexe_list)) # for installers to include submodules
1717
# END IMPORT ALL MODULES
18+
# Import modules which failed to be included in the auto-generated list.
19+
import setuptools._vendor.pyparsing # noqa
1820

1921

2022
def alternate_raw_input(prompt=None):
@@ -27,25 +29,26 @@ def alternate_raw_input(prompt=None):
2729
if prompt and len(prompt):
2830
sys.stderr.write(prompt)
2931
sys.stderr.flush()
30-
return raw_input('')
32+
return six.moves.input('')
3133

3234

3335
def print_version(details=1):
3436
"""
3537
Print the current version.
3638
37-
Enter: details: if 2, print more details.
39+
Enter: details: 0 if part of help, 1 for basic verison, 2 for more details.
3840
"""
3941
from py_version import Version, Description
4042

4143
print('%s, Version %s' % (Description, Version))
4244
if details > 1:
4345
print('Python %s' % (sys.version))
44-
import importlib
4546
# pywin32
4647
import win32api
4748
fileinfo = win32api.GetFileVersionInfo(win32api.__file__, '\\')
4849
print('pywin32: %s' % str(fileinfo['FileVersionLS'] >> 16))
50+
# Others
51+
import importlib
4952
for module_name in ('pip', 'psutil', 'setuptools', 'six'):
5053
module = importlib.import_module(module_name)
5154
print('%s: %s' % (module_name, module.__version__))
@@ -54,65 +57,78 @@ def print_version(details=1):
5457
if hasattr(sys, 'frozen'):
5558
delattr(sys, 'frozen')
5659
Help = False
57-
DirectCmd = None
58-
ImportSite = True
59-
Interactive = 'check'
60+
NoSiteFlag = False
61+
Interactive = None
62+
InteractiveArgv = None
6063
PrintVersion = 0
61-
RunModule = False
64+
RunCommand = None
65+
RunModule = None
6266
SkipFirstLine = False
6367
Start = None
6468
Unbuffered = False
6569
UseEnvironment = True
6670
skip = 0
6771
for i in six.moves.range(1, len(sys.argv)): # noqa
68-
if DirectCmd is not None:
69-
break
7072
if skip:
7173
skip -= 1
7274
continue
7375
arg = sys.argv[i]
74-
if arg.startswith('-') and arg[1:2] != '-':
76+
if arg.startswith('-') and len(arg) > 1 and arg[1:2] != '-':
7577
for let in arg[1:]:
7678
if let == 'c':
77-
DirectCmd = sys.argv[i+1+skip]
78-
DirectArgv = ['-c'] + sys.argv[i+2+skip:]
79+
RunCommand = sys.argv[i+1+skip]
80+
RunCommandArgv = ['-c'] + sys.argv[i+2+skip:]
7981
skip = len(sys.argv)
8082
elif let == 'E':
8183
UseEnvironment = False
84+
elif let == 'h':
85+
Help = True
8286
elif let == 'i':
8387
Interactive = True
8488
elif let == 'm' and i+1 < len(sys.argv):
8589
RunModule = sys.argv[i+1+skip]
86-
RunArgv = sys.argv[i+1+skip:]
90+
RunModuleArgv = sys.argv[i+1+skip:]
8791
skip = len(sys.argv)
8892
break
8993
elif let == 'S':
90-
ImportSite = False
94+
NoSiteFlag = True
9195
elif let == 'u':
9296
Unbuffered = True
9397
elif let == 'V':
9498
PrintVersion += 1
9599
elif let == 'x':
96100
SkipFirstLine = True
97-
elif let in ('B', 'E', 'O', 's'):
101+
elif let in ('b', 'B', 'd', 'I', 'O', 'q', 's', 'v'):
102+
# ignore these options
103+
pass
104+
elif let in ('W', 'X'):
98105
# ignore these options
106+
skip += 1
99107
pass
100108
else:
101109
Help = True
110+
elif arg == '--check-hash-based-pycs':
111+
# ignore this option
112+
skip += 1
113+
pass
102114
elif arg == '--all':
103115
continue
104-
elif arg == '--help' or arg == '-h' or arg == '/?':
116+
elif arg == '--help' or arg == '/?':
105117
Help = True
106118
elif arg == '--version':
107119
PrintVersion += 1
120+
elif arg == '-':
121+
Interactive = 'check'
122+
InteractiveArgv = ['-'] + sys.argv[i+1+skip:]
123+
skip = len(sys.argv)
124+
break
108125
elif arg.startswith('-'):
109126
Help = True
110127
elif not Start:
111-
Start = i
128+
Start = i + skip
112129
break
113130
if Help:
114-
from py_version import Version, Description
115-
print('%s, Version %s' % (Description, Version))
131+
print_version(0)
116132
print('usage: %s [option] ... [-c cmd | -m mod | file | -] [arg] ...' % sys.argv[0])
117133
print("""Stand-alone specific options:
118134
--all attempts to import all modules.
@@ -129,24 +145,23 @@ def print_version(details=1):
129145
-x skips the first line of a source file.
130146
If no file is specified and stdin is a terminal, the interactive interpreter is
131147
started.""")
132-
print(repr(sys.argv))
133148
sys.exit(0)
134149
if PrintVersion:
135150
print_version(PrintVersion)
136151
sys.exit(0)
137-
if Interactive == 'check' and UseEnvironment:
152+
if Interactive is not True and UseEnvironment:
138153
if os.environ.get('PYTHONINSPECT'):
139-
Interactive = True
154+
Interactive = 'check'
140155
if Unbuffered is False and UseEnvironment:
141156
if os.environ.get('PYTHONUNBUFFERED'):
142157
Unbuffered = True
158+
bufsize = 1 if sys.version_info >= (3, ) else 0
143159
if Unbuffered:
144-
bufsize = 1 if sys.version_info >= (3, ) else 0
145160
sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', bufsize)
146161
sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', bufsize)
147162
sys.stderr = os.fdopen(sys.stderr.fileno(), 'a+', bufsize)
148163
globenv = {}
149-
if ImportSite:
164+
if not NoSiteFlag:
150165
import site
151166
site.main()
152167
# Generate the globals/locals environment
@@ -162,52 +177,45 @@ def print_version(details=1):
162177
if SkipFirstLine:
163178
discard = fptr.readline()
164179
src = fptr.read()
165-
# If we the simplified global dictionary, multiprocessing doesn't work
180+
# If we use the simplified global dictionary, multiprocessing doesn't
181+
# work (this should be investigated further)
166182
six.exec_(src)
167183
elif RunModule:
168184
import runpy
169-
sys.argv[:] = RunArgv
185+
sys.argv[:] = RunModuleArgv
170186
runpy.run_module(RunModule, run_name='__main__')
171-
elif DirectCmd:
187+
elif RunCommand is not None:
172188
sys.path[0:0] = ['']
173-
sys.argv[:] = DirectArgv
174-
six.exec_(DirectCmd, globenv)
175-
else:
176-
if Interactive == 'check':
177-
Interactive = sys.stdin.isatty()
189+
sys.argv[:] = RunCommandArgv
190+
six.exec_(RunCommand, globenv)
191+
elif Interactive is None:
192+
Interactive = 'check'
193+
if Interactive:
178194
sys.path[0:0] = ['']
179-
if Interactive:
195+
if InteractiveArgv:
196+
sys.argv[:] = InteractiveArgv
197+
if Interactive is True or sys.stdin.isatty():
180198
import code
181199
cons = code.InteractiveConsole(locals=globenv)
182200
if not sys.stdout.isatty():
183201
cons.raw_input = alternate_raw_input
184202
if not Unbuffered:
185-
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
186-
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
187-
cons.interact()
188-
elif False:
189-
# This will run code as it comes it, rather than wait until it has
190-
# parsed it all; it doesn't seem to be what the main python interpreter
191-
# ever does, however.
192-
import code
193-
interp = code.InteractiveInterpreter(locals=globenv)
194-
src = []
195-
for line in sys.stdin:
196-
if not len(line.rstrip('\r\n')):
197-
continue
198-
if line.startswith('#'):
199-
continue
200-
if line.rstrip('\r\n')[0:1] not in (' ', '\t'):
201-
if len(src):
202-
interp.runsource(''.join(src), '<stdin>')
203-
src = []
204-
src.append(line)
205-
if len(src):
206-
interp.runsource(''.join(src))
207-
elif not Start:
203+
sys.stdout = os.fdopen(sys.stdout.fileno(), 'a+', bufsize)
204+
sys.stderr = os.fdopen(sys.stderr.fileno(), 'a+', bufsize)
205+
banner = 'Python %s' % sys.version
206+
if not NoSiteFlag:
207+
banner += '\nType "help", "copyright", "credits" or "license" for more information.'
208+
if RunModule or RunCommand:
209+
banner = ''
210+
kwargs = {}
211+
if sys.version_info >= (3, 6):
212+
kwargs['exitmsg'] = ''
213+
cons.interact(banner=banner, **kwargs)
214+
else:
208215
src = sys.stdin.read()
209216
# This doesn't work the way I expect for some reason
210217
# interp = code.InteractiveInterpreter(locals=globenv)
211218
# interp.runsource(src, '<stdin>')
212219
# But an exec works fine
220+
globenv['__file__'] = '<stdin>'
213221
six.exec_(src, globenv)

0 commit comments

Comments
 (0)