Skip to content
This repository was archived by the owner on Nov 19, 2021. It is now read-only.

Commit 82fb24f

Browse files
bryanhelmigcharettes
authored andcommitted
Add an option for --single-quote, but strongly prefer double quote in readme.
1 parent ed9b31b commit 82fb24f

File tree

2 files changed

+45
-16
lines changed

2 files changed

+45
-16
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ Options:
8181
source on standard input).
8282
-S, --skip-string-normalization
8383
Don't normalize string quotes or prefixes.
84+
--single-quote Use single quotes instead of double quotes in
85+
strings except for triple-quoted strings.
8486
--check Don't write the files back, just return the
8587
status. Return code 0 means nothing would
8688
change. Return code 1 means some files would be
@@ -361,6 +363,9 @@ a one double-quote regardless of fonts and syntax highlighting used.
361363
On top of this, double quotes for strings are consistent with C which
362364
Python interacts a lot with.
363365

366+
> While we strongly recommend double quotes, we also provide a
367+
> `--single-quote` option if you prefer.
368+
364369
On certain keyboard layouts like US English, typing single quotes is
365370
a bit easier than double quotes. The latter requires use of the Shift
366371
key. My recommendation here is to keep using whatever is faster to type

black.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
from blib2to3.pgen2.parse import ParseError
4848

4949

