]> git.madduck.net Git - etc/vim.git/blobdiff - 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:

Don't split on for-loop variable unpacks
[etc/vim.git] / black.py
index 75fbdcadd41159f0b38446288f1b05c5f1408995..774d91dc2a7f64ec2dbc252a55e0fe051d5c429c 100644 (file)
--- a/black.py
+++ b/black.py
@@ -55,6 +55,15 @@ class CannotSplit(Exception):
     help='How many character per line to allow.',
     show_default=True,
 )
     help='How many character per line to allow.',
     show_default=True,
 )
+@click.option(
+    '--check',
+    is_flag=True,
+    help=(
+        "Don't write back the files, just return the status.  Return code 0 "
+        "means nothing changed.  Return code 1 means some files were "
+        "reformatted.  Return code 123 means there was an internal error."
+    ),
+)
 @click.option(
     '--fast/--safe',
     is_flag=True,
 @click.option(
     '--fast/--safe',
     is_flag=True,
@@ -67,7 +76,9 @@ class CannotSplit(Exception):
     type=click.Path(exists=True, file_okay=True, dir_okay=True, readable=True),
 )
 @click.pass_context
     type=click.Path(exists=True, file_okay=True, dir_okay=True, readable=True),
 )
 @click.pass_context
-def main(ctx: click.Context, line_length: int, fast: bool, src: List[str]) -> None:
+def main(
+    ctx: click.Context, line_length: int, check: bool, fast: bool, src: List[str]
+) -> None:
     """The uncompromising code formatter."""
     sources: List[Path] = []
     for s in src:
     """The uncompromising code formatter."""
     sources: List[Path] = []
     for s in src:
@@ -85,7 +96,9 @@ def main(ctx: click.Context, line_length: int, fast: bool, src: List[str]) -> No
         p = sources[0]
         report = Report()
         try:
         p = sources[0]
         report = Report()
         try:
-            changed = format_file_in_place(p, line_length=line_length, fast=fast)
+            changed = format_file_in_place(
+                p, line_length=line_length, fast=fast, write_back=not check
+            )
             report.done(p, changed)
         except Exception as exc:
             report.failed(p, str(exc))
             report.done(p, changed)
         except Exception as exc:
             report.failed(p, str(exc))
@@ -96,7 +109,9 @@ def main(ctx: click.Context, line_length: int, fast: bool, src: List[str]) -> No
         return_code = 1
         try:
             return_code = loop.run_until_complete(
         return_code = 1
         try:
             return_code = loop.run_until_complete(
-                schedule_formatting(sources, line_length, fast, loop, executor)
+                schedule_formatting(
+                    sources, line_length, not check, fast, loop, executor
+                )
             )
         finally:
             loop.close()
             )
         finally:
             loop.close()
@@ -106,13 +121,14 @@ def main(ctx: click.Context, line_length: int, fast: bool, src: List[str]) -> No
 async def schedule_formatting(
     sources: List[Path],
     line_length: int,
 async def schedule_formatting(
     sources: List[Path],
     line_length: int,
+    write_back: bool,
     fast: bool,
     loop: BaseEventLoop,
     executor: Executor,
 ) -> int:
     tasks = {
         src: loop.run_in_executor(
     fast: bool,
     loop: BaseEventLoop,
     executor: Executor,
 ) -> int:
     tasks = {
         src: loop.run_in_executor(
-            executor, format_file_in_place, src, line_length, fast
+            executor, format_file_in_place, src, line_length, fast, write_back
         )
         for src in sources
     }
         )
         for src in sources
     }
