From f71f9d06ef9a989cd970903ace95541bf0351885 Mon Sep 17 00:00:00 2001 From: Kieran Ryan Date: Sat, 8 Jul 2023 00:13:37 +0100 Subject: [PATCH] fix: Reformat for any modification (#36) --- README.md | 1 + pyprojectsort/__version__.py | 2 +- pyprojectsort/main.py | 15 ++++---- tests/unit/test_main.py | 66 +++++++++++++++++++++++++++++------- 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 563fdd7..4956a7e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This package enforces consistent formatting of pyproject.toml files, reducing me - double quotations - trailing commas - indentation + - end of file newline ## Installation diff --git a/pyprojectsort/__version__.py b/pyprojectsort/__version__.py index 31c0b07..0235cc6 100644 --- a/pyprojectsort/__version__.py +++ b/pyprojectsort/__version__.py @@ -6,6 +6,6 @@ MAJOR = 0 MINOR = 2 -MICRO = 1 +MICRO = 2 __version__ = f"{MAJOR}.{MINOR}.{MICRO}" diff --git a/pyprojectsort/main.py b/pyprojectsort/main.py index 574cde3..89c6054 100644 --- a/pyprojectsort/main.py +++ b/pyprojectsort/main.py @@ -48,8 +48,8 @@ def _read_config_file(config: pathlib.Path) -> pathlib.Path: def _parse_pyproject_toml(file: pathlib.Path) -> dict[str, Any]: """Parse pyproject.toml file.""" - with file.open("rb") as pyproject_file: - return tomllib.load(pyproject_file) + with file.open("r") as pyproject_file: + return pyproject_file.read() def reformat_pyproject(pyproject: dict | list) -> dict: @@ -64,7 +64,7 @@ def reformat_pyproject(pyproject: dict | list) -> dict: return pyproject -def _check_format_needed(original: dict, reformatted: dict) -> bool: +def _check_format_needed(original: str, reformatted: str) -> bool: """Check if there are any differences between original and reformatted.""" return original != reformatted @@ -79,10 +79,13 @@ def main() -> None: """Run application.""" args = _read_cli(sys.argv[1:]) pyproject_file = _read_config_file(pathlib.Path(args.file)) - pyproject_toml = _parse_pyproject_toml(pyproject_file) - reformatted_pyproject = reformat_pyproject(pyproject_toml) - will_reformat = _check_format_needed(pyproject_toml, reformatted_pyproject) + pyproject_toml: str = _parse_pyproject_toml(pyproject_file) + pyproject: dict = tomllib.loads(pyproject_toml) + reformatted_pyproject: dict = reformat_pyproject(pyproject) + reformatted_pyproject_toml: str = tomli_w.dumps(reformatted_pyproject) + + will_reformat = _check_format_needed(pyproject_toml, reformatted_pyproject_toml) if args.check: if will_reformat: diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 7773d25..905f255 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -116,14 +116,14 @@ def test_main_with_file_reformatted( args = CLIArgs() read_cli.return_value = args read_config.return_value = pathlib.Path() - parse_pyproject.return_value = {} + parse_pyproject.return_value = "change = 1" reformat_pyproject.return_value = {"change": 1} with pytest.raises(SystemExit) as reformatted, OutputCapture() as output: main() assert reformatted.value.code == 1 - assert output.text == f"Reformatted '{args.file}'" + assert f"Reformatted '{args.file}'" in output.text @unittest.mock.patch("pyprojectsort.main._save_pyproject") @@ -142,13 +142,13 @@ def test_main_with_file_unchanged( args = CLIArgs() read_cli.return_value = args read_config.return_value = pathlib.Path() - parse_pyproject.return_value = {} + parse_pyproject.return_value = "" reformat_pyproject.return_value = {} with OutputCapture() as output: main() - assert output.text == f"'{args.file}' left unchanged" + assert f"'{args.file}' left unchanged" in output.text @unittest.mock.patch("pyprojectsort.main.reformat_pyproject") @@ -165,14 +165,48 @@ def test_check_option_reformat_needed( args = CLIArgs(check=True) read_cli.return_value = args read_config.return_value = pathlib.Path() - parse_pyproject.return_value = {} + parse_pyproject.return_value = "change = 1" reformat_pyproject.return_value = {"change": 1} - with pytest.raises(SystemExit) as pytest_wrapped_e, OutputCapture() as output: + with pytest.raises(SystemExit) as would_reformat, OutputCapture() as output: main() - assert output.text == f"'{args.file}' would be reformatted" - assert pytest_wrapped_e.value.code == 1 + assert f"'{args.file}' would be reformatted" in output.text + assert would_reformat.value.code == 1 + + +@pytest.mark.parametrize( + ("original"), + [ + ('unsorted = [\n "tests",\n "docs",\n]\n'), + ('not-indented = [\n "docs",\n"tests",\n]\n'), + ('no-trailing-comma = [\n "docs",\n "tests"\n]\n'), + ('not-line-per-list-value = ["docs","tests"]\n'), + ('extra_spaces = "value"\n'), + ('no-newline-at-end-of-file = "value"'), + ("single-quotes = 'value'\n"), + ], +) +@unittest.mock.patch("pyprojectsort.main._parse_pyproject_toml") +@unittest.mock.patch("pyprojectsort.main._read_config_file") +@unittest.mock.patch("pyprojectsort.main._read_cli") +def test_would_reformat( + read_cli, + read_config, + parse_pyproject, + original, +): + """Test --check option when reformat occurs.""" + args = CLIArgs(check=True) + read_cli.return_value = args + read_config.return_value = pathlib.Path() + parse_pyproject.return_value = original + + with pytest.raises(SystemExit) as would_reformat, OutputCapture() as output: + main() + print(output.text) + assert f"'{args.file}' would be reformatted" in output.text + assert would_reformat.value.code == 1 @unittest.mock.patch("pyprojectsort.main.reformat_pyproject") @@ -189,20 +223,28 @@ def test_check_option_reformat_not_needed( args = CLIArgs(check=True) read_cli.return_value = args read_config.return_value = pathlib.Path() - parse_pyproject.return_value = {"unchanged": 1} + parse_pyproject.return_value = "unchanged = 1\n" reformat_pyproject.return_value = {"unchanged": 1} with OutputCapture() as output: main() - assert output.text == f"'{args.file}' would be left unchanged" + assert f"'{args.file}' would be left unchanged" in output.text @pytest.mark.parametrize( ("original", "reformatted", "expected_result"), [ - ({"unchanged": 1}, {"unchanged": 1}, False), - ({"should_be_changed": 1}, {"changed": 1}, True), + ( + '[tool.pylint]\nignore = [\n\t"tests",\n\t"docs",\n]', + '[tool.pylint]\nignore = [\n\t"tests",\n\t"docs",\n]', + False, + ), + ( + '[tool.pylint]\nignore = [\n\t"tests",\n\t"docs",\n]', + '[tool.pylint]\nignore = [\n\t"docs",\n\t"tests",\n]', + True, + ), ], ) def test_check_format_needed(original, reformatted, expected_result):