from blib2to3.pgen2.parse import ParseError
-__version__ = "18.6b1"
+__version__ = "18.6b2"
DEFAULT_LINE_LENGTH = 88
DEFAULT_EXCLUDES = (
r"/(\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)/"
return value
-@click.command()
+@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
@click.option(
"-l",
"--line-length",
Provide a non-negative leaf `_index` to speed up the function.
"""
+ if not self.comments:
+ return
+
if _index == -1:
for _index, _leaf in enumerate(self.leaves):
if leaf is _leaf:
def is_complex_subscript(self, leaf: Leaf) -> bool:
"""Return True iff `leaf` is part of a slice with non-trivial exprs."""
- open_lsqb = (
- leaf if leaf.type == token.LSQB else self.bracket_tracker.get_open_lsqb()
- )
+ open_lsqb = self.bracket_tracker.get_open_lsqb()
if open_lsqb is None:
return False
subscript_start = open_lsqb.next_sibling
- if (
- isinstance(subscript_start, Node)
- and subscript_start.type == syms.subscriptlist
- ):
- subscript_start = child_towards(subscript_start, leaf)
+
+ if isinstance(subscript_start, Node):
+ if subscript_start.type == syms.listmaker:
+ return False
+
+ if subscript_start.type == syms.subscriptlist:
+ subscript_start = child_towards(subscript_start, leaf)
return subscript_start is not None and any(
n.type in TEST_DESCENDANTS for n in subscript_start.pre_order()
)
prefix = leaf.value[:first_quote_pos]
unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
- escaped_new_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{new_quote}")
- escaped_orig_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{orig_quote}")
+ escaped_new_quote = re.compile(rf"([^\\]|^)\\((?:\\\\)*){new_quote}")
+ escaped_orig_quote = re.compile(rf"([^\\]|^)\\((?:\\\\)*){orig_quote}")
body = leaf.value[first_quote_pos + len(orig_quote) : -len(orig_quote)]
if "r" in prefix.casefold():
if unescaped_new_quote.search(body):
# Do not introduce or remove backslashes in raw strings
new_body = body
else:
- # remove unnecessary quotes
+ # remove unnecessary escapes
new_body = sub_twice(escaped_new_quote, rf"\1\2{new_quote}", body)
if body != new_body:
- # Consider the string without unnecessary quotes as the original
+ # Consider the string without unnecessary escapes as the original
body = new_body
leaf.value = f"{prefix}{orig_quote}{body}{orig_quote}"
new_body = sub_twice(escaped_orig_quote, rf"\1\2{orig_quote}", new_body)
new_body = sub_twice(unescaped_new_quote, rf"\1\\{new_quote}", new_body)
- if new_quote == '"""' and new_body[-1] == '"':
+ if "f" in prefix.casefold():
+ matches = re.findall(r"[^{]\{(.*?)\}[^}]", new_body)
+ for m in matches:
+ if "\\" in str(m):
+ # Do not introduce backslashes in interpolated expressions
+ return
+ if new_quote == '"""' and new_body[-1:] == '"':
# edge case:
new_body = new_body[:-1] + '\\"'
orig_escape_count = body.count("\\")
"""Generate all files under `path` whose paths are not excluded by the
`exclude` regex, but are included by the `include` regex.
+ Symbolic links pointing outside of the root directory are ignored.
+
`report` is where output about exclusions goes.
"""
assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}"
for child in path.iterdir():
- normalized_path = "/" + child.resolve().relative_to(root).as_posix()
+ try:
+ normalized_path = "/" + child.resolve().relative_to(root).as_posix()
+ except ValueError:
+ if child.is_symlink():
+ report.path_ignored(
+ child,
+ "is a symbolic link that points outside of the root directory",
+ )
+ continue
+
+ raise
+
if child.is_dir():
normalized_path += "/"
exclude_match = exclude.search(normalized_path)