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

Commit 4a44add

Browse files
committed
Add an option for --single-quote, but strongly prefer double quote in readme.
1 parent e94a41f commit 4a44add

File tree

2 files changed

+57
-18
lines changed

2 files changed

+57
-18
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ Options:
7777
source on standard input).
7878
-S, --skip-string-normalization
7979
Don't normalize string quotes or prefixes.
80+
--single-quote Use single quotes instead of double quotes in
81+
strings except for triple-quoted strings.
8082
--check Don't write the files back, just return the
8183
status. Return code 0 means nothing would
8284
change. Return code 1 means some files would be
@@ -357,6 +359,9 @@ a one double-quote regardless of fonts and syntax highlighting used.
357359
On top of this, double quotes for strings are consistent with C which
358360
Python interacts a lot with.
359361

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

black.py

Lines changed: 52 additions & 18 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.6b4"
50+
__version__ = "18.6z4"
5151
DEFAULT_LINE_LENGTH = 88
5252
DEFAULT_EXCLUDES = (
5353
r"/(\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)/"
@@ -114,10 +114,16 @@ class FileMode(Flag):
114114
PYTHON36 = 1
115115
PYI = 2
116116
NO_STRING_NORMALIZATION = 4
117+
SINGLE_QUOTE = 32
117118

118119
@classmethod
119120
def from_configuration(
120-
cls, *, py36: bool, pyi: bool, skip_string_normalization: bool
121+
cls,
122+
*,
123+
py36: bool,
124+
pyi: bool,
125+
skip_string_normalization: bool,
126+
single_quote: bool,
121127
) -> "FileMode":
122128
mode = cls.AUTO_DETECT
123129
if py36:
@@ -126,6 +132,8 @@ def from_configuration(
126132
mode |= cls.PYI
127133
if skip_string_normalization:
128134
mode |= cls.NO_STRING_NORMALIZATION
135+
if single_quote:
136+
mode |= cls.SINGLE_QUOTE
129137
return mode
130138

131139

@@ -195,6 +203,14 @@ def read_pyproject_toml(
195203
is_flag=True,
196204
help="Don't normalize string quotes or prefixes.",
197205
)
206+
@click.option(
207+
"--single-quote",
208+
is_flag=True,
209+
help=(
210+
"Use single quotes instead of double quotes in strings except for "
211+
"triple-quoted strings."
212+
),
213+
)
198214
@click.option(
199215
"--check",
200216
is_flag=True,
@@ -280,6 +296,7 @@ def main(
280296
ctx: click.Context,
281297
line_length: int,
282298
check: bool,
299+
single_quote: bool,
283300
diff: bool,
284301
fast: bool,
285302
pyi: bool,
@@ -295,7 +312,10 @@ def main(
295312
"""The uncompromising code formatter."""
296313
write_back = WriteBack.from_configuration(check=check, diff=diff)
297314
mode = FileMode.from_configuration(
298-
py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization
315+
py36=py36,
316+
pyi=pyi,
317+
skip_string_normalization=skip_string_normalization,
318+
single_quote=single_quote,
299319
)
300320
if config and verbose:
301321
out(f"Using configuration from {config}.", bold=False, fg="blue")
@@ -323,6 +343,7 @@ def main(
323343
sources.add(p)
324344
else:
325345
err(f"invalid path: {s}")
346+
326347
if len(sources) == 0:
327348
if verbose or not quiet:
328349
out("No paths given. Nothing to do 😴")
@@ -600,11 +621,13 @@ def format_str(
600621
is_pyi = bool(mode & FileMode.PYI)
601622
py36 = bool(mode & FileMode.PYTHON36) or is_python36(src_node)
602623
normalize_strings = not bool(mode & FileMode.NO_STRING_NORMALIZATION)
624+
single_quote = bool(mode & FileMode.SINGLE_QUOTE)
603625
normalize_fmt_off(src_node)
604626
lines = LineGenerator(
605627
remove_u_prefix=py36 or "unicode_literals" in future_imports,
606628
is_pyi=is_pyi,
607629
normalize_strings=normalize_strings,
630+
single_quote=single_quote,
608631
)
609632
elt = EmptyLineTracker(is_pyi=is_pyi)
610633
empty_line = Line()
@@ -1389,6 +1412,7 @@ class LineGenerator(Visitor[Line]):
13891412

13901413
is_pyi: bool = False
13911414
normalize_strings: bool = True
1415+
single_quote: bool = False
13921416
current_line: Line = Factory(Line)
13931417
remove_u_prefix: bool = False
13941418

@@ -1431,7 +1455,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:
14311455
normalize_prefix(node, inside_brackets=any_open_brackets)
14321456
if self.normalize_strings and node.type == token.STRING:
14331457
normalize_string_prefix(node, remove_u_prefix=self.remove_u_prefix)
1434-
normalize_string_quotes(node)
1458+
normalize_string_quotes(node, single_quote=self.single_quote)
14351459
if node.type not in WHITESPACE:
14361460
self.current_line.append(node)
14371461
yield from super().visit_default(node)
@@ -2426,7 +2450,7 @@ def normalize_string_prefix(leaf: Leaf, remove_u_prefix: bool = False) -> None:
24262450
leaf.value = f"{new_prefix}{match.group(2)}"
24272451

24282452

2429-
def normalize_string_quotes(leaf: Leaf) -> None:
2453+
def normalize_string_quotes(leaf: Leaf, single_quote: bool = False) -> None:
24302454
"""Prefer double quotes but only if it doesn't cause more escaping.
24312455
24322456
Adds or removes backslashes as appropriate. Doesn't parse and fix
@@ -2435,18 +2459,28 @@ def normalize_string_quotes(leaf: Leaf) -> None:
24352459
Note: Mutates its argument.
24362460
"""
24372461
value = leaf.value.lstrip("furbFURB")
2438-
if value[:3] == '"""':
2462+
2463+
quote_char = '"'
2464+
alt_quote_char = "'"
2465+
triple_quote_chars = '"""'
2466+
alt_triple_quote_chars = "'''"
2467+
2468+
if single_quote:
2469+
quote_char = "'"
2470+
alt_quote_char = '"'
2471+
2472+
if value[:3] == triple_quote_chars:
24392473
return
24402474

2441-
elif value[:3] == "'''":
2442-
orig_quote = "'''"
2443-
new_quote = '"""'
2444-
elif value[0] == '"':
2445-
orig_quote = '"'
2446-
new_quote = "'"
2475+
elif value[:3] == alt_triple_quote_chars:
2476+
orig_quote = alt_triple_quote_chars
2477+
new_quote = triple_quote_chars
2478+
elif value[0] == quote_char:
2479+
orig_quote = quote_char
2480+
new_quote = alt_quote_char
24472481
else:
2448-
orig_quote = "'"
2449-
new_quote = '"'
2482+
orig_quote = alt_quote_char
2483+
new_quote = quote_char
24502484
first_quote_pos = leaf.value.find(orig_quote)
24512485
if first_quote_pos == -1:
24522486
return # There's an internal error
@@ -2479,16 +2513,16 @@ def normalize_string_quotes(leaf: Leaf) -> None:
24792513
if "\\" in str(m):
24802514
# Do not introduce backslashes in interpolated expressions
24812515
return
2482-
if new_quote == '"""' and new_body[-1:] == '"':
2516+
if new_quote == triple_quote_chars and new_body[-1:] == triple_quote_chars[0]:
24832517
# edge case:
2484-
new_body = new_body[:-1] + '\\"'
2518+
new_body = new_body[:-1] + "\\" + triple_quote_chars[0]
24852519
orig_escape_count = body.count("\\")
24862520
new_escape_count = new_body.count("\\")
24872521
if new_escape_count > orig_escape_count:
24882522
return # Do not introduce more escaping
24892523

2490-
if new_escape_count == orig_escape_count and orig_quote == '"':
2491-
return # Prefer double quotes
2524+
if new_escape_count == orig_escape_count and orig_quote == quote_char:
2525+
return
24922526

24932527
leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}"
24942528

0 commit comments

Comments
 (0)