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

Commit

Permalink
Add an option for --single-quote, but strongly prefer double quote in…
Browse files Browse the repository at this point in the history
… readme.
  • Loading branch information
bryanhelmig committed Aug 10, 2018
1 parent e94a41f commit 4a44add
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 18 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
70 changes: 52 additions & 18 deletions black.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)/"
Expand Down Expand Up @@ -114,10 +114,16 @@ class FileMode(Flag):
PYTHON36 = 1
PYI = 2
NO_STRING_NORMALIZATION = 4
SINGLE_QUOTE = 32

@classmethod
def from_configuration(
cls, *, py36: bool, pyi: bool, skip_string_normalization: bool
cls,
*,
py36: bool,
pyi: bool,
skip_string_normalization: bool,
single_quote: bool,
) -> "FileMode":
mode = cls.AUTO_DETECT
if py36:
Expand All @@ -126,6 +132,8 @@ def from_configuration(
mode |= cls.PYI
if skip_string_normalization:
mode |= cls.NO_STRING_NORMALIZATION
if single_quote:
mode |= cls.SINGLE_QUOTE
return mode


Expand Down Expand Up @@ -195,6 +203,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,
Expand Down Expand Up @@ -280,6 +296,7 @@ def main(
ctx: click.Context,
line_length: int,
check: bool,
single_quote: bool,
diff: bool,
fast: bool,
pyi: bool,
Expand All @@ -295,7 +312,10 @@ def main(
"""The uncompromising code formatter."""
write_back = WriteBack.from_configuration(check=check, diff=diff)
mode = FileMode.from_configuration(
py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization
py36=py36,
pyi=pyi,
skip_string_normalization=skip_string_normalization,
single_quote=single_quote,
)
if config and verbose:
out(f"Using configuration from {config}.", bold=False, fg="blue")
Expand Down Expand Up @@ -323,6 +343,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 😴")
Expand Down Expand Up @@ -600,11 +621,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()
Expand Down Expand Up @@ -1389,6 +1412,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

Expand Down Expand Up @@ -1431,7 +1455,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)
Expand Down Expand Up @@ -2426,7 +2450,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
Expand All @@ -2435,18 +2459,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
Expand Down Expand Up @@ -2479,16 +2513,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}"

Expand Down

0 comments on commit 4a44add

Please sign in to comment.