@@ -135,15 +151,18 @@ async def schedule_formatting(
     return report.return_code
 
 
     return report.return_code
 
 
-def format_file_in_place(src: Path, line_length: int, fast: bool) -> bool:
+def format_file_in_place(
+    src: Path, line_length: int, fast: bool, write_back: bool = False
+) -> bool:
     """Format the file and rewrite if changed. Return True if changed."""
     try:
         contents, encoding = format_file(src, line_length=line_length, fast=fast)
     except NothingChanged:
         return False
 
     """Format the file and rewrite if changed. Return True if changed."""
     try:
         contents, encoding = format_file(src, line_length=line_length, fast=fast)
     except NothingChanged:
         return False
 
-    with open(src, "w", encoding=encoding) as f:
-        f.write(contents)
+    if write_back:
+        with open(src, "w", encoding=encoding) as f:
+            f.write(contents)
     return True
 
 
     return True
 
 
@@ -373,6 +392,8 @@ class Line:
     comments: Dict[LeafID, Leaf] = attrib(default=Factory(dict))
     bracket_tracker: BracketTracker = attrib(default=Factory(BracketTracker))
     inside_brackets: bool = attrib(default=False)
     comments: Dict[LeafID, Leaf] = attrib(default=Factory(dict))
     bracket_tracker: BracketTracker = attrib(default=Factory(BracketTracker))
     inside_brackets: bool = attrib(default=False)
+    has_for: bool = attrib(default=False)
+    _for_loop_variable: bool = attrib(default=False, init=False)
 
     def append(self, leaf: Leaf, preformatted: bool = False) -> None:
         has_value = leaf.value.strip()
 
     def append(self, leaf: Leaf, preformatted: bool = False) -> None:
         has_value = leaf.value.strip()
@@ -384,8 +405,10 @@ class Line:
             # imports, for which we only preserve newlines.
             leaf.prefix += whitespace(leaf)
         if self.inside_brackets or not preformatted:
             # imports, for which we only preserve newlines.
             leaf.prefix += whitespace(leaf)
         if self.inside_brackets or not preformatted:
+            self.maybe_decrement_after_for_loop_variable(leaf)
             self.bracket_tracker.mark(leaf)
             self.maybe_remove_trailing_comma(leaf)
             self.bracket_tracker.mark(leaf)
             self.maybe_remove_trailing_comma(leaf)
+            self.maybe_increment_for_loop_variable(leaf)
             if self.maybe_adapt_standalone_comment(leaf):
                 return
 
             if self.maybe_adapt_standalone_comment(leaf):
                 return
 
@@ -489,6 +512,29 @@ class Line:
 
         return False
 
 
         return False
 
+    def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
+        """In a for loop, or comprehension, the variables are often unpacks.
+
+        To avoid splitting on the comma in this situation, we will increase
+        the depth of tokens between `for` and `in`.
+        """
+        if leaf.type == token.NAME and leaf.value == 'for':
+            self.has_for = True
+            self.bracket_tracker.depth += 1
+            self._for_loop_variable = True
+            return True
+
+        return False
+
+    def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool:
+        # See `maybe_increment_for_loop_variable` above for explanation.
+        if self._for_loop_variable and leaf.type == token.NAME and leaf.value == 'in':
+            self.bracket_tracker.depth -= 1
+            self._for_loop_variable = False
+            return True
+
+        return False
+
     def maybe_adapt_standalone_comment(self, comment: Leaf) -> bool:
         """Hack a standalone comment to act as a trailing comment for line splitting.
 
     def maybe_adapt_standalone_comment(self, comment: Leaf) -> bool:
         """Hack a standalone comment to act as a trailing comment for line splitting.
 
@@ -815,6 +861,9 @@ def whitespace(leaf: Leaf) -> str:
             if prevp.parent and prevp.parent.type == syms.subscript:
                 return NO
 
             if prevp.parent and prevp.parent.type == syms.subscript:
                 return NO
 
+        elif prevp.parent and prevp.parent.type == syms.factor:
+            return NO
+
     elif prev.type in OPENING_BRACKETS:
         return NO
 
     elif prev.type in OPENING_BRACKETS:
         return NO
 
@@ -1318,7 +1367,15 @@ class Report:
     @property
     def return_code(self) -> int:
         """Which return code should the app use considering the current state."""
     @property
     def return_code(self) -> int:
         """Which return code should the app use considering the current state."""
-        return 1 if self.failure_count else 0
+        # According to http://tldp.org/LDP/abs/html/exitcodes.html starting with
+        # 126 we have special returncodes reserved by the shell.
+        if self.failure_count:
+            return 123
+
+        elif self.change_count:
+            return 1
+
+        return 0
 
     def __str__(self) -> str:
         """A color report of the current state.
 
     def __str__(self) -> str:
         """A color report of the current state.