X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/65c52a655fd67480a2017a79c99094039dcaffa3..ecdbf085a772e8d737b8a8735d39a7af413cecfb:/black.py diff --git a/black.py b/black.py index 6bb29fe..19871aa 100644 --- a/black.py +++ b/black.py @@ -10,6 +10,7 @@ import logging from multiprocessing import Manager import os from pathlib import Path +import re import tokenize import signal import sys @@ -129,6 +130,15 @@ class WriteBack(Enum): is_flag=True, help="If --fast given, skip temporary sanity checks. [default: --safe]", ) +@click.option( + "-q", + "--quiet", + is_flag=True, + help=( + "Don't emit non-error messages to stderr. Errors are still emitted, " + "silence those with 2>/dev/null." + ), +) @click.version_option(version=__version__) @click.argument( "src", @@ -144,6 +154,7 @@ def main( check: bool, diff: bool, fast: bool, + quiet: bool, src: List[str], ) -> None: """The uncompromising code formatter.""" @@ -174,7 +185,7 @@ def main( ctx.exit(0) elif len(sources) == 1: p = sources[0] - report = Report(check=check) + report = Report(check=check, quiet=quiet) try: if not p.is_file() and str(p) == "-": changed = format_stdin_to_stdout( @@ -195,7 +206,7 @@ def main( try: return_code = loop.run_until_complete( schedule_formatting( - sources, line_length, write_back, fast, loop, executor + sources, line_length, write_back, fast, quiet, loop, executor ) ) finally: @@ -208,6 +219,7 @@ async def schedule_formatting( line_length: int, write_back: WriteBack, fast: bool, + quiet: bool, loop: BaseEventLoop, executor: Executor, ) -> int: @@ -235,7 +247,7 @@ async def schedule_formatting( loop.add_signal_handler(signal.SIGTERM, cancel, _task_values) await asyncio.wait(tasks.values()) cancelled = [] - report = Report(check=not write_back) + report = Report(check=write_back is WriteBack.NO, quiet=quiet) for src, task in tasks.items(): if not task.done(): report.failed(src, "timed out, cancelling") @@ -249,9 +261,10 @@ async def schedule_formatting( report.done(src, task.result()) if cancelled: await asyncio.gather(*cancelled, loop=loop, return_exceptions=True) - else: + elif not quiet: out("All done! ✨ 🍰 ✨") - click.echo(str(report)) + if not quiet: + click.echo(str(report)) return report.return_code @@ -549,12 +562,13 @@ class BracketTracker: leaf.opening_bracket = opening_bracket leaf.bracket_depth = self.depth if self.depth == 0: - after_delim = is_split_after_delimiter(leaf, self.previous) - before_delim = is_split_before_delimiter(leaf, self.previous) - if after_delim > before_delim: - self.delimiters[id(leaf)] = after_delim - elif before_delim > after_delim and self.previous is not None: - self.delimiters[id(self.previous)] = before_delim + delim = is_split_before_delimiter(leaf, self.previous) + if delim and self.previous is not None: + self.delimiters[id(self.previous)] = delim + else: + delim = is_split_after_delimiter(leaf, self.previous) + if delim: + self.delimiters[id(leaf)] = delim if leaf.type in OPENING_BRACKETS: self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf self.depth += 1 @@ -1426,13 +1440,6 @@ def is_split_after_delimiter(leaf: Leaf, previous: Leaf = None) -> int: if leaf.type == token.COMMA: return COMMA_PRIORITY - if ( - leaf.type in VARARGS - and leaf.parent - and leaf.parent.type in {syms.argument, syms.typedargslist} - ): - return MATH_PRIORITY - return 0 @@ -1444,6 +1451,15 @@ def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int: Higher numbers are higher priority. """ + if ( + leaf.type in VARARGS + and leaf.parent + and leaf.parent.type in {syms.argument, syms.typedargslist} + ): + # * and ** might also be MATH_OPERATORS but in this case they are not. + # Don't treat them as a delimiter. + return 0 + if ( leaf.type in MATH_OPERATORS and leaf.parent @@ -1905,10 +1921,21 @@ def normalize_string_quotes(leaf: Leaf) -> None: if first_quote_pos == -1: return # There's an internal error + prefix = leaf.value[:first_quote_pos] body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)] - new_body = body.replace(f"\\{orig_quote}", orig_quote).replace( - new_quote, f"\\{new_quote}" - ) + unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}") + escaped_orig_quote = re.compile(rf"\\(\\\\)*{orig_quote}") + if "r" in prefix.casefold(): + if unescaped_new_quote.search(body): + # There's at least one unescaped new_quote in this raw string + # so converting is impossible + return + + # Do not introduce or remove backslashes in raw strings + new_body = body + else: + new_body = escaped_orig_quote.sub(rf"\1{orig_quote}", body) + new_body = unescaped_new_quote.sub(rf"\1\\{new_quote}", new_body) if new_quote == '"""' and new_body[-1] == '"': # edge case: new_body = new_body[:-1] + '\\"' @@ -1920,7 +1947,6 @@ def normalize_string_quotes(leaf: Leaf) -> None: if new_escape_count == orig_escape_count and orig_quote == '"': return # Prefer double quotes - prefix = leaf.value[:first_quote_pos] leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}" @@ -1974,6 +2000,7 @@ def gen_python_files_in_dir(path: Path) -> Iterator[Path]: class Report: """Provides a reformatting counter. Can be rendered with `str(report)`.""" check: bool = False + quiet: bool = False change_count: int = 0 same_count: int = 0 failure_count: int = 0 @@ -1982,10 +2009,12 @@ class Report: """Increment the counter for successful reformatting. Write out a message.""" if changed: reformatted = "would reformat" if self.check else "reformatted" - out(f"{reformatted} {src}") + if not self.quiet: + out(f"{reformatted} {src}") self.change_count += 1 else: - out(f"{src} already well formatted, good job.", bold=False) + if not self.quiet: + out(f"{src} already well formatted, good job.", bold=False) self.same_count += 1 def failed(self, src: Path, message: str) -> None: