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

feature/black command line args #3

Merged
merged 25 commits into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d7e1de8
Add line-length & skip-string-normalization args
CorreyL May 7, 2020
5c606cf
Add support for sending `black` args to `FileMode`
CorreyL May 7, 2020
6b2cdb6
Add supported `black` command line args to README
CorreyL May 7, 2020
df4d812
Use correct block specifier and language
CorreyL Jun 21, 2020
eea6472
Add empty line before closing triple-double-quote
CorreyL Jun 21, 2020
d31ee37
Use help string from black
CorreyL Jun 21, 2020
f5c3b16
Use help string from black
CorreyL Jun 21, 2020
e17e07a
Merge remote-tracking branch 'remotes/original-repo/master' into feat…
CorreyL Jun 21, 2020
74c6c4e
Add CorreyL to list of contributors
CorreyL Jun 21, 2020
5e84d3c
Merge branch 'feature/black-command-line-args' of github.com:CorreyL/…
CorreyL Jun 21, 2020
8b9c0c1
Merge remote-tracking branch 'master'
CorreyL Jun 25, 2020
0e822e9
Remove default values for -l and -S
CorreyL Jun 25, 2020
4422679
Override defaults & toml with cmd line args
CorreyL Jun 25, 2020
588c740
Update version # & add new cmd line arg options
CorreyL Jun 25, 2020
b6434c8
Include black `--config` in `black_args` parameter
akaihola Jun 25, 2020
8b2d205
Only get FileMode options from Black config files
akaihola Jun 25, 2020
58e05a2
Simpler merge of config file and cmdline options
akaihola Jun 25, 2020
be2ddb5
Add unit tests for configfile/cmdline processing
akaihola Jun 25, 2020
8156c79
Typing fixes to make Mypy happy
akaihola Jun 25, 2020
175ae0e
Update CHANGES.rst
CorreyL Jun 25, 2020
9eecb66
Improve suggested typing of black_args parameter
CorreyL Jun 25, 2020
292c71d
Merge branch 'feature/black-command-line-args' into feature/unify-com…
CorreyL Jun 25, 2020
4a2567d
Merge pull request #1 from akaihola/feature/unify-command-line-proces…
CorreyL Jun 25, 2020
15cb524
Update -S and -l help text in README
CorreyL Jun 25, 2020
5833bf2
Add -c/--config to README black cmd line options
CorreyL Jun 25, 2020
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
4 changes: 2 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
0.3.0.dev / 2020-06-22
0.3.0.dev / yyyy-mm-dd
----------------------

- Feature: Add support for black config

- Feature: Add support for ``-l``/``--line-length`` and ``-S``/``--skip-string-normalization``

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 @@ -6,3 +6,4 @@

- Alexander Tishin (@Mystic-Mirage)
- Antti Kaihola (@akaihola)
- Correy Lim (@CorreyL)
15 changes: 15 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,21 @@ Example:

if False: print('there')

Black Command Line Arguments
============================

``black`` `command line arguments`_ that are currently supported:

.. code-block:: shell

-c PATH, --config PATH
Ask `black` to read configuration from PATH.
-S, --skip-string-normalization
Don't normalize string quotes or prefixes
-l LINE_LENGTH, --line-length LINE_LENGTH
How many characters per line to allow [default: 88]

.. _command line arguments: https://black.readthedocs.io/en/stable/installation_and_usage.html#command-line-options

Editor integration
==================
Expand Down
6 changes: 6 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ allow_untyped_globals = False
implicit_reexport = False
strict_equality = True

[mypy-darker.tests.conftest]
disallow_any_unimported = False

[mypy-darker.tests.*]
disallow_any_decorated = False
disallow_untyped_defs = False
Expand All @@ -41,5 +44,8 @@ ignore_missing_imports = True
[mypy-isort.*]
ignore_missing_imports = True

[mypy-py.path.*]
ignore_missing_imports = True

[mypy-pytest.*]
ignore_missing_imports = True
19 changes: 13 additions & 6 deletions src/darker/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import sys
from pathlib import Path
from typing import Iterable, List, Optional, Set
from typing import Dict, Iterable, List, Set, Union

from darker.black_diff import diff_and_get_opcodes, opcodes_to_chunks, run_black
from darker.chooser import choose_lines
Expand All @@ -21,7 +21,7 @@


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

