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/_cmdline.py ADDED
@@ -0,0 +1,531 @@
1
+ # pyflyby/_cmdline.py.
2
+ # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+
6
+
7
+ import optparse
8
+ import os
9
+ import signal
10
+ from builtins import input
11
+ import sys
12
+ from textwrap import dedent
13
+ import traceback
14
+ from typing import List
15
+
16
+
17
+ from pyflyby._file import (FileText, Filename, atomic_write_file,
18
+ expand_py_files_from_args, read_file)
19
+ from pyflyby._importstmt import ImportFormatParams
20
+ from pyflyby._log import logger
21
+ from pyflyby._util import cached_attribute, indent
22
+
23
+
24
+ def hfmt(s):
25
+ return dedent(s).strip()
26
+
27
+ def maindoc():
28
+ import __main__
29
+ return (__main__.__doc__ or '').strip()
30
+
31
+
32
+ def _sigpipe_handler(*args):
33
+ # The parent process piped our stdout and closed the pipe before we
34
+ # finished writing, e.g. "tidy-imports ... | head" or "tidy-imports ... |
35
+ # less". Exit quietly - squelch the "close failed in file object
36
+ # destructor" message would otherwise be raised.
37
+ raise SystemExit(1)
38
+
39
+
40
+ def parse_args(
41
+ addopts=None, import_format_params=False, modify_action_params=False, defaults=None
42
+ ):
43
+ """
44
+ Do setup for a top-level script and parse arguments.
45
+ """
46
+
47
+ if defaults is None:
48
+ defaults = {}
49
+
50
+ ### Setup.
51
+ # Register a SIGPIPE handler.
52
+ signal.signal(signal.SIGPIPE, _sigpipe_handler)
53
+ ### Parse args.
54
+ parser = optparse.OptionParser(usage='\n'+maindoc())
55
+
56
+ def log_level_callbacker(level):
57
+ def callback(option, opt_str, value, parser):
58
+ logger.set_level(level)
59
+ return callback
60
+
61
+ def debug_callback(option, opt_str, value, parser):
62
+ logger.set_level("DEBUG")
63
+
64
+ parser.add_option("--debug", action="callback",
65
+ callback=debug_callback,
66
+ help="Debug mode (noisy and fail fast).")
67
+
68
+ parser.add_option("--verbose", action="callback",
69
+ callback=log_level_callbacker("DEBUG"),
70
+ help="Be noisy.")
71
+
72
+ parser.add_option("--quiet", action="callback",
73
+ callback=log_level_callbacker("ERROR"),
74
+ help="Be quiet.")
75
+
76
+ parser.add_option("--version", action="callback",
77
+ callback=lambda *args: print_version_and_exit(),
78
+ help="Print pyflyby version and exit.")
79
+
80
+ if modify_action_params:
81
+ group = optparse.OptionGroup(parser, "Action options")
82
+ action_diff = action_external_command('pyflyby-diff')
83
+ def parse_action(v):
84
+ V = v.strip().upper()
85
+ if V == 'PRINT':
86
+ return action_print
87
+ elif V == 'REPLACE':
88
+ return action_replace
89
+ elif V == 'QUERY':
90
+ return action_query()
91
+ elif V == "DIFF":
92
+ return action_diff
93
+ elif V.startswith("QUERY:"):
94
+ return action_query(v[6:])
95
+ elif V.startswith("EXECUTE:"):
96
+ return action_external_command(v[8:])
97
+ elif V == "IFCHANGED":
98
+ return action_ifchanged
99
+ elif V == "EXIT1":
100
+ return action_exit1
101
+ else:
102
+ raise Exception(
103
+ "Bad argument %r to --action; "
104
+ "expected PRINT or REPLACE or QUERY or IFCHANGED or EXIT1 "
105
+ "or EXECUTE:..." % (v,))
106
+
107
+ def set_actions(actions):
108
+ actions = tuple(actions)
109
+ parser.values.actions = actions
110
+
111
+ def action_callback(option, opt_str, value, parser):
112
+ action_args = value.split(',')
113
+ set_actions([parse_action(v) for v in action_args])
114
+
115
+ def action_callbacker(actions):
116
+ def callback(option, opt_str, value, parser):
117
+ set_actions(actions)
118
+ return callback
119
+
120
+ group.add_option(
121
+ "--actions", type='string', action='callback',
122
+ callback=action_callback,
123
+ metavar='PRINT|REPLACE|IFCHANGED|QUERY|DIFF|EXIT1:EXECUTE:mycommand',
124
+ help=hfmt('''
125
+ Comma-separated list of action(s) to take. If PRINT, print
126
+ the changed file to stdout. If REPLACE, then modify the
127
+ file in-place. If EXECUTE:mycommand, then execute
128
+ 'mycommand oldfile tmpfile'. If DIFF, then execute
129
+ 'pyflyby-diff'. If QUERY, then query user to continue.
130
+ If IFCHANGED, then continue actions only if file was
131
+ changed. If EXIT1, then exit with exit code 1 after all
132
+ files/actions are processed.'''))
133
+ group.add_option(
134
+ "--print", "-p", action='callback',
135
+ callback=action_callbacker([action_print]),
136
+ help=hfmt('''
137
+ Equivalent to --action=PRINT (default when stdin or stdout is
138
+ not a tty) '''))
139
+ group.add_option(
140
+ "--diff", "-d", action='callback',
141
+ callback=action_callbacker([action_diff]),
142
+ help=hfmt('''Equivalent to --action=DIFF'''))
143
+ group.add_option(
144
+ "--replace", "-r", action='callback',
145
+ callback=action_callbacker([action_ifchanged, action_replace]),
146
+ help=hfmt('''Equivalent to --action=IFCHANGED,REPLACE'''))
147
+ group.add_option(
148
+ "--diff-replace", "-R", action='callback',
149
+ callback=action_callbacker([action_ifchanged, action_diff, action_replace]),
150
+ help=hfmt('''Equivalent to --action=IFCHANGED,DIFF,REPLACE'''))
151
+ actions_interactive = [
152
+ action_ifchanged, action_diff,
153
+ action_query("Replace {filename}?"), action_replace]
154
+ group.add_option(
155
+ "--interactive", "-i", action='callback',
156
+ callback=action_callbacker(actions_interactive),
157
+ help=hfmt('''
158
+ Equivalent to --action=IFCHANGED,DIFF,QUERY,REPLACE (default
159
+ when stdin & stdout are ttys) '''))
160
+ if os.isatty(0) and os.isatty(1):
161
+ default_actions = actions_interactive
162
+ else:
163
+ default_actions = [action_print]
164
+ parser.set_default('actions', tuple(default_actions))
165
+ parser.add_option_group(group)
166
+
167
+ parser.add_option(
168
+ '--symlinks', action='callback', nargs=1, type=str,
169
+ dest='symlinks', callback=symlink_callback, help="--symlinks should be one of: " + symlinks_help,
170
+ )
171
+ parser.set_defaults(symlinks='error')
172
+
173
+ if import_format_params:
174
+ group = optparse.OptionGroup(parser, "Pretty-printing options")
175
+ group.add_option('--align-imports', '--align', type='str', default="32",
176
+ metavar='N',
177
+ help=hfmt('''
178
+ Whether and how to align the 'import' keyword in
179
+ 'from modulename import aliases...'. If 0, then
180
+ don't align. If 1, then align within each block
181
+ of imports. If an integer > 1, then align at
182
+ that column, wrapping with a backslash if
183
+ necessary. If a comma-separated list of integers
184
+ (tab stops), then pick the column that results in
185
+ the fewest number of lines total per block.'''))
186
+ group.add_option('--from-spaces', type='int', default=3, metavar='N',
187
+ help=hfmt('''
188
+ The number of spaces after the 'from' keyword.
189
+ (Must be at least 1; default is 3.)'''))
190
+ group.add_option('--separate-from-imports', action='store_true',
191
+ default=False,
192
+ help=hfmt('''
193
+ Separate 'from ... import ...'
194
+ statements from 'import ...' statements.'''))
195
+ group.add_option('--no-separate-from-imports', action='store_false',
196
+ dest='separate_from_imports',
197
+ help=hfmt('''
198
+ (Default) Don't separate 'from ... import ...'
199
+ statements from 'import ...' statements.'''))
200
+ group.add_option('--align-future', action='store_true',
201
+ default=False,
202
+ help=hfmt('''
203
+ Align the 'from __future__ import ...' statement
204
+ like others.'''))
205
+ group.add_option('--no-align-future', action='store_false',
206
+ dest='align_future',
207
+ help=hfmt('''
208
+ (Default) Don't align the 'from __future__ import
209
+ ...' statement.'''))
210
+ group.add_option('--width', type='int', default=None, metavar='N',
211
+ help=hfmt('''
212
+ Maximum line length (default: 79).'''))
213
+ group.add_option('--black', action='store_true', default=False,
214
+ help=hfmt('''
215
+ Use black to format imports. If this option is
216
+ used, all other formatting options are ignored,
217
+ except width'''))
218
+ group.add_option('--hanging-indent', type='choice', default='never',
219
+ choices=['never','auto','always'],
220
+ metavar='never|auto|always',
221
+ dest='hanging_indent',
222
+ help=hfmt('''
223
+ How to wrap import statements that don't fit on
224
+ one line.
225
+ If --hanging-indent=always, then always indent
226
+ imported tokens at column 4 on the next line.
227
+ If --hanging-indent=never (default), then align
228
+ import tokens after "import (" (by default column
229
+ 40); do so even if some symbols are so long that
230
+ this would exceed the width (by default 79)).
231
+ If --hanging-indent=auto, then use hanging indent
232
+ only if it is necessary to prevent exceeding the
233
+ width (by default 79).
234
+ '''))
235
+ def uniform_callback(option, opt_str, value, parser):
236
+ parser.values.separate_from_imports = False
237
+ parser.values.from_spaces = 3
238
+ parser.values.align_imports = '32'
239
+ group.add_option('--uniform', '-u', action="callback",
240
+ callback=uniform_callback,
241
+ help=hfmt('''
242
+ (Default) Shortcut for --no-separate-from-imports
243
+ --from-spaces=3 --align-imports=32.'''))
244
+ def unaligned_callback(option, opt_str, value, parser):
245
+ parser.values.separate_from_imports = True
246
+ parser.values.from_spaces = 1
247
+ parser.values.align_imports = '0'
248
+ group.add_option('--unaligned', '-n', action="callback",
249
+ callback=unaligned_callback,
250
+ help=hfmt('''
251
+ Shortcut for --separate-from-imports
252
+ --from-spaces=1 --align-imports=0.'''))
253
+
254
+ parser.add_option_group(group)
255
+ if addopts is not None:
256
+ addopts(parser)
257
+ # This is the only way to provide a default value for an option with a
258
+ # callback.
259
+ if modify_action_params:
260
+ args = ["--symlinks=error"] + sys.argv[1:]
261
+ else:
262
+ args = None
263
+ options, args = parser.parse_args(args=args)
264
+ if import_format_params:
265
+ align_imports_args = [int(x.strip())
266
+ for x in options.align_imports.split(",")]
267
+ if len(align_imports_args) == 1 and align_imports_args[0] == 1:
268
+ align_imports = True
269
+ elif len(align_imports_args) == 1 and align_imports_args[0] == 0:
270
+ align_imports = False
271
+ else:
272
+ align_imports = tuple(sorted(set(align_imports_args)))
273
+ options.params = ImportFormatParams(
274
+ align_imports =align_imports,
275
+ from_spaces =options.from_spaces,
276
+ separate_from_imports =options.separate_from_imports,
277
+ max_line_length =options.width,
278
+ use_black =options.black,
279
+ align_future =options.align_future,
280
+ hanging_indent =options.hanging_indent,
281
+ )
282
+ return options, args
283
+
284
+
285
+ def _default_on_error(filename):
286
+ raise SystemExit("bad filename %s" % (filename,))
287
+
288
+ def filename_args(args, on_error=_default_on_error):
289
+ """
290
+ Return list of filenames given command-line arguments.
291
+
292
+ :rtype:
293
+ ``list`` of `Filename`
294
+ """
295
+ if args:
296
+ return expand_py_files_from_args(args, on_error)
297
+ elif not os.isatty(0):
298
+ return [Filename.STDIN]
299
+ else:
300
+ syntax()
301
+
302
+
303
+ def print_version_and_exit(extra=None):
304
+ from pyflyby._version import __version__
305
+ msg = "pyflyby %s" % (__version__,)
306
+ progname = os.path.realpath(sys.argv[0])
307
+ if os.path.exists(progname):
308
+ msg += " (%s)" % (os.path.basename(progname),)
309
+ print(msg)
310
+ if extra:
311
+ print(extra)
312
+ raise SystemExit(0)
313
+
314
+
315
+ def syntax(message=None, usage=None):
316
+ if message:
317
+ logger.error(message)
318
+ outmsg = ((usage or maindoc()) +
319
+ '\n\nFor usage, see: %s --help' % (sys.argv[0],))
320
+ print(outmsg, file=sys.stderr)
321
+ raise SystemExit(1)
322
+
323
+
324
+ class AbortActions(Exception):
325
+ pass
326
+
327
+
328
+ class Exit1(Exception):
329
+ pass
330
+
331
+
332
+ class Modifier(object):
333
+ def __init__(self, modifier, filename):
334
+ self.modifier = modifier
335
+ self.filename = filename
336
+ self._tmpfiles = []
337
+
338
+ @cached_attribute
339
+ def input_content(self):
340
+ return read_file(self.filename)
341
+
342
+ # TODO: refactor to avoid having these heavy-weight things inside a
343
+ # cached_attribute, which causes annoyance while debugging.
344
+ @cached_attribute
345
+ def output_content(self):
346
+ return FileText(self.modifier(self.input_content), filename=self.filename)
347
+
348
+ def _tempfile(self):
349
+ from tempfile import NamedTemporaryFile
350
+ f = NamedTemporaryFile()
351
+ self._tmpfiles.append(f)
352
+ return f, Filename(f.name)
353
+
354
+
355
+ @cached_attribute
356
+ def output_content_filename(self):
357
+ f, fname = self._tempfile()
358
+ f.write(bytes(self.output_content.joined, "utf-8"))
359
+ f.flush()
360
+ return fname
361
+
362
+ @cached_attribute
363
+ def input_content_filename(self):
364
+ if isinstance(self.filename, Filename):
365
+ return self.filename
366
+ # If the input was stdin, and the user wants a diff, then we need to
367
+ # write it to a temp file.
368
+ f, fname = self._tempfile()
369
+ f.write(bytes(self.input_content, "utf-8"))
370
+ f.flush()
371
+ return fname
372
+
373
+
374
+ def __del__(self):
375
+ for f in self._tmpfiles:
376
+ f.close()
377
+
378
+
379
+ def process_actions(filenames:List[str], actions, modify_function,
380
+ reraise_exceptions=()):
381
+ errors = []
382
+ def on_error_filename_arg(arg):
383
+ print("%s: bad filename %s" % (sys.argv[0], arg), file=sys.stderr)
384
+ errors.append("%s: bad filename" % (arg,))
385
+ filenames = filename_args(filenames, on_error=on_error_filename_arg)
386
+ exit_code = 0
387
+ for filename in filenames:
388
+ try:
389
+ m = Modifier(modify_function, filename)
390
+ for action in actions:
391
+ action(m)
392
+ except AbortActions:
393
+ continue
394
+ except reraise_exceptions:
395
+ raise
396
+ except Exit1:
397
+ exit_code = 1
398
+ except Exception as e:
399
+ errors.append("%s: %s: %s" % (filename, type(e).__name__, e))
400
+ type_e = type(e)
401
+ try:
402
+ tb = sys.exc_info()[2]
403
+ if str(filename) not in str(e):
404
+ try:
405
+ e = type_e("While processing %s: %s" % (filename, e))
406
+ pass
407
+ except TypeError:
408
+ # Exception takes more than one argument
409
+ pass
410
+ if logger.debug_enabled:
411
+ raise
412
+ traceback.print_exception(type(e), e, tb)
413
+ finally:
414
+ tb = None # avoid refcycles involving tb
415
+ continue
416
+ if errors:
417
+ msg = "\n%s: encountered the following problems:\n" % (sys.argv[0],)
418
+ for er in errors:
419
+ lines = er.splitlines()
420
+ msg += " " + lines[0] + '\n'.join(
421
+ (" %s"%line for line in lines[1:]))
422
+ raise SystemExit(msg)
423
+ else:
424
+ raise SystemExit(exit_code)
425
+
426
+
427
+ def action_print(m):
428
+ output_content = m.output_content
429
+ sys.stdout.write(output_content.joined)
430
+
431
+
432
+ def action_ifchanged(m):
433
+ if m.output_content.joined == m.input_content.joined:
434
+ logger.debug("unmodified: %s", m.filename)
435
+ raise AbortActions
436
+
437
+
438
+ def action_replace(m):
439
+ if m.filename == Filename.STDIN:
440
+ raise Exception("Can't replace stdio in-place")
441
+ logger.info("%s: *** modified ***", m.filename)
442
+ atomic_write_file(m.filename, m.output_content)
443
+
444
+
445
+ def action_exit1(m):
446
+ logger.debug("action_exit1")
447
+ raise Exit1
448
+
449
+
450
+ def action_external_command(command):
451
+ import subprocess
452
+ def action(m):
453
+ bindir = os.path.dirname(os.path.realpath(sys.argv[0]))
454
+ env = os.environ
455
+ env['PATH'] = env['PATH'] + ":" + bindir
456
+ fullcmd = "%s %s %s" % (
457
+ command, m.input_content_filename, m.output_content_filename)
458
+ logger.debug("Executing external command: %s", fullcmd)
459
+ ret = subprocess.call(fullcmd, shell=True, env=env)
460
+ logger.debug("External command returned %d", ret)
461
+ return action
462
+
463
+
464
+ def action_query(prompt="Proceed?"):
465
+ def action(m):
466
+ p = prompt.format(filename=m.filename)
467
+ print()
468
+ print("%s [y/N] " % (p), end="")
469
+ try:
470
+ if input().strip().lower().startswith('y'):
471
+ return True
472
+ except KeyboardInterrupt:
473
+ print("KeyboardInterrupt", file=sys.stderr)
474
+ raise SystemExit(1)
475
+ print("Aborted")
476
+ raise AbortActions
477
+ return action
478
+
479
+ def symlink_callback(option, opt_str, value, parser):
480
+ parser.values.actions = tuple(i for i in parser.values.actions if i not in
481
+ symlink_callbacks.values())
482
+ if value in symlink_callbacks:
483
+ parser.values.actions = (symlink_callbacks[value],) + parser.values.actions
484
+ else:
485
+ raise optparse.OptionValueError("--symlinks must be one of 'error', 'follow', 'skip', or 'replace'. Got %r" % value)
486
+
487
+ symlinks_help = """\
488
+ --symlinks=error (default; gives an error on symlinks),
489
+ --symlinks=follow (follows symlinks),
490
+ --symlinks=skip (skips symlinks),
491
+ --symlinks=replace (replaces symlinks with the target file\
492
+ """
493
+
494
+ # Warning, the symlink actions will only work if they are run first.
495
+ # Otherwise, output_content may already be cached
496
+ def symlink_error(m):
497
+ if m.filename == Filename.STDIN:
498
+ return symlink_follow(m)
499
+ if m.filename.islink:
500
+ raise SystemExit("""\
501
+ Error: %s appears to be a symlink. Use one of the following options to allow symlinks:
502
+ %s
503
+ """ % (m.filename, indent(symlinks_help, ' ')))
504
+
505
+ def symlink_follow(m):
506
+ if m.filename == Filename.STDIN:
507
+ return
508
+ if m.filename.islink:
509
+ logger.info("Following symlink %s" % m.filename)
510
+ m.filename = m.filename.realpath
511
+
512
+ def symlink_skip(m):
513
+ if m.filename == Filename.STDIN:
514
+ return symlink_follow(m)
515
+ if m.filename.islink:
516
+ logger.info("Skipping symlink %s" % m.filename)
517
+ raise AbortActions
518
+
519
+ def symlink_replace(m):
520
+ if m.filename == Filename.STDIN:
521
+ return symlink_follow(m)
522
+ if m.filename.islink:
523
+ logger.info("Replacing symlink %s" % m.filename)
524
+ # The current behavior automatically replaces symlinks, so do nothing
525
+
526
+ symlink_callbacks = {
527
+ 'error': symlink_error,
528
+ 'follow': symlink_follow,
529
+ 'skip': symlink_skip,
530
+ 'replace': symlink_replace,
531
+ }