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

Add a --diff option to match black behavior. #4

Merged
merged 22 commits into from
Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

- Feature: Add support for black config
- Feature: Add support for ``-l``/``--line-length`` and ``-S``/``--skip-string-normalization``
- Feature: ``--diff`` outputs a diff for each file on standard output


0.2.0 / 2020-03-11
------------------
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
- Alexander Tishin (@Mystic-Mirage)
- Antti Kaihola (@akaihola)
- Correy Lim (@CorreyL)
- Matthias Bussonnier (@Carreau)
24 changes: 24 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,30 @@ PyCharm/IntelliJ IDEA

__ https://plugins.jetbrains.com/plugin/7177-file-watchers

Visual Studio Code
------------------

1. Install ``darker``::

$ pip install darker

2. Locate your ``darker`` installation folder.

On macOS / Linux / BSD::

$ which darker
/usr/local/bin/darker # possible location

On Windows::

$ where darker
%LocalAppData%\Programs\Python\Python36-32\Scripts\darker.exe # possible location

3. Add these configuration options::

"python.formatting.provider": "black",
"python.formatting.blackPath": "<install_location_from_step_2>"


How does it work?
=================
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools", "wheel"] # PEP 508 specifications.

[tool.black]
skip-string-normalization = true
26 changes: 23 additions & 3 deletions src/darker/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
import sys
from difflib import unified_diff
from pathlib import Path
from typing import Dict, Iterable, List, Set, Union

Expand All @@ -21,7 +22,10 @@


def format_edited_parts(
srcs: Iterable[Path], isort: bool, black_args: Dict[str, Union[bool, int]]
srcs: Iterable[Path],
isort: bool,
black_args: Dict[str, Union[bool, int]],
print_diff: bool,
) -> None:
"""Black (and optional isort) formatting for chunks with edits since the last commit

Expand All @@ -43,12 +47,16 @@ def format_edited_parts(
:param srcs: Directories and files to re-format
:param isort: ``True`` to also run ``isort`` first on each changed file
:param black_args: Command-line arguments to send to ``black.FileMode``
:param print_diff: ``True`` to output diffs instead of modifying source files

"""
remaining_srcs: Set[Path] = set(srcs)
git_root = get_common_root(srcs)

# 1. run isort
if isort:
if print_diff:
raise NotImplementedError('--isort is not supported with --print-diff')
changed_files = git_diff_name_only(remaining_srcs, git_root)
apply_isort(changed_files)