Expand All @@ -42,8 +42,7 @@ 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 config: Path to the black configuration file (optional)

:param black_args: Command-line arguments to send to ``black.FileMode``
"""
remaining_srcs: Set[Path] = set(srcs)
git_root = get_common_root(srcs)
Expand All @@ -68,7 +67,7 @@ def format_edited_parts(
continue

# 4. run black
edited, formatted = run_black(src, config)
edited, formatted = run_black(src, black_args)
logger.debug("Read %s lines from edited file %s", len(edited), src)
logger.debug("Black reformat resulted in %s lines", len(formatted))

Expand Down Expand Up @@ -136,8 +135,16 @@ def main(argv: List[str] = None) -> None:
logger.error(f"{ISORT_INSTRUCTION} to use the `--isort` option.")
exit(1)

black_args = {}
if args.config:
black_args["config"] = args.config
if args.line_length:
black_args["line_length"] = args.line_length
if args.skip_string_normalization:
black_args["skip_string_normalization"] = args.skip_string_normalization

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


if __name__ == "__main__":
Expand Down
37 changes: 31 additions & 6 deletions src/darker/black_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
from difflib import SequenceMatcher
from functools import lru_cache
from pathlib import Path
from typing import Any, Dict, Generator, List, Optional, Tuple
from typing import Dict, Generator, List, Optional, Tuple, Union

from black import FileMode, format_str, read_pyproject_toml
from click import Command, Context, Option
Expand All @@ -85,28 +85,53 @@


@lru_cache(maxsize=1)
def read_black_config(src: Path, value: Optional[str]) -> Dict[str, Any]:
def read_black_config(src: Path, value: Optional[str]) -> Dict[str, Union[bool, int]]:
"""Read the black configuration from pyproject.toml"""
command = Command("main")

context = Context(command)
context.params["src"] = (str(src),)

parameter = Option(("--config",))
parameter = Option(["--config"])

read_pyproject_toml(context, parameter, value)

return context.default_map or {}
return {
key: value
for key, value in (context.default_map or {}).items()
if key in ["line_length", "skip_string_normalization"]
}


def run_black(src: Path, config: Optional[str]) -> Tuple[List[str], List[str]]:
def run_black(
src: Path, black_args: Dict[str, Union[bool, int]]
) -> Tuple[List[str], List[str]]:
"""Run the black formatter for the contents of the given Python file

Return lines of the original file as well as the formatted content.

:param black_args: Command-line arguments to send to ``black.FileMode``
CorreyL marked this conversation as resolved.
Show resolved Hide resolved

