From 70dd1f2afb5b4561311a141f03a1d2e4df581b15 Mon Sep 17 00:00:00 2001 From: Bryan Helmig Date: Tue, 29 May 2018 12:42:15 -0700 Subject: [PATCH] Add an option for --single-quote, but strongly prefer double quote in readme. --- README.md | 5 +++++ black.py | 56 +++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 61cb88ae623..615d65be794 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ Options: source on standard input). -S, --skip-string-normalization Don't normalize string quotes or prefixes. + --single-quote Use single quotes instead of double quotes in + strings except for triple-quoted strings. --check Don't write the files back, just return the status. Return code 0 means nothing would change. Return code 1 means some files would be @@ -357,6 +359,9 @@ a one double-quote regardless of fonts and syntax highlighting used. On top of this, double quotes for strings are consistent with C which Python interacts a lot with. +> While we strongly recommend double quotes, we also provide a +> `--single-quote` option if you prefer. + On certain keyboard layouts like US English, typing single quotes is a bit easier than double quotes. The latter requires use of the Shift key. My recommendation here is to keep using whatever is faster to type diff --git a/black.py b/black.py index c9b8be975d2..c094ba80e40 100644 --- a/black.py +++ b/black.py @@ -47,7 +47,7 @@ from blib2to3.pgen2.parse import ParseError -__version__ = "18.6b4" +__version__ = "18.6z4" DEFAULT_LINE_LENGTH = 88 DEFAULT_EXCLUDES = ( r"/(\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)/" @@ -114,6 +114,7 @@ class FileMode(Flag): PYTHON36 = 1 PYI = 2 NO_STRING_NORMALIZATION = 4 + SINGLE_QUOTE = 32 @classmethod def from_configuration( @@ -195,6 +196,14 @@ def read_pyproject_toml( is_flag=True, help="Don't normalize string quotes or prefixes.", ) +@click.option( + "--single-quote", + is_flag=True, + help=( + "Use single quotes instead of double quotes in strings except for " + "triple-quoted strings." + ), +) @click.option( "--check", is_flag=True, @@ -280,6 +289,7 @@ def main( ctx: click.Context, line_length: int, check: bool, + single_quote: bool, diff: bool, fast: bool, pyi: bool, @@ -323,6 +333,7 @@ def main( sources.add(p) else: err(f"invalid path: {s}") + if len(sources) == 0: if verbose or not quiet: out("No paths given. Nothing to do 😴") @@ -600,11 +611,13 @@ def format_str( is_pyi = bool(mode & FileMode.PYI) py36 = bool(mode & FileMode.PYTHON36) or is_python36(src_node) normalize_strings = not bool(mode & FileMode.NO_STRING_NORMALIZATION) + single_quote = bool(mode & FileMode.SINGLE_QUOTE) normalize_fmt_off(src_node) lines = LineGenerator( remove_u_prefix=py36 or "unicode_literals" in future_imports, is_pyi=is_pyi, normalize_strings=normalize_strings, + single_quote=single_quote, ) elt = EmptyLineTracker(is_pyi=is_pyi) empty_line = Line() @@ -1389,6 +1402,7 @@ class LineGenerator(Visitor[Line]): is_pyi: bool = False normalize_strings: bool = True + single_quote: bool = False current_line: Line = Factory(Line) remove_u_prefix: bool = False @@ -1431,7 +1445,7 @@ def visit_default(self, node: LN) -> Iterator[Line]: normalize_prefix(node, inside_brackets=any_open_brackets) if self.normalize_strings and node.type == token.STRING: normalize_string_prefix(node, remove_u_prefix=self.remove_u_prefix) - normalize_string_quotes(node) + normalize_string_quotes(node, single_quote=self.single_quote) if node.type not in WHITESPACE: self.current_line.append(node) yield from super().visit_default(node) @@ -2426,7 +2440,7 @@ def normalize_string_prefix(leaf: Leaf, remove_u_prefix: bool = False) -> None: leaf.value = f"{new_prefix}{match.group(2)}" -def normalize_string_quotes(leaf: Leaf) -> None: +def normalize_string_quotes(leaf: Leaf, single_quote: bool = False) -> None: """Prefer double quotes but only if it doesn't cause more escaping. Adds or removes backslashes as appropriate. Doesn't parse and fix @@ -2435,18 +2449,28 @@ def normalize_string_quotes(leaf: Leaf) -> None: Note: Mutates its argument. """ value = leaf.value.lstrip("furbFURB") - if value[:3] == '"""': + + quote_char = '"' + alt_quote_char = "'" + triple_quote_chars = '"""' + alt_triple_quote_chars = "'''" + + if single_quote: + quote_char = "'" + alt_quote_char = '"' + + if value[:3] == triple_quote_chars: return - elif value[:3] == "'''": - orig_quote = "'''" - new_quote = '"""' - elif value[0] == '"': - orig_quote = '"' - new_quote = "'" + elif value[:3] == alt_triple_quote_chars: + orig_quote = alt_triple_quote_chars + new_quote = triple_quote_chars + elif value[0] == quote_char: + orig_quote = quote_char + new_quote = alt_quote_char else: - orig_quote = "'" - new_quote = '"' + orig_quote = alt_quote_char + new_quote = quote_char first_quote_pos = leaf.value.find(orig_quote) if first_quote_pos == -1: return # There's an internal error @@ -2479,16 +2503,16 @@ def normalize_string_quotes(leaf: Leaf) -> None: if "\\" in str(m): # Do not introduce backslashes in interpolated expressions return - if new_quote == '"""' and new_body[-1:] == '"': + if new_quote == triple_quote_chars and new_body[-1:] == triple_quote_chars[0]: # edge case: - new_body = new_body[:-1] + '\\"' + new_body = new_body[:-1] + "\\" + triple_quote_chars[0] orig_escape_count = body.count("\\") new_escape_count = new_body.count("\\") if new_escape_count > orig_escape_count: return # Do not introduce more escaping - if new_escape_count == orig_escape_count and orig_quote == '"': - return # Prefer double quotes + if new_escape_count == orig_escape_count and orig_quote == quote_char: + return leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}"