pyflyby 1.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyflyby might be problematic. Click here for more details.

Files changed (54) hide show
  1. pyflyby/__init__.py +56 -0
  2. pyflyby/__main__.py +9 -0
  3. pyflyby/_autoimp.py +2114 -0
  4. pyflyby/_cmdline.py +531 -0
  5. pyflyby/_comms.py +221 -0
  6. pyflyby/_dbg.py +1339 -0
  7. pyflyby/_docxref.py +379 -0
  8. pyflyby/_file.py +738 -0
  9. pyflyby/_flags.py +230 -0
  10. pyflyby/_format.py +182 -0
  11. pyflyby/_idents.py +233 -0
  12. pyflyby/_import_sorting.py +165 -0
  13. pyflyby/_importclns.py +642 -0
  14. pyflyby/_importdb.py +588 -0
  15. pyflyby/_imports2s.py +639 -0
  16. pyflyby/_importstmt.py +662 -0
  17. pyflyby/_interactive.py +2605 -0
  18. pyflyby/_livepatch.py +793 -0
  19. pyflyby/_log.py +199 -0
  20. pyflyby/_modules.py +515 -0
  21. pyflyby/_parse.py +1441 -0
  22. pyflyby/_py.py +2078 -0
  23. pyflyby/_util.py +459 -0
  24. pyflyby/_version.py +7 -0
  25. pyflyby/autoimport.py +20 -0
  26. pyflyby/importdb.py +19 -0
  27. pyflyby-1.9.4.data/data/etc/pyflyby/canonical.py +10 -0
  28. pyflyby-1.9.4.data/data/etc/pyflyby/common.py +27 -0
  29. pyflyby-1.9.4.data/data/etc/pyflyby/forget.py +10 -0
  30. pyflyby-1.9.4.data/data/etc/pyflyby/mandatory.py +10 -0
  31. pyflyby-1.9.4.data/data/etc/pyflyby/numpy.py +156 -0
  32. pyflyby-1.9.4.data/data/etc/pyflyby/std.py +335 -0
  33. pyflyby-1.9.4.data/data/libexec/pyflyby/colordiff +34 -0
  34. pyflyby-1.9.4.data/data/libexec/pyflyby/diff-colorize +148 -0
  35. pyflyby-1.9.4.data/data/share/doc/pyflyby/LICENSE.txt +23 -0
  36. pyflyby-1.9.4.data/data/share/doc/pyflyby/TODO.txt +115 -0
  37. pyflyby-1.9.4.data/data/share/doc/pyflyby/testing.txt +13 -0
  38. pyflyby-1.9.4.data/data/share/emacs/site-lisp/pyflyby.el +108 -0
  39. pyflyby-1.9.4.data/scripts/collect-exports +76 -0
  40. pyflyby-1.9.4.data/scripts/collect-imports +58 -0
  41. pyflyby-1.9.4.data/scripts/find-import +38 -0
  42. pyflyby-1.9.4.data/scripts/list-bad-xrefs +34 -0
  43. pyflyby-1.9.4.data/scripts/prune-broken-imports +34 -0
  44. pyflyby-1.9.4.data/scripts/pyflyby-diff +34 -0
  45. pyflyby-1.9.4.data/scripts/reformat-imports +27 -0
  46. pyflyby-1.9.4.data/scripts/replace-star-imports +37 -0
  47. pyflyby-1.9.4.data/scripts/tidy-imports +191 -0
  48. pyflyby-1.9.4.data/scripts/transform-imports +47 -0
  49. pyflyby-1.9.4.dist-info/LICENSE.txt +23 -0
  50. pyflyby-1.9.4.dist-info/METADATA +507 -0
  51. pyflyby-1.9.4.dist-info/RECORD +54 -0
  52. pyflyby-1.9.4.dist-info/WHEEL +5 -0
  53. pyflyby-1.9.4.dist-info/entry_points.txt +3 -0
  54. pyflyby-1.9.4.dist-info/top_level.txt +1 -0