Expand Down Expand Up @@ -109,7 +117,19 @@ def format_edited_parts(
# 10. A re-formatted Python file which produces an identical AST was
# created successfully - write an updated file
logger.info("Writing %s bytes into %s", len(result_str), src)
src.write_text(result_str)
if print_diff:
difflines = list(
unified_diff(
edited, chosen_lines, src.as_posix(), src.as_posix(),
)
)
if len(difflines) > 2:
h1, h2, *rest = difflines
print(h1, end="")
print(h2, end="")
print("\n".join(rest))
else:
src.write_text(result_str)
if not remaining_srcs:
break

Expand Down Expand Up @@ -144,7 +164,7 @@ def main(argv: List[str] = None) -> None:
black_args["skip_string_normalization"] = args.skip_string_normalization

paths = {Path(p) for p in args.src}
format_edited_parts(paths, args.isort, black_args)
format_edited_parts(paths, args.isort, black_args, args.diff)


if __name__ == "__main__":
Expand Down
5 changes: 5 additions & 0 deletions src/darker/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def parse_command_line(argv: List[str]) -> Namespace:
isort_help = ["Also sort imports using the `isort` package"]
if not isort:
isort_help.append(f". {ISORT_INSTRUCTION} to enable usage of this option.")
parser.add_argument(
"--diff",
action="store_true",
help="Don't write the files back, just output a diff for each file on stdout",
)
parser.add_argument(
"-i", "--isort", action="store_true", help="".join(isort_help),
)
Expand Down
3 changes: 2 additions & 1 deletion src/darker/import_sorting.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def apply_isort(srcs: List[Path]) -> None:
logger.debug(
f"SortImports({str(src)!r}, multi_line_output=3, "
f"include_trailing_comma=True, force_grid_wrap=0, use_parentheses=True,"
f" line_length=88)"
f" line_length=88, quiet=True)"
)
_ = SortImports(
str(src),
Expand All @@ -24,4 +24,5 @@ def apply_isort(srcs: List[Path]) -> None:
force_grid_wrap=0,
use_parentheses=True,
line_length=88,
quiet=True,
)
29 changes: 29 additions & 0 deletions src/darker/tests/test_command_line.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from pathlib import Path
from textwrap import dedent
from unittest.mock import call, patch

Expand Down Expand Up @@ -78,3 +79,31 @@ def test_black_options(monkeypatch, tmpdir, git_repo, options, expect):

_, expect_args, expect_kwargs = expect
FileMode.assert_called_once_with(*expect_args, **expect_kwargs)


@pytest.mark.parametrize(
'options, expect',
[
(['a.py'], ({Path('a.py')}, False, {}, False)),
(['--isort', 'a.py'], ({Path('a.py')}, True, {}, False)),
(
['--config', 'my.cfg', 'a.py'],
({Path('a.py')}, False, {'config': 'my.cfg'}, False),
),
(
['--line-length', '90', 'a.py'],
({Path('a.py')}, False, {'line_length': 90}, False),
),
(
['--skip-string-normalization', 'a.py'],
({Path('a.py')}, False, {'skip_string_normalization': True}, False),
),
(['--diff', 'a.py'], ({Path('a.py')}, False, {}, True)),
],
)
def test_options(options, expect):
with patch('darker.__main__.format_edited_parts') as format_edited_parts:

main(options)

format_edited_parts.assert_called_once_with(*expect)
85 changes: 84 additions & 1 deletion src/darker/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import darker.__main__
import darker.import_sorting
from darker.tests.git_diff_example_output import CHANGE_SECOND_LINE


def test_isort_option_without_isort(tmpdir, without_isort, caplog):
Expand Down Expand Up @@ -49,4 +48,88 @@ def test_isort_option_with_isort_calls_sortimports(run_isort):
line_length=88,
multi_line_output=3,
use_parentheses=True,
quiet=True,
)


def test_format_edited_parts_empty():
with pytest.raises(ValueError):

darker.__main__.format_edited_parts([], False, {}, True)


def test_format_edited_parts_isort_print_diff():
with pytest.raises(NotImplementedError):

darker.__main__.format_edited_parts([Path('test.py')], True, {}, True)


A_PY = ['import sys', 'import os', "print( '42')", '']
A_PY_BLACK = ['import sys', 'import os', '', 'print("42")', '']
A_PY_BLACK_ISORT = ['import os', 'import sys', '', 'print("42")', '']

A_PY_DIFF_BLACK = [
'--- /a.py',
'+++ /a.py',
'@@ -1,3 +1,4 @@',
'',
' import sys',
' import os',
"-print( '42')",
'+',
'+print("42")',
'',
]

A_PY_DIFF_BLACK_NO_STR_NORMALIZE = [
'--- /a.py',
'+++ /a.py',
'@@ -1,3 +1,4 @@',
'',
' import sys',
' import os',
"-print( '42')",
'+',
"+print('42')",
'',
]


@pytest.mark.parametrize(
'srcs, isort, black_args, print_diff, expect_stdout, expect_a_py',
[
(['a.py'], False, {}, True, A_PY_DIFF_BLACK, A_PY),
(['a.py'], True, {}, False, [''], A_PY_BLACK_ISORT),
(
['a.py'],
False,
{'skip_string_normalization': True},
True,
A_PY_DIFF_BLACK_NO_STR_NORMALIZE,
A_PY,
),
(['a.py'], False, {}, False, [''], A_PY_BLACK),
],
)
def test_format_edited_parts(
git_repo,
monkeypatch,
capsys,
srcs,
isort,
black_args,
print_diff,
expect_stdout,
expect_a_py,
):
monkeypatch.chdir(git_repo.root)
paths = git_repo.add({'a.py': '\n', 'b.py': '\n'}, commit='Initial commit')
paths['a.py'].write('\n'.join(A_PY))
paths['b.py'].write('print(42 )\n')
darker.__main__.format_edited_parts(
[Path(src) for src in srcs], isort, black_args, print_diff
)
stdout = capsys.readouterr().out.replace(str(git_repo.root), '')
assert stdout.split('\n') == expect_stdout
assert paths['a.py'].readlines(cr=False) == expect_a_py
assert paths['b.py'].readlines(cr=False) == ['print(42 )', '']