-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpydialect
executable file
·214 lines (186 loc) · 9.05 KB
/
pydialect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Bootstrapper for Python programs powered by Pydialect and/or MacroPy3."""
from importlib import import_module
from importlib.util import resolve_name
from types import ModuleType
import os
import sys
import argparse
try: # Python 3.5+
from importlib.util import module_from_spec as stdlib_module_from_spec
except ImportError:
stdlib_module_from_spec = None
try: # Python 3.6+
MyModuleNotFoundError = ModuleNotFoundError
except NameError:
MyModuleNotFoundError = ImportError
try:
import macropy.activate
except ImportError:
macropy = None
try: # this is all we need to enable dialect support.
import dialects.activate
except ImportError:
dialects = None
__version__ = '1.5.0'
def module_from_spec(spec):
"""Compatibility wrapper.
Call ``importlib.util.module_from_spec`` if available (Python 3.5+),
otherwise approximate it manually (Python 3.4).
"""
if stdlib_module_from_spec:
return stdlib_module_from_spec(spec)
loader = spec.loader
module = None
if loader is not None and hasattr(loader, "create_module"):
module = loader.create_module(spec)
if module is None:
module = ModuleType(spec.name)
module.__loader__ = loader
module.__spec__ = spec
module.__file__ = spec.origin
module.__cached__ = spec.cached
# module.__package__ and module.__path__ are filled later
return module
def import_module_as_main(name, script_mode):
"""Import a module, pretending it's __main__.
Upon success, replaces ``sys.modules["__main__"]`` with the module that
was imported. Upon failure, propagates any exception raised.
This is a customized approximation of the standard import semantics, based on:
https://docs.python.org/3/library/importlib.html#approximating-importlib-import-module
https://docs.python.org/3/reference/import.html#loading
https://docs.python.org/3/reference/import.html#import-related-module-attributes
https://docs.python.org/3/reference/import.html#module-path
https://docs.python.org/3/reference/import.html#special-considerations-for-main
"""
# We perform only the user-specified import ourselves; that we must, in order to
# load it as "__main__". We delegate all the rest to the stdlib import machinery.
absolute_name = resolve_name(name, package=None)
# Normally we should return the module from sys.modules if already loaded,
# but the __main__ in sys.modules is this bootstrapper program, not the user
# __main__ we're loading. So pretend whatever we're loading isn't loaded yet.
#
# Note Python treats __main__ and somemod as distinct modules, even when it's
# the same file, because __main__ triggers "if __name__ == '__main__':" checks
# whereas somemod doesn't.
# try:
# return sys.modules[absolute_name]
# except KeyError:
# pass
if "" not in sys.path: # Python 3.6 seems to have removed the special entry "" (the cwd) from sys.path
# Placing it first overrides installed pydialect with the local one when running tests
# (the installed one won't have the "test" submodules).
sys.path.insert(0, "")
# path should be folder containing something.py if we are being run as "pydialect something.py" (script_mode=True), and cwd if run as "pydialect -m something"
path = None
if '.' in absolute_name:
parent_name, _, child_name = absolute_name.rpartition('.')
if not script_mode:
parent_module = import_module(parent_name)
path = parent_module.__spec__.submodule_search_locations
else: # HACK: try to approximate what "python3 some/path/to/script.py" does
cwd = os.getcwd()
path_components = parent_name.split('.')
path = [os.path.join(*([cwd] + path_components))]
absolute_name = child_name
for finder in sys.meta_path:
if not hasattr(finder, "find_spec"): # Python 3.6: pkg_resources.extern.VendorImporter has no find_spec
continue
spec = finder.find_spec(absolute_name, path)
if spec is not None:
break
else:
msg = 'No module named {}'.format(absolute_name)
raise MyModuleNotFoundError(msg, name=absolute_name)
spec.name = "__main__"
if spec.loader:
spec.loader.name = "__main__" # fool importlib._bootstrap.check_name_wrapper
# TODO: support old-style loaders that have load_module (no create_module, exec_module)?
module = module_from_spec(spec)
try_mainpy = False
if script_mode: # e.g. "macropy3 somemod/__init__.py"
module.__package__ = ""
elif path:
module.__package__ = parent_name
elif spec.origin.endswith("__init__.py"): # e.g. "macropy3 -m unpythonic"
module.__package__ = absolute_name
try_mainpy = True
# TODO: is this sufficient? Any other cases where we directly handle a package?
if spec.origin == "namespace":
module.__path__ = spec.submodule_search_locations
if try_mainpy:
# e.g. "import unpythonic" in the above case; it's not the one running as main, so import it normally
if not script_mode:
parent_module = import_module(absolute_name)
elif spec.loader is not None: # namespace packages have loader=None
# There's already a __main__ in sys.modules, so the most logical thing
# to do is to switch it **after** a successful import, not before import
# as for usual imports (where the ordering prevents infinite recursion
# and multiple loading).
spec.loader.exec_module(module)
sys.modules["__main__"] = module
# # __main__ has no parent module.
# if path is not None:
# setattr(parent_module, child_name, module)
else: # namespace package
try_mainpy = True
if try_mainpy: # __init__.py (if any) has run; now run __main__.py, like "python3 -m mypackage" does
has_mainpy = True
try:
# __main__.py doesn't need the name "__main__" so we can just import it normally
import_module("{}.__main__".format(absolute_name))
except ImportError as e:
if "No module named" in e.msg:
has_mainpy = False
else:
raise
if not has_mainpy:
raise ImportError("No module named {}.__main__; '{}' is a package and cannot be directly executed".format(absolute_name, absolute_name))
return module
def main():
"""Handle command-line arguments and run the specified main program."""
parser = argparse.ArgumentParser(description="""Run a Python program with Pydialect and MacroPy3 enabled (if installed).""",
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument( '-v', '--version', action='version', version=('%(prog)s-bootstrapper ' + __version__) )
parser.add_argument(dest='filename', nargs='?', default=None, type=str, metavar='file',
help='script to run')
parser.add_argument('-m', '--module', dest='module', default=None, type=str, metavar='mod',
help='run library module as a script (like python3 -m mod)')
parser.add_argument('-d', '--debug', dest='debug', action="store_true", default=False,
help='enable MacroPy logging (does nothing if MacroPy not installed)')
opts = parser.parse_args()
if not opts.filename and not opts.module:
parser.print_help()
sys.exit(0)
raise ValueError("Please specify the program to run (either filename or -m module).")
if opts.filename and opts.module:
raise ValueError("Please specify just one program to run (either filename or -m module, not both).")
if opts.debug and macropy:
import macropy.logging
# Import the module, pretending its name is "__main__".
#
# We must import so that macros get expanded, so we can't use
# runpy.run_module here (which just execs without importing).
if opts.filename:
# like "python3 foo/bar.py", we don't initialize any parent packages.
if not os.path.isfile(opts.filename):
raise FileNotFoundError("Can't open file '{}'".format(opts.filename))
# This finds the wrong (standard) loader for macro-enabled scripts...
# spec = spec_from_file_location("__main__", opts.filename)
# if not spec:
# raise ImportError("Not a Python module: '{}'".format(opts.filename))
# module = module_from_spec(spec)
# spec.loader.exec_module(module)
# FIXME: wild guess (see Pyan3 for a better guess?)
module_name = opts.filename.replace(os.path.sep, '.')
if module_name.endswith(".__init__.py"):
module_name = module_name[:-12]
elif module_name.endswith(".py"):
module_name = module_name[:-3]
import_module_as_main(module_name, script_mode=True)
else: # opts.module
# like "python3 -m foo.bar", we initialize parent packages.
import_module_as_main(opts.module, script_mode=False)
if __name__ == '__main__':
main()