Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: triple-quoted f-strings #56

Merged
merged 5 commits into from
Jun 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ repos:
- id: black
language_version: python3.6
- repo: https://gitlab.com/pycqa/flake8
rev: '3.7.9'
rev: 3.8.3
hooks:
- id: flake8
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.1]

### Fixed

- f-strings with triple quotes are now correctly handled [[#55][55]]

## [0.1.0]

### Added
- First release - so everything you see is new!

[unreleased]: https://github.com/snakemake/snakefmt/compare/0.1.0...HEAD
[unreleased]: https://github.com/snakemake/snakefmt/compare/0.1.1...HEAD
[0.1.1]: https://github.com/snakemake/snakefmt/releases/tag/0.1.1
[0.1.0]: https://github.com/snakemake/snakefmt/releases/tag/0.1.0

[55]: https://github.com/snakemake/snakefmt/issues/55
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ singularity exec "$URI" snakefmt --help
The above will use the latest version. If you want to specify a version then use a [tag][dockerhub] like so.

```sh
VERSION="0.1.0"
VERSION="0.1.1"
URI="docker://snakemake/snakefmt:${VERSION}"
```

Expand Down
154 changes: 96 additions & 58 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "snakefmt"
version = "0.1.0"
version = "0.1.1"
description = "The uncompromising Snakemake code formatter"
authors = ["Michael Hall <[email protected]>", "Brice Letcher <[email protected]>"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion snakefmt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = "0.1.0"
__version__ = "0.1.1"
DEFAULT_LINE_LENGTH = 88
17 changes: 9 additions & 8 deletions snakefmt/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
PathLike = Union[Path, str]
rule_like_formatted = {"rule", "checkpoint"}

triple_quote_matcher = re.compile(r"(\"{3}.*?\"{3})|('{3}.*?'{3})", re.DOTALL)
triple_quote_matcher = re.compile(r"(^\s*\"{3}.*?\"{3})|(^\s'{3}.*?'{3})", re.DOTALL)
contextual_matcher = re.compile(
r"(.*)^(if|elif|else|with|for|while)(.*)(:.*)", re.S | re.M
)
Expand Down Expand Up @@ -166,16 +166,17 @@ def align_strings(self, string: str, target_indent: int) -> str:
for match in re.finditer(triple_quote_matcher, string):
indented += textwrap.indent(string[pos : match.start()], used_indent)
match_slice = string[match.start() : match.end()].replace("\t", TAB)
if match_slice.count("\n") > 1 and target_indent > 0:
all_lines = match_slice.splitlines(keepends=True)
first = textwrap.indent(textwrap.dedent(all_lines[0]), used_indent)
last = textwrap.indent(textwrap.dedent(all_lines[-1]), used_indent)
all_lines = match_slice.splitlines(keepends=True)
first = textwrap.indent(textwrap.dedent(all_lines[0]), used_indent)
indented += first
if len(all_lines) > 2:
middle = textwrap.indent(
textwrap.dedent("".join(all_lines[1:-1])), used_indent
)
indented += f"{first}{middle}{last}"
else:
indented += f"{used_indent}{match_slice}"
indented += middle
if len(all_lines) > 1:
last = textwrap.indent(textwrap.dedent(all_lines[-1]), used_indent)
indented += last
pos = match.end()
indented += textwrap.indent(string[pos:], used_indent)

Expand Down
59 changes: 22 additions & 37 deletions tests/test_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,80 +383,67 @@ def test_parameter_keywords_inside_python_code(self):
class TestStringFormatting:
"""Naming: tpq = triple quoted string"""

def test_param_with_string_mixture_reindented_and_string_normalised(self):
def test_param_with_string_mixture_retabbed_and_string_normalised(self):
snakecode = (
"rule a:\n"
f"{TAB * 1}message:\n"
f'{TAB * 2}"Hello"\n'
f'{TAB * 2}"""Hello"""\n'
f"{TAB * 2}''' a string'''\n"
f'{TAB * 3}"World"\n'
f'{TAB * 3}""" Yes"""\n'
)
expected = (
"rule a:\n"
f"{TAB * 1}message:\n"
f'{TAB * 2}"Hello"\n'
f'{TAB * 2}"""Hello"""\n'
f'{TAB * 2}""" a string"""\n' # Quotes normalised
f'{TAB * 2}"World"\n'
f'{TAB * 2}""" Yes"""\n'
)
formatter = setup_formatter(snakecode)
assert formatter.get_formatted() == expected

def test_tabbed_tpq_gets_retabbed(self):
snakecode = f'''
rule a:
{TAB * 1}shell:
{TAB * 2}"""
\t\tHello
\t\t\t\tWorld
{TAB * 2}"""
'''
def test_keyword_with_tpq_inside_expression_left_alone(self):
snakecode = (
"rule test:\n" f"{TAB * 1}run:\n" f'{TAB * 2}shell(f"""shell stuff""")\n'
)
formatter = setup_formatter(snakecode)
expected = f'''
rule a:
{TAB * 1}shell:
{TAB * 2}"""
{TAB * 2}Hello
{TAB * 4}World
{TAB * 2}"""
'''
assert formatter.get_formatted() == expected
assert formatter.get_formatted() == snakecode

def test_tpq_retabbing_and_keep_relative_indenting(self):
"""The "\\n" is produced when a "\n" is used in the snakefile."""
def test_tpq_alignment_and_keep_relative_indenting(self):
snakecode = '''
rule a:
shell:
"""
Hello
World
"""Starts here
Hello
World
\t\tTabbed
"""
'''
formatter = setup_formatter(snakecode)

expected = f'''
rule a:
{TAB * 1}shell:
{TAB * 2}"""
{TAB * 2}"""Starts here
{TAB * 2}Hello
{TAB * 2} World
{TAB * 4}Tabbed
{TAB * 2}"""
'''
assert formatter.get_formatted() == expected

def test_docstrings_get_retabbed(self):
snakecode = f'''def f():
def test_docstrings_get_retabbed_for_snakecode_only(self):
"""Black only retabs the first tpq in a docstring."""
snakecode = '''def f():
"""Does not do
much
"""
"""
pass


rule a:
"""
{' ' * 2}The rule
{' ' * 8}a
"""The rule a
"""
message:
"a"
Expand All @@ -465,14 +452,12 @@ def test_docstrings_get_retabbed(self):
expected = f'''def f():
{TAB * 1}"""Does not do
much
{TAB * 1}"""
"""
{TAB * 1}pass


rule a:
{TAB * 1}"""
{TAB * 1}The rule
{TAB * 1}{' ' * 6}a
{TAB * 1}"""The rule a
{TAB * 1}"""
{TAB * 1}message:
{TAB * 2}"a"
Expand Down