"""
config = black_args.pop("config", None)
defaults = read_black_config(src, config)
mode = FileMode(**defaults)
combined_args = {**defaults, **black_args}

effective_args = {}
if "line_length" in combined_args:
effective_args["line_length"] = combined_args["line_length"]
if "skip_string_normalization" in combined_args:
# The ``black`` command line argument is
# ``--skip-string-normalization``, but the parameter for
# ``black.FileMode`` needs to be the opposite boolean of
# ``skip-string-normalization``, hence the inverse boolean
effective_args["string_normalization"] = not combined_args[
"skip_string_normalization"
]

# Override defaults and pyproject.toml settings if they've been specified
# from the command line arguments
mode = FileMode(**effective_args)

src_contents = src.read_text()
dst_contents = format_str(src_contents, mode=mode)
Expand Down
14 changes: 14 additions & 0 deletions src/darker/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,18 @@ def parse_command_line(argv: List[str]) -> Namespace:
parser.add_argument(
"--version", action="store_true", help="Show the version of `darker`"
)
parser.add_argument(
"-S",
"--skip-string-normalization",
action="store_true",
dest="skip_string_normalization",
help="Don't normalize string quotes or prefixes",
)
parser.add_argument(
"-l",
"--line-length",
type=int,
dest="line_length",
help="How many characters per line to allow [default: 88]",
)
return parser.parse_args(argv)
2 changes: 1 addition & 1 deletion src/darker/git_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def skip_file(lines: Buf, path: Path) -> None:
next(lines)


def should_reformat_file(path: Path):
def should_reformat_file(path: Path) -> bool:
return path.suffix == ".py"


Expand Down
28 changes: 28 additions & 0 deletions src/darker/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import sys
import types
from subprocess import check_call
from typing import Dict
from unittest.mock import patch

import pytest
from py.path import local as LocalPath


@pytest.fixture
Expand All @@ -15,3 +18,28 @@ def without_isort():
def with_isort():
with patch.dict(sys.modules, {"isort": types.ModuleType("isort")}):
yield


class GitRepoFixture:
def __init__(self, root: LocalPath):
self.root = root

def add(
self, paths_and_contents: Dict[str, str], commit: str = None
) -> Dict[str, LocalPath]:
absolute_paths = {
relative_path: self.root / relative_path
for relative_path in paths_and_contents
}
for relative_path, content in paths_and_contents.items():
absolute_paths[relative_path].write(content)
check_call(["git", "add", relative_path], cwd=self.root)
if commit:
check_call(["git", "commit", "-m", commit], cwd=self.root)
return absolute_paths


@pytest.fixture
def git_repo(tmpdir):
check_call(["git", "init"], cwd=tmpdir)
return GitRepoFixture(tmpdir)
31 changes: 25 additions & 6 deletions src/darker/tests/test_black_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,32 @@ def test_mixed():
]


@pytest.mark.parametrize("config,line_length", ((None, 79), ("custom.toml", 99)))
def test_black_config(tmpdir, config, line_length):
@pytest.mark.parametrize(
"config_path, config_lines, expect",
[
(None, ['line-length = 79'], {'line_length': 79}),
("custom.toml", ['line-length = 99'], {'line_length': 99}),
(
"custom.toml",
['skip-string-normalization = true'],
{'skip_string_normalization': True},
),
(
"custom.toml",
['skip-string-normalization = false'],
{'skip_string_normalization': False},
),
("custom.toml", ["target-version = ['py37']"], {}),
("custom.toml", ["include = '\\.pyi$'"], {}),
("custom.toml", ["exclude = '\\.pyx$'"], {}),
],
)
def test_black_config(tmpdir, config_path, config_lines, expect):
tmpdir = Path(tmpdir)
src = tmpdir / "src.py"
toml = tmpdir / (config or "pyproject.toml")
toml = tmpdir / (config_path or "pyproject.toml")

toml.write_text("[tool.black]\nline-length = {}\n".format(line_length))
toml.write_text("[tool.black]\n{}\n".format('\n'.join(config_lines)))

config = read_black_config(src, config and str(toml))
assert config == {"line_length": line_length}
config = read_black_config(src, config_path and str(toml))
assert config == expect
50 changes: 50 additions & 0 deletions src/darker/tests/test_command_line.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import re
from textwrap import dedent
from unittest.mock import call, patch

import pytest

from darker import black_diff
from darker.__main__ import main
from darker.command_line import parse_command_line


Expand All @@ -28,3 +32,49 @@ def test_help_isort_option_without_isort_package(without_isort, darker_help_outp

def test_help_with_isort_package(with_isort, darker_help_output):
assert "Please run" not in darker_help_output


@pytest.mark.parametrize(
"options, expect",
[
([], call()),
(['-c', 'black.cfg'], call(line_length=81, string_normalization=True)),
(['--config', 'black.cfg'], call(line_length=81, string_normalization=True)),
(['-S'], call(string_normalization=False)),
(['--skip-string-normalization'], call(string_normalization=False)),
(['-l', '90'], call(line_length=90)),
(['--line-length', '90'], call(line_length=90)),
(['-c', 'black.cfg', '-S'], call(line_length=81, string_normalization=False)),
(
['-c', 'black.cfg', '-l', '90'],
call(line_length=90, string_normalization=True),
),
(['-l', '90', '-S'], call(line_length=90, string_normalization=False)),
(
['-c', 'black.cfg', '-l', '90', '-S'],
call(line_length=90, string_normalization=False),
),
],
)
def test_black_options(monkeypatch, tmpdir, git_repo, options, expect):
monkeypatch.chdir(tmpdir)
(tmpdir / 'pyproject.toml').write("[tool.black]\n")
(tmpdir / 'black.cfg').write(
dedent(
"""
[tool.black]
line-length = 81
skip-string-normalization = false
"""
)
)
added_files = git_repo.add(
{"main.py": 'print("Hello World!")\n'}, commit="Initial commit"
)
added_files["main.py"].write('print ("Hello World!")\n')
with patch.object(black_diff, 'FileMode', wraps=black_diff.FileMode) as FileMode:

main(options + [str(path) for path in added_files.values()])

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