from blib2to3.pgen2.parse import ParseError
-__version__ = "18.6b4"
+__version__ = "18.9b0"
DEFAULT_LINE_LENGTH = 88
DEFAULT_EXCLUDES = (
- r"/(\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)/"
+ r"/(\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist)/"
)
DEFAULT_INCLUDES = r"\.pyi?$"
CACHE_DIR = Path(user_cache_dir("black", version=__version__))
PYTHON36 = 1
PYI = 2
NO_STRING_NORMALIZATION = 4
+ NO_NUMERIC_UNDERSCORE_NORMALIZATION = 8
@classmethod
def from_configuration(
- cls, *, py36: bool, pyi: bool, skip_string_normalization: bool
+ cls,
+ *,
+ py36: bool,
+ pyi: bool,
+ skip_string_normalization: bool,
+ skip_numeric_underscore_normalization: bool,
) -> "FileMode":
mode = cls.AUTO_DETECT
if py36:
mode |= cls.PYI
if skip_string_normalization:
mode |= cls.NO_STRING_NORMALIZATION
+ if skip_numeric_underscore_normalization:
+ mode |= cls.NO_NUMERIC_UNDERSCORE_NORMALIZATION
return mode
is_flag=True,
help="Don't normalize string quotes or prefixes.",
)
+@click.option(
+ "-N",
+ "--skip-numeric-underscore-normalization",
+ is_flag=True,
+ help="Don't normalize underscores in numeric literals.",
+)
@click.option(
"--check",
is_flag=True,
pyi: bool,
py36: bool,
skip_string_normalization: bool,
+ skip_numeric_underscore_normalization: bool,
quiet: bool,
verbose: bool,
include: str,
"""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,
+ skip_numeric_underscore_normalization=skip_numeric_underscore_normalization,
)
if config and verbose:
out(f"Using configuration from {config}.", bold=False, fg="blue")
`line_length` determines how many characters per line are allowed.
"""
- src_node = lib2to3_parse(src_contents)
+ src_node = lib2to3_parse(src_contents.lstrip())
dst_contents = ""
future_imports = get_future_imports(src_node)
is_pyi = bool(mode & FileMode.PYI)
remove_u_prefix=py36 or "unicode_literals" in future_imports,
is_pyi=is_pyi,
normalize_strings=normalize_strings,
- allow_underscores=py36,
+ allow_underscores=py36
+ and not bool(mode & FileMode.NO_NUMERIC_UNDERSCORE_NORMALIZATION),
)
elt = EmptyLineTracker(is_pyi=is_pyi)
empty_line = Line()
def lib2to3_parse(src_txt: str) -> Node:
"""Given a string with source, return the lib2to3 Node."""
- grammar = pygram.python_grammar_no_print_statement
if src_txt[-1:] != "\n":
src_txt += "\n"
for grammar in GRAMMARS:
return container
-def is_split_after_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
+def is_split_after_delimiter(leaf: Leaf, previous: Optional[Leaf] = None) -> int:
"""Return the priority of the `leaf` delimiter, given a line break after it.
The delimiter priorities returned here are from those delimiters that would
return 0
-def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
+def is_split_before_delimiter(leaf: Leaf, previous: Optional[Leaf] = None) -> int:
"""Return the priority of the `leaf` delimiter, given a line break before it.
The delimiter priorities returned here are from those delimiters that would
def make_comment(content: str) -> str:
"""Return a consistently formatted comment from the given `content` string.
- All comments (except for "##", "#!", "#:") should have a single space between
- the hash sign and the content.
+ All comments (except for "##", "#!", "#:", '#'", "#%%") should have a single
+ space between the hash sign and the content.
If `content` didn't start with a hash sign, one is provided.
"""
if content[0] == "#":
content = content[1:]
- if content and content[0] not in " !:#":
+ if content and content[0] not in " !:#'%":
content = " " + content
return "#" + content
result.extend(
split_line(l, line_length=line_length, inner=True, py36=py36)
)
- except CannotSplit as cs:
+ except CannotSplit:
continue
else:
Prefer RHS otherwise. This is why this function is not symmetrical with
:func:`right_hand_split` which also handles optional parentheses.
"""
- head = Line(depth=line.depth)
- body = Line(depth=line.depth + 1, inside_brackets=True)
- tail = Line(depth=line.depth)
tail_leaves: List[Leaf] = []
body_leaves: List[Leaf] = []
head_leaves: List[Leaf] = []
if leaf.type in OPENING_BRACKETS:
matching_bracket = leaf
current_leaves = body_leaves
- # Since body is a new indent level, remove spurious leading whitespace.
- if body_leaves:
- normalize_prefix(body_leaves[0], inside_brackets=True)
- # Build the new lines.
- for result, leaves in (head, head_leaves), (body, body_leaves), (tail, tail_leaves):
- for leaf in leaves:
- result.append(leaf, preformatted=True)
- for comment_after in line.comments_after(leaf):
- result.append(comment_after, preformatted=True)
+ if not matching_bracket:
+ raise CannotSplit("No brackets found")
+
+ head = bracket_split_build_line(head_leaves, line, matching_bracket)
+ body = bracket_split_build_line(body_leaves, line, matching_bracket, is_body=True)
+ tail = bracket_split_build_line(tail_leaves, line, matching_bracket)
bracket_split_succeeded_or_raise(head, body, tail)
for result in (head, body, tail):
if result:
Note: running this function modifies `bracket_depth` on the leaves of `line`.
"""
- head = Line(depth=line.depth)
- body = Line(depth=line.depth + 1, inside_brackets=True)
- tail = Line(depth=line.depth)
tail_leaves: List[Leaf] = []
body_leaves: List[Leaf] = []
head_leaves: List[Leaf] = []
opening_bracket = leaf.opening_bracket
closing_bracket = leaf
current_leaves = body_leaves
- tail_leaves.reverse()
- body_leaves.reverse()
- head_leaves.reverse()
- # Since body is a new indent level, remove spurious leading whitespace.
- if body_leaves:
- normalize_prefix(body_leaves[0], inside_brackets=True)
- if not head_leaves:
- # No `head` means the split failed. Either `tail` has all content or
+ if not (opening_bracket and closing_bracket and head_leaves):
+ # If there is no opening or closing_bracket that means the split failed and
+ # all content is in the tail. Otherwise, if `head_leaves` are empty, it means
# the matching `opening_bracket` wasn't available on `line` anymore.
raise CannotSplit("No brackets found")
- # Build the new lines.
- for result, leaves in (head, head_leaves), (body, body_leaves), (tail, tail_leaves):
- for leaf in leaves:
- result.append(leaf, preformatted=True)
- for comment_after in line.comments_after(leaf):
- result.append(comment_after, preformatted=True)
- assert opening_bracket and closing_bracket
- body.should_explode = should_explode(body, opening_bracket)
+ tail_leaves.reverse()
+ body_leaves.reverse()
+ head_leaves.reverse()
+ head = bracket_split_build_line(head_leaves, line, opening_bracket)
+ body = bracket_split_build_line(body_leaves, line, opening_bracket, is_body=True)
+ tail = bracket_split_build_line(tail_leaves, line, opening_bracket)
bracket_split_succeeded_or_raise(head, body, tail)
if (
# the body shouldn't be exploded
)
+def bracket_split_build_line(
+ leaves: List[Leaf], original: Line, opening_bracket: Leaf, *, is_body: bool = False
+) -> Line:
+ """Return a new line with given `leaves` and respective comments from `original`.
+
+ If `is_body` is True, the result line is one-indented inside brackets and as such
+ has its first leaf's prefix normalized and a trailing comma added when expected.
+ """
+ result = Line(depth=original.depth)
+ if is_body:
+ result.inside_brackets = True
+ result.depth += 1
+ if leaves:
+ # Since body is a new indent level, remove spurious leading whitespace.
+ normalize_prefix(leaves[0], inside_brackets=True)
+ # Ensure a trailing comma when expected.
+ if original.is_import:
+ if leaves[-1].type != token.COMMA:
+ leaves.append(Leaf(token.COMMA, ","))
+ # Populate the line
+ for leaf in leaves:
+ result.append(leaf, preformatted=True)
+ for comment_after in original.comments_after(leaf):
+ result.append(comment_after, preformatted=True)
+ if is_body:
+ result.should_explode = should_explode(result, opening_bracket)
+ return result
+
+
def dont_increase_indentation(split_func: SplitFunc) -> SplitFunc:
"""Normalize prefix of the first leaf in every line returned by `split_func`.
nonlocal current_line
try:
current_line.append_safe(leaf, preformatted=True)
- except ValueError as ve:
+ except ValueError:
yield current_line
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
nonlocal current_line
try:
current_line.append_safe(leaf, preformatted=True)
- except ValueError as ve:
+ except ValueError:
yield current_line
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets)
return text
text = text.replace("_", "")
- if len(text) <= 6:
- # No underscores for numbers <= 6 digits long.
+ if len(text) <= 5:
+ # No underscores for numbers <= 5 digits long.
return text
if count_from_end:
module._verify_python3_env = lambda: None
-if __name__ == "__main__":
+def patched_main() -> None:
patch_click()
main()
+
+
+if __name__ == "__main__":
+ patched_main()