47
47
from blib2to3 .pgen2 .parse import ParseError
48
48
49
49
50
- __version__ = "18.6b4 "
50
+ __version__ = "18.6z4 "
51
51
DEFAULT_LINE_LENGTH = 88
52
52
DEFAULT_EXCLUDES = (
53
53
r"/(\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)/"
@@ -114,10 +114,16 @@ class FileMode(Flag):
114
114
PYTHON36 = 1
115
115
PYI = 2
116
116
NO_STRING_NORMALIZATION = 4
117
+ SINGLE_QUOTE = 32
117
118
118
119
@classmethod
119
120
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 ,
121
127
) -> "FileMode" :
122
128
mode = cls .AUTO_DETECT
123
129
if py36 :
@@ -126,6 +132,8 @@ def from_configuration(
126
132
mode |= cls .PYI
127
133
if skip_string_normalization :
128
134
mode |= cls .NO_STRING_NORMALIZATION
135
+ if single_quote :
136
+ mode |= cls .SINGLE_QUOTE
129
137
return mode
130
138
131
139
@@ -195,6 +203,14 @@ def read_pyproject_toml(
195
203
is_flag = True ,
196
204
help = "Don't normalize string quotes or prefixes." ,
197
205
)
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
+ )
198
214
@click .option (
199
215
"--check" ,
200
216
is_flag = True ,
@@ -280,6 +296,7 @@ def main(
280
296
ctx : click .Context ,
281
297
line_length : int ,
282
298
check : bool ,
299
+ single_quote : bool ,
283
300
diff : bool ,
284
301
fast : bool ,
285
302
pyi : bool ,
@@ -295,7 +312,10 @@ def main(
295
312
"""The uncompromising code formatter."""
296
313
write_back = WriteBack .from_configuration (check = check , diff = diff )
297
314
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 ,
299
319
)
300
320
if config and verbose :
301
321
out (f"Using configuration from { config } ." , bold = False , fg = "blue" )
@@ -323,6 +343,7 @@ def main(
323
343
sources .add (p )
324
344
else :
325
345
err (f"invalid path: { s } " )
346
+
326
347
if len (sources ) == 0 :
327
348
if verbose or not quiet :
328
349
out ("No paths given. Nothing to do 😴" )
@@ -600,11 +621,13 @@ def format_str(
600
621
is_pyi = bool (mode & FileMode .PYI )
601
622
py36 = bool (mode & FileMode .PYTHON36 ) or is_python36 (src_node )
602
623
normalize_strings = not bool (mode & FileMode .NO_STRING_NORMALIZATION )
624
+ single_quote = bool (mode & FileMode .SINGLE_QUOTE )
603
625
normalize_fmt_off (src_node )
604
626
lines = LineGenerator (
605
627
remove_u_prefix = py36 or "unicode_literals" in future_imports ,
606
628
is_pyi = is_pyi ,
607
629
normalize_strings = normalize_strings ,
630
+ single_quote = single_quote ,
608
631
)
609
632
elt = EmptyLineTracker (is_pyi = is_pyi )
610
633
empty_line = Line ()
@@ -1389,6 +1412,7 @@ class LineGenerator(Visitor[Line]):
1389
1412
1390
1413
is_pyi : bool = False
1391
1414
normalize_strings : bool = True
1415
+ single_quote : bool = False
1392
1416
current_line : Line = Factory (Line )
1393
1417
remove_u_prefix : bool = False
1394
1418
@@ -1431,7 +1455,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:
1431
1455
normalize_prefix (node , inside_brackets = any_open_brackets )
1432
1456
if self .normalize_strings and node .type == token .STRING :
1433
1457
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 )
1435
1459
if node .type not in WHITESPACE :
1436
1460
self .current_line .append (node )
1437
1461
yield from super ().visit_default (node )
@@ -2426,7 +2450,7 @@ def normalize_string_prefix(leaf: Leaf, remove_u_prefix: bool = False) -> None:
2426
2450
leaf .value = f"{ new_prefix } { match .group (2 )} "
2427
2451
2428
2452
2429
- def normalize_string_quotes (leaf : Leaf ) -> None :
2453
+ def normalize_string_quotes (leaf : Leaf , single_quote : bool = False ) -> None :
2430
2454
"""Prefer double quotes but only if it doesn't cause more escaping.
2431
2455
2432
2456
Adds or removes backslashes as appropriate. Doesn't parse and fix
@@ -2435,18 +2459,28 @@ def normalize_string_quotes(leaf: Leaf) -> None:
2435
2459
Note: Mutates its argument.
2436
2460
"""
2437
2461
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 :
2439
2473
return
2440
2474
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
2447
2481
else :
2448
- orig_quote = "'"
2449
- new_quote = '"'
2482
+ orig_quote = alt_quote_char
2483
+ new_quote = quote_char
2450
2484
first_quote_pos = leaf .value .find (orig_quote )
2451
2485
if first_quote_pos == - 1 :
2452
2486
return # There's an internal error
@@ -2479,16 +2513,16 @@ def normalize_string_quotes(leaf: Leaf) -> None:
2479
2513
if "\\ " in str (m ):
2480
2514
# Do not introduce backslashes in interpolated expressions
2481
2515
return
2482
- if new_quote == '"""' and new_body [- 1 :] == '"' :
2516
+ if new_quote == triple_quote_chars and new_body [- 1 :] == triple_quote_chars [ 0 ] :
2483
2517
# edge case:
2484
- new_body = new_body [:- 1 ] + ' \\ "'
2518
+ new_body = new_body [:- 1 ] + " \\ " + triple_quote_chars [ 0 ]
2485
2519
orig_escape_count = body .count ("\\ " )
2486
2520
new_escape_count = new_body .count ("\\ " )
2487
2521
if new_escape_count > orig_escape_count :
2488
2522
return # Do not introduce more escaping
2489
2523
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
2492
2526
2493
2527
leaf .value = f"{ prefix } { new_quote } { new_body } { new_quote } "
2494
2528
0 commit comments