diff --git a/src/mdformat/_cli.py b/src/mdformat/_cli.py index a4f3aca..db4a949 100644 --- a/src/mdformat/_cli.py +++ b/src/mdformat/_cli.py @@ -3,6 +3,7 @@ import argparse from collections.abc import Callable, Generator, Iterable, Mapping, Sequence import contextlib +from datetime import datetime import itertools import logging import os.path @@ -14,6 +15,7 @@ import mdformat from mdformat._compat import importlib_metadata from mdformat._conf import DEFAULT_OPTS, InvalidConfError, read_toml_opts +from mdformat._output import diff from mdformat._util import detect_newline_type, is_md_equal import mdformat.plugins import mdformat.renderer @@ -99,6 +101,17 @@ def run(cli_args: Sequence[str]) -> int: # noqa: C901 if formatted_str != original_str: format_errors_found = True print_error(f'File "{path_str}" is not formatted.') + + if opts["diff"]: + then = datetime.utcfromtimestamp(path.stat().st_mtime) + now = datetime.utcnow() + src_name = f"{path}\t{then} +0000" + dst_name = f"{path}\t{now} +0000" + + diff_contents = diff( + original_str, formatted_str, src_name, dst_name + ) + print(diff_contents) else: if not changes_ast and not is_md_equal( original_str, @@ -151,6 +164,11 @@ def make_arg_parser( parser.add_argument( "--check", action="store_true", help="do not apply changes to files" ) + parser.add_argument( + "--diff", + action="store_true", + help="show diff of what would be changed when running with --check", + ) version_str = f"mdformat {mdformat.__version__}" if plugin_versions_str: version_str += f" ({plugin_versions_str})" diff --git a/src/mdformat/_output.py b/src/mdformat/_output.py new file mode 100644 index 0000000..fa9d122 --- /dev/null +++ b/src/mdformat/_output.py @@ -0,0 +1,22 @@ +import difflib + + +def diff(a: str, b: str, a_name: str, b_name: str) -> str: + """Return a unified diff string between strings `a` and `b`. + + Highly inspired by Black's diff function. + """ + a_lines = a.splitlines(keepends=True) + b_lines = b.splitlines(keepends=True) + + diff_lines = [] + for line in difflib.unified_diff( + a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5 + ): + if line[-1] == "\n": + diff_lines.append(line) + else: + diff_lines.append(line + "\n") + diff_lines.append("\\ No newline at end of file\n") + + return "".join(diff_lines) diff --git a/tests/test_cli.py b/tests/test_cli.py index 2bdf668..111d37a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -112,6 +112,21 @@ def test_check__fail(tmp_path): assert run((str(file_path), "--check")) == 1 +def test_check_fail_diff(capsys, tmp_path): + """Test for --check flag and --diff flag combined on unformatted files. + + Test that when an unformatted file fails, a diff is writtin to + stdout. + """ + + file_path = tmp_path / "test_markdown.md" + file_path.write_text(UNFORMATTED_MARKDOWN) + assert run((str(file_path), "--check", "--diff")) == 1 + captured = capsys.readouterr() + assert str(file_path) in captured.out + assert "-\n-\n # A header\n-\n" in captured.out + + def test_check__multi_fail(capsys, tmp_path): """Test for --check flag when multiple files are unformatted.