50-
__version__ = "18.9b0"
50+
__version__ = "18.9b0.post1"
5151
DEFAULT_LINE_LENGTH = 88
5252
DEFAULT_EXCLUDES = (
5353
r"/(\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist)/"
@@ -116,6 +116,7 @@ class FileMode(Flag):
116116
PYI = 2
117117
NO_STRING_NORMALIZATION = 4
118118
NO_NUMERIC_UNDERSCORE_NORMALIZATION = 8
119+
SINGLE_QUOTE = 16
119120

120121
@classmethod
121122
def from_configuration(
@@ -125,6 +126,7 @@ def from_configuration(
125126
pyi: bool,
126127
skip_string_normalization: bool,
127128
skip_numeric_underscore_normalization: bool,
129+
single_quote: bool,
128130
) -> "FileMode":
129131
mode = cls.AUTO_DETECT
130132
if py36:
@@ -135,6 +137,8 @@ def from_configuration(
135137
mode |= cls.NO_STRING_NORMALIZATION
136138
if skip_numeric_underscore_normalization:
137139
mode |= cls.NO_NUMERIC_UNDERSCORE_NORMALIZATION
140+
if single_quote:
141+
mode |= cls.SINGLE_QUOTE
138142
return mode
139143

140144

@@ -210,6 +214,11 @@ def read_pyproject_toml(
210214
is_flag=True,
211215
help="Don't normalize underscores in numeric literals.",
212216
)
217+
@click.option(
218+
"--single-quote",
219+
is_flag=True,
220+
help="Use single quotes instead of double quotes in strings.",
221+
)
213222
@click.option(
214223
"--check",
215224
is_flag=True,
@@ -301,6 +310,7 @@ def main(
301310
py36: bool,
302311
skip_string_normalization: bool,
303312
skip_numeric_underscore_normalization: bool,
313+
single_quote: bool,
304314
quiet: bool,
305315
verbose: bool,
306316
include: str,
@@ -315,6 +325,7 @@ def main(
315325
pyi=pyi,
316326
skip_string_normalization=skip_string_normalization,
317327
skip_numeric_underscore_normalization=skip_numeric_underscore_normalization,
328+
single_quote=single_quote,
318329
)
319330
if config and verbose:
320331
out(f"Using configuration from {config}.", bold=False, fg="blue")
@@ -631,13 +642,15 @@ def format_str(
631642
is_pyi = bool(mode & FileMode.PYI)
632643
py36 = bool(mode & FileMode.PYTHON36) or is_python36(src_node)
633644
normalize_strings = not bool(mode & FileMode.NO_STRING_NORMALIZATION)
645+
single_quote = bool(mode & FileMode.SINGLE_QUOTE)
634646
normalize_fmt_off(src_node)
635647
lines = LineGenerator(
636648
remove_u_prefix=py36 or "unicode_literals" in future_imports,
637649
is_pyi=is_pyi,
638650
normalize_strings=normalize_strings,
639651
allow_underscores=py36
640652
and not bool(mode & FileMode.NO_NUMERIC_UNDERSCORE_NORMALIZATION),
653+
single_quote=single_quote,
641654
)
642655
elt = EmptyLineTracker(is_pyi=is_pyi)
643656
empty_line = Line()
@@ -1431,6 +1444,7 @@ class LineGenerator(Visitor[Line]):
14311444

14321445
is_pyi: bool = False
14331446
normalize_strings: bool = True
1447+
single_quote: bool = False
14341448
current_line: Line = Factory(Line)
14351449
remove_u_prefix: bool = False
14361450
allow_underscores: bool = False
@@ -1474,7 +1488,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:
14741488
normalize_prefix(node, inside_brackets=any_open_brackets)
14751489
if self.normalize_strings and node.type == token.STRING:
14761490
normalize_string_prefix(node, remove_u_prefix=self.remove_u_prefix)
1477-
normalize_string_quotes(node)
1491+
normalize_string_quotes(node, single_quote=self.single_quote)
14781492
if node.type == token.NUMBER:
14791493
normalize_numeric_literal(node, self.allow_underscores)
14801494
if node.type not in WHITESPACE:
@@ -2500,7 +2514,7 @@ def normalize_string_prefix(leaf: Leaf, remove_u_prefix: bool = False) -> None:
25002514
leaf.value = f"{new_prefix}{match.group(2)}"
25012515

25022516

2503-
def normalize_string_quotes(leaf: Leaf) -> None:
2517+
def normalize_string_quotes(leaf: Leaf, single_quote: bool = False) -> None:
25042518
"""Prefer double quotes but only if it doesn't cause more escaping.
25052519
25062520
Adds or removes backslashes as appropriate. Doesn't parse and fix
@@ -2509,18 +2523,28 @@ def normalize_string_quotes(leaf: Leaf) -> None:
25092523
Note: Mutates its argument.
25102524
"""
25112525
value = leaf.value.lstrip("furbFURB")
2512-
if value[:3] == '"""':
2526+
2527+
quote_char = '"'
2528+
alt_quote_char = "'"
2529+
triple_quote_chars = '"""'
2530+
alt_triple_quote_chars = "'''"
2531+
2532+
if single_quote:
2533+
quote_char = "'"
2534+
alt_quote_char = '"'
2535+
2536+
if value[:3] == triple_quote_chars:
25132537
return
25142538

2515-
elif value[:3] == "'''":
2516-
orig_quote = "'''"
2517-
new_quote = '"""'
2518-
elif value[0] == '"':
2519-
orig_quote = '"'
2520-
new_quote = "'"
2539+
elif value[:3] == alt_triple_quote_chars:
2540+
orig_quote = alt_triple_quote_chars
2541+
new_quote = triple_quote_chars
2542+
elif value[0] == quote_char:
2543+
orig_quote = quote_char
2544+
new_quote = alt_quote_char
25212545
else:
2522-
orig_quote = "'"
2523-
new_quote = '"'
2546+
orig_quote = alt_quote_char
2547+
new_quote = quote_char
25242548
first_quote_pos = leaf.value.find(orig_quote)
25252549
if first_quote_pos == -1:
25262550
return # There's an internal error
@@ -2553,16 +2577,16 @@ def normalize_string_quotes(leaf: Leaf) -> None:
25532577
if "\\" in str(m):
25542578
# Do not introduce backslashes in interpolated expressions
25552579
return
2556-
if new_quote == '"""' and new_body[-1:] == '"':
2580+
if new_quote == triple_quote_chars and new_body[-1] == triple_quote_chars[0]:
25572581
# edge case:
2558-
new_body = new_body[:-1] + '\\"'
2582+
new_body = new_body[:-1] + "\\" + triple_quote_chars[0]
25592583
orig_escape_count = body.count("\\")
25602584
new_escape_count = new_body.count("\\")
25612585
if new_escape_count > orig_escape_count:
25622586
return # Do not introduce more escaping
25632587

2564-
if new_escape_count == orig_escape_count and orig_quote == '"':
2565-
return # Prefer double quotes
2588+
if new_escape_count == orig_escape_count and orig_quote == quote_char:
2589+
return
25662590

25672591
leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}"
25682592

0 commit comments

Comments
 (0)