pyflyby/_py.py ADDED
@@ -0,0 +1,2078 @@
1
+ # pyflyby/_py.py
2
+ # Copyright (C) 2014, 2015, 2018, 2019 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+ r"""
6
+ The `py` program (part of the pyflyby project) is a command-line multitool for
7
+ running python code, with heuristic intention guessing, automatic importing,
8
+ and debugging support.
9
+
10
+ Invocation summary
11
+ ==================
12
+
13
+ .. code::
14
+
15
+ py [--file] filename.py arg1 arg2 Execute a file
16
+ py [--eval] 'function(arg1, arg2)' Evaluate an expression/statement
17
+ py [--apply] function arg1 arg2 Call function(arg1, arg2)
18
+ py [--module] modname arg1 arg2 Run a module
19
+
20
+ py --map function arg1 arg2 Call function(arg1); function(arg2)
21
+
22
+ py -i 'function(arg1, arg2)' Run file/code/etc, then run IPython
23
+ py --debug 'function(arg1, arg2)' Debug file/code/etc
24
+ py --debug PID Attach debugger to PID
25
+
26
+ py function? Get help for a function or module
27
+ py function?? Get source of a function or module
28
+
29
+ py Start IPython with autoimporter
30
+ py nb Start IPython Notebook with autoimporter
31
+
32
+
33
+ Features
34
+ ========
35
+
36
+ * Heuristic action mode guessing: If none of --file, --eval, --apply,
37
+ --module, or --map is specified, then guess what to do, choosing one of
38
+ these actions:
39
+
40
+ * Execute (run) a file
41
+ * Evaluate concatenated arguments
42
+ * Run a module
43
+ * Call (apply) a function
44
+ * Evaluate first argument
45
+
46
+ * Automatic importing: All action modes (except run_module) automatically
47
+ import as needed.
48
+
49
+ * Heuristic argument evaluation: By default, `py --eval`, `py --apply`, and
50
+ `py --map` guess whether the arguments should be interpreted as
51
+ expressions or literal strings. A "--" by itself will designate subsequent
52
+ args as strings. A "-" by itself will be replaced by the contents of
53
+ stdin as a string.
54
+
55
+ * Merged eval/exec: Code is eval()ed or exec()ed as appropriate.
56
+
57
+ * Result printing: By default, results are pretty-printed if not None.
58
+
59
+ * Heuristic flags: "print" can be used as a function or a statement.
60
+
61
+ * Matplotlib/pylab integration: show() is called if appropriate to block on
62
+ plots.
63
+
64
+ * Enter debugger upon unhandled exception. (This functionality is enabled
65
+ by default when stdout is a tty. Use --postmortem=no to never use the
66
+ postmortem debugger. Use --postmortem=yes enable even if stdout is not a
67
+ tty. If the postmortem debugger is enabled but /dev/tty is not available,
68
+ then if an exception occurs, py will email instructions for attaching a
69
+ debugger.)
70
+
71
+ * Control-\\ (SIGQUIT) enters debugger while running (and allows continuing).
72
+
73
+ * New builtin functions such as "debugger()".
74
+
75
+ Warning
76
+ =======
77
+ `py` is intended as an interactive tool. When writing shell aliases for
78
+ interactive use, the `--safe` option can be useful. When writing scripts,
79
+ it's better to avoid all heuristic guessing; use regular `python -c ...`, or
80
+ better yet, a full-fledged python program (and run tidy-imports).
81
+
82
+
83
+ Options
84
+ =======
85
+
86
+ .. code::
87
+
88
+ Global options valid before code argument:
89
+
90
+ --args=string Interpret all arguments as literal strings.
91
+ (The "--" argument also specifies remaining arguments to be
92
+ literal strings.)
93
+ --args=eval Evaluate all arguments as expressions.
94
+ --args=auto (Default) Heuristically guess whether to evaluate arguments
95
+ as literal strings or expressions.
96
+ --output=silent Don't print the result of evaluation.
97
+ --output=str Print str(result).
98
+ --output=repr Print repr(result).
99
+ --output=pprint Print pprint.pformat(result).
100
+ --output=repr-if-not-none
101
+ Print repr(result), but only if result is not None.
102
+ --output=pprint-if-not-none
103
+ Print pprint.pformat(result), but only if result is not None.
104
+ --output=interactive
105
+ (Default) Print result.__interactive_display__() if defined,
106
+ else pprint if result is not None.
107
+ --output=exit Raise SystemExit(result).
108
+ --safe Equivalent to --args=strings and PYFLYBY_PATH=EMPTY.
109
+ --quiet, --q Log only error messages to stderr; omit info and warnings.
110
+ --interactive, --i
111
+ Run an IPython shell after completion
112
+ --debug, --d Run the target code file etc under the debugger. If a PID is
113
+ given, then instead attach a debugger to the target PID.
114
+ --verbose Turn on verbose messages from pyflyby.
115
+
116
+ Pseudo-actions valid before, after, or without code argument:
117
+
118
+ --version Print pyflyby version or version of a module.
119
+ --help, --h, --? Print this help or help for a function or module.
120
+ --source, --?? Print source code for a function or module.
121
+
122
+
123
+ Examples
124
+ ========
125
+
126
+ Start IPython with pyflyby autoimporter enabled::
127
+
128
+ $ py
129
+
130
+ Start IPython/Jupyter Notebook with pyflyby autoimporter enabled::
131
+
132
+ $ py nb
133
+
134
+ Find the ASCII value of the letter "j" (apply builtin function)::
135
+
136
+ $ py ord j
137
+ [PYFLYBY] ord('j')
138
+ 106
139
+
140
+ Decode a base64-encoded string (apply autoimported function)::
141
+
142
+ $ py b64decode aGVsbG8=
143
+ [PYFLYBY] from base64 import b64decode
144
+ [PYFLYBY] b64decode('aGVsbG8=', altchars=None)
145
+ b'hello'
146
+
147
+ Find the day of the week of some date (apply function in module)::
148
+
149
+ $ py calendar.weekday 2014 7 18
150
+ [PYFLYBY] import calendar
151
+ [PYFLYBY] calendar.weekday(2014, 7, 18)
152
+ 4
153
+
154
+ Using named arguments::
155
+
156
+ $ py calendar.weekday --day=16 --month=7 --year=2014
157
+ [PYFLYBY] import calendar
158
+ [PYFLYBY] calendar.weekday(2014, 7, 16)
159
+ 2
160
+
161
+ Using short named arguments::
162
+
163
+ $ py calendar.weekday -m 7 -d 15 -y 2014
164
+ [PYFLYBY] import calendar
165
+ [PYFLYBY] calendar.weekday(2014, 7, 15)
166
+ 1
167
+
168
+ Invert a matrix (evaluate expression, with autoimporting)::
169
+
170
+ $ py 'matrix("1 3 3; 1 4 3; 1 3 4").I'
171
+ [PYFLYBY] from numpy import matrix
172
+ [PYFLYBY] matrix("1 3 3; 1 4 3; 1 3 4").I
173
+ matrix([[ 7., -3., -3.],
174
+ [-1., 1., 0.],
175
+ [-1., 0., 1.]])
176
+
177
+ Plot cosine (evaluate expression, with autoimporting)::
178
+
179
+ $ py 'plot(cos(arange(30)))'
180
+ [PYFLYBY] from numpy import arange
181
+ [PYFLYBY] from numpy import cos
182
+ [PYFLYBY] from matplotlib.pyplot import plot
183
+ [PYFLYBY] plot(cos(arange(30)))
184
+ <plot>
185
+
186
+ Command-line calculator (multiple arguments)::
187
+
188
+ $ py 3 / 4
189
+ 0.75
190
+
191
+ Command-line calculator (single arguments)::
192
+
193
+ $ py '(5+7j) \** 12'
194
+ (65602966976-150532462080j)
195
+
196
+ Rationalize a decimal (apply bound method)::
197
+
198
+ $ py 2.5.as_integer_ratio
199
+ [PYFLYBY] 2.5.as_integer_ratio()
200
+ (5, 2)
201
+
202
+ Rationalize a decimal (apply unbound method)::
203
+
204
+ $ py float.as_integer_ratio 2.5
205
+ [PYFLYBY] float.as_integer_ratio(2.5)
206
+ (5, 2)
207
+
208
+ Rationalize decimals (map/apply)::
209
+
210
+ $ py --map float.as_integer_ratio 2.5 3.5
211
+ [PYFLYBY] float.as_integer_ratio(2.5)
212
+ (5, 2)
213
+ [PYFLYBY] float.as_integer_ratio(3.5)
214
+ (7, 2)
215
+
216
+ Square numbers (map lambda)::
217
+
218
+ $ py --map 'lambda x: x \**2' 3 4 5
219
+ [PYFLYBY] (lambda x: x \**2)(3)
220
+ 9
221
+ [PYFLYBY] (lambda x: x \**2)(4)
222
+ 16
223
+ [PYFLYBY] (lambda x: x \**2)(5)
224
+ 25
225
+
226
+ Find length of string (using "-" for stdin)::
227
+
228
+ $ echo hello | py len -
229
+ [PYFLYBY] len('hello\\n')
230
+ 6
231
+
232
+ Run stdin as code::
233
+
234
+ $ echo 'print(sys.argv[1:])' | py - hello world
235
+ [PYFLYBY] import sys
236
+ ['hello', 'world']
237
+
238
+ Run libc functions::
239
+
240
+ $ py --quiet --output=none 'CDLL("libc.so.6").printf' %03d 7
241
+ 007
242
+
243
+ Download web page::
244
+
245
+ $ py --print 'requests.get(sys.argv[1]).text' http://example.com
246
+
247
+ Get function help::
248
+
249
+ $ py b64decode?
250
+ [PYFLYBY] from base64 import b64decode
251
+ Python signature::
252
+
253
+ >> b64decode(s, altchars=None, validate=False)
254
+
255
+ Command-line signature::
256
+
257
+ $ py b64decode s [altchars [validate]]
258
+ $ py b64decode --s=... [--altchars=...] [--validate=...]
259
+ ...
260
+
261
+ Get module help::
262
+
263
+ $ py pandas?
264
+ [PYFLYBY] import pandas
265
+ Version:
266
+ 0.13.1
267
+ Filename:
268
+ /usr/local/lib/python2.7/site-packages/pandas/__init__.pyc
269
+ Docstring:
270
+ pandas - a powerful data analysis and manipulation library for Python
271
+ ...
272
+
273
+ """
274
+
275
+
276
+
277
+ from functools import total_ordering
278
+ from typing import Any
279
+
280
+ from pyflyby._util import cmp
281
+ from shlex import quote as shquote
282
+
283
+ usage = """
284
+ py --- command-line python multitool with automatic importing
285
+
286
+ $ py [--file] filename.py arg1 arg2 Execute file
287
+ $ py [--apply] function arg1 arg2 Call function
288
+ $ py [--eval] 'function(arg1, arg2)' Evaluate code
289
+ $ py [--module] modname arg1 arg2 Run a module
290
+
291
+ $ py --debug file/code... args... Debug code
292
+ $ py --debug PID Attach debugger to PID
293
+
294
+ $ py IPython shell
295
+ """.strip()
296
+
297
+ # TODO: add --tidy-imports, etc
298
+
299
+ # TODO: new --action="concat_eval eval apply" etc. specifying multiple
300
+ # actions means try each of them in that order. then --safe can exclude
301
+ # concat-eval, and users can customize which action modes are included.
302
+ # --apply would be equivalent to --action=apply.
303
+
304
+ # TODO: plug-in system. 'py foo' should attempt something that the
305
+ # user/vendor can add to the system. leading candidate: use entry_point
306
+ # system (http://stackoverflow.com/a/774859). other candidates: maybe use
307
+ # python namespace like pyflyby.vendor.foo or pyflyby.commands.foo or
308
+ # pyflyby.magics.foo, or maybe a config file.
309
+ # TODO: note additional features in documentation feature list
310
+
311
+ # TODO: somehow do the right thing with glob.glob vs glob, pprint.pprint vs
312
+ # pprint, etc. Ideas:
313
+ # - make --apply special case detect modules and take module.module
314
+ # - enhance auto_import() to keep track of the context while finding missing imports
315
+ # - enhance auto_import() to scan for calls after importing
316
+
317
+ # TODO: pipe help/source output (all output?) through $PYFLYBY_PAGER (default "less -FRX").
318
+
319
+ # TODO: unparse ast node for info logging
320
+ # https://hg.python.org/cpython/log/tip/Tools/parser/unparse.py
321
+
322
+ # TODO: run_module should detect if the module doesn't check __name__ and
323
+ # therefore is unlikely to be meaningful to use with run_module.
324
+
325
+ # TODO: make sure run_modules etc work correctly with modules under namespace
326
+ # packages.
327
+
328
+ # TODO: detect deeper ImportError, e.g. suppose user accesses module1; module1
329
+ # imports badmodule, which can't be imported successfully and raises
330
+ # ImportError; we should get that ImportError instead of trying other things
331
+ # or turning it into a string. probably do this by changing the place where
332
+ # we import modules to first get the loader, then if import fails, raise a
333
+ # subclass of ImportError.
334
+
335
+ # TODO: provide a way to omit newline in output. maybe --output=write.
336
+
337
+ # TODO: make sure 'py -c' matches behavior of 'python -c' w.r.t. sys.modules["__main__"]
338
+ # $ py -c 'x=3;import sys; print sys.modules["__main__"].__dict__["x"]'
339
+ # mimic runpy.{_TempModule,_run_code}
340
+
341
+ # TODO: refactor this module - maybe to _heuristic.py
342
+
343
+ # TODO: add --profile, --runsnake
344
+
345
+ import ast
346
+ from contextlib import contextmanager
347
+ import inspect
348
+ import os
349
+ import re
350
+ import builtins
351
+ import sys
352
+ import types
353
+ from types import FunctionType, MethodType
354
+
355
+ from pyflyby._autoimp import auto_import, find_missing_imports
356
+ from pyflyby._cmdline import print_version_and_exit, syntax
357
+ from pyflyby._dbg import (add_debug_functions_to_builtins,
358
+ attach_debugger, debug_statement,
359
+ debugger, enable_faulthandler,
360
+ enable_signal_handler_debugger,
361
+ enable_sigterm_handler,
362
+ remote_print_stack)
363
+ from pyflyby._file import Filename, UnsafeFilenameError, which
364
+ from pyflyby._flags import CompilerFlags
365
+ from pyflyby._idents import is_identifier
366
+ from pyflyby._interactive import (get_ipython_terminal_app_with_autoimporter,
367
+ run_ipython_line_magic,
368
+ start_ipython_with_autoimporter)
369
+ from pyflyby._log import logger
370
+ from pyflyby._modules import ModuleHandle
371
+ from pyflyby._parse import PythonBlock
372
+ from pyflyby._util import indent, prefixes
373
+
374
+
375
+ # Default compiler flags (feature flags) used for all user code. We include
376
+ # "print_function" here, but we also use auto_flags=True, which means
377
+ # print_function may be flipped off if the code contains print statements.
378
+ FLAGS = CompilerFlags(["absolute_import", "with_statement", "division",
379
+ "print_function"])
380
+
381
+
382
+ def _get_argspec(arg:Any) -> inspect.FullArgSpec:
383
+ from inspect import getfullargspec as getargspec, FullArgSpec as ArgSpec
384
+ if isinstance(arg, FunctionType):
385
+ return getargspec(arg)
386
+ elif isinstance(arg, MethodType):
387
+ argspec = getargspec(arg)
388
+ if arg.__self__ is not None:
389
+ # For bound methods, ignore the "self" argument.
390
+ return ArgSpec(argspec.args[1:], *argspec[1:])
391
+ return argspec
392
+ elif isinstance(arg, type):
393
+ if arg.__new__ is not object.__new__:
394
+ argspec = _get_argspec(arg.__new__)
395
+ return ArgSpec(argspec.args[1:], *argspec[1:])
396
+ else:
397
+ argspec = _get_argspec(arg.__init__)
398
+ return ArgSpec(argspec.args[1:], *argspec[1:])
399
+ elif callable(arg):
400
+ # Unknown, probably a built-in method.
401
+ return ArgSpec([], "args", "kwargs", None, [], None, {})
402
+ raise TypeError(
403
+ "_get_argspec: unexpected %s" % (type(arg).__name__,))
404
+
405
+
406
+ def _requires_parens_as_function(function_name:str):
407
+ """
408
+ Returns whether the given string of a callable would require parentheses
409
+ around it to call it.
410
+
411
+ >>> _requires_parens_as_function("foo.bar[4]")
412
+ False
413
+
414
+ >>> _requires_parens_as_function("foo+bar")
415
+ True
416
+
417
+ >>> _requires_parens_as_function("(foo+bar)()")
418
+ False
419
+
420
+ >>> _requires_parens_as_function("(foo+bar)")
421
+ False
422
+
423
+ >>> _requires_parens_as_function("(foo)+(bar)")
424
+ True
425
+
426
+ :type function_name:
427
+ ``str``
428
+ :rtype:
429
+ ``bool``
430
+ """
431
+ # TODO: this might be obsolete if we use unparse instead of keeping original
432
+ # user formatting (or alternatively, unparse should use something like this).
433
+
434
+ assert isinstance(function_name, str)
435
+
436
+ block = PythonBlock(function_name, flags=FLAGS)
437
+ node = block.expression_ast_node
438
+ if not node:
439
+ # Couldn't parse? Just assume we do need parens for now. Or should
440
+ # we raise an exception here?
441
+ return True
442
+ body = node.body
443
+ # Is it something that doesn't need parentheses?
444
+ if isinstance(body, (ast.Name, ast.Attribute, ast.Call, ast.Subscript)):
445
+ return False
446
+ # Does it already have parentheses?
447
+ n = str(function_name)
448
+ if n.startswith("(") and n.endswith(")"):
449
+ # It has parentheses, superficially. Make sure it's not something
450
+ # like "(foo)+(bar)".
451
+ flags = int(FLAGS) | ast.PyCF_ONLY_AST
452
+ try:
453
+ tnode = compile(n[1:-1], "<unknown>", "eval", flags)
454
+ except SyntaxError:
455
+ return True
456
+ if ast.dump(tnode) == ast.dump(node):
457
+ return False
458
+ else:
459
+ return True
460
+ return True
461
+
462
+
463
+ def _format_call_spec(function_name:str, obj:Any)-> str:
464
+ # using signature() is not strictly identical
465
+ # as it might look at __text_signature__ and/or respect possitional only
466
+ # forward slash:
467
+ # >>> def foo(a, /, b)
468
+ # whcih formatargspec did not do.
469
+ # argspec = _get_argspec(obj)
470
+ # old_callspect = inspect.formatargspec(*argspec)
471
+ # assert old_callspect == callspec , f"{old_callspect} !={callspec}"
472
+ callspec = str(inspect.signature(obj))
473
+ if _requires_parens_as_function(function_name):
474
+ return "(%s)%s" % (function_name, callspec)
475
+ else:
476
+ return "%s%s" % (function_name, callspec)
477
+
478
+
479
+
480
+
481
+ def _build_function_usage_string(function_name:str, obj:Any, prefix:str):
482
+ argspec = _get_argspec(obj)
483
+
484
+ usage = []
485
+ # TODO: colorize
486
+ usage.append("Python signature:")
487
+ usage.append(" >"+">> " + _format_call_spec(function_name, obj))
488
+ usage.append("")
489
+ usage.append("Command-line signature:")
490
+ keywords = argspec.varkw
491
+ if not argspec.args and argspec.varargs and keywords:
492
+ # We have no information about the arguments. It's probably a
493
+ # built-in where getargspec failed.
494
+ usage.append(" $ %s%s ...\n" % (prefix, function_name))
495
+ return "\n".join(usage)
496
+ defaults = argspec.defaults or ()
497
+ first_with_default = len(argspec.args) - len(defaults)
498
+ # Show first alternative of command-line syntax.
499
+ syntax1 = " $ %s%s" % (prefix, shquote(function_name),)
500
+ for i, arg in enumerate(argspec.args):
501
+ if i >= first_with_default:
502
+ syntax1 += " [%s" % (arg,)
503
+ else:
504
+ syntax1 += " %s" % (arg,)
505
+ if argspec.varargs:
506
+ syntax1 += " %s..." % argspec.varargs
507
+ syntax1 += "]" * len(defaults)
508
+ for arg in argspec.kwonlyargs:
509
+ if argspec.kwonlydefaults and arg in argspec.kwonlydefaults:
510
+ syntax1 += " [--%s=...]" % (arg,)
511
+ else:
512
+ syntax1 += " --%s=..." % (arg,)
513
+ if keywords:
514
+ syntax1 += " [--...]"
515
+ usage.append(syntax1)
516
+ # usage.append("or:")
517
+ syntax2 = " $ %s%s" % (prefix, shquote(function_name),)
518
+ for i, arg in enumerate(argspec.args):
519
+ if i >= first_with_default:
520
+ syntax2 += " [--%s=...]" % (arg,)
521
+ else:
522
+ syntax2 += " --%s=..." % (arg,)
523
+ for arg in argspec.kwonlyargs:
524
+ if argspec.kwonlydefaults and arg in argspec.kwonlydefaults:
525
+ syntax2 += " [--%s=...]" % (arg,)
526
+ else:
527
+ syntax2 += " --%s=..." % (arg,)
528
+ if argspec.varargs:
529
+ syntax2 += " %s..." % argspec.varargs
530
+ if keywords:
531
+ syntax2 += " [--...]"
532
+ usage.append(syntax2)
533
+ usage.append("")
534
+ return "\n".join(usage)
535
+
536
+
537
+ class ParseError(Exception):
538
+ pass
539
+
540
+
541
+ class _ParseInterruptedWantHelp(Exception):
542
+ pass
543
+
544
+
545
+ class _ParseInterruptedWantSource(Exception):
546
+ pass
547
+
548
+
549
+ class UserExpr(object):
550
+ """
551
+ An expression from user input, and its evaluated value.
552
+
553
+ The expression can come from a string literal or other raw value, or a
554
+ string that is evaluated as an expression, or heuristically chosen.
555
+
556
+ >>> ns = _Namespace()
557
+
558
+ Heuristic auto-evaluation::
559
+
560
+ >>> UserExpr('5+2', ns, "auto").value
561
+ 7
562
+
563
+ >>> UserExpr('5j+2', ns, "auto").value
564
+ (2+5j)
565
+
566
+ >>> UserExpr('base64.b64decode("SGFsbG93ZWVu")', ns, "auto").value
567
+ [PYFLYBY] import base64
568
+ b'Halloween'
569
+
570
+ Returning an unparsable argument as a string::
571
+
572
+ >>> UserExpr('Victory Loop', ns, "auto").value
573
+ 'Victory Loop'
574
+
575
+ Returning an undefined (and not auto-importable) argument as a string::
576
+
577
+ >>> UserExpr('Willowbrook29817621+5', ns, "auto").value
578
+ 'Willowbrook29817621+5'
579
+
580
+ Explicit literal string::
581
+
582
+ >>> UserExpr("2+3", ns, "raw_value").value
583
+ '2+3'
584
+
585
+ >>> UserExpr("'2+3'", ns, "raw_value").value
586
+ "'2+3'"
587
+
588
+ Other raw values::
589
+
590
+ >>> UserExpr(sys.exit, ns, "raw_value").value
591
+ <built-in function exit>
592
+ """
593
+
594
+ def __init__(self, arg, namespace, arg_mode, source=None):
595
+ """
596
+ Construct a new UserExpr.
597
+
598
+ :type arg:
599
+ ``str`` if ``arg_mode`` is "eval" or "auto"; anything if ``arg_mode``
600
+ is "raw_value"
601
+ :param arg:
602
+ Input user argument.
603
+ :type namespace:
604
+ `_Namespace`
605
+ :type arg_mode:
606
+ ``str``
607
+ :param arg_mode:
608
+ If ``"raw_value"``, then return ``arg`` unchanged. If ``"eval"``, then
609
+ always evaluate ``arg``. If ``"auto"``, then heuristically evaluate
610
+ if appropriate.
611
+ """
612
+ if arg_mode == "string":
613
+ arg_mode = "raw_value"
614
+ self._namespace = namespace
615
+ self._original_arg_mode = arg_mode
616
+ self._original_arg = arg
617
+ if arg_mode == "raw_value":
618
+ # self.inferred_arg_mode = "raw_value"
619
+ # self.original_source = None
620
+ if source is None:
621
+ source = PythonBlock(repr(self._original_arg))
622
+ else:
623
+ source = PythonBlock(source)
624
+ self.source = source
625
+ self.value = self._original_arg
626
+ elif arg_mode == "eval":
627
+ if source is not None:
628
+ raise ValueError(
629
+ "UserExpr(): source argument not allowed for eval")
630
+ # self.inferred_arg_mode = "eval"
631
+ self._original_arg_as_source = PythonBlock(arg, flags=FLAGS)
632
+ # self.original_source = self._original_arg_as_source
633
+ elif arg_mode == "auto":
634
+ if source is not None:
635
+ raise ValueError(
636
+ "UserExpr(): source argument not allowed for auto")
637
+ if not isinstance(arg, str):
638
+ raise ValueError(
639
+ "UserExpr(): arg must be a string if arg_mode='auto'")
640
+ self._original_arg_as_source = PythonBlock(arg, flags=FLAGS)
641
+ else:
642
+ raise ValueError("UserExpr(): bad arg_mode=%r" % (arg_mode,))
643
+
644
+ def _infer_and_evaluate(self):
645
+ if self._original_arg_mode == "raw_value":
646
+ pass
647
+ elif self._original_arg_mode == "eval":
648
+ block = self._original_arg_as_source
649
+ if not (str(block).strip()):
650
+ raise ValueError("empty input")
651
+ self.value = self._namespace.auto_eval(block)
652
+ self.source = self._original_arg_as_source #.pretty_print() # TODO
653
+ elif self._original_arg_mode == "auto":
654
+ block = self._original_arg_as_source
655
+ ERROR = object()
656
+ if not (str(block).strip()):
657
+ value = ERROR
658
+ elif not block.parsable_as_expression:
659
+ value = ERROR
660
+ else:
661
+ try:
662
+ value = self._namespace.auto_eval(block)
663
+ except UnimportableNameError:
664
+ value = ERROR
665
+ if value is ERROR:
666
+ # self.inferred_arg_mode = "raw_value"
667
+ self.value = self._original_arg
668
+ # self.original_source = None
669
+ self.source = PythonBlock(repr(self.value))
670
+ else:
671
+ # self.inferred_arg_mode = "eval"
672
+ self.value = value
673
+ # self.original_source = block
674
+ self.source = block #.pretty_print() # TODO
675
+ else:
676
+ raise AssertionError("internal error")
677
+ self._infer_and_evaluate = lambda: None
678
+
679
+ def __getattr__(self, k):
680
+ self._infer_and_evaluate()
681
+ return object.__getattribute__(self, k)
682
+
683
+ def __str__(self):
684
+ return str(self._original_arg)
685
+
686
+
687
+ def _parse_auto_apply_args(argspec, commandline_args, namespace, arg_mode="auto"):
688
+ """
689
+ Parse command-line arguments heuristically. Arguments that can be
690
+ evaluated are evaluated; otherwise they are treated as strings.
691
+
692
+ :returns:
693
+ ``args``, ``kwargs``
694
+ """
695
+ # This is implemented manually instead of using optparse or argparse. We
696
+ # do so because neither supports dynamic keyword arguments well. Optparse
697
+ # doesn't support parsing known arguments only, and argparse doesn't
698
+ # support turning off interspersed positional arguments.
699
+ def make_expr(arg, arg_mode=arg_mode):
700
+ return UserExpr(arg, namespace, arg_mode)
701
+
702
+ # Create a map from argname to default value.
703
+ defaults = argspec.defaults or ()
704
+ argname2default = {}
705
+ for argname, default in zip(argspec.args[len(argspec.args)-len(defaults):],
706
+ defaults):
707
+ argname2default[argname] = make_expr(default, "raw_value")
708
+ if argspec.kwonlydefaults:
709
+ for argname, default in argspec.kwonlydefaults.items():
710
+ argname2default[argname] = make_expr(default, "raw_value")
711
+ # Create a map from prefix to arguments with that prefix. E.g. {"foo":
712
+ # ["foobar", "foobaz"]}
713
+ prefix2argname = {}
714
+ for argname in argspec.args:
715
+ for prefix in prefixes(argname):
716
+ prefix2argname.setdefault(prefix, []).append(argname)
717
+ for argname in argspec.kwonlyargs:
718
+ for prefix in prefixes(argname):
719
+ prefix2argname.setdefault(prefix, []).append(argname)
720
+ # Enumerate over input arguments.
721
+ got_pos_args = []
722
+ got_keyword_args = {}
723
+ args = list(commandline_args)
724
+ while args:
725
+ arg = args.pop(0)
726
+ if arg in ["--?", "-?", "?"]:
727
+ raise _ParseInterruptedWantHelp
728
+ elif arg in ["--??", "-??", "??"]:
729
+ raise _ParseInterruptedWantSource
730
+ elif arg.startswith("-"):
731
+ if arg == "-":
732
+ # Read from stdin and stuff into next argument as a string.
733
+ data = sys.stdin.read()
734
+ got_pos_args.append(make_expr(data, "string"))
735
+ continue
736
+ elif arg == "--":
737
+ # Treat remaining arguments as strings.
738
+ got_pos_args.extend([make_expr(x, "string") for x in args])
739
+ del args[:]
740
+ continue
741
+ elif arg.startswith("--"):
742
+ argname = arg[2:]
743
+ else:
744
+ argname = arg[1:]
745
+ argname, equalsign, value = argname.partition("=")
746
+ argname = argname.replace("-", "_")
747
+ if not is_identifier(argname):
748
+ raise ParseError("Invalid option name %s" % (argname,))
749
+ matched_argnames = prefix2argname.get(argname, [])
750
+ if len(matched_argnames) == 1:
751
+ argname, = matched_argnames
752
+ elif len(matched_argnames) == 0:
753
+ if equalsign == "":
754
+ if argname in ["help", "h"]:
755
+ raise _ParseInterruptedWantHelp
756
+ if argname in ["source"]:
757
+ raise _ParseInterruptedWantSource
758
+ if not argspec.varkw:
759
+ raise ParseError("Unknown option name %s" %
760
+ (argname,))
761
+
762
+ elif len(matched_argnames) > 1:
763
+ raise ParseError(
764
+ "Ambiguous %s: could mean one of: %s"
765
+ % (argname,
766
+ ", ".join("--%s"%s for s in matched_argnames)))
767
+ else:
768
+ raise AssertionError
769
+ if not value:
770
+ try:
771
+ value = args.pop(0)
772
+ except IndexError:
773
+ raise ParseError("Missing argument to %s" % (arg,))
774
+ if value.startswith("--"):
775
+ raise ParseError(
776
+ "Missing argument to %s. "
777
+ "If you really want to use %r as the argument to %s, "
778
+ "then use %s=%s."
779
+ % (arg, value, arg, arg, value))
780
+ got_keyword_args[argname] = make_expr(value)
781
+ else:
782
+ got_pos_args.append(make_expr(arg))
783
+
784
+ parsed_args = []
785
+ parsed_kwargs = {}
786
+ for i, argname in enumerate(argspec.args):
787
+ if i < len(got_pos_args):
788
+ if argname in got_keyword_args:
789
+ raise ParseError(
790
+ "%s specified both as positional argument (%s) "
791
+ "and keyword argument (%s)"
792
+ % (argname, got_pos_args[i], got_keyword_args[argname]))
793
+ expr = got_pos_args[i]
794
+ else:
795
+ try:
796
+ expr = got_keyword_args.pop(argname)
797
+ except KeyError:
798
+ try:
799
+ expr = argname2default[argname]
800
+ except KeyError:
801
+ raise ParseError(
802
+ "missing required argument %s" % (argname,))
803
+ try:
804
+ value = expr.value
805
+ except Exception as e:
806
+ raise ParseError(
807
+ "Error parsing value for --%s=%s: %s: %s"
808
+ % (argname, expr, type(e).__name__, e))
809
+ parsed_args.append(value)
810
+
811
+ for argname in argspec.kwonlyargs:
812
+ try:
813
+ expr = got_keyword_args.pop(argname)
814
+ except KeyError:
815
+ try:
816
+ expr = argname2default[argname]
817
+ except KeyError:
818
+ raise ParseError(
819
+ "missing required keyword argument %s" % (argname,))
820
+
821
+ try:
822
+ value = expr.value
823
+ except Exception as e:
824
+ raise ParseError(
825
+ "Error parsing value for --%s=%s: %s: %s"
826
+ % (argname, expr, type(e).__name__, e))
827
+ parsed_kwargs[argname] = value
828
+
829
+ if len(got_pos_args) > len(argspec.args):
830
+ if argspec.varargs:
831
+ for expr in got_pos_args[len(argspec.args):]:
832
+ try:
833
+ value = expr.value
834
+ except Exception as e:
835
+ raise ParseError(
836
+ "Error parsing value for *%s: %s: %s: %s"
837
+ % (argspec.varargs, expr, type(e).__name__, e))
838
+ parsed_args.append(value)
839
+ else:
840
+ max_nargs = len(argspec.args)
841
+ if argspec.defaults:
842
+ expected = "%d-%d" % (max_nargs-len(argspec.defaults),max_nargs)
843
+ else:
844
+ expected = "%d" % (max_nargs,)
845
+ raise ParseError(
846
+ "Too many positional arguments. "
847
+ "Expected %s positional argument(s): %s. Got %d args: %s"
848
+ % (expected, ", ".join(argspec.args),
849
+ len(got_pos_args), " ".join(map(str, got_pos_args))))
850
+
851
+ for argname, expr in sorted(got_keyword_args.items()):
852
+ try:
853
+ parsed_kwargs[argname] = expr.value
854
+ except Exception as e:
855
+ raise ParseError(
856
+ "Error parsing value for --%s=%s: %s: %s"
857
+ % (argname, expr, type(e).__name__, e))
858
+
859
+ return parsed_args, parsed_kwargs
860
+
861
+
862
+ def _format_call(function_name:str, argspec, args, kwargs):
863
+ # TODO: print original unparsed arg strings
864
+ defaults = argspec.defaults or ()
865
+ first_with_default = len(argspec.args) - len(defaults)
866
+ argparts = []
867
+ for i in range(max(len(args), len(argspec.args))):
868
+ if i >= first_with_default and len(args) <= len(argspec.args):
869
+ argparts.append("%s=%r" % (argspec.args[i], args[i]))
870
+ else:
871
+ argparts.append(repr(args[i]))
872
+ for k, v in sorted(kwargs.items()):
873
+ argparts.append("%s=%r" % (k, v))
874
+ if _requires_parens_as_function(function_name):
875
+ function_name = "(%s)" % (function_name,)
876
+ r = "%s(%s)" % (function_name, ", ".join(argparts))
877
+ return r
878
+
879
+
880
+ class UnimportableNameError(NameError):
881
+ pass
882
+
883
+
884
+ class NotAFunctionError(Exception):
885
+ pass
886
+
887
+
888
+ def _get_help(expr, verbosity=1):
889
+ """
890
+ Construct a help string.
891
+
892
+ :type expr:
893
+ `UserExpr`
894
+ :param expr:
895
+ Object to generate help for.
896
+ :rtype:
897
+ ``str``
898
+ """
899
+ # TODO: colorize headers
900
+ result = ""
901
+ obj = expr.value
902
+ name:str = str(expr.source)
903
+ if callable(obj):
904
+ prefix = os.path.basename(sys.orig_argv[0]) + " "
905
+ result += _build_function_usage_string(name, obj, prefix)
906
+ if verbosity == 0:
907
+ include_filename = False
908
+ include_doc = False
909
+ include_source = False
910
+ elif verbosity == 1:
911
+ include_filename = True
912
+ include_doc = True
913
+ include_source = False
914
+ elif verbosity == 2:
915
+ include_filename = True
916
+ include_doc = False
917
+ include_source = True
918
+ else:
919
+ raise ValueError("invalid verbosity=%r" % (verbosity,))
920
+ try:
921
+ version = obj.__version__
922
+ except Exception:
923
+ pass
924
+ else:
925
+ result += "\nVersion:\n %s\n" % (version,)
926
+ if include_filename:
927
+ try:
928
+ filename = inspect.getfile(obj)
929
+ except Exception:
930
+ pass
931
+ else:
932
+ result += "\nFilename:\n %s\n" % (filename,)
933
+ if include_source:
934
+ try:
935
+ source = inspect.getsource(obj)
936
+ except Exception:
937
+ source = ""
938
+ if source:
939
+ # TODO: colorize source
940
+ result += "\nSource:\n%s\n" % (indent(source, " "))
941
+ else:
942
+ source = "(Not available)"
943
+ include_doc = True
944
+ if include_doc:
945
+ doc = (inspect.getdoc(obj) or "").strip() or "(No docstring)"
946
+ result += "\nDocstring:\n%s" % (indent(doc, " "))
947
+ return result
948
+
949
+
950
+ _enable_postmortem_debugger = None
951
+
952
+ def _handle_user_exception(exc_info=None):
953
+ """
954
+ Handle an exception in user code.
955
+ """
956
+ # TODO: Make tracebacks show user code being executed. IPython does that
957
+ # by stuffing linecache.cache, and also advising linecache.checkcache. We
958
+ # can either advise it ourselves also, or re-use IPython.core.compilerop.
959
+ # Probably better to advise ourselves. Add "<pyflyby-input-md5[:12]>" to
960
+ # linecache. Perhaps do it in a context manager that removes from
961
+ # linecache when done. Advise checkcache to protect any "<pyflyby-*>".
962
+ # Do it for all code compiled from here, including args, debug_statement,
963
+ # etc.
964
+ if exc_info is None:
965
+ exc_info = sys.exc_info()
966
+ if exc_info[2].tb_next:
967
+ exc_info = (exc_info[0], exc_info[1],
968
+ exc_info[2].tb_next) # skip this traceback
969
+ # If ``_enable_postmortem_debugger`` is enabled, then debug the exception.
970
+ # By default, this is enabled run running in a tty.
971
+ # We check isatty(1) here because we want 'py ... | cat' to never go into
972
+ # the debugger. Note that debugger() also checks whether /dev/tty is
973
+ # usable (and if not, waits for attach).
974
+ if _enable_postmortem_debugger:
975
+ # *** Run debugger. ***
976
+ debugger(exc_info)
977
+ # TODO: consider using print_verbose_tb(*exc_info)
978
+ import traceback
979
+ traceback.print_exception(*exc_info)
980
+ raise SystemExit(1)
981
+
982
+
983
+ def auto_apply(function, commandline_args, namespace, arg_mode=None,
984
+ debug=False):
985
+ """
986
+ Call ``function`` on command-line arguments. Arguments can be positional
987
+ or keyword arguments like "--foo=bar". Arguments are by default
988
+ heuristically evaluated.
989
+
990
+ :type function:
991
+ ``UserExpr``
992
+ :param function:
993
+ Function to apply.
994
+ :type commandline_args:
995
+ ``list`` of ``str``
996
+ :param commandline_args:
997
+ Arguments to ``function`` as strings.
998
+ :param arg_mode:
999
+ How to interpret ``commandline_args``. If ``"string"``, then treat them
1000
+ as literal strings. If ``"eval"``, then evaluate all arguments as
1001
+ expressions. If ``"auto"`` (the default), then heuristically decide
1002
+ whether to treat as expressions or strings.
1003
+ """
1004
+ if not isinstance(function, UserExpr):
1005
+ raise TypeError
1006
+ if not callable(function.value):
1007
+ raise NotAFunctionError("Not a function", function.value)
1008
+ arg_mode = _interpret_arg_mode(arg_mode, default="auto")
1009
+ # Parse command-line arguments.
1010
+ argspec = _get_argspec(function.value)
1011
+ try:
1012
+ args, kwargs = _parse_auto_apply_args(argspec, commandline_args,
1013
+ namespace, arg_mode=arg_mode)
1014
+ except _ParseInterruptedWantHelp:
1015
+ usage = _get_help(function, verbosity=1)
1016
+ print(usage)
1017
+ raise SystemExit(0)
1018
+ except _ParseInterruptedWantSource:
1019
+ usage = _get_help(function, verbosity=2)
1020
+ print(usage)
1021
+ raise SystemExit(0)
1022
+ except ParseError as e:
1023
+ # Failed parsing command-line arguments. Print usage.
1024
+ logger.error(e)
1025
+ usage = _get_help(function, verbosity=(1 if logger.info_enabled else 0))
1026
+ sys.stderr.write("\n" + usage)
1027
+ raise SystemExit(1)
1028
+ # Log what we're doing.
1029
+
1030
+ logger.info("%s", _format_call(str(function.source), argspec, args, kwargs))
1031
+
1032
+ # *** Call the function. ***
1033
+ f = function.value
1034
+ try:
1035
+ if debug:
1036
+ result = debug_statement("f(*args, **kwargs)")
1037
+ else:
1038
+ result = f(*args, **kwargs)
1039
+ return result
1040
+ except SystemExit:
1041
+ raise
1042
+ # TODO: handle "quit" by user here specially instead of returning None.
1043
+ # May need to reimplement pdb.runeval() so we can catch BdbQuit.
1044
+ except:
1045
+ # Handle exception in user code.
1046
+ _handle_user_exception()
1047
+
1048
+
1049
+ @total_ordering
1050
+ class LoggedList(object):
1051
+ """
1052
+ List that logs which members have not yet been accessed (nor removed).
1053
+ """
1054
+
1055
+ _ACCESSED = object()
1056
+
1057
+ def __init__(self, items):
1058
+ self._items = list(items)
1059
+ self._unaccessed = list(self._items)
1060
+
1061
+ def append(self, x):
1062
+ self._unaccessed.append(self._ACCESSED)
1063
+ self._items.append(x)
1064
+
1065
+ def count(self):
1066
+ return self._items.count()
1067
+
1068
+ def extend(self, new_items):
1069
+ new_items = list(new_items)
1070
+ self._unaccessed.extend([self._ACCESSED] * len(new_items))
1071
+ self._items.extend(new_items)
1072
+
1073
+ def index(self, x, *start_stop):
1074
+ index = self._items.index(x, *start_stop) # may raise ValueError
1075
+ self._unaccessed[index] = self._ACCESSED
1076
+ return index
1077
+
1078
+ def insert(self, index, x):
1079
+ self._unaccessed.insert(index, self._ACCESSED)
1080
+ self._items.insert(index, x)
1081
+
1082
+ def pop(self, index):
1083
+ self._unaccessed.pop(index)
1084
+ return self._items.pop(index)
1085
+
1086
+ def remove(self, x):
1087
+ index = self._items.index(x)
1088
+ self.pop(index)
1089
+
1090
+ def reverse(self):
1091
+ self._items.reverse()
1092
+ self._unaccessed.reverse()
1093
+
1094
+ def sort(self):
1095
+ indexes = range(len(self._items))
1096
+ indexes.sort(key=self._items.__getitem__) # argsort
1097
+ self._items = [self._items[i] for i in indexes]
1098
+ self._unaccessed = [self._unaccessed[i] for i in indexes]
1099
+
1100
+ def __add__(self, other):
1101
+ return self._items + other
1102
+
1103
+ def __contains__(self, x):
1104
+ try:
1105
+ self.index(x)
1106
+ return True
1107
+ except ValueError:
1108
+ return False
1109
+
1110
+ def __delitem__(self, x):
1111
+ del self._items[x]
1112
+ del self._unaccessed[x]
1113
+
1114
+ def __eq__(self, other):
1115
+ if not isinstance(other, LoggedList):
1116
+ return NotImplemented
1117
+ return self._items == other._items
1118
+
1119
+ def __ne__(self, other):
1120
+ return not (self == other)
1121
+
1122
+ # The rest are defined by total_ordering
1123
+ def __lt__(self, other):
1124
+ if not isinstance(other, LoggedList):
1125
+ return NotImplemented
1126
+ return self._items < other._items
1127
+
1128
+ def __cmp__(self, x):
1129
+ return cmp(self._items, x)
1130
+
1131
+ def __getitem__(self, idx):
1132
+ result = self._items[idx]
1133
+ if isinstance(idx, slice):
1134
+ self._unaccessed[idx] = [self._ACCESSED]*len(result)
1135
+ else:
1136
+ self._unaccessed[idx] = self._ACCESSED
1137
+ return result
1138
+
1139
+ def __hash__(self):
1140
+ raise TypeError("unhashable type: 'LoggedList'")
1141
+
1142
+ def __iadd__(self, x):
1143
+ self.extend(x)
1144
+
1145
+ def __imul__(self, n):
1146
+ self._items *= n
1147
+ self._unaccessed *= n
1148
+
1149
+ def __iter__(self):
1150
+ # Todo: detect mutation while iterating.
1151
+ for i, x in enumerate(self._items):
1152
+ self._unaccessed[i] = self._ACCESSED
1153
+ yield x
1154
+
1155
+ def __len__(self):
1156
+ return len(self._items)
1157
+
1158
+
1159
+ def __mul__(self, n):
1160
+ return self._items * n
1161
+
1162
+ def __reduce__(self):
1163
+ return
1164
+
1165
+ def __repr__(self):
1166
+ self._unaccessed[:] = [self._ACCESSED]*len(self._unaccessed)
1167
+ return repr(self._items)
1168
+
1169
+ def __reversed__(self):
1170
+ # Todo: detect mutation while iterating.
1171
+ for i in reversed(range(len(self._items))):
1172
+ self._unaccessed[i] = self._ACCESSED
1173
+ yield self._items[i]
1174
+
1175
+ def __rmul__(self, n):
1176
+ return self._items * n
1177
+
1178
+ def __setitem__(self, idx, value):
1179
+ self._items[idx] = value
1180
+ if isinstance(idx, slice):
1181
+ self._unaccessed[idx] = [self._ACCESSED]*len(value)
1182
+ else:
1183
+ self._unaccessed[idx] = value
1184
+
1185
+ def __str__(self):
1186
+ self._unaccessed[:] = [self._ACCESSED]*len(self._unaccessed)
1187
+ return str(self._items)
1188
+
1189
+ @property
1190
+ def unaccessed(self):
1191
+ return [x for x in self._unaccessed if x is not self._ACCESSED]
1192
+
1193
+
1194
+ @contextmanager
1195
+ def SysArgvCtx(*args):
1196
+ """
1197
+ Context manager that:
1198
+ * Temporarily sets sys.argv = args.
1199
+ * At end of context, complains if any args were never accessed.
1200
+ """
1201
+ # There should always be at least one arg, since the first one is
1202
+ # the program name.
1203
+ if not args:
1204
+ raise ValueError("Missing args")
1205
+ nargs = len(args) - 1
1206
+ # Create a list proxy that will log accesses.
1207
+ argv = LoggedList(args)
1208
+ # Don't consider first argument to be interesting.
1209
+ argv[0]
1210
+ orig_argv = list(sys.argv)
1211
+ try:
1212
+ sys.argv = argv
1213
+ # Run context code.
1214
+ yield
1215
+ # Complain if there are unaccessed arguments.
1216
+ unaccessed = argv.unaccessed
1217
+ if not unaccessed:
1218
+ pass
1219
+ else:
1220
+ if nargs == 1:
1221
+ msg = ("You specified a command-line argument, but your code didn't use it: %s"
1222
+ % (unaccessed[0],))
1223
+ elif len(unaccessed) == nargs:
1224
+ msg = ("You specified %d command-line arguments, but your code didn't use them: %s"
1225
+ % (len(unaccessed), " ".join(unaccessed)))
1226
+ else:
1227
+ msg = ("You specified %d command-line arguments, but your code didn't use %d of them: %s"
1228
+ % (nargs, len(unaccessed), " ".join(unaccessed)))
1229
+ msg2 = "\nIf this is intentional, access 'sys.argv[:]' somewhere in your code."
1230
+ logger.error(msg + msg2)
1231
+ raise SystemExit(1)
1232
+ finally:
1233
+ sys.argv = orig_argv
1234
+
1235
+
1236
+ def _as_filename_if_seems_like_filename(arg):
1237
+ """
1238
+ If ``arg`` seems like a filename, then return it as one.
1239
+
1240
+ >>> bool(_as_filename_if_seems_like_filename("foo.py"))
1241
+ True
1242
+
1243
+ >>> bool(_as_filename_if_seems_like_filename("%foo.py"))
1244
+ False
1245
+
1246
+ >>> bool(_as_filename_if_seems_like_filename("foo(bar)"))
1247
+ False
1248
+
1249
+ >>> bool(_as_filename_if_seems_like_filename("/foo/bar/baz.quux-660470"))
1250
+ True
1251
+
1252
+ >>> bool(_as_filename_if_seems_like_filename("../foo/bar-24084866"))
1253
+ True
1254
+
1255
+ :type arg:
1256
+ ``str``
1257
+ :rtype:
1258
+ ``Filename``
1259
+ """
1260
+ try:
1261
+ filename = Filename(arg)
1262
+ except UnsafeFilenameError:
1263
+ # If the filename isn't a "safe" filename, then don't treat it as one,
1264
+ # and don't even check whether it exists. This means that for an
1265
+ # argument like "foo(bar)" or "lambda x:x*y" we won't even check
1266
+ # existence. This is both a performance optimization and a safety
1267
+ # valve to avoid unsafe filenames being created to intercept expressions.
1268
+ return None
1269
+ # If the argument "looks" like a filename and is unlikely to be a python
1270
+ # expression, then assume it is a filename. We assume so regardless of
1271
+ # whether the file actually exists; if it turns out to not exist, we'll
1272
+ # complain later.
1273
+ if arg.startswith("/") or arg.startswith("./") or arg.startswith("../"):
1274
+ return filename
1275
+ if filename.ext == ".py":
1276
+ # TODO: .pyc, .pyo
1277
+ return which(arg) or filename
1278
+ # Even if it doesn't obviously look like a filename, but it does exist as
1279
+ # a filename, then use it as one.
1280
+ if filename.exists:
1281
+ return filename
1282
+ # If it's a plain name and we can find an executable on $PATH, then use
1283
+ # that.
1284
+ if re.match("^[a-zA-Z0-9_-]+$", arg):
1285
+ filename = which(arg)
1286
+ if not filename:
1287
+ return None
1288
+ if not _has_python_shebang(filename):
1289
+ logger.debug("Found %s but it doesn't seem like a python script",
1290
+ filename)
1291
+ return None
1292
+ return filename
1293
+ return None
1294
+
1295
+
1296
+ def _has_python_shebang(filename):
1297
+ """
1298
+ Return whether the first line contains #!...python...
1299
+
1300
+ Used for confirming that an executable script found via which() is
1301
+ actually supposed to be a python script.
1302
+
1303
+ Note that this test is only needed for scripts found via which(), since
1304
+ otherwise the shebang is not necessary.
1305
+ """
1306
+ filename = Filename(filename)
1307
+ with open(str(filename), 'rb') as f:
1308
+ line = f.readline(1024)
1309
+ return line.startswith(b"#!") and b"python" in line
1310
+
1311
+
1312
+
1313
+ def _interpret_arg_mode(arg, default="auto"):
1314
+ """
1315
+ >>> _interpret_arg_mode("Str")
1316
+ 'string'
1317
+ """
1318
+ if arg is None:
1319
+ arg = default
1320
+ if arg == "auto" or arg == "eval" or arg == "string":
1321
+ return arg # optimization for interned strings
1322
+ rarg = str(arg).strip().lower()
1323
+ if rarg in ["eval", "evaluate", "exprs", "expr", "expressions", "expression", "e"]:
1324
+ return "eval"
1325
+ elif rarg in ["strings", "string", "str", "strs", "literal", "literals", "s"]:
1326
+ return "string"
1327
+ elif rarg in ["auto", "automatic", "a"]:
1328
+ return "auto"
1329
+ elif rarg == "error":
1330
+ # Intentionally not documented to user
1331
+ return "error"
1332
+ else:
1333
+ raise ValueError(
1334
+ "Invalid arg_mode=%r; expected one of eval/string/auto"
1335
+ % (arg,))
1336
+
1337
+
1338
+ def _interpret_output_mode(arg, default="interactive"):
1339
+ """
1340
+ >>> _interpret_output_mode('Repr_If_Not_None')
1341
+ 'repr-if-not-none'
1342
+ """
1343
+ if arg is None:
1344
+ arg = default
1345
+ rarg = str(arg).strip().lower().replace("-", "").replace("_", "")
1346
+ if rarg in ["none", "no", "n", "silent"]:
1347
+ return "silent"
1348
+ elif rarg in ["interactive", "i"]:
1349
+ return "interactive"
1350
+ elif rarg in ["print", "p", "string", "str"]:
1351
+ return "str"
1352
+ elif rarg in ["repr", "r"]:
1353
+ return "repr"
1354
+ elif rarg in ["pprint", "pp"]:
1355
+ return "pprint"
1356
+ elif rarg in ["reprifnotnone", "reprunlessnone", "rn"]:
1357
+ return "repr-if-not-none"
1358
+ elif rarg in ["pprintifnotnone", "pprintunlessnone", "ppn"]:
1359
+ return "pprint-if-not-none"
1360
+ elif rarg in ["systemexit", "exit", "raise"]:
1361
+ return "exit"
1362
+ else:
1363
+ raise ValueError(
1364
+ "Invalid output=%r; expected one of "
1365
+ "silent/interactive/str/repr/pprint/repr-if-not-none/pprint-if-not-none/exit"
1366
+ % (arg,))
1367
+
1368
+
1369
+ def print_result(result, output_mode):
1370
+ output_mode = _interpret_output_mode(output_mode)
1371
+ if output_mode == "silent":
1372
+ return
1373
+ if output_mode == "interactive":
1374
+ # TODO: support IPython output stuff (text/plain)
1375
+ try:
1376
+ idisp = result.__interactive_display__
1377
+ except Exception:
1378
+ output_mode = "pprint-if-not-none"
1379
+ else:
1380
+ result = idisp()
1381
+ output_mode = "print-if-not-none"
1382
+ # Fall through.
1383
+ if output_mode == "str":
1384
+ print(str(result))
1385
+ elif output_mode == "repr":
1386
+ print(repr(result))
1387
+ elif output_mode == "pprint":
1388
+ import pprint
1389
+ pprint.pprint(result) # or equivalently, print pprint.pformat(result)
1390
+ elif output_mode == "repr-if-not-none":
1391
+ if result is not None:
1392
+ print(repr(result))
1393
+ elif output_mode == "print-if-not-none":
1394
+ if result is not None:
1395
+ print(result)
1396
+ elif output_mode == "pprint-if-not-none":
1397
+ if result is not None:
1398
+ import pprint
1399
+ pprint.pprint(result)
1400
+ elif output_mode == "exit":
1401
+ # TODO: only raise at end after pre_exit
1402
+ raise SystemExit(result)
1403
+ else:
1404
+ raise AssertionError("unexpected output_mode=%r" % (output_mode,))
1405
+
1406
+
1407
+ class _Namespace(object):
1408
+
1409
+ def __init__(self):
1410
+ self.globals = {"__name__": "__main__",
1411
+ "__builtin__": builtins,
1412
+ "__builtins__": builtins}
1413
+ self.autoimported = {}
1414
+
1415
+ def auto_import(self, arg):
1416
+ return auto_import(arg, [self.globals], autoimported=self.autoimported)
1417
+
1418
+ def auto_eval(self, block, mode=None, info=False, auto_import=True,
1419
+ debug=False):
1420
+ """
1421
+ Evaluate ``block`` with auto-importing.
1422
+ """
1423
+ # Equivalent to::
1424
+ # auto_eval(arg, mode=mode, flags=FLAGS, globals=self.globals)
1425
+ # but better logging and error raising.
1426
+ if not isinstance(block, PythonBlock):
1427
+ block = PythonBlock(block, flags=FLAGS, auto_flags=True)
1428
+ if auto_import and not self.auto_import(block):
1429
+ missing = find_missing_imports(block, [self.globals])
1430
+ mstr = ", ".join(repr(str(x)) for x in missing)
1431
+ if len(missing) == 1:
1432
+ msg = "name %s is not defined and not importable" % mstr
1433
+ elif len(missing) > 1:
1434
+ msg = "names %s are not defined and not importable" % mstr
1435
+ else:
1436
+ raise AssertionError
1437
+ raise UnimportableNameError(msg)
1438
+ if info:
1439
+ logger.info(block)
1440
+ try:
1441
+ # TODO: enter text into linecache
1442
+ if debug:
1443
+ return debug_statement(block, self.globals)
1444
+ else:
1445
+ code = block.compile(mode=mode)
1446
+ return eval(code, self.globals, self.globals)
1447
+ except SystemExit:
1448
+ raise
1449
+ except:
1450
+ _handle_user_exception()
1451
+
1452
+ def __repr__(self):
1453
+ return "<{} object at 0x{:0x} \nglobals:{} \nautoimported:{}>".format(
1454
+ type(self).__name__, id(self), self.globals, self.autoimported
1455
+ )
1456
+
1457
+
1458
+ class _PyMain(object):
1459
+
1460
+ def __init__(self, args):
1461
+ self.main_args = args
1462
+ self.namespace = _Namespace()
1463
+ self.result = None
1464
+ self.ipython_app = None
1465
+
1466
+ def exec_stdin(self, cmd_args):
1467
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1468
+ output_mode = _interpret_output_mode(self.output_mode, default="silent")
1469
+ cmd_args = [UserExpr(a, self.namespace, arg_mode).value
1470
+ for a in cmd_args]
1471
+ with SysArgvCtx(*cmd_args):
1472
+ result = self.namespace.auto_eval(Filename.STDIN, debug=self.debug)
1473
+ print_result(result, output_mode)
1474
+ self.result = result
1475
+
1476
+ def eval(self, cmd, cmd_args):
1477
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1478
+ output_mode = _interpret_output_mode(self.output_mode)
1479
+ cmd_args = [UserExpr(a, self.namespace, arg_mode).value
1480
+ for a in cmd_args]
1481
+ with SysArgvCtx("-c", *cmd_args):
1482
+ cmd = PythonBlock(cmd)
1483
+ result = self.namespace.auto_eval(cmd, info=True, debug=self.debug)
1484
+ # TODO: make auto_eval() plow ahead even if there are unimportable
1485
+ # names, after warning
1486
+ print_result(result, output_mode)
1487
+ self.result = result
1488
+
1489
+ def execfile(self, filename_arg, cmd_args):
1490
+ # TODO: pass filename to import db target_filename; unit test.
1491
+ # TODO: set __file__
1492
+ # TODO: support compiled (.pyc/.pyo) files
1493
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1494
+ output_mode = _interpret_output_mode(self.output_mode)
1495
+ cmd_args = [UserExpr(a, self.namespace, arg_mode).value
1496
+ for a in cmd_args]
1497
+ additional_msg = ""
1498
+ if isinstance(filename_arg, Filename):
1499
+ filename = filename_arg
1500
+ elif filename_arg == "-":
1501
+ filename = Filename.STDIN
1502
+ elif "/" in filename_arg:
1503
+ filename = Filename(filename_arg)
1504
+ else:
1505
+ filename = which(filename_arg)
1506
+ if not filename:
1507
+ filename = Filename(filename_arg)
1508
+ additional_msg = (" (and didn't find %s on $PATH)"
1509
+ % (filename_arg,))
1510
+ elif not _has_python_shebang(filename):
1511
+ additional_msg = (" (found %s but it doesn't look "
1512
+ "like python source"
1513
+ % (filename,))
1514
+ filename = Filename(filename_arg)
1515
+ if not filename.exists:
1516
+ raise Exception("No such file: %s%s" % (filename, additional_msg))
1517
+ with SysArgvCtx(str(filename), *cmd_args):
1518
+ sys.path.insert(0, str(filename.dir))
1519
+ self.namespace.globals["__file__"] = str(filename)
1520
+ result = self.namespace.auto_eval(filename, debug=self.debug)
1521
+ print_result(result, output_mode)
1522
+ self.result = result
1523
+
1524
+ def apply(self, function, cmd_args):
1525
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="auto")
1526
+ output_mode = _interpret_output_mode(self.output_mode)
1527
+ # Todo: what should we set argv to?
1528
+ result = auto_apply(function, cmd_args, self.namespace, arg_mode,
1529
+ debug=self.debug)
1530
+ print_result(result, output_mode)
1531
+ self.result = result
1532
+
1533
+ def _seems_like_runnable_module(self, arg):
1534
+ if not is_identifier(arg, dotted=True):
1535
+ # It's not a single (dotted) identifier.
1536
+ return False
1537
+ if not find_missing_imports(arg, [{}]):
1538
+ # It's off of a builtin, e.g. "str.upper"
1539
+ return False
1540
+ m = ModuleHandle(arg)
1541
+ if m.parent:
1542
+ # Auto-import the parent, which is necessary in order to get the
1543
+ # filename of the module. ``ModuleHandle.filename`` does this
1544
+ # automatically, but we do it explicitly here so that we log
1545
+ # the import of the parent module.
1546
+ if not self.namespace.auto_import(str(m.parent.name)):
1547
+ return False
1548
+ if not m.filename:
1549
+ logger.debug("Module %s doesn't have a source filename", m)
1550
+ return False
1551
+ # TODO: check that the source accesses __main__ (ast traversal?)
1552
+ return True
1553
+
1554
+ def heuristic_cmd(self, cmd, cmd_args, function_name=None):
1555
+ output_mode = _interpret_output_mode(self.output_mode)
1556
+ # If the "command" is just a module name, then call run_module. Make
1557
+ # sure we check that it's not a builtin.
1558
+ if self._seems_like_runnable_module(str(cmd)):
1559
+ self.heuristic_run_module(str(cmd), cmd_args)
1560
+ return
1561
+ # FIXME TODO heed arg_mode for non-apply case. This is tricky to
1562
+ # implement; will require assigning some proxy class to sys.argv
1563
+ # that's more sophisticated than just logging.
1564
+ with SysArgvCtx("-c", *cmd_args):
1565
+ # Log the expression before we evaluate it, unless we're likely to
1566
+ # log another substantially similar line. (We can only guess
1567
+ # heuristically whether it's interesting enough to log it. And we
1568
+ # can't know whether the result will be callable until we evaluate
1569
+ # it.)
1570
+ info = not re.match("^[a-zA-Z0-9_.]+$", function_name)
1571
+ result = self.namespace.auto_eval(cmd, info=info, debug=self.debug)
1572
+ if callable(result):
1573
+ function = UserExpr(
1574
+ result, self.namespace, "raw_value", function_name)
1575
+ result = auto_apply(function, cmd_args, self.namespace,
1576
+ self.arg_mode, debug=self.debug)
1577
+ print_result(result, output_mode)
1578
+ self.result = result
1579
+ sys.argv[:] # mark as accessed
1580
+ else:
1581
+ if not info:
1582
+ # We guessed wrong earlier and didn't log yet; log now.
1583
+ logger.info(cmd)
1584
+ print_result(result, output_mode)
1585
+ self.result = result
1586
+ unaccessed = sys.argv.unaccessed
1587
+ if unaccessed:
1588
+ logger.error(
1589
+ "%s is not callable. Unexpected argument(s): %s",
1590
+ result, " ".join(unaccessed))
1591
+ sys.argv[:] # don't complain again
1592
+
1593
+ def run_module(self, module, args):
1594
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1595
+ if arg_mode != "string":
1596
+ raise NotImplementedError(
1597
+ "run_module only supports string arguments")
1598
+ module = ModuleHandle(module)
1599
+ logger.info("python -m %s", ' '.join([str(module.name)] + args))
1600
+ # Imitate 'python -m'.
1601
+ # TODO: include only the traceback below runpy.run_module
1602
+ # os.execvp(sys.executable, [sys.argv[0], "-m", modname] + args)
1603
+ sys.argv = [str(module.filename)] + args
1604
+ import runpy
1605
+ if self.debug:
1606
+ # TODO: break closer to user code
1607
+ debugger()
1608
+ try:
1609
+ runpy.run_module(str(module.name), run_name="__main__")
1610
+ except SystemExit:
1611
+ raise
1612
+ except:
1613
+ _handle_user_exception()
1614
+
1615
+ def heuristic_run_module(self, module, args):
1616
+ module = ModuleHandle(module)
1617
+ # If the user ran 'py numpy --version', then print the numpy
1618
+ # version, i.e. same as 'py --version numpy'. This has the
1619
+ # downside of shadowing a possible "--version" feature
1620
+ # implemented by the module itself. However, this is probably
1621
+ # not a big deal, because (1) a full-featured program that
1622
+ # supports --version would normally have a driver script and
1623
+ # not rely on 'python -m foo'; (2) it would probably do
1624
+ # something similar anyway; (3) the user can do 'py -m foo
1625
+ # --version' if necessary.
1626
+ if len(args)==1 and args[0] in ["--version", "-version"]:
1627
+ self.print_version(module)
1628
+ return
1629
+ if len(args)==1 and args[0] in ["--help", "-help", "--h", "-h",
1630
+ "--?", "-?", "?"]:
1631
+ expr = UserExpr(module.module, None, "raw_value",
1632
+ source=str(module.name))
1633
+ usage = _get_help(expr, 1)
1634
+ print(usage)
1635
+ return
1636
+ if len(args)==1 and args[0] in ["--source", "-source",
1637
+ "--??", "-??", "??"]:
1638
+ expr = UserExpr(module.module, None, "raw_value",
1639
+ source=str(module.name))
1640
+ usage = _get_help(expr, 2)
1641
+ print(usage)
1642
+ return
1643
+ # TODO: check if module checks __main__
1644
+ self.run_module(module, args)
1645
+
1646
+ def print_version(self, arg):
1647
+ if not arg:
1648
+ print_version_and_exit()
1649
+ return
1650
+ if isinstance(arg, (ModuleHandle, types.ModuleType)):
1651
+ module = ModuleHandle(arg).module
1652
+ else:
1653
+ module = self.namespace.auto_eval(arg, mode="eval")
1654
+ if not isinstance(module, types.ModuleType):
1655
+ raise TypeError("print_version(): got a %s instead of a module"
1656
+ % (type(module).__name__,))
1657
+ try:
1658
+ version = module.__version__
1659
+ except AttributeError:
1660
+ raise AttributeError(
1661
+ "Module %s does not have a __version__ attribute"
1662
+ % (module.__name__,))
1663
+ print(version)
1664
+
1665
+ def print_help(self, objname, verbosity=1):
1666
+ objname = objname and objname.strip()
1667
+ if not objname:
1668
+ print(__doc__)
1669
+ return
1670
+ expr = UserExpr(objname, self.namespace, "eval")
1671
+ usage = _get_help(expr, verbosity)
1672
+ print(usage)
1673
+
1674
+ def create_ipython_app(self):
1675
+ """
1676
+ Create an IPython application and initialize it, but don't start it.
1677
+ """
1678
+ assert self.ipython_app is None
1679
+ self.ipython_app = get_ipython_terminal_app_with_autoimporter()
1680
+
1681
+ def start_ipython(self, args=[]):
1682
+ user_ns = self.namespace.globals
1683
+ start_ipython_with_autoimporter(args, _user_ns=user_ns,
1684
+ app=self.ipython_app)
1685
+ # Don't need to do another interactive session after this one
1686
+ # (i.e. make 'py --interactive' the same as 'py').
1687
+ self.interactive = False
1688
+
1689
+ def _parse_global_opts(self):
1690
+ args = list(self.main_args)
1691
+ self.debug = False
1692
+ self.interactive = False
1693
+ self.verbosity = 1
1694
+ self.arg_mode = None
1695
+ self.output_mode = None
1696
+ postmortem = 'auto'
1697
+ while args:
1698
+ arg = args[0]
1699
+ if arg in ["debug", "pdb", "ipdb", "dbg"]:
1700
+ argname = "debug"
1701
+ elif not arg.startswith("-"):
1702
+ break
1703
+ elif arg.startswith("--"):
1704
+ argname = arg[2:]
1705
+ else:
1706
+ argname = arg[1:]
1707
+ argname, equalsign, value = argname.partition("=")
1708
+ def popvalue():
1709
+ if equalsign:
1710
+ return value
1711
+ else:
1712
+ try:
1713
+ return args.pop(0)
1714
+ except IndexError:
1715
+ raise ValueError("expected argument to %s" % arg)
1716
+ def novalue():
1717
+ if equalsign:
1718
+ raise ValueError("unexpected argument %s" % arg)
1719
+ if argname in ["interactive", "i"]:
1720
+ novalue()
1721
+ self.interactive = True
1722
+ del args[0]
1723
+ # Create and initialize the IPython app now (but don't start
1724
+ # it yet). We'll use it later. The reason to initialize it
1725
+ # now is that the code that we're running might check if it's
1726
+ # running in interactive mode based on whether an IPython app
1727
+ # has been initialized. Some user code even initializes
1728
+ # things differently at module import time based on this.
1729
+ self.create_ipython_app()
1730
+ elif argname in ["debug", "pdb", "ipdb", "dbg", "d"]:
1731
+ novalue()
1732
+ self.debug = True
1733
+ del args[0]
1734
+ if argname == "verbose":
1735
+ novalue()
1736
+ logger.set_level("DEBUG")
1737
+ del args[0]
1738
+ continue
1739
+ if argname in ["quiet", "q"]:
1740
+ novalue()
1741
+ logger.set_level("ERROR")
1742
+ del args[0]
1743
+ continue
1744
+ if argname in ["safe"]:
1745
+ del args[0]
1746
+ novalue()
1747
+ self.arg_mode = _interpret_arg_mode("string")
1748
+ # TODO: make this less hacky, something like
1749
+ # self.import_db = ""
1750
+ # TODO: also turn off which() behavior
1751
+ os.environ["PYFLYBY_PATH"] = "EMPTY"
1752
+ os.environ["PYFLYBY_KNOWN_IMPORTS_PATH"] = ""
1753
+ os.environ["PYFLYBY_MANDATORY_IMPORTS_PATH"] = ""
1754
+ continue
1755
+ if argname in ["arguments", "argument", "args", "arg",
1756
+ "arg_mode", "arg-mode", "argmode"]:
1757
+ # Interpret --args=eval|string|auto.
1758
+ # Note that if the user didn't specify --args, then we
1759
+ # intentionally leave ``opts.arg_mode`` set to ``None`` for now,
1760
+ # because the default varies per action.
1761
+ del args[0]
1762
+ self.arg_mode = _interpret_arg_mode(popvalue())
1763
+ continue
1764
+ if argname in ["output", "output_mode", "output-mode",
1765
+ "out", "outmode", "out_mode", "out-mode", "o"]:
1766
+ del args[0]
1767
+ self.output_mode = _interpret_output_mode(popvalue())
1768
+ continue
1769
+ if argname in ["print", "pprint", "silent", "repr"]:
1770
+ del args[0]
1771
+ novalue()
1772
+ self.output_mode = _interpret_output_mode(argname)
1773
+ continue
1774
+ if argname in ["postmortem"]:
1775
+ del args[0]
1776
+ v = (value or "").lower().strip()
1777
+ if v in ["yes", "y", "always", "true", "t", "1", "enable", ""]:
1778
+ postmortem = True
1779
+ elif v in ["no", "n", "never", "false", "f", "0", "disable"]:
1780
+ postmortem = False
1781
+ elif v in ["auto", "automatic", "default", "if-tty"]:
1782
+ postmortem = "auto"
1783
+ else:
1784
+ raise ValueError(
1785
+ "unexpected %s=%s. "
1786
+ "Try --postmortem=yes or --postmortem=no."
1787
+ % (argname, value))
1788
+ continue
1789
+ if argname in ["no-postmortem", "np"]:
1790
+ del args[0]
1791
+ novalue()
1792
+ postmortem = False
1793
+ continue
1794
+ break
1795
+ self.args = args
1796
+ if postmortem == "auto":
1797
+ postmortem = os.isatty(1)
1798
+ global _enable_postmortem_debugger
1799
+ _enable_postmortem_debugger = postmortem
1800
+
1801
+ def _enable_debug_tools(self):
1802
+ # Enable a bunch of debugging tools.
1803
+ enable_faulthandler()
1804
+ enable_signal_handler_debugger()
1805
+ enable_sigterm_handler()
1806
+ add_debug_functions_to_builtins()
1807
+
1808
+ def run(self):
1809
+ # Parse global options.
1810
+ sys.orig_argv = list(sys.argv)
1811
+ self._parse_global_opts()
1812
+ self._enable_debug_tools()
1813
+ self._run_action()
1814
+ self._pre_exit()
1815
+
1816
+ def _run_action(self):
1817
+ args = self.args
1818
+ if not args or args[0] == "-":
1819
+ if os.isatty(0):
1820
+ # The user specified no arguments (or only a "-") and stdin is a
1821
+ # tty. Run ipython with autoimporter enabled, i.e. equivalent to
1822
+ # autoipython. Note that we directly start IPython in the same
1823
+ # process, instead of using subprocess.call(['autoipython']),
1824
+ # because the latter messes with Control-C handling.
1825
+ # TODO: add 'py shell' and make this an alias.
1826
+ # TODO: if IPython isn't installed, then do our own
1827
+ # interactive REPL with code.InteractiveConsole, readline, and
1828
+ # autoimporter.
1829
+ self.start_ipython()
1830
+ return
1831
+ else:
1832
+ # Emulate python args.
1833
+ cmd_args = args or [""]
1834
+ # Execute code from stdin, with auto importing.
1835
+ self.exec_stdin(cmd_args)
1836
+ return
1837
+
1838
+ # Consider --action=arg, --action arg, -action=arg, -action arg,
1839
+ # %action arg.
1840
+ arg0 = args.pop(0)
1841
+ if not arg0.strip():
1842
+ raise ValueError("got empty string as first argument")
1843
+ if arg0.startswith("--"):
1844
+ action, equalsign, cmdarg = arg0[2:].partition("=")
1845
+ elif arg0.startswith("-"):
1846
+ action, equalsign, cmdarg = arg0[1:].partition("=")
1847
+ elif arg0.startswith("%"):
1848
+ action, equalsign, cmdarg = arg0[1:], None, None
1849
+ elif len(arg0) > 1 or arg0 == "?":
1850
+ action, equalsign, cmdarg = arg0, None, None
1851
+ else:
1852
+ action, equalsign, cmdarg = None, None, None
1853
+ def popcmdarg():
1854
+ if equalsign:
1855
+ return cmdarg
1856
+ else:
1857
+ try:
1858
+ return args.pop(0)
1859
+ except IndexError:
1860
+ raise ValueError("expected argument to %s" % arg0)
1861
+ def nocmdarg():
1862
+ if equalsign:
1863
+ raise ValueError("unexpected argument %s" % arg0)
1864
+
1865
+ if action in ["eval", "c", "e"]:
1866
+ # Evaluate a command from the command-line, with auto importing.
1867
+ # Supports expressions as well as statements.
1868
+ # Note: Regular python supports "python -cfoo" as equivalent to
1869
+ # "python -c foo". For now, we intentionally don't support that.
1870
+ cmd = popcmdarg()
1871
+ self.eval(cmd, args)
1872
+ elif action in ["file", "execfile", "execf", "runfile", "run", "f",
1873
+ "python"]:
1874
+ # Execute a file named on the command-line, with auto importing.
1875
+ cmd = popcmdarg()
1876
+ self.execfile(cmd, args)
1877
+ elif action in ["apply", "call"]:
1878
+ # Call a function named on the command-line, with auto importing and
1879
+ # auto evaluation of arguments.
1880
+ function_name = popcmdarg()
1881
+ function = UserExpr(function_name, self.namespace, "eval")
1882
+ self.apply(function, args)
1883
+ elif action in ["map"]:
1884
+ # Call function on each argument.
1885
+ # TODO: instead of making this a standalone mode, change this to a
1886
+ # flag that can eval/apply/exec/etc. Set "_" to each argument.
1887
+ # E.g. py --map 'print _' obj1 obj2
1888
+ # py --map _.foo obj1 obj2
1889
+ # py --map '_**2' 3 4 5
1890
+ # when using heuristic mode, "lock in" the action mode on the
1891
+ # first argument.
1892
+ function_name = popcmdarg()
1893
+ function = UserExpr(function_name, self.namespace, "eval")
1894
+ if args and args[0] == '--':
1895
+ for arg in args[1:]:
1896
+ self.apply(function, ['--', arg])
1897
+ else:
1898
+ for arg in args:
1899
+ self.apply(function, [arg])
1900
+ elif action in ["xargs"]:
1901
+ # TODO: read lines from stdin and map. default arg_mode=string
1902
+ raise NotImplementedError("TODO: xargs")
1903
+ elif action in ["module", "m", "runmodule", "run_module", "run-module"]:
1904
+ # Exactly like `python -m'. Intentionally does NOT do auto
1905
+ # importing within the module, because modules should not be
1906
+ # sloppy; they should instead be tidied to have the correct
1907
+ # imports.
1908
+ modname = popcmdarg()
1909
+ self.run_module(modname, args)
1910
+ elif arg0.startswith("-m"):
1911
+ # Support "py -mbase64" in addition to "py -m base64".
1912
+ modname = arg0[2:]
1913
+ self.run_module(modname, args)
1914
+ elif action in ["attach"]:
1915
+ pid = int(popcmdarg())
1916
+ nocmdarg()
1917
+ attach_debugger(pid)
1918
+ elif action in ["stack", "stack_trace", "stacktrace", "backtrace", "bt"]:
1919
+ pid = int(popcmdarg())
1920
+ nocmdarg()
1921
+ print("Stack trace for process %s:" % (pid,))
1922
+ remote_print_stack(pid)
1923
+ elif action in ["ipython", "ip"]:
1924
+ # Start IPython.
1925
+ self.start_ipython(args)
1926
+ elif action in ["notebook", "nb"]:
1927
+ # Start IPython notebook.
1928
+ nocmdarg()
1929
+ self.start_ipython(["notebook"] + args)
1930
+ elif action in ["kernel"]:
1931
+ # Start IPython kernel.
1932
+ nocmdarg()
1933
+ self.start_ipython(["kernel"] + args)
1934
+ elif action in ["qtconsole", "qt"]:
1935
+ # Start IPython qtconsole.
1936
+ nocmdarg()
1937
+ self.start_ipython(["qtconsole"] + args)
1938
+ elif action in ["console"]:
1939
+ # Start IPython console (with new kernel).
1940
+ nocmdarg()
1941
+ self.start_ipython(["console"] + args)
1942
+ elif action in ["existing"]:
1943
+ # Start IPython console (with existing kernel).
1944
+ if equalsign:
1945
+ args.insert(0, cmdarg)
1946
+ self.start_ipython(["console", "--existing"] + args)
1947
+ elif action in ["nbconvert"]:
1948
+ # Start IPython nbconvert. (autoimporter is irrelevant.)
1949
+ if equalsign:
1950
+ args.insert(0, cmdarg)
1951
+ start_ipython_with_autoimporter(["nbconvert"] + args)
1952
+ elif action in ["timeit"]:
1953
+ # TODO: make --timeit and --time flags which work with any mode
1954
+ # and heuristic, instead of only eval.
1955
+ # TODO: fallback if IPython isn't available. above todo probably
1956
+ # requires not using IPython anyway.
1957
+ nocmdarg()
1958
+ run_ipython_line_magic("%timeit " + ' '.join(args))
1959
+ elif action in ["time"]:
1960
+ # TODO: make --timeit and --time flags which work with any mode
1961
+ # and heuristic, instead of only eval.
1962
+ # TODO: fallback if IPython isn't available. above todo probably
1963
+ # requires not using IPython anyway.
1964
+ nocmdarg()
1965
+ run_ipython_line_magic("%time " + ' '.join(args))
1966
+ elif action in ["version"]:
1967
+ if equalsign:
1968
+ args.insert(0, cmdarg)
1969
+ self.print_version(args[0] if args else None)
1970
+ elif action in ["help", "h", "?"]:
1971
+ if equalsign:
1972
+ args.insert(0, cmdarg)
1973
+ self.print_help(args[0] if args else None, verbosity=1)
1974
+ elif action in ["pinfo"]:
1975
+ self.print_help(popcmdarg(), verbosity=1)
1976
+ elif action in ["source", "pinfo2", "??"]:
1977
+ self.print_help(popcmdarg(), verbosity=2)
1978
+
1979
+ elif arg0.startswith("-"):
1980
+ # Unknown argument.
1981
+ msg = "Unknown option %s" % (arg0,)
1982
+ if arg0.startswith("-c"):
1983
+ msg += "; do you mean -c %s?" % (arg0[2:])
1984
+ syntax(msg, usage=usage)
1985
+
1986
+ elif arg0.startswith("??"):
1987
+ # TODO: check number of args
1988
+ self.print_help(arg0[2:], verbosity=2)
1989
+
1990
+ elif arg0.endswith("??"):
1991
+ # TODO: check number of args
1992
+ self.print_help(arg0[:-2], verbosity=2)
1993
+
1994
+ elif arg0.startswith("?"):
1995
+ # TODO: check number of args
1996
+ self.print_help(arg0[1:], verbosity=1)
1997
+
1998
+ elif arg0.endswith("?"):
1999
+ # TODO: check number of args
2000
+ self.print_help(arg0[:-1], verbosity=1)
2001
+
2002
+ elif arg0.startswith("%"):
2003
+ run_ipython_line_magic(' '.join([arg0]+args))
2004
+
2005
+ # Heuristically choose the behavior automatically based on what the
2006
+ # argument looks like.
2007
+ else:
2008
+ filename = _as_filename_if_seems_like_filename(arg0)
2009
+ if filename:
2010
+ # Implied --execfile.
2011
+ self.execfile(filename, args)
2012
+ return
2013
+ if not args and arg0.isdigit():
2014
+ if self.debug:
2015
+ attach_debugger(int(arg0, 10))
2016
+ return
2017
+ else:
2018
+ logger.error(
2019
+ "Use py -d %s if you want to attach a debugger", arg0)
2020
+ raise SystemExit(1)
2021
+ # Implied --eval.
2022
+ # When given multiple arguments, first see if the args can be
2023
+ # concatenated and parsed as a single python program/expression.
2024
+ # But don't try this if any arguments look like options, empty
2025
+ # string or whitespace, etc.
2026
+ # TODO: refactor
2027
+ if (args and
2028
+ self.arg_mode == None and
2029
+ not any(re.match(r"\s*$|-[a-zA-Z-]", a) for a in args)):
2030
+ cmd = PythonBlock(" ".join([arg0]+args),
2031
+ flags=FLAGS, auto_flags=True)
2032
+ if cmd.parsable and self.namespace.auto_import(cmd):
2033
+ with SysArgvCtx("-c"):
2034
+ output_mode = _interpret_output_mode(self.output_mode)
2035
+ result = self.namespace.auto_eval(
2036
+ cmd, info=True, auto_import=False)
2037
+ print_result(result, output_mode)
2038
+ self.result = result
2039
+ return
2040
+ # else fall through
2041
+ # Heuristic based on first arg: try run_module, apply, or exec/eval.
2042
+ cmd = PythonBlock(arg0, flags=FLAGS, auto_flags=True)
2043
+ if not cmd.parsable:
2044
+ logger.error(
2045
+ "Could not interpret as filename or expression: %s",
2046
+ arg0)
2047
+ syntax(usage=usage)
2048
+ self.heuristic_cmd(cmd, args, function_name=arg0)
2049
+
2050
+ def _pre_exit(self):
2051
+ self._pre_exit_matplotlib_show()
2052
+ self._pre_exit_interactive_shell()
2053
+
2054
+ def _pre_exit_matplotlib_show(self):
2055
+ """
2056
+ If matplotlib.pyplot (pylab) is loaded, then call the show() function.
2057
+ This will cause the program to block until all figures are closed.
2058
+ Without this, a command like 'py plot(...)' would exit immediately.
2059
+ """
2060
+ if self.interactive:
2061
+ return
2062
+ try:
2063
+ pyplot = sys.modules["matplotlib.pyplot"]
2064
+ except KeyError:
2065
+ return
2066
+ pyplot.show()
2067
+
2068
+ def _pre_exit_interactive_shell(self):
2069
+ if self.interactive:
2070
+ assert self.ipython_app is not None
2071
+ self.namespace.globals["_"] = self.result
2072
+ self.start_ipython()
2073
+
2074
+
2075
+ def py_main(args=None):
2076
+ if args is None:
2077
+ args = sys.argv[1:]
2078
+ _PyMain(args).run()