From ccc73bec6c46b45dc491c26e7ea9ea544b2ec712 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 08:56:39 -0800 Subject: [PATCH 01/18] Add --config option to explicitly set configuration file path --- src/mdformat/_cli.py | 25 ++++++++++++++++++++++--- src/mdformat/_conf.py | 16 ++++++++++++++++ tests/test_cli.py | 26 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/mdformat/_cli.py b/src/mdformat/_cli.py index 682b789..11a036b 100644 --- a/src/mdformat/_cli.py +++ b/src/mdformat/_cli.py @@ -12,7 +12,7 @@ import textwrap import mdformat -from mdformat._conf import DEFAULT_OPTS, InvalidConfError, read_toml_opts +from mdformat._conf import DEFAULT_OPTS, InvalidConfError, read_toml_opts, read_single_config_file, from mdformat._util import detect_newline_type, is_md_equal import mdformat.plugins @@ -34,6 +34,10 @@ def run(cli_args: Sequence[str], cache_toml: bool = True) -> int: # noqa: C901 } cli_core_opts, cli_plugin_opts = separate_core_and_plugin_opts(cli_opts) + config_override_path = cli_core_opts.pop("config", None) + if config_override_path and not isinstance(config_override_path, Path): + config_override_path = Path(config_override_path) + if not cli_opts["paths"]: print_paragraphs(["No files have been passed in. Doing nothing."]) return 0 @@ -46,12 +50,20 @@ def run(cli_args: Sequence[str], cache_toml: bool = True) -> int: # noqa: C901 format_errors_found = False renderer_warning_printer = RendererWarningPrinter() for path in file_paths: - read_toml = read_toml_opts if cache_toml else read_toml_opts.__wrapped__ try: - toml_opts, toml_path = read_toml(path.parent if path else Path.cwd()) + if config_override_path: + toml_opts, toml_path = read_single_config_file(config_override_path) + else: + read_toml = read_toml_opts if cache_toml else read_toml_opts.__wrapped__ + toml_opts, toml_path = read_toml(path.parent if path else Path.cwd()) except InvalidConfError as e: print_error(str(e)) return 1 + except FileNotFoundError as e: + if config_override_path and config_override_path.samefile(Path(e.filename)): + print_error(f"Configuration file not found at: {e.filename}") + return 1 + raise opts = {**DEFAULT_OPTS, **toml_opts, **cli_core_opts} @@ -241,6 +253,13 @@ def make_arg_parser( choices=("lf", "crlf", "keep"), help="output file line ending mode (default: lf)", ) + + parser.add_argument( + "--config", + type=Path, + help="path to a TOML configuration file to use (overrides auto-detection)", + ) + if sys.version_info >= (3, 13): # pragma: >=3.13 cover parser.add_argument( "--exclude", diff --git a/src/mdformat/_conf.py b/src/mdformat/_conf.py index d50a798..3791029 100644 --- a/src/mdformat/_conf.py +++ b/src/mdformat/_conf.py @@ -31,6 +31,22 @@ class InvalidConfError(Exception): - invalid conf value """ +def read_single_config_file(config_path: Path) -> tuple[Mapping, Path | None]: + """Read configuration from a single specified TOML file.""" + if not config_path.is_file(): + raise FileNotFoundError(config_path) + + with open(config_path, "rb") as f: + try: + toml_opts = tomllib.load(f) + except tomllib.TOMLDecodeError as e: + raise InvalidConfError(f"Invalid TOML syntax in {config_path}: {e}") + + _validate_keys(toml_opts, config_path) + _validate_values(toml_opts, config_path) + + return toml_opts, config_path + @functools.lru_cache def read_toml_opts(conf_dir: Path) -> tuple[Mapping, Path | None]: diff --git a/tests/test_cli.py b/tests/test_cli.py index 28c3622..e0b5187 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -512,3 +512,29 @@ def test_no_extensions(tmp_path, monkeypatch): file_path.write_text(original_md) assert run((str(file_path), "--no-extensions")) == 0 assert file_path.read_text() == original_md + + +def test_config_override_precedence(tmp_path): + explicit_config_path = tmp_path / "explicit.toml" + explicit_config_path.write_text("wrap = 50\nend_of_line = 'crlf'") + + auto_config_path = tmp_path / ".mdformat.toml" + auto_config_path.write_text("wrap = 100") + + file_path = tmp_path / "test.md" + file_path.write_text( + "A very long line to test wrapping and EOLs.\nA very very long line." + ) + + expected_content = ( + "A very long line to test wrapping and EOLs.\r\nA very very long\r\nline.\r\n" + ) + + assert run([str(file_path), "--config", str(explicit_config_path)]) == 0 + assert file_path.read_bytes() == expected_content.encode("utf-8") + + non_existent_path = tmp_path / "non_existent.toml" + unformatted_content = "unformatted content" + file_path.write_text(unformatted_content) + assert run([str(file_path), "--config", str(non_existent_path)]) == 1 + assert file_path.read_text() == unformatted_content From 7984bd787385bffde7955bbede46d4152dc71043 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 09:29:17 -0800 Subject: [PATCH 02/18] Add documentation for --config --- docs/users/configuration_file.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/users/configuration_file.md b/docs/users/configuration_file.md index 508f19e..cdde1c6 100644 --- a/docs/users/configuration_file.md +++ b/docs/users/configuration_file.md @@ -8,6 +8,16 @@ When formatting standard input stream, resolution will be started from current w Command line interface arguments take precedence over the configuration file. +### Explicit Configuration Path + +Alternatively, you can explicitly point to a configuration file using the **`--config`** command-line argument. When this argument is used, the default recursive search for `.mdformat.toml` is **disabled**, and only the configuration found at the specified path will be loaded. This provides direct control over the configuration file location, which is useful when integrating `mdformat` into tools like `pre-commit` hooks that require configs to be stored in custom locations. + +**Example:** + +```bash +mdformat file.md --config .config/mdformat.toml +``` + ## Example configuration ```toml From b0b7b06c6fe5019b3d00f3461ab9c92236a042d3 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 10:07:59 -0800 Subject: [PATCH 03/18] Address pre-commit failures. --- src/mdformat/_cli.py | 10 ++++++++-- src/mdformat/_conf.py | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/mdformat/_cli.py b/src/mdformat/_cli.py index 11a036b..29de3d2 100644 --- a/src/mdformat/_cli.py +++ b/src/mdformat/_cli.py @@ -12,7 +12,12 @@ import textwrap import mdformat -from mdformat._conf import DEFAULT_OPTS, InvalidConfError, read_toml_opts, read_single_config_file, +from mdformat._conf import ( + DEFAULT_OPTS, + InvalidConfError, + read_single_config_file, + read_toml_opts, +) from mdformat._util import detect_newline_type, is_md_equal import mdformat.plugins @@ -345,7 +350,8 @@ def separate_core_and_plugin_opts(opts: Mapping) -> tuple[dict, dict]: class InvalidPath(Exception): """Exception raised when a path does not exist.""" - def __init__(self, path: Path): + def __init__(self, path: Path) -> None: + super().__init__(path) self.path = path diff --git a/src/mdformat/_conf.py b/src/mdformat/_conf.py index 3791029..a95ac9e 100644 --- a/src/mdformat/_conf.py +++ b/src/mdformat/_conf.py @@ -31,6 +31,7 @@ class InvalidConfError(Exception): - invalid conf value """ + def read_single_config_file(config_path: Path) -> tuple[Mapping, Path | None]: """Read configuration from a single specified TOML file.""" if not config_path.is_file(): From a31fceb9889e5f7923ea5ee950b03f75001ec522 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 10:18:53 -0800 Subject: [PATCH 04/18] Fix test failure. --- tests/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index e0b5187..0fe2d5e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -527,7 +527,7 @@ def test_config_override_precedence(tmp_path): ) expected_content = ( - "A very long line to test wrapping and EOLs.\r\nA very very long\r\nline.\r\n" + "A very long line to test wrapping and EOLs.\r\nA very very long line.\r\n" ) assert run([str(file_path), "--config", str(explicit_config_path)]) == 0 From b99cf54884c9ea5e639e79968336dc57e9abc520 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 10:24:23 -0800 Subject: [PATCH 05/18] Fix 50 character limit test failure. --- tests/test_cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 0fe2d5e..56ce493 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -527,7 +527,8 @@ def test_config_override_precedence(tmp_path): ) expected_content = ( - "A very long line to test wrapping and EOLs.\r\nA very very long line.\r\n" + "A very long line to test wrapping and EOLs. A very\r\n" + "very long line.\r\n" ) assert run([str(file_path), "--config", str(explicit_config_path)]) == 0 From 44c375c03cccd8ee9ee9c0bff026d5a07326a2a1 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 10:34:37 -0800 Subject: [PATCH 06/18] Resolve test failures. --- tests/test_cli.py | 25 +++++++++++++++++++++++++ tests/test_config_file.py | 12 ++++++++++++ 2 files changed, 37 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 56ce493..fbb2b3c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,5 @@ import os +from pathlib import Path import sys from unittest.mock import patch @@ -539,3 +540,27 @@ def test_config_override_precedence(tmp_path): file_path.write_text(unformatted_content) assert run([str(file_path), "--config", str(non_existent_path)]) == 1 assert file_path.read_text() == unformatted_content + + +def test_config_search_from_stdin(tmp_path): + """Test that config is searched from CWD when reading from stdin (path is + None).""" + + config_path = tmp_path / ".mdformat.toml" + config_path.write_text("wrap = 100") + + with patch("os.getcwd", return_value=str(tmp_path)): + + input_content = ( + "This is a very long line that should be wrapped if config is read." + ) + + expected_content = ( + "This is a very long line that should be wrapped if config is read.\n" + ) + + with patch("sys.stdin", io.StringIO(input_content)): + with patch("sys.stdout", io.StringIO()) as mock_stdout: + assert run([]) == 0 + + assert mock_stdout.getvalue() == expected_content diff --git a/tests/test_config_file.py b/tests/test_config_file.py index ec16eea..ba8627c 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -168,3 +168,15 @@ def test_conf_no_validate(tmp_path): assert run((str(file_path),), cache_toml=False) == 0 assert file_path.read_text() == "1? ordered\n" + + +def test_single_config_file_invalid_toml(tmp_path): + """Test that reading an explicitly supplied config file with invalid TOML + raises InvalidConfError.""" + invalid_toml_path = tmp_path / "invalid.toml" + invalid_toml_path.write_text("key = 'value\n[broken") + + with pytest.raises(InvalidConfError) as excinfo: + read_single_config_file(invalid_toml_path) + + assert f"Invalid TOML syntax in {invalid_toml_path}" in str(excinfo.value) From ce1482eb20b9b95ad26697d0afca95549b6afab8 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 10:40:03 -0800 Subject: [PATCH 07/18] Address pre-commit failures. --- tests/test_cli.py | 5 ++--- tests/test_config_file.py | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index fbb2b3c..845607d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,5 @@ +import io import os -from pathlib import Path import sys from unittest.mock import patch @@ -528,8 +528,7 @@ def test_config_override_precedence(tmp_path): ) expected_content = ( - "A very long line to test wrapping and EOLs. A very\r\n" - "very long line.\r\n" + "A very long line to test wrapping and EOLs. A very\r\n" "very long line.\r\n" ) assert run([str(file_path), "--config", str(explicit_config_path)]) == 0 diff --git a/tests/test_config_file.py b/tests/test_config_file.py index ba8627c..7fe27a3 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -4,6 +4,7 @@ import pytest from mdformat._cli import run +from mdformat._conf import InvalidConfError, read_single_config_file from tests.utils import FORMATTED_MARKDOWN, UNFORMATTED_MARKDOWN From 6e8317de0b204d2f011dc0b827f4555bd0dfe6d1 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 10:53:37 -0800 Subject: [PATCH 08/18] Address test failure caused by pre-commit linting. --- tests/test_cli.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 845607d..eccb3a9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -546,20 +546,18 @@ def test_config_search_from_stdin(tmp_path): None).""" config_path = tmp_path / ".mdformat.toml" - config_path.write_text("wrap = 100") + config_path.write_text("wrap = 50") - with patch("os.getcwd", return_value=str(tmp_path)): - - input_content = ( - "This is a very long line that should be wrapped if config is read." - ) + input_content = "This is a very long line that should be wrapped if config is read." - expected_content = ( - "This is a very long line that should be wrapped if config is read.\n" - ) + expected_content = """\ +This is a very long line that should be wrapped if +config is read. +""" + with patch("os.getcwd", return_value=str(tmp_path)): with patch("sys.stdin", io.StringIO(input_content)): with patch("sys.stdout", io.StringIO()) as mock_stdout: - assert run([]) == 0 + assert run(("-",)) == 0 assert mock_stdout.getvalue() == expected_content From db3482cd64fcdfa8aafc97eccf47783809e795b0 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 11:02:17 -0800 Subject: [PATCH 09/18] Commit without pre-commit. --- tests/test_cli.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index eccb3a9..5b3ac09 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -527,8 +527,9 @@ def test_config_override_precedence(tmp_path): "A very long line to test wrapping and EOLs.\nA very very long line." ) - expected_content = ( - "A very long line to test wrapping and EOLs. A very\r\n" "very long line.\r\n" +expected_content = ( + "A very long line to test wrapping and EOLs. A very" + "\r\n" + + "very long line." + "\r\n" ) assert run([str(file_path), "--config", str(explicit_config_path)]) == 0 @@ -537,11 +538,12 @@ def test_config_override_precedence(tmp_path): non_existent_path = tmp_path / "non_existent.toml" unformatted_content = "unformatted content" file_path.write_text(unformatted_content) + assert run([str(file_path), "--config", str(non_existent_path)]) == 1 assert file_path.read_text() == unformatted_content -def test_config_search_from_stdin(tmp_path): +def test_config_search_from_stdin(tmp_path, capfd, patch_stdin): """Test that config is searched from CWD when reading from stdin (path is None).""" @@ -556,8 +558,10 @@ def test_config_search_from_stdin(tmp_path): """ with patch("os.getcwd", return_value=str(tmp_path)): - with patch("sys.stdin", io.StringIO(input_content)): - with patch("sys.stdout", io.StringIO()) as mock_stdout: - assert run(("-",)) == 0 + patch_stdin(input_content) + + assert run(("-",)) == 0 + + captured = capfd.readouterr() - assert mock_stdout.getvalue() == expected_content + assert captured.out == expected_content \ No newline at end of file From d4802603651b8620c1b14c65ea579aff174926d5 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 11:07:58 -0800 Subject: [PATCH 10/18] Fix tests. --- tests/test_cli.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 5b3ac09..0e7774c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,3 @@ -import io import os import sys from unittest.mock import patch @@ -527,9 +526,11 @@ def test_config_override_precedence(tmp_path): "A very long line to test wrapping and EOLs.\nA very very long line." ) -expected_content = ( - "A very long line to test wrapping and EOLs. A very" + "\r\n" - + "very long line." + "\r\n" + expected_content = ( + "A very long line to test wrapping and EOLs. A very" + + "\r\n" + + "very long line." + + "\r\n" ) assert run([str(file_path), "--config", str(explicit_config_path)]) == 0 @@ -564,4 +565,4 @@ def test_config_search_from_stdin(tmp_path, capfd, patch_stdin): captured = capfd.readouterr() - assert captured.out == expected_content \ No newline at end of file + assert captured.out == expected_content From c1ee98c2e6205e92afa2559e81457e71ef836151 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 11:31:41 -0800 Subject: [PATCH 11/18] Fixing test. --- src/mdformat/_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mdformat/_cli.py b/src/mdformat/_cli.py index 29de3d2..aba27b9 100644 --- a/src/mdformat/_cli.py +++ b/src/mdformat/_cli.py @@ -65,8 +65,8 @@ def run(cli_args: Sequence[str], cache_toml: bool = True) -> int: # noqa: C901 print_error(str(e)) return 1 except FileNotFoundError as e: - if config_override_path and config_override_path.samefile(Path(e.filename)): - print_error(f"Configuration file not found at: {e.filename}") + if config_override_path and str(config_override_path) == str(e.args[0]): + print_error(f"Configuration file not found at: {e.args[0]}") return 1 raise From dd007081069607ec125438a3d48c2b8d679d4f74 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 11:37:10 -0800 Subject: [PATCH 12/18] Get tests to 100% coverage. --- tests/test_config_file.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_config_file.py b/tests/test_config_file.py index 7fe27a3..3989ede 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -181,3 +181,18 @@ def test_single_config_file_invalid_toml(tmp_path): read_single_config_file(invalid_toml_path) assert f"Invalid TOML syntax in {invalid_toml_path}" in str(excinfo.value) + + +def test_invalid_toml_in_parent_dir(tmp_path, capsys): + + config_path = tmp_path / ".mdformat.toml" + config_path.write_text("]invalid TOML[") + + subdir_path = tmp_path / "subdir" + subdir_path.mkdir() + file_path = subdir_path / "test_markdown.md" + file_path.write_text("# Test Markdown") + + assert run((str(file_path),), cache_toml=False) == 1 + captured = capsys.readouterr() + assert "Invalid TOML syntax" in captured.err From fa936af828277e8b19ad5e2bafc5e306bd1c97d6 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 12:14:33 -0800 Subject: [PATCH 13/18] Get to 100% coverage. --- tests/test_cli.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 0e7774c..b0e264c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,3 +1,4 @@ +import argparse import os import sys from unittest.mock import patch @@ -566,3 +567,33 @@ def test_config_search_from_stdin(tmp_path, capfd, patch_stdin): captured = capfd.readouterr() assert captured.out == expected_content + + +def test_config_manual_path_conversion_coverage(tmp_path): + """Tests the edge case where config path is passed as a string, covering + the manual Path(config_path) conversion in run().""" + + config_file = tmp_path / "custom.toml" + config_file.write_text("wrap = 40") + + test_file = tmp_path / "test.md" + test_file.write_text("placeholder") + + mock_args = { + "paths": [str(test_file)], + "config": str(config_file), + "check": False, + "validate": True, + "number": None, + "wrap": None, + "end_of_line": None, + "exclude": (), + "extensions": None, + "codeformatters": None, + } + + with patch( + "mdformat._cli.argparse.ArgumentParser.parse_args", + return_value=argparse.Namespace(**mock_args), + ): + assert run(["placeholder"]) == 0 From 181bf736147a5397bdd49064914f88e29ccc62fc Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 12:25:43 -0800 Subject: [PATCH 14/18] Crawling to 100% coverage. --- tests/test_config_file.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_config_file.py b/tests/test_config_file.py index 3989ede..11749d0 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -196,3 +196,25 @@ def test_invalid_toml_in_parent_dir(tmp_path, capsys): assert run((str(file_path),), cache_toml=False) == 1 captured = capsys.readouterr() assert "Invalid TOML syntax" in captured.err + + +def test_read_toml_opts_invalid_value_coverage(tmp_path, capsys): + """Tests an invalid value inside a recursively discovered config file. + + This ensures coverage for the validation error paths inside + read_toml_opts. + """ + + invalid_val = "wrap = 'not-a-mode'" + config_path = tmp_path / ".mdformat.toml" + config_path.write_text(invalid_val) + + subdir_path = tmp_path / "subdir" + subdir_path.mkdir() + file_path = subdir_path / "test.md" + file_path.write_text("# Placeholder") + + assert run((str(file_path),), cache_toml=False) == 1 + captured = capsys.readouterr() + assert "Invalid 'wrap' value" in captured.err + assert "in " in captured.err From a8c1770bb7011ad556dbab8e722599e2b104bc4d Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 12:28:43 -0800 Subject: [PATCH 15/18] Address warnings. --- tests/test_plugins.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index d36e41f..536c546 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -1,5 +1,6 @@ import argparse import importlib.metadata +import inspect from textwrap import dedent from unittest.mock import patch @@ -461,3 +462,44 @@ def test_no_extensions__toml(tmp_path, monkeypatch): config_path.write_text("extensions = []") assert run((str(tmp_path),), cache_toml=False) == 0 assert file1_path.read_text() == unformatted + + +class MockPluginWithDefault: + @staticmethod + def update_mdit(mdit): + pass + + @staticmethod + def add_cli_argument_group(group: argparse._ArgumentGroup) -> None: + + group.add_argument("--test-arg", default="a") + + RENDERERS = {} + + +def test_cli_argument_default_warning(monkeypatch, caplog): + """Checks that the internal code path for checking plugin argument defaults + is covered.""" + from mdformat import plugins + from mdformat._cli import make_arg_parser + + plugin_name = "test-warn-plugin" + monkeypatch.setitem(plugins.PARSER_EXTENSIONS, plugin_name, MockPluginWithDefault) + + with monkeypatch.context() as m: + m.setattr( + plugins, + "get_source_file_and_line", + lambda obj: (inspect.getsourcefile(MockPluginWithDefault), 175), + ) + + parser = make_arg_parser( + plugins._PARSER_EXTENSION_DISTS, + plugins._CODEFORMATTER_DISTS, + plugins.PARSER_EXTENSIONS, + ) + + cli_args = ["--test-warn-plugin.test-arg", "new-value", "dummy.md"] + opts = parser.parse_args(cli_args) + + assert getattr(opts, f"plugin.{plugin_name}.test_arg") == "new-value" From d36964052ced618498e25927fe161d933890899c Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 12:40:52 -0800 Subject: [PATCH 16/18] Revert "Address warnings." This reverts commit a8c1770bb7011ad556dbab8e722599e2b104bc4d. --- tests/test_plugins.py | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 536c546..d36e41f 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -1,6 +1,5 @@ import argparse import importlib.metadata -import inspect from textwrap import dedent from unittest.mock import patch @@ -462,44 +461,3 @@ def test_no_extensions__toml(tmp_path, monkeypatch): config_path.write_text("extensions = []") assert run((str(tmp_path),), cache_toml=False) == 0 assert file1_path.read_text() == unformatted - - -class MockPluginWithDefault: - @staticmethod - def update_mdit(mdit): - pass - - @staticmethod - def add_cli_argument_group(group: argparse._ArgumentGroup) -> None: - - group.add_argument("--test-arg", default="a") - - RENDERERS = {} - - -def test_cli_argument_default_warning(monkeypatch, caplog): - """Checks that the internal code path for checking plugin argument defaults - is covered.""" - from mdformat import plugins - from mdformat._cli import make_arg_parser - - plugin_name = "test-warn-plugin" - monkeypatch.setitem(plugins.PARSER_EXTENSIONS, plugin_name, MockPluginWithDefault) - - with monkeypatch.context() as m: - m.setattr( - plugins, - "get_source_file_and_line", - lambda obj: (inspect.getsourcefile(MockPluginWithDefault), 175), - ) - - parser = make_arg_parser( - plugins._PARSER_EXTENSION_DISTS, - plugins._CODEFORMATTER_DISTS, - plugins.PARSER_EXTENSIONS, - ) - - cli_args = ["--test-warn-plugin.test-arg", "new-value", "dummy.md"] - opts = parser.parse_args(cli_args) - - assert getattr(opts, f"plugin.{plugin_name}.test_arg") == "new-value" From 3787417b35f7afee79f71d02dbc8e41038abc1f0 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 12:42:06 -0800 Subject: [PATCH 17/18] Revert "Crawling to 100% coverage." This reverts commit 181bf736147a5397bdd49064914f88e29ccc62fc. --- tests/test_config_file.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/test_config_file.py b/tests/test_config_file.py index 11749d0..3989ede 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -196,25 +196,3 @@ def test_invalid_toml_in_parent_dir(tmp_path, capsys): assert run((str(file_path),), cache_toml=False) == 1 captured = capsys.readouterr() assert "Invalid TOML syntax" in captured.err - - -def test_read_toml_opts_invalid_value_coverage(tmp_path, capsys): - """Tests an invalid value inside a recursively discovered config file. - - This ensures coverage for the validation error paths inside - read_toml_opts. - """ - - invalid_val = "wrap = 'not-a-mode'" - config_path = tmp_path / ".mdformat.toml" - config_path.write_text(invalid_val) - - subdir_path = tmp_path / "subdir" - subdir_path.mkdir() - file_path = subdir_path / "test.md" - file_path.write_text("# Placeholder") - - assert run((str(file_path),), cache_toml=False) == 1 - captured = capsys.readouterr() - assert "Invalid 'wrap' value" in captured.err - assert "in " in captured.err From 8c216671ecf1da394a17fcc82e45e50ae11f8569 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Tue, 4 Nov 2025 13:06:14 -0800 Subject: [PATCH 18/18] Address failure on python 3.10 CI workflow. --- tests/test_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index b0e264c..7f28650 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -559,10 +559,10 @@ def test_config_search_from_stdin(tmp_path, capfd, patch_stdin): config is read. """ - with patch("os.getcwd", return_value=str(tmp_path)): + with patch("mdformat._cli.Path.cwd", return_value=tmp_path): patch_stdin(input_content) - assert run(("-",)) == 0 + assert run(("-",), cache_toml=False) == 0 captured = capfd.readouterr()