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