]> git.madduck.net Git - etc/vim.git/blob - black.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Handle backslashes in raw strings while normalizing (#105)
[etc/vim.git] / black.py
1 #!/usr/bin/env python3
2
3 import asyncio
4 from asyncio.base_events import BaseEventLoop
5 from concurrent.futures import Executor, ProcessPoolExecutor
6 from enum import Enum
7 from functools import partial, wraps
8 import keyword
9 import logging
10 from multiprocessing import Manager
11 import os
12 from pathlib import Path
13 import tokenize
14 import signal
15 import sys
16 from typing import (
17     Any,
18     Callable,
19     Dict,
20     Generic,
21     Iterable,
22     Iterator,
23     List,
24     Optional,
25     Set,
26     Tuple,
27     Type,
28     TypeVar,
29     Union,
30 )
31
32 from attr import dataclass, Factory
33 import click
34
35 # lib2to3 fork
36 from blib2to3.pytree import Node, Leaf, type_repr
37 from blib2to3 import pygram, pytree
38 from blib2to3.pgen2 import driver, token
39 from blib2to3.pgen2.parse import ParseError
40
41 __version__ = "18.4a0"
42 DEFAULT_LINE_LENGTH = 88
43 # types
44 syms = pygram.python_symbols
45 FileContent = str
46 Encoding = str
47 Depth = int
48 NodeType = int
49 LeafID = int
50 Priority = int
51 Index = int
52 LN = Union[Leaf, Node]
53 SplitFunc = Callable[["Line", bool], Iterator["Line"]]
54 out = partial(click.secho, bold=True, err=True)
55 err = partial(click.secho, fg="red", err=True)
56
57
58 class NothingChanged(UserWarning):
59     """Raised by :func:`format_file` when reformatted code is the same as source."""
60
61
62 class CannotSplit(Exception):
63     """A readable split that fits the allotted line length is impossible.
64
65     Raised by :func:`left_hand_split`, :func:`right_hand_split`, and
66     :func:`delimiter_split`.
67     """
68
69
70 class FormatError(Exception):
71     """Base exception for `# fmt: on` and `# fmt: off` handling.
72
73     It holds the number of bytes of the prefix consumed before the format
74     control comment appeared.
75     """
76
77     def __init__(self, consumed: int) -> None:
78         super().__init__(consumed)
79         self.consumed = consumed
80
81     def trim_prefix(self, leaf: Leaf) -> None:
82         leaf.prefix = leaf.prefix[self.consumed:]
83
84     def leaf_from_consumed(self, leaf: Leaf) -> Leaf:
85         """Returns a new Leaf from the consumed part of the prefix."""
86         unformatted_prefix = leaf.prefix[:self.consumed]
87         return Leaf(token.NEWLINE, unformatted_prefix)
88
89
90 class FormatOn(FormatError):
91     """Found a comment like `# fmt: on` in the file."""
92
93
94 class FormatOff(FormatError):
95     """Found a comment like `# fmt: off` in the file."""
96
97
98 class WriteBack(Enum):
99     NO = 0
100     YES = 1
101     DIFF = 2
102
103
104 @click.command()
105 @click.option(
106     "-l",
107     "--line-length",
108     type=int,
109     default=DEFAULT_LINE_LENGTH,
110     help="How many character per line to allow.",
111     show_default=True,
112 )
113 @click.option(
114     "--check",
115     is_flag=True,
116     help=(
117         "Don't write the files back, just return the status.  Return code 0 "
118         "means nothing would change.  Return code 1 means some files would be "
119         "reformatted.  Return code 123 means there was an internal error."
120     ),
121 )
122 @click.option(
123     "--diff",
124     is_flag=True,
125     help="Don't write the files back, just output a diff for each file on stdout.",
126 )
127 @click.option(
128     "--fast/--safe",
129     is_flag=True,
130     help="If --fast given, skip temporary sanity checks. [default: --safe]",
131 )
132 @click.option(
133     "-q",
134     "--quiet",
135     is_flag=True,
136     help=(
137         "Don't emit non-error messages to stderr. Errors are still emitted, "
138         "silence those with 2>/dev/null."
139     ),
140 )
141 @click.version_option(version=__version__)
142 @click.argument(
143     "src",
144     nargs=-1,
145     type=click.Path(
146         exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
147     ),
148 )
149 @click.pass_context
150 def main(
151     ctx: click.Context,
152     line_length: int,
153     check: bool,
154     diff: bool,
155     fast: bool,
156     quiet: bool,
157     src: List[str],
158 ) -> None:
159     """The uncompromising code formatter."""
160     sources: List[Path] = []
161     for s in src:
162         p = Path(s)
163         if p.is_dir():
164             sources.extend(gen_python_files_in_dir(p))
165         elif p.is_file():
166             # if a file was explicitly given, we don't care about its extension
167             sources.append(p)
168         elif s == "-":
169             sources.append(Path("-"))
170         else:
171             err(f"invalid path: {s}")
172     if check and diff:
173         exc = click.ClickException("Options --check and --diff are mutually exclusive")
174         exc.exit_code = 2
175         raise exc
176
177     if check:
178         write_back = WriteBack.NO
179     elif diff:
180         write_back = WriteBack.DIFF
181     else:
182         write_back = WriteBack.YES
183     if len(sources) == 0:
184         ctx.exit(0)
185     elif len(sources) == 1:
186         p = sources[0]
187         report = Report(check=check, quiet=quiet)
188         try:
189             if not p.is_file() and str(p) == "-":
190                 changed = format_stdin_to_stdout(
191                     line_length=line_length, fast=fast, write_back=write_back
192                 )
193             else:
194                 changed = format_file_in_place(
195                     p, line_length=line_length, fast=fast, write_back=write_back
196                 )
197             report.done(p, changed)
198         except Exception as exc:
199             report.failed(p, str(exc))
200         ctx.exit(report.return_code)
201     else:
202         loop = asyncio.get_event_loop()
203         executor = ProcessPoolExecutor(max_workers=os.cpu_count())
204         return_code = 1
205         try:
206             return_code = loop.run_until_complete(
207                 schedule_formatting(
208                     sources, line_length, write_back, fast, quiet, loop, executor
209                 )
210             )
211         finally:
212             shutdown(loop)
213             ctx.exit(return_code)
214
215
216 async def schedule_formatting(
217     sources: List[Path],
218     line_length: int,
219     write_back: WriteBack,
220     fast: bool,
221     quiet: bool,
222     loop: BaseEventLoop,
223     executor: Executor,
224 ) -> int:
225     """Run formatting of `sources` in parallel using the provided `executor`.
226
227     (Use ProcessPoolExecutors for actual parallelism.)
228
229     `line_length`, `write_back`, and `fast` options are passed to
230     :func:`format_file_in_place`.
231     """
232     lock = None
233     if write_back == WriteBack.DIFF:
234         # For diff output, we need locks to ensure we don't interleave output
235         # from different processes.
236         manager = Manager()
237         lock = manager.Lock()
238     tasks = {
239         src: loop.run_in_executor(
240             executor, format_file_in_place, src, line_length, fast, write_back, lock
241         )
242         for src in sources
243     }
244     _task_values = list(tasks.values())
245     loop.add_signal_handler(signal.SIGINT, cancel, _task_values)
246     loop.add_signal_handler(signal.SIGTERM, cancel, _task_values)
247     await asyncio.wait(tasks.values())
248     cancelled = []
249     report = Report(check=write_back is WriteBack.NO, quiet=quiet)
250     for src, task in tasks.items():
251         if not task.done():
252             report.failed(src, "timed out, cancelling")
253             task.cancel()
254             cancelled.append(task)
255         elif task.cancelled():
256             cancelled.append(task)
257         elif task.exception():
258             report.failed(src, str(task.exception()))
259         else:
260             report.done(src, task.result())
261     if cancelled:
262         await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
263     elif not quiet:
264         out("All done! ✨ 🍰 ✨")
265     if not quiet:
266         click.echo(str(report))
267     return report.return_code
268
269
270 def format_file_in_place(
271     src: Path,
272     line_length: int,
273     fast: bool,
274     write_back: WriteBack = WriteBack.NO,
275     lock: Any = None,  # multiprocessing.Manager().Lock() is some crazy proxy
276 ) -> bool:
277     """Format file under `src` path. Return True if changed.
278
279     If `write_back` is True, write reformatted code back to stdout.
280     `line_length` and `fast` options are passed to :func:`format_file_contents`.
281     """
282     with tokenize.open(src) as src_buffer:
283         src_contents = src_buffer.read()
284     try:
285         dst_contents = format_file_contents(
286             src_contents, line_length=line_length, fast=fast
287         )
288     except NothingChanged:
289         return False
290
291     if write_back == write_back.YES:
292         with open(src, "w", encoding=src_buffer.encoding) as f:
293             f.write(dst_contents)
294     elif write_back == write_back.DIFF:
295         src_name = f"{src.name}  (original)"
296         dst_name = f"{src.name}  (formatted)"
297         diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
298         if lock:
299             lock.acquire()
300         try:
301             sys.stdout.write(diff_contents)
302         finally:
303             if lock:
304                 lock.release()
305     return True
306
307
308 def format_stdin_to_stdout(
309     line_length: int, fast: bool, write_back: WriteBack = WriteBack.NO
310 ) -> bool:
311     """Format file on stdin. Return True if changed.
312
313     If `write_back` is True, write reformatted code back to stdout.
314     `line_length` and `fast` arguments are passed to :func:`format_file_contents`.
315     """
316     src = sys.stdin.read()
317     try:
318         dst = format_file_contents(src, line_length=line_length, fast=fast)
319         return True
320
321     except NothingChanged:
322         dst = src
323         return False
324
325     finally:
326         if write_back == WriteBack.YES:
327             sys.stdout.write(dst)
328         elif write_back == WriteBack.DIFF:
329             src_name = "<stdin>  (original)"
330             dst_name = "<stdin>  (formatted)"
331             sys.stdout.write(diff(src, dst, src_name, dst_name))
332
333
334 def format_file_contents(
335     src_contents: str, line_length: int, fast: bool
336 ) -> FileContent:
337     """Reformat contents a file and return new contents.
338
339     If `fast` is False, additionally confirm that the reformatted code is
340     valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
341     `line_length` is passed to :func:`format_str`.
342     """
343     if src_contents.strip() == "":
344         raise NothingChanged
345
346     dst_contents = format_str(src_contents, line_length=line_length)
347     if src_contents == dst_contents:
348         raise NothingChanged
349
350     if not fast:
351         assert_equivalent(src_contents, dst_contents)
352         assert_stable(src_contents, dst_contents, line_length=line_length)
353     return dst_contents
354
355
356 def format_str(src_contents: str, line_length: int) -> FileContent:
357     """Reformat a string and return new contents.
358
359     `line_length` determines how many characters per line are allowed.
360     """
361     src_node = lib2to3_parse(src_contents)
362     dst_contents = ""
363     lines = LineGenerator()
364     elt = EmptyLineTracker()
365     py36 = is_python36(src_node)
366     empty_line = Line()
367     after = 0
368     for current_line in lines.visit(src_node):
369         for _ in range(after):
370             dst_contents += str(empty_line)
371         before, after = elt.maybe_empty_lines(current_line)
372         for _ in range(before):
373             dst_contents += str(empty_line)
374         for line in split_line(current_line, line_length=line_length, py36=py36):
375             dst_contents += str(line)
376     return dst_contents
377
378
379 GRAMMARS = [
380     pygram.python_grammar_no_print_statement_no_exec_statement,
381     pygram.python_grammar_no_print_statement,
382     pygram.python_grammar_no_exec_statement,
383     pygram.python_grammar,
384 ]
385
386
387 def lib2to3_parse(src_txt: str) -> Node:
388     """Given a string with source, return the lib2to3 Node."""
389     grammar = pygram.python_grammar_no_print_statement
390     if src_txt[-1] != "\n":
391         nl = "\r\n" if "\r\n" in src_txt[:1024] else "\n"
392         src_txt += nl
393     for grammar in GRAMMARS:
394         drv = driver.Driver(grammar, pytree.convert)
395         try:
396             result = drv.parse_string(src_txt, True)
397             break
398
399         except ParseError as pe:
400             lineno, column = pe.context[1]
401             lines = src_txt.splitlines()
402             try:
403                 faulty_line = lines[lineno - 1]
404             except IndexError:
405                 faulty_line = "<line number missing in source>"
406             exc = ValueError(f"Cannot parse: {lineno}:{column}: {faulty_line}")
407     else:
408         raise exc from None
409
410     if isinstance(result, Leaf):
411         result = Node(syms.file_input, [result])
412     return result
413
414
415 def lib2to3_unparse(node: Node) -> str:
416     """Given a lib2to3 node, return its string representation."""
417     code = str(node)
418     return code
419
420
421 T = TypeVar("T")
422
423
424 class Visitor(Generic[T]):
425     """Basic lib2to3 visitor that yields things of type `T` on `visit()`."""
426
427     def visit(self, node: LN) -> Iterator[T]:
428         """Main method to visit `node` and its children.
429
430         It tries to find a `visit_*()` method for the given `node.type`, like
431         `visit_simple_stmt` for Node objects or `visit_INDENT` for Leaf objects.
432         If no dedicated `visit_*()` method is found, chooses `visit_default()`
433         instead.
434
435         Then yields objects of type `T` from the selected visitor.
436         """
437         if node.type < 256:
438             name = token.tok_name[node.type]
439         else:
440             name = type_repr(node.type)
441         yield from getattr(self, f"visit_{name}", self.visit_default)(node)
442
443     def visit_default(self, node: LN) -> Iterator[T]:
444         """Default `visit_*()` implementation. Recurses to children of `node`."""
445         if isinstance(node, Node):
446             for child in node.children:
447                 yield from self.visit(child)
448
449
450 @dataclass
451 class DebugVisitor(Visitor[T]):
452     tree_depth: int = 0
453
454     def visit_default(self, node: LN) -> Iterator[T]:
455         indent = " " * (2 * self.tree_depth)
456         if isinstance(node, Node):
457             _type = type_repr(node.type)
458             out(f"{indent}{_type}", fg="yellow")
459             self.tree_depth += 1
460             for child in node.children:
461                 yield from self.visit(child)
462
463             self.tree_depth -= 1
464             out(f"{indent}/{_type}", fg="yellow", bold=False)
465         else:
466             _type = token.tok_name.get(node.type, str(node.type))
467             out(f"{indent}{_type}", fg="blue", nl=False)
468             if node.prefix:
469                 # We don't have to handle prefixes for `Node` objects since
470                 # that delegates to the first child anyway.
471                 out(f" {node.prefix!r}", fg="green", bold=False, nl=False)
472             out(f" {node.value!r}", fg="blue", bold=False)
473
474     @classmethod
475     def show(cls, code: str) -> None:
476         """Pretty-print the lib2to3 AST of a given string of `code`.
477
478         Convenience method for debugging.
479         """
480         v: DebugVisitor[None] = DebugVisitor()
481         list(v.visit(lib2to3_parse(code)))
482
483
484 KEYWORDS = set(keyword.kwlist)
485 WHITESPACE = {token.DEDENT, token.INDENT, token.NEWLINE}
486 FLOW_CONTROL = {"return", "raise", "break", "continue"}
487 STATEMENT = {
488     syms.if_stmt,
489     syms.while_stmt,
490     syms.for_stmt,
491     syms.try_stmt,
492     syms.except_clause,
493     syms.with_stmt,
494     syms.funcdef,
495     syms.classdef,
496 }
497 STANDALONE_COMMENT = 153
498 LOGIC_OPERATORS = {"and", "or"}
499 COMPARATORS = {
500     token.LESS,
501     token.GREATER,
502     token.EQEQUAL,
503     token.NOTEQUAL,
504     token.LESSEQUAL,
505     token.GREATEREQUAL,
506 }
507 MATH_OPERATORS = {
508     token.PLUS,
509     token.MINUS,
510     token.STAR,
511     token.SLASH,
512     token.VBAR,
513     token.AMPER,
514     token.PERCENT,
515     token.CIRCUMFLEX,
516     token.TILDE,
517     token.LEFTSHIFT,
518     token.RIGHTSHIFT,
519     token.DOUBLESTAR,
520     token.DOUBLESLASH,
521 }
522 VARARGS = {token.STAR, token.DOUBLESTAR}
523 COMPREHENSION_PRIORITY = 20
524 COMMA_PRIORITY = 10
525 LOGIC_PRIORITY = 5
526 STRING_PRIORITY = 4
527 COMPARATOR_PRIORITY = 3
528 MATH_PRIORITY = 1
529
530
531 @dataclass
532 class BracketTracker:
533     """Keeps track of brackets on a line."""
534
535     depth: int = 0
536     bracket_match: Dict[Tuple[Depth, NodeType], Leaf] = Factory(dict)
537     delimiters: Dict[LeafID, Priority] = Factory(dict)
538     previous: Optional[Leaf] = None
539
540     def mark(self, leaf: Leaf) -> None:
541         """Mark `leaf` with bracket-related metadata. Keep track of delimiters.
542
543         All leaves receive an int `bracket_depth` field that stores how deep
544         within brackets a given leaf is. 0 means there are no enclosing brackets
545         that started on this line.
546
547         If a leaf is itself a closing bracket, it receives an `opening_bracket`
548         field that it forms a pair with. This is a one-directional link to
549         avoid reference cycles.
550
551         If a leaf is a delimiter (a token on which Black can split the line if
552         needed) and it's on depth 0, its `id()` is stored in the tracker's
553         `delimiters` field.
554         """
555         if leaf.type == token.COMMENT:
556             return
557
558         if leaf.type in CLOSING_BRACKETS:
559             self.depth -= 1
560             opening_bracket = self.bracket_match.pop((self.depth, leaf.type))
561             leaf.opening_bracket = opening_bracket
562         leaf.bracket_depth = self.depth
563         if self.depth == 0:
564             after_delim = is_split_after_delimiter(leaf, self.previous)
565             before_delim = is_split_before_delimiter(leaf, self.previous)
566             if after_delim > before_delim:
567                 self.delimiters[id(leaf)] = after_delim
568             elif before_delim > after_delim and self.previous is not None:
569                 self.delimiters[id(self.previous)] = before_delim
570         if leaf.type in OPENING_BRACKETS:
571             self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf
572             self.depth += 1
573         self.previous = leaf
574
575     def any_open_brackets(self) -> bool:
576         """Return True if there is an yet unmatched open bracket on the line."""
577         return bool(self.bracket_match)
578
579     def max_delimiter_priority(self, exclude: Iterable[LeafID] = ()) -> int:
580         """Return the highest priority of a delimiter found on the line.
581
582         Values are consistent with what `is_delimiter()` returns.
583         """
584         return max(v for k, v in self.delimiters.items() if k not in exclude)
585
586
587 @dataclass
588 class Line:
589     """Holds leaves and comments. Can be printed with `str(line)`."""
590
591     depth: int = 0
592     leaves: List[Leaf] = Factory(list)
593     comments: List[Tuple[Index, Leaf]] = Factory(list)
594     bracket_tracker: BracketTracker = Factory(BracketTracker)
595     inside_brackets: bool = False
596     has_for: bool = False
597     _for_loop_variable: bool = False
598
599     def append(self, leaf: Leaf, preformatted: bool = False) -> None:
600         """Add a new `leaf` to the end of the line.
601
602         Unless `preformatted` is True, the `leaf` will receive a new consistent
603         whitespace prefix and metadata applied by :class:`BracketTracker`.
604         Trailing commas are maybe removed, unpacked for loop variables are
605         demoted from being delimiters.
606
607         Inline comments are put aside.
608         """
609         has_value = leaf.value.strip()
610         if not has_value:
611             return
612
613         if self.leaves and not preformatted:
614             # Note: at this point leaf.prefix should be empty except for
615             # imports, for which we only preserve newlines.
616             leaf.prefix += whitespace(leaf)
617         if self.inside_brackets or not preformatted:
618             self.maybe_decrement_after_for_loop_variable(leaf)
619             self.bracket_tracker.mark(leaf)
620             self.maybe_remove_trailing_comma(leaf)
621             self.maybe_increment_for_loop_variable(leaf)
622
623         if not self.append_comment(leaf):
624             self.leaves.append(leaf)
625
626     def append_safe(self, leaf: Leaf, preformatted: bool = False) -> None:
627         """Like :func:`append()` but disallow invalid standalone comment structure.
628
629         Raises ValueError when any `leaf` is appended after a standalone comment
630         or when a standalone comment is not the first leaf on the line.
631         """
632         if self.bracket_tracker.depth == 0:
633             if self.is_comment:
634                 raise ValueError("cannot append to standalone comments")
635
636             if self.leaves and leaf.type == STANDALONE_COMMENT:
637                 raise ValueError(
638                     "cannot append standalone comments to a populated line"
639                 )
640
641         self.append(leaf, preformatted=preformatted)
642
643     @property
644     def is_comment(self) -> bool:
645         """Is this line a standalone comment?"""
646         return len(self.leaves) == 1 and self.leaves[0].type == STANDALONE_COMMENT
647
648     @property
649     def is_decorator(self) -> bool:
650         """Is this line a decorator?"""
651         return bool(self) and self.leaves[0].type == token.AT
652
653     @property
654     def is_import(self) -> bool:
655         """Is this an import line?"""
656         return bool(self) and is_import(self.leaves[0])
657
658     @property
659     def is_class(self) -> bool:
660         """Is this line a class definition?"""
661         return (
662             bool(self)
663             and self.leaves[0].type == token.NAME
664             and self.leaves[0].value == "class"
665         )
666
667     @property
668     def is_def(self) -> bool:
669         """Is this a function definition? (Also returns True for async defs.)"""
670         try:
671             first_leaf = self.leaves[0]
672         except IndexError:
673             return False
674
675         try:
676             second_leaf: Optional[Leaf] = self.leaves[1]
677         except IndexError:
678             second_leaf = None
679         return (
680             (first_leaf.type == token.NAME and first_leaf.value == "def")
681             or (
682                 first_leaf.type == token.ASYNC
683                 and second_leaf is not None
684                 and second_leaf.type == token.NAME
685                 and second_leaf.value == "def"
686             )
687         )
688
689     @property
690     def is_flow_control(self) -> bool:
691         """Is this line a flow control statement?
692
693         Those are `return`, `raise`, `break`, and `continue`.
694         """
695         return (
696             bool(self)
697             and self.leaves[0].type == token.NAME
698             and self.leaves[0].value in FLOW_CONTROL
699         )
700
701     @property
702     def is_yield(self) -> bool:
703         """Is this line a yield statement?"""
704         return (
705             bool(self)
706             and self.leaves[0].type == token.NAME
707             and self.leaves[0].value == "yield"
708         )
709
710     @property
711     def contains_standalone_comments(self) -> bool:
712         """If so, needs to be split before emitting."""
713         for leaf in self.leaves:
714             if leaf.type == STANDALONE_COMMENT:
715                 return True
716
717         return False
718
719     def maybe_remove_trailing_comma(self, closing: Leaf) -> bool:
720         """Remove trailing comma if there is one and it's safe."""
721         if not (
722             self.leaves
723             and self.leaves[-1].type == token.COMMA
724             and closing.type in CLOSING_BRACKETS
725         ):
726             return False
727
728         if closing.type == token.RBRACE:
729             self.remove_trailing_comma()
730             return True
731
732         if closing.type == token.RSQB:
733             comma = self.leaves[-1]
734             if comma.parent and comma.parent.type == syms.listmaker:
735                 self.remove_trailing_comma()
736                 return True
737
738         # For parens let's check if it's safe to remove the comma.  If the
739         # trailing one is the only one, we might mistakenly change a tuple
740         # into a different type by removing the comma.
741         depth = closing.bracket_depth + 1
742         commas = 0
743         opening = closing.opening_bracket
744         for _opening_index, leaf in enumerate(self.leaves):
745             if leaf is opening:
746                 break
747
748         else:
749             return False
750
751         for leaf in self.leaves[_opening_index + 1:]:
752             if leaf is closing:
753                 break
754
755             bracket_depth = leaf.bracket_depth
756             if bracket_depth == depth and leaf.type == token.COMMA:
757                 commas += 1
758                 if leaf.parent and leaf.parent.type == syms.arglist:
759                     commas += 1
760                     break
761
762         if commas > 1:
763             self.remove_trailing_comma()
764             return True
765
766         return False
767
768     def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
769         """In a for loop, or comprehension, the variables are often unpacks.
770
771         To avoid splitting on the comma in this situation, increase the depth of
772         tokens between `for` and `in`.
773         """
774         if leaf.type == token.NAME and leaf.value == "for":
775             self.has_for = True
776             self.bracket_tracker.depth += 1
777             self._for_loop_variable = True
778             return True
779
780         return False
781
782     def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool:
783         """See `maybe_increment_for_loop_variable` above for explanation."""
784         if self._for_loop_variable and leaf.type == token.NAME and leaf.value == "in":
785             self.bracket_tracker.depth -= 1
786             self._for_loop_variable = False
787             return True
788
789         return False
790
791     def append_comment(self, comment: Leaf) -> bool:
792         """Add an inline or standalone comment to the line."""
793         if (
794             comment.type == STANDALONE_COMMENT
795             and self.bracket_tracker.any_open_brackets()
796         ):
797             comment.prefix = ""
798             return False
799
800         if comment.type != token.COMMENT:
801             return False
802
803         after = len(self.leaves) - 1
804         if after == -1:
805             comment.type = STANDALONE_COMMENT
806             comment.prefix = ""
807             return False
808
809         else:
810             self.comments.append((after, comment))
811             return True
812
813     def comments_after(self, leaf: Leaf) -> Iterator[Leaf]:
814         """Generate comments that should appear directly after `leaf`."""
815         for _leaf_index, _leaf in enumerate(self.leaves):
816             if leaf is _leaf:
817                 break
818
819         else:
820             return
821
822         for index, comment_after in self.comments:
823             if _leaf_index == index:
824                 yield comment_after
825
826     def remove_trailing_comma(self) -> None:
827         """Remove the trailing comma and moves the comments attached to it."""
828         comma_index = len(self.leaves) - 1
829         for i in range(len(self.comments)):
830             comment_index, comment = self.comments[i]
831             if comment_index == comma_index:
832                 self.comments[i] = (comma_index - 1, comment)
833         self.leaves.pop()
834
835     def __str__(self) -> str:
836         """Render the line."""
837         if not self:
838             return "\n"
839
840         indent = "    " * self.depth
841         leaves = iter(self.leaves)
842         first = next(leaves)
843         res = f"{first.prefix}{indent}{first.value}"
844         for leaf in leaves:
845             res += str(leaf)
846         for _, comment in self.comments:
847             res += str(comment)
848         return res + "\n"
849
850     def __bool__(self) -> bool:
851         """Return True if the line has leaves or comments."""
852         return bool(self.leaves or self.comments)
853
854
855 class UnformattedLines(Line):
856     """Just like :class:`Line` but stores lines which aren't reformatted."""
857
858     def append(self, leaf: Leaf, preformatted: bool = True) -> None:
859         """Just add a new `leaf` to the end of the lines.
860
861         The `preformatted` argument is ignored.
862
863         Keeps track of indentation `depth`, which is useful when the user
864         says `# fmt: on`. Otherwise, doesn't do anything with the `leaf`.
865         """
866         try:
867             list(generate_comments(leaf))
868         except FormatOn as f_on:
869             self.leaves.append(f_on.leaf_from_consumed(leaf))
870             raise
871
872         self.leaves.append(leaf)
873         if leaf.type == token.INDENT:
874             self.depth += 1
875         elif leaf.type == token.DEDENT:
876             self.depth -= 1
877
878     def __str__(self) -> str:
879         """Render unformatted lines from leaves which were added with `append()`.
880
881         `depth` is not used for indentation in this case.
882         """
883         if not self:
884             return "\n"
885
886         res = ""
887         for leaf in self.leaves:
888             res += str(leaf)
889         return res
890
891     def append_comment(self, comment: Leaf) -> bool:
892         """Not implemented in this class. Raises `NotImplementedError`."""
893         raise NotImplementedError("Unformatted lines don't store comments separately.")
894
895     def maybe_remove_trailing_comma(self, closing: Leaf) -> bool:
896         """Does nothing and returns False."""
897         return False
898
899     def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
900         """Does nothing and returns False."""
901         return False
902
903
904 @dataclass
905 class EmptyLineTracker:
906     """Provides a stateful method that returns the number of potential extra
907     empty lines needed before and after the currently processed line.
908
909     Note: this tracker works on lines that haven't been split yet.  It assumes
910     the prefix of the first leaf consists of optional newlines.  Those newlines
911     are consumed by `maybe_empty_lines()` and included in the computation.
912     """
913     previous_line: Optional[Line] = None
914     previous_after: int = 0
915     previous_defs: List[int] = Factory(list)
916
917     def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
918         """Return the number of extra empty lines before and after the `current_line`.
919
920         This is for separating `def`, `async def` and `class` with extra empty
921         lines (two on module-level), as well as providing an extra empty line
922         after flow control keywords to make them more prominent.
923         """
924         if isinstance(current_line, UnformattedLines):
925             return 0, 0
926
927         before, after = self._maybe_empty_lines(current_line)
928         before -= self.previous_after
929         self.previous_after = after
930         self.previous_line = current_line
931         return before, after
932
933     def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
934         max_allowed = 1
935         if current_line.depth == 0:
936             max_allowed = 2
937         if current_line.leaves:
938             # Consume the first leaf's extra newlines.
939             first_leaf = current_line.leaves[0]
940             before = first_leaf.prefix.count("\n")
941             before = min(before, max_allowed)
942             first_leaf.prefix = ""
943         else:
944             before = 0
945         depth = current_line.depth
946         while self.previous_defs and self.previous_defs[-1] >= depth:
947             self.previous_defs.pop()
948             before = 1 if depth else 2
949         is_decorator = current_line.is_decorator
950         if is_decorator or current_line.is_def or current_line.is_class:
951             if not is_decorator:
952                 self.previous_defs.append(depth)
953             if self.previous_line is None:
954                 # Don't insert empty lines before the first line in the file.
955                 return 0, 0
956
957             if self.previous_line and self.previous_line.is_decorator:
958                 # Don't insert empty lines between decorators.
959                 return 0, 0
960
961             newlines = 2
962             if current_line.depth:
963                 newlines -= 1
964             return newlines, 0
965
966         if current_line.is_flow_control:
967             return before, 1
968
969         if (
970             self.previous_line
971             and self.previous_line.is_import
972             and not current_line.is_import
973             and depth == self.previous_line.depth
974         ):
975             return (before or 1), 0
976
977         if (
978             self.previous_line
979             and self.previous_line.is_yield
980             and (not current_line.is_yield or depth != self.previous_line.depth)
981         ):
982             return (before or 1), 0
983
984         return before, 0
985
986
987 @dataclass
988 class LineGenerator(Visitor[Line]):
989     """Generates reformatted Line objects.  Empty lines are not emitted.
990
991     Note: destroys the tree it's visiting by mutating prefixes of its leaves
992     in ways that will no longer stringify to valid Python code on the tree.
993     """
994     current_line: Line = Factory(Line)
995
996     def line(self, indent: int = 0, type: Type[Line] = Line) -> Iterator[Line]:
997         """Generate a line.
998
999         If the line is empty, only emit if it makes sense.
1000         If the line is too long, split it first and then generate.
1001
1002         If any lines were generated, set up a new current_line.
1003         """
1004         if not self.current_line:
1005             if self.current_line.__class__ == type:
1006                 self.current_line.depth += indent
1007             else:
1008                 self.current_line = type(depth=self.current_line.depth + indent)
1009             return  # Line is empty, don't emit. Creating a new one unnecessary.
1010
1011         complete_line = self.current_line
1012         self.current_line = type(depth=complete_line.depth + indent)
1013         yield complete_line
1014
1015     def visit(self, node: LN) -> Iterator[Line]:
1016         """Main method to visit `node` and its children.
1017
1018         Yields :class:`Line` objects.
1019         """
1020         if isinstance(self.current_line, UnformattedLines):
1021             # File contained `# fmt: off`
1022             yield from self.visit_unformatted(node)
1023
1024         else:
1025             yield from super().visit(node)
1026
1027     def visit_default(self, node: LN) -> Iterator[Line]:
1028         """Default `visit_*()` implementation. Recurses to children of `node`."""
1029         if isinstance(node, Leaf):
1030             any_open_brackets = self.current_line.bracket_tracker.any_open_brackets()
1031             try:
1032                 for comment in generate_comments(node):
1033                     if any_open_brackets:
1034                         # any comment within brackets is subject to splitting
1035                         self.current_line.append(comment)
1036                     elif comment.type == token.COMMENT:
1037                         # regular trailing comment
1038                         self.current_line.append(comment)
1039                         yield from self.line()
1040
1041                     else:
1042                         # regular standalone comment
1043                         yield from self.line()
1044
1045                         self.current_line.append(comment)
1046                         yield from self.line()
1047
1048             except FormatOff as f_off:
1049                 f_off.trim_prefix(node)
1050                 yield from self.line(type=UnformattedLines)
1051                 yield from self.visit(node)
1052
1053             except FormatOn as f_on:
1054                 # This only happens here if somebody says "fmt: on" multiple
1055                 # times in a row.
1056                 f_on.trim_prefix(node)
1057                 yield from self.visit_default(node)
1058
1059             else:
1060                 normalize_prefix(node, inside_brackets=any_open_brackets)
1061                 if node.type == token.STRING:
1062                     normalize_string_quotes(node)
1063                 if node.type not in WHITESPACE:
1064                     self.current_line.append(node)
1065         yield from super().visit_default(node)
1066
1067     def visit_INDENT(self, node: Node) -> Iterator[Line]:
1068         """Increase indentation level, maybe yield a line."""
1069         # In blib2to3 INDENT never holds comments.
1070         yield from self.line(+1)
1071         yield from self.visit_default(node)
1072
1073     def visit_DEDENT(self, node: Node) -> Iterator[Line]:
1074         """Decrease indentation level, maybe yield a line."""
1075         # DEDENT has no value. Additionally, in blib2to3 it never holds comments.
1076         yield from self.line(-1)
1077
1078     def visit_stmt(self, node: Node, keywords: Set[str]) -> Iterator[Line]:
1079         """Visit a statement.
1080
1081         This implementation is shared for `if`, `while`, `for`, `try`, `except`,
1082         `def`, `with`, and `class`.
1083
1084         The relevant Python language `keywords` for a given statement will be NAME
1085         leaves within it. This methods puts those on a separate line.
1086         """
1087         for child in node.children:
1088             if child.type == token.NAME and child.value in keywords:  # type: ignore
1089                 yield from self.line()
1090
1091             yield from self.visit(child)
1092
1093     def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
1094         """Visit a statement without nested statements."""
1095         is_suite_like = node.parent and node.parent.type in STATEMENT
1096         if is_suite_like:
1097             yield from self.line(+1)
1098             yield from self.visit_default(node)
1099             yield from self.line(-1)
1100
1101         else:
1102             yield from self.line()
1103             yield from self.visit_default(node)
1104
1105     def visit_async_stmt(self, node: Node) -> Iterator[Line]:
1106         """Visit `async def`, `async for`, `async with`."""
1107         yield from self.line()
1108
1109         children = iter(node.children)
1110         for child in children:
1111             yield from self.visit(child)
1112
1113             if child.type == token.ASYNC:
1114                 break
1115
1116         internal_stmt = next(children)
1117         for child in internal_stmt.children:
1118             yield from self.visit(child)
1119
1120     def visit_decorators(self, node: Node) -> Iterator[Line]:
1121         """Visit decorators."""
1122         for child in node.children:
1123             yield from self.line()
1124             yield from self.visit(child)
1125
1126     def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]:
1127         """Remove a semicolon and put the other statement on a separate line."""
1128         yield from self.line()
1129
1130     def visit_ENDMARKER(self, leaf: Leaf) -> Iterator[Line]:
1131         """End of file. Process outstanding comments and end with a newline."""
1132         yield from self.visit_default(leaf)
1133         yield from self.line()
1134
1135     def visit_unformatted(self, node: LN) -> Iterator[Line]:
1136         """Used when file contained a `# fmt: off`."""
1137         if isinstance(node, Node):
1138             for child in node.children:
1139                 yield from self.visit(child)
1140
1141         else:
1142             try:
1143                 self.current_line.append(node)
1144             except FormatOn as f_on:
1145                 f_on.trim_prefix(node)
1146                 yield from self.line()
1147                 yield from self.visit(node)
1148
1149             if node.type == token.ENDMARKER:
1150                 # somebody decided not to put a final `# fmt: on`
1151                 yield from self.line()
1152
1153     def __attrs_post_init__(self) -> None:
1154         """You are in a twisty little maze of passages."""
1155         v = self.visit_stmt
1156         self.visit_if_stmt = partial(v, keywords={"if", "else", "elif"})
1157         self.visit_while_stmt = partial(v, keywords={"while", "else"})
1158         self.visit_for_stmt = partial(v, keywords={"for", "else"})
1159         self.visit_try_stmt = partial(v, keywords={"try", "except", "else", "finally"})
1160         self.visit_except_clause = partial(v, keywords={"except"})
1161         self.visit_funcdef = partial(v, keywords={"def"})
1162         self.visit_with_stmt = partial(v, keywords={"with"})
1163         self.visit_classdef = partial(v, keywords={"class"})
1164         self.visit_async_funcdef = self.visit_async_stmt
1165         self.visit_decorated = self.visit_decorators
1166
1167
1168 BRACKET = {token.LPAR: token.RPAR, token.LSQB: token.RSQB, token.LBRACE: token.RBRACE}
1169 OPENING_BRACKETS = set(BRACKET.keys())
1170 CLOSING_BRACKETS = set(BRACKET.values())
1171 BRACKETS = OPENING_BRACKETS | CLOSING_BRACKETS
1172 ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT}
1173
1174
1175 def whitespace(leaf: Leaf) -> str:  # noqa C901
1176     """Return whitespace prefix if needed for the given `leaf`."""
1177     NO = ""
1178     SPACE = " "
1179     DOUBLESPACE = "  "
1180     t = leaf.type
1181     p = leaf.parent
1182     v = leaf.value
1183     if t in ALWAYS_NO_SPACE:
1184         return NO
1185
1186     if t == token.COMMENT:
1187         return DOUBLESPACE
1188
1189     assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
1190     if t == token.COLON and p.type not in {syms.subscript, syms.subscriptlist}:
1191         return NO
1192
1193     prev = leaf.prev_sibling
1194     if not prev:
1195         prevp = preceding_leaf(p)
1196         if not prevp or prevp.type in OPENING_BRACKETS:
1197             return NO
1198
1199         if t == token.COLON:
1200             return SPACE if prevp.type == token.COMMA else NO
1201
1202         if prevp.type == token.EQUAL:
1203             if prevp.parent:
1204                 if prevp.parent.type in {
1205                     syms.arglist, syms.argument, syms.parameters, syms.varargslist
1206                 }:
1207                     return NO
1208
1209                 elif prevp.parent.type == syms.typedargslist:
1210                     # A bit hacky: if the equal sign has whitespace, it means we
1211                     # previously found it's a typed argument.  So, we're using
1212                     # that, too.
1213                     return prevp.prefix
1214
1215         elif prevp.type == token.DOUBLESTAR:
1216             if prevp.parent and prevp.parent.type in {
1217                 syms.arglist,
1218                 syms.argument,
1219                 syms.dictsetmaker,
1220                 syms.parameters,
1221                 syms.typedargslist,
1222                 syms.varargslist,
1223             }:
1224                 return NO
1225
1226         elif prevp.type == token.COLON:
1227             if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}:
1228                 return NO
1229
1230         elif (
1231             prevp.parent
1232             and prevp.parent.type in {syms.factor, syms.star_expr}
1233             and prevp.type in MATH_OPERATORS
1234         ):
1235             return NO
1236
1237         elif (
1238             prevp.type == token.RIGHTSHIFT
1239             and prevp.parent
1240             and prevp.parent.type == syms.shift_expr
1241             and prevp.prev_sibling
1242             and prevp.prev_sibling.type == token.NAME
1243             and prevp.prev_sibling.value == "print"  # type: ignore
1244         ):
1245             # Python 2 print chevron
1246             return NO
1247
1248     elif prev.type in OPENING_BRACKETS:
1249         return NO
1250
1251     if p.type in {syms.parameters, syms.arglist}:
1252         # untyped function signatures or calls
1253         if t == token.RPAR:
1254             return NO
1255
1256         if not prev or prev.type != token.COMMA:
1257             return NO
1258
1259     elif p.type == syms.varargslist:
1260         # lambdas
1261         if t == token.RPAR:
1262             return NO
1263
1264         if prev and prev.type != token.COMMA:
1265             return NO
1266
1267     elif p.type == syms.typedargslist:
1268         # typed function signatures
1269         if not prev:
1270             return NO
1271
1272         if t == token.EQUAL:
1273             if prev.type != syms.tname:
1274                 return NO
1275
1276         elif prev.type == token.EQUAL:
1277             # A bit hacky: if the equal sign has whitespace, it means we
1278             # previously found it's a typed argument.  So, we're using that, too.
1279             return prev.prefix
1280
1281         elif prev.type != token.COMMA:
1282             return NO
1283
1284     elif p.type == syms.tname:
1285         # type names
1286         if not prev:
1287             prevp = preceding_leaf(p)
1288             if not prevp or prevp.type != token.COMMA:
1289                 return NO
1290
1291     elif p.type == syms.trailer:
1292         # attributes and calls
1293         if t == token.LPAR or t == token.RPAR:
1294             return NO
1295
1296         if not prev:
1297             if t == token.DOT:
1298                 prevp = preceding_leaf(p)
1299                 if not prevp or prevp.type != token.NUMBER:
1300                     return NO
1301
1302             elif t == token.LSQB:
1303                 return NO
1304
1305         elif prev.type != token.COMMA:
1306             return NO
1307
1308     elif p.type == syms.argument:
1309         # single argument
1310         if t == token.EQUAL:
1311             return NO
1312
1313         if not prev:
1314             prevp = preceding_leaf(p)
1315             if not prevp or prevp.type == token.LPAR:
1316                 return NO
1317
1318         elif prev.type == token.EQUAL or prev.type == token.DOUBLESTAR:
1319             return NO
1320
1321     elif p.type == syms.decorator:
1322         # decorators
1323         return NO
1324
1325     elif p.type == syms.dotted_name:
1326         if prev:
1327             return NO
1328
1329         prevp = preceding_leaf(p)
1330         if not prevp or prevp.type == token.AT or prevp.type == token.DOT:
1331             return NO
1332
1333     elif p.type == syms.classdef:
1334         if t == token.LPAR:
1335             return NO
1336
1337         if prev and prev.type == token.LPAR:
1338             return NO
1339
1340     elif p.type == syms.subscript:
1341         # indexing
1342         if not prev:
1343             assert p.parent is not None, "subscripts are always parented"
1344             if p.parent.type == syms.subscriptlist:
1345                 return SPACE
1346
1347             return NO
1348
1349         else:
1350             return NO
1351
1352     elif p.type == syms.atom:
1353         if prev and t == token.DOT:
1354             # dots, but not the first one.
1355             return NO
1356
1357     elif (
1358         p.type == syms.listmaker
1359         or p.type == syms.testlist_gexp
1360         or p.type == syms.subscriptlist
1361     ):
1362         # list interior, including unpacking
1363         if not prev:
1364             return NO
1365
1366     elif p.type == syms.dictsetmaker:
1367         # dict and set interior, including unpacking
1368         if not prev:
1369             return NO
1370
1371         if prev.type == token.DOUBLESTAR:
1372             return NO
1373
1374     elif p.type in {syms.factor, syms.star_expr}:
1375         # unary ops
1376         if not prev:
1377             prevp = preceding_leaf(p)
1378             if not prevp or prevp.type in OPENING_BRACKETS:
1379                 return NO
1380
1381             prevp_parent = prevp.parent
1382             assert prevp_parent is not None
1383             if prevp.type == token.COLON and prevp_parent.type in {
1384                 syms.subscript, syms.sliceop
1385             }:
1386                 return NO
1387
1388             elif prevp.type == token.EQUAL and prevp_parent.type == syms.argument:
1389                 return NO
1390
1391         elif t == token.NAME or t == token.NUMBER:
1392             return NO
1393
1394     elif p.type == syms.import_from:
1395         if t == token.DOT:
1396             if prev and prev.type == token.DOT:
1397                 return NO
1398
1399         elif t == token.NAME:
1400             if v == "import":
1401                 return SPACE
1402
1403             if prev and prev.type == token.DOT:
1404                 return NO
1405
1406     elif p.type == syms.sliceop:
1407         return NO
1408
1409     return SPACE
1410
1411
1412 def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]:
1413     """Return the first leaf that precedes `node`, if any."""
1414     while node:
1415         res = node.prev_sibling
1416         if res:
1417             if isinstance(res, Leaf):
1418                 return res
1419
1420             try:
1421                 return list(res.leaves())[-1]
1422
1423             except IndexError:
1424                 return None
1425
1426         node = node.parent
1427     return None
1428
1429
1430 def is_split_after_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
1431     """Return the priority of the `leaf` delimiter, given a line break after it.
1432
1433     The delimiter priorities returned here are from those delimiters that would
1434     cause a line break after themselves.
1435
1436     Higher numbers are higher priority.
1437     """
1438     if leaf.type == token.COMMA:
1439         return COMMA_PRIORITY
1440
1441     if (
1442         leaf.type in VARARGS
1443         and leaf.parent
1444         and leaf.parent.type in {syms.argument, syms.typedargslist}
1445     ):
1446         return MATH_PRIORITY
1447
1448     return 0
1449
1450
1451 def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
1452     """Return the priority of the `leaf` delimiter, given a line before after it.
1453
1454     The delimiter priorities returned here are from those delimiters that would
1455     cause a line break before themselves.
1456
1457     Higher numbers are higher priority.
1458     """
1459     if (
1460         leaf.type in MATH_OPERATORS
1461         and leaf.parent
1462         and leaf.parent.type not in {syms.factor, syms.star_expr}
1463     ):
1464         return MATH_PRIORITY
1465
1466     if leaf.type in COMPARATORS:
1467         return COMPARATOR_PRIORITY
1468
1469     if (
1470         leaf.type == token.STRING
1471         and previous is not None
1472         and previous.type == token.STRING
1473     ):
1474         return STRING_PRIORITY
1475
1476     if (
1477         leaf.type == token.NAME
1478         and leaf.value == "for"
1479         and leaf.parent
1480         and leaf.parent.type in {syms.comp_for, syms.old_comp_for}
1481     ):
1482         return COMPREHENSION_PRIORITY
1483
1484     if (
1485         leaf.type == token.NAME
1486         and leaf.value == "if"
1487         and leaf.parent
1488         and leaf.parent.type in {syms.comp_if, syms.old_comp_if}
1489     ):
1490         return COMPREHENSION_PRIORITY
1491
1492     if leaf.type == token.NAME and leaf.value in LOGIC_OPERATORS and leaf.parent:
1493         return LOGIC_PRIORITY
1494
1495     return 0
1496
1497
1498 def is_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
1499     """Return the priority of the `leaf` delimiter. Return 0 if not delimiter.
1500
1501     Higher numbers are higher priority.
1502     """
1503     return max(
1504         is_split_before_delimiter(leaf, previous),
1505         is_split_after_delimiter(leaf, previous),
1506     )
1507
1508
1509 def generate_comments(leaf: Leaf) -> Iterator[Leaf]:
1510     """Clean the prefix of the `leaf` and generate comments from it, if any.
1511
1512     Comments in lib2to3 are shoved into the whitespace prefix.  This happens
1513     in `pgen2/driver.py:Driver.parse_tokens()`.  This was a brilliant implementation
1514     move because it does away with modifying the grammar to include all the
1515     possible places in which comments can be placed.
1516
1517     The sad consequence for us though is that comments don't "belong" anywhere.
1518     This is why this function generates simple parentless Leaf objects for
1519     comments.  We simply don't know what the correct parent should be.
1520
1521     No matter though, we can live without this.  We really only need to
1522     differentiate between inline and standalone comments.  The latter don't
1523     share the line with any code.
1524
1525     Inline comments are emitted as regular token.COMMENT leaves.  Standalone
1526     are emitted with a fake STANDALONE_COMMENT token identifier.
1527     """
1528     p = leaf.prefix
1529     if not p:
1530         return
1531
1532     if "#" not in p:
1533         return
1534
1535     consumed = 0
1536     nlines = 0
1537     for index, line in enumerate(p.split("\n")):
1538         consumed += len(line) + 1  # adding the length of the split '\n'
1539         line = line.lstrip()
1540         if not line:
1541             nlines += 1
1542         if not line.startswith("#"):
1543             continue
1544
1545         if index == 0 and leaf.type != token.ENDMARKER:
1546             comment_type = token.COMMENT  # simple trailing comment
1547         else:
1548             comment_type = STANDALONE_COMMENT
1549         comment = make_comment(line)
1550         yield Leaf(comment_type, comment, prefix="\n" * nlines)
1551
1552         if comment in {"# fmt: on", "# yapf: enable"}:
1553             raise FormatOn(consumed)
1554
1555         if comment in {"# fmt: off", "# yapf: disable"}:
1556             if comment_type == STANDALONE_COMMENT:
1557                 raise FormatOff(consumed)
1558
1559             prev = preceding_leaf(leaf)
1560             if not prev or prev.type in WHITESPACE:  # standalone comment in disguise
1561                 raise FormatOff(consumed)
1562
1563         nlines = 0
1564
1565
1566 def make_comment(content: str) -> str:
1567     """Return a consistently formatted comment from the given `content` string.
1568
1569     All comments (except for "##", "#!", "#:") should have a single space between
1570     the hash sign and the content.
1571
1572     If `content` didn't start with a hash sign, one is provided.
1573     """
1574     content = content.rstrip()
1575     if not content:
1576         return "#"
1577
1578     if content[0] == "#":
1579         content = content[1:]
1580     if content and content[0] not in " !:#":
1581         content = " " + content
1582     return "#" + content
1583
1584
1585 def split_line(
1586     line: Line, line_length: int, inner: bool = False, py36: bool = False
1587 ) -> Iterator[Line]:
1588     """Split a `line` into potentially many lines.
1589
1590     They should fit in the allotted `line_length` but might not be able to.
1591     `inner` signifies that there were a pair of brackets somewhere around the
1592     current `line`, possibly transitively. This means we can fallback to splitting
1593     by delimiters if the LHS/RHS don't yield any results.
1594
1595     If `py36` is True, splitting may generate syntax that is only compatible
1596     with Python 3.6 and later.
1597     """
1598     if isinstance(line, UnformattedLines) or line.is_comment:
1599         yield line
1600         return
1601
1602     line_str = str(line).strip("\n")
1603     if (
1604         len(line_str) <= line_length
1605         and "\n" not in line_str  # multiline strings
1606         and not line.contains_standalone_comments
1607     ):
1608         yield line
1609         return
1610
1611     split_funcs: List[SplitFunc]
1612     if line.is_def:
1613         split_funcs = [left_hand_split]
1614     elif line.inside_brackets:
1615         split_funcs = [delimiter_split, standalone_comment_split, right_hand_split]
1616     else:
1617         split_funcs = [right_hand_split]
1618     for split_func in split_funcs:
1619         # We are accumulating lines in `result` because we might want to abort
1620         # mission and return the original line in the end, or attempt a different
1621         # split altogether.
1622         result: List[Line] = []
1623         try:
1624             for l in split_func(line, py36):
1625                 if str(l).strip("\n") == line_str:
1626                     raise CannotSplit("Split function returned an unchanged result")
1627
1628                 result.extend(
1629                     split_line(l, line_length=line_length, inner=True, py36=py36)
1630                 )
1631         except CannotSplit as cs:
1632             continue
1633
1634         else:
1635             yield from result
1636             break
1637
1638     else:
1639         yield line
1640
1641
1642 def left_hand_split(line: Line, py36: bool = False) -> Iterator[Line]:
1643     """Split line into many lines, starting with the first matching bracket pair.
1644
1645     Note: this usually looks weird, only use this for function definitions.
1646     Prefer RHS otherwise.
1647     """
1648     head = Line(depth=line.depth)
1649     body = Line(depth=line.depth + 1, inside_brackets=True)
1650     tail = Line(depth=line.depth)
1651     tail_leaves: List[Leaf] = []
1652     body_leaves: List[Leaf] = []
1653     head_leaves: List[Leaf] = []
1654     current_leaves = head_leaves
1655     matching_bracket = None
1656     for leaf in line.leaves:
1657         if (
1658             current_leaves is body_leaves
1659             and leaf.type in CLOSING_BRACKETS
1660             and leaf.opening_bracket is matching_bracket
1661         ):
1662             current_leaves = tail_leaves if body_leaves else head_leaves
1663         current_leaves.append(leaf)
1664         if current_leaves is head_leaves:
1665             if leaf.type in OPENING_BRACKETS:
1666                 matching_bracket = leaf
1667                 current_leaves = body_leaves
1668     # Since body is a new indent level, remove spurious leading whitespace.
1669     if body_leaves:
1670         normalize_prefix(body_leaves[0], inside_brackets=True)
1671     # Build the new lines.
1672     for result, leaves in (
1673         (head, head_leaves), (body, body_leaves), (tail, tail_leaves)
1674     ):
1675         for leaf in leaves:
1676             result.append(leaf, preformatted=True)
1677             for comment_after in line.comments_after(leaf):
1678                 result.append(comment_after, preformatted=True)
1679     bracket_split_succeeded_or_raise(head, body, tail)
1680     for result in (head, body, tail):
1681         if result:
1682             yield result
1683
1684
1685 def right_hand_split(line: Line, py36: bool = False) -> Iterator[Line]:
1686     """Split line into many lines, starting with the last matching bracket pair."""
1687     head = Line(depth=line.depth)
1688     body = Line(depth=line.depth + 1, inside_brackets=True)
1689     tail = Line(depth=line.depth)
1690     tail_leaves: List[Leaf] = []
1691     body_leaves: List[Leaf] = []
1692     head_leaves: List[Leaf] = []
1693     current_leaves = tail_leaves
1694     opening_bracket = None
1695     for leaf in reversed(line.leaves):
1696         if current_leaves is body_leaves:
1697             if leaf is opening_bracket:
1698                 current_leaves = head_leaves if body_leaves else tail_leaves
1699         current_leaves.append(leaf)
1700         if current_leaves is tail_leaves:
1701             if leaf.type in CLOSING_BRACKETS:
1702                 opening_bracket = leaf.opening_bracket
1703                 current_leaves = body_leaves
1704     tail_leaves.reverse()
1705     body_leaves.reverse()
1706     head_leaves.reverse()
1707     # Since body is a new indent level, remove spurious leading whitespace.
1708     if body_leaves:
1709         normalize_prefix(body_leaves[0], inside_brackets=True)
1710     # Build the new lines.
1711     for result, leaves in (
1712         (head, head_leaves), (body, body_leaves), (tail, tail_leaves)
1713     ):
1714         for leaf in leaves:
1715             result.append(leaf, preformatted=True)
1716             for comment_after in line.comments_after(leaf):
1717                 result.append(comment_after, preformatted=True)
1718     bracket_split_succeeded_or_raise(head, body, tail)
1719     for result in (head, body, tail):
1720         if result:
1721             yield result
1722
1723
1724 def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None:
1725     """Raise :exc:`CannotSplit` if the last left- or right-hand split failed.
1726
1727     Do nothing otherwise.
1728
1729     A left- or right-hand split is based on a pair of brackets. Content before
1730     (and including) the opening bracket is left on one line, content inside the
1731     brackets is put on a separate line, and finally content starting with and
1732     following the closing bracket is put on a separate line.
1733
1734     Those are called `head`, `body`, and `tail`, respectively. If the split
1735     produced the same line (all content in `head`) or ended up with an empty `body`
1736     and the `tail` is just the closing bracket, then it's considered failed.
1737     """
1738     tail_len = len(str(tail).strip())
1739     if not body:
1740         if tail_len == 0:
1741             raise CannotSplit("Splitting brackets produced the same line")
1742
1743         elif tail_len < 3:
1744             raise CannotSplit(
1745                 f"Splitting brackets on an empty body to save "
1746                 f"{tail_len} characters is not worth it"
1747             )
1748
1749
1750 def dont_increase_indentation(split_func: SplitFunc) -> SplitFunc:
1751     """Normalize prefix of the first leaf in every line returned by `split_func`.
1752
1753     This is a decorator over relevant split functions.
1754     """
1755
1756     @wraps(split_func)
1757     def split_wrapper(line: Line, py36: bool = False) -> Iterator[Line]:
1758         for l in split_func(line, py36):
1759             normalize_prefix(l.leaves[0], inside_brackets=True)
1760             yield l
1761
1762     return split_wrapper
1763
1764
1765 @dont_increase_indentation
1766 def delimiter_split(line: Line, py36: bool = False) -> Iterator[Line]:
1767     """Split according to delimiters of the highest priority.
1768
1769     If `py36` is True, the split will add trailing commas also in function
1770     signatures that contain `*` and `**`.
1771     """
1772     try:
1773         last_leaf = line.leaves[-1]
1774     except IndexError:
1775         raise CannotSplit("Line empty")
1776
1777     delimiters = line.bracket_tracker.delimiters
1778     try:
1779         delimiter_priority = line.bracket_tracker.max_delimiter_priority(
1780             exclude={id(last_leaf)}
1781         )
1782     except ValueError:
1783         raise CannotSplit("No delimiters found")
1784
1785     current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1786     lowest_depth = sys.maxsize
1787     trailing_comma_safe = True
1788
1789     def append_to_line(leaf: Leaf) -> Iterator[Line]:
1790         """Append `leaf` to current line or to new line if appending impossible."""
1791         nonlocal current_line
1792         try:
1793             current_line.append_safe(leaf, preformatted=True)
1794         except ValueError as ve:
1795             yield current_line
1796
1797             current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1798             current_line.append(leaf)
1799
1800     for leaf in line.leaves:
1801         yield from append_to_line(leaf)
1802
1803         for comment_after in line.comments_after(leaf):
1804             yield from append_to_line(comment_after)
1805
1806         lowest_depth = min(lowest_depth, leaf.bracket_depth)
1807         if (
1808             leaf.bracket_depth == lowest_depth
1809             and leaf.type == token.STAR
1810             or leaf.type == token.DOUBLESTAR
1811         ):
1812             trailing_comma_safe = trailing_comma_safe and py36
1813         leaf_priority = delimiters.get(id(leaf))
1814         if leaf_priority == delimiter_priority:
1815             yield current_line
1816
1817             current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1818     if current_line:
1819         if (
1820             trailing_comma_safe
1821             and delimiter_priority == COMMA_PRIORITY
1822             and current_line.leaves[-1].type != token.COMMA
1823             and current_line.leaves[-1].type != STANDALONE_COMMENT
1824         ):
1825             current_line.append(Leaf(token.COMMA, ","))
1826         yield current_line
1827
1828
1829 @dont_increase_indentation
1830 def standalone_comment_split(line: Line, py36: bool = False) -> Iterator[Line]:
1831     """Split standalone comments from the rest of the line."""
1832     for leaf in line.leaves:
1833         if leaf.type == STANDALONE_COMMENT:
1834             if leaf.bracket_depth == 0:
1835                 break
1836
1837     else:
1838         raise CannotSplit("Line does not have any standalone comments")
1839
1840     current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1841
1842     def append_to_line(leaf: Leaf) -> Iterator[Line]:
1843         """Append `leaf` to current line or to new line if appending impossible."""
1844         nonlocal current_line
1845         try:
1846             current_line.append_safe(leaf, preformatted=True)
1847         except ValueError as ve:
1848             yield current_line
1849
1850             current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
1851             current_line.append(leaf)
1852
1853     for leaf in line.leaves:
1854         yield from append_to_line(leaf)
1855
1856         for comment_after in line.comments_after(leaf):
1857             yield from append_to_line(comment_after)
1858
1859     if current_line:
1860         yield current_line
1861
1862
1863 def is_import(leaf: Leaf) -> bool:
1864     """Return True if the given leaf starts an import statement."""
1865     p = leaf.parent
1866     t = leaf.type
1867     v = leaf.value
1868     return bool(
1869         t == token.NAME
1870         and (
1871             (v == "import" and p and p.type == syms.import_name)
1872             or (v == "from" and p and p.type == syms.import_from)
1873         )
1874     )
1875
1876
1877 def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
1878     """Leave existing extra newlines if not `inside_brackets`. Remove everything
1879     else.
1880
1881     Note: don't use backslashes for formatting or you'll lose your voting rights.
1882     """
1883     if not inside_brackets:
1884         spl = leaf.prefix.split("#")
1885         if "\\" not in spl[0]:
1886             nl_count = spl[-1].count("\n")
1887             if len(spl) > 1:
1888                 nl_count -= 1
1889             leaf.prefix = "\n" * nl_count
1890             return
1891
1892     leaf.prefix = ""
1893
1894
1895 def normalize_string_quotes(leaf: Leaf) -> None:
1896     """Prefer double quotes but only if it doesn't cause more escaping.
1897
1898     Adds or removes backslashes as appropriate. Doesn't parse and fix
1899     strings nested in f-strings (yet).
1900
1901     Note: Mutates its argument.
1902     """
1903     value = leaf.value.lstrip("furbFURB")
1904     if value[:3] == '"""':
1905         return
1906
1907     elif value[:3] == "'''":
1908         orig_quote = "'''"
1909         new_quote = '"""'
1910     elif value[0] == '"':
1911         orig_quote = '"'
1912         new_quote = "'"
1913     else:
1914         orig_quote = "'"
1915         new_quote = '"'
1916     first_quote_pos = leaf.value.find(orig_quote)
1917     if first_quote_pos == -1:
1918         return  # There's an internal error
1919
1920     prefix = leaf.value[:first_quote_pos]
1921     body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
1922     if "r" in prefix.casefold():
1923         if body.count(new_quote) != body.count(f"\\{new_quote}"):
1924             # There's at least one unescaped new_quote in this raw string
1925             # so converting is impossible
1926             return
1927
1928         # Do not introduce or remove backslashes in raw strings
1929         new_body = body
1930     else:
1931         new_body = body.replace(f"\\{orig_quote}", orig_quote).replace(
1932             new_quote, f"\\{new_quote}"
1933         )
1934     if new_quote == '"""' and new_body[-1] == '"':
1935         # edge case:
1936         new_body = new_body[:-1] + '\\"'
1937     orig_escape_count = body.count("\\")
1938     new_escape_count = new_body.count("\\")
1939     if new_escape_count > orig_escape_count:
1940         return  # Do not introduce more escaping
1941
1942     if new_escape_count == orig_escape_count and orig_quote == '"':
1943         return  # Prefer double quotes
1944
1945     leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}"
1946
1947
1948 def is_python36(node: Node) -> bool:
1949     """Return True if the current file is using Python 3.6+ features.
1950
1951     Currently looking for:
1952     - f-strings; and
1953     - trailing commas after * or ** in function signatures.
1954     """
1955     for n in node.pre_order():
1956         if n.type == token.STRING:
1957             value_head = n.value[:2]  # type: ignore
1958             if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
1959                 return True
1960
1961         elif (
1962             n.type == syms.typedargslist
1963             and n.children
1964             and n.children[-1].type == token.COMMA
1965         ):
1966             for ch in n.children:
1967                 if ch.type == token.STAR or ch.type == token.DOUBLESTAR:
1968                     return True
1969
1970     return False
1971
1972
1973 PYTHON_EXTENSIONS = {".py"}
1974 BLACKLISTED_DIRECTORIES = {
1975     "build", "buck-out", "dist", "_build", ".git", ".hg", ".mypy_cache", ".tox", ".venv"
1976 }
1977
1978
1979 def gen_python_files_in_dir(path: Path) -> Iterator[Path]:
1980     """Generate all files under `path` which aren't under BLACKLISTED_DIRECTORIES
1981     and have one of the PYTHON_EXTENSIONS.
1982     """
1983     for child in path.iterdir():
1984         if child.is_dir():
1985             if child.name in BLACKLISTED_DIRECTORIES:
1986                 continue
1987
1988             yield from gen_python_files_in_dir(child)
1989
1990         elif child.suffix in PYTHON_EXTENSIONS:
1991             yield child
1992
1993
1994 @dataclass
1995 class Report:
1996     """Provides a reformatting counter. Can be rendered with `str(report)`."""
1997     check: bool = False
1998     quiet: bool = False
1999     change_count: int = 0
2000     same_count: int = 0
2001     failure_count: int = 0
2002
2003     def done(self, src: Path, changed: bool) -> None:
2004         """Increment the counter for successful reformatting. Write out a message."""
2005         if changed:
2006             reformatted = "would reformat" if self.check else "reformatted"
2007             if not self.quiet:
2008                 out(f"{reformatted} {src}")
2009             self.change_count += 1
2010         else:
2011             if not self.quiet:
2012                 out(f"{src} already well formatted, good job.", bold=False)
2013             self.same_count += 1
2014
2015     def failed(self, src: Path, message: str) -> None:
2016         """Increment the counter for failed reformatting. Write out a message."""
2017         err(f"error: cannot format {src}: {message}")
2018         self.failure_count += 1
2019
2020     @property
2021     def return_code(self) -> int:
2022         """Return the exit code that the app should use.
2023
2024         This considers the current state of changed files and failures:
2025         - if there were any failures, return 123;
2026         - if any files were changed and --check is being used, return 1;
2027         - otherwise return 0.
2028         """
2029         # According to http://tldp.org/LDP/abs/html/exitcodes.html starting with
2030         # 126 we have special returncodes reserved by the shell.
2031         if self.failure_count:
2032             return 123
2033
2034         elif self.change_count and self.check:
2035             return 1
2036
2037         return 0
2038
2039     def __str__(self) -> str:
2040         """Render a color report of the current state.
2041
2042         Use `click.unstyle` to remove colors.
2043         """
2044         if self.check:
2045             reformatted = "would be reformatted"
2046             unchanged = "would be left unchanged"
2047             failed = "would fail to reformat"
2048         else:
2049             reformatted = "reformatted"
2050             unchanged = "left unchanged"
2051             failed = "failed to reformat"
2052         report = []
2053         if self.change_count:
2054             s = "s" if self.change_count > 1 else ""
2055             report.append(
2056                 click.style(f"{self.change_count} file{s} {reformatted}", bold=True)
2057             )
2058         if self.same_count:
2059             s = "s" if self.same_count > 1 else ""
2060             report.append(f"{self.same_count} file{s} {unchanged}")
2061         if self.failure_count:
2062             s = "s" if self.failure_count > 1 else ""
2063             report.append(
2064                 click.style(f"{self.failure_count} file{s} {failed}", fg="red")
2065             )
2066         return ", ".join(report) + "."
2067
2068
2069 def assert_equivalent(src: str, dst: str) -> None:
2070     """Raise AssertionError if `src` and `dst` aren't equivalent."""
2071
2072     import ast
2073     import traceback
2074
2075     def _v(node: ast.AST, depth: int = 0) -> Iterator[str]:
2076         """Simple visitor generating strings to compare ASTs by content."""
2077         yield f"{'  ' * depth}{node.__class__.__name__}("
2078
2079         for field in sorted(node._fields):
2080             try:
2081                 value = getattr(node, field)
2082             except AttributeError:
2083                 continue
2084
2085             yield f"{'  ' * (depth+1)}{field}="
2086
2087             if isinstance(value, list):
2088                 for item in value:
2089                     if isinstance(item, ast.AST):
2090                         yield from _v(item, depth + 2)
2091
2092             elif isinstance(value, ast.AST):
2093                 yield from _v(value, depth + 2)
2094
2095             else:
2096                 yield f"{'  ' * (depth+2)}{value!r},  # {value.__class__.__name__}"
2097
2098         yield f"{'  ' * depth})  # /{node.__class__.__name__}"
2099
2100     try:
2101         src_ast = ast.parse(src)
2102     except Exception as exc:
2103         major, minor = sys.version_info[:2]
2104         raise AssertionError(
2105             f"cannot use --safe with this file; failed to parse source file "
2106             f"with Python {major}.{minor}'s builtin AST. Re-run with --fast "
2107             f"or stop using deprecated Python 2 syntax. AST error message: {exc}"
2108         )
2109
2110     try:
2111         dst_ast = ast.parse(dst)
2112     except Exception as exc:
2113         log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
2114         raise AssertionError(
2115             f"INTERNAL ERROR: Black produced invalid code: {exc}. "
2116             f"Please report a bug on https://github.com/ambv/black/issues.  "
2117             f"This invalid output might be helpful: {log}"
2118         ) from None
2119
2120     src_ast_str = "\n".join(_v(src_ast))
2121     dst_ast_str = "\n".join(_v(dst_ast))
2122     if src_ast_str != dst_ast_str:
2123         log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
2124         raise AssertionError(
2125             f"INTERNAL ERROR: Black produced code that is not equivalent to "
2126             f"the source.  "
2127             f"Please report a bug on https://github.com/ambv/black/issues.  "
2128             f"This diff might be helpful: {log}"
2129         ) from None
2130
2131
2132 def assert_stable(src: str, dst: str, line_length: int) -> None:
2133     """Raise AssertionError if `dst` reformats differently the second time."""
2134     newdst = format_str(dst, line_length=line_length)
2135     if dst != newdst:
2136         log = dump_to_file(
2137             diff(src, dst, "source", "first pass"),
2138             diff(dst, newdst, "first pass", "second pass"),
2139         )
2140         raise AssertionError(
2141             f"INTERNAL ERROR: Black produced different code on the second pass "
2142             f"of the formatter.  "
2143             f"Please report a bug on https://github.com/ambv/black/issues.  "
2144             f"This diff might be helpful: {log}"
2145         ) from None
2146
2147
2148 def dump_to_file(*output: str) -> str:
2149     """Dump `output` to a temporary file. Return path to the file."""
2150     import tempfile
2151
2152     with tempfile.NamedTemporaryFile(
2153         mode="w", prefix="blk_", suffix=".log", delete=False
2154     ) as f:
2155         for lines in output:
2156             f.write(lines)
2157             if lines and lines[-1] != "\n":
2158                 f.write("\n")
2159     return f.name
2160
2161
2162 def diff(a: str, b: str, a_name: str, b_name: str) -> str:
2163     """Return a unified diff string between strings `a` and `b`."""
2164     import difflib
2165
2166     a_lines = [line + "\n" for line in a.split("\n")]
2167     b_lines = [line + "\n" for line in b.split("\n")]
2168     return "".join(
2169         difflib.unified_diff(a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5)
2170     )
2171
2172
2173 def cancel(tasks: List[asyncio.Task]) -> None:
2174     """asyncio signal handler that cancels all `tasks` and reports to stderr."""
2175     err("Aborted!")
2176     for task in tasks:
2177         task.cancel()
2178
2179
2180 def shutdown(loop: BaseEventLoop) -> None:
2181     """Cancel all pending tasks on `loop`, wait for them, and close the loop."""
2182     try:
2183         # This part is borrowed from asyncio/runners.py in Python 3.7b2.
2184         to_cancel = [task for task in asyncio.Task.all_tasks(loop) if not task.done()]
2185         if not to_cancel:
2186             return
2187
2188         for task in to_cancel:
2189             task.cancel()
2190         loop.run_until_complete(
2191             asyncio.gather(*to_cancel, loop=loop, return_exceptions=True)
2192         )
2193     finally:
2194         # `concurrent.futures.Future` objects cannot be cancelled once they
2195         # are already running. There might be some when the `shutdown()` happened.
2196         # Silence their logger's spew about the event loop being closed.
2197         cf_logger = logging.getLogger("concurrent.futures")
2198         cf_logger.setLevel(logging.CRITICAL)
2199         loop.close()
2200
2201
2202 if __name__ == "__main__":
2203     main()