Skip to content

Commit aaa06ae

Browse files
kdeldyckecsala
andauthored
Makes .mdformat.toml take precedence (#20)
* Makes `.mdformat.toml` takes precedence Closes #17 * Improve tests to cover all scenarios * Use 2 separate files to test pyproject.toml and .mdproject.toml via pre-commit * Fix pre-commit-test regex --------- Co-authored-by: Carles Sala <[email protected]>
1 parent b8b8031 commit aaa06ae

File tree

7 files changed

+208
-84
lines changed

7 files changed

+208
-84
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ repos:
2929
- flake8-builtins
3030
- flake8-comprehensions
3131
- repo: https://github.com/hukkin/mdformat
32-
rev: 0.7.21
32+
rev: 1.0.0
3333
hooks:
3434
- id: mdformat
3535
additional_dependencies:

.pre-commit-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ repos:
44
- id: mdformat
55
name: mdformat
66
entry: mdformat
7-
files: "tests/pre-commit-test.md"
7+
files: ".*/pre-commit-test.md"
88
types: [markdown]
99
language: system

mdformat_pyproject/plugin.py

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import markdown_it
1010
import mdformat
11+
from mdformat._cli import print_paragraphs
1112

1213
TYPE_CHECKING = False
1314
if TYPE_CHECKING:
@@ -23,63 +24,94 @@
2324
import tomli as tomllib
2425

2526

26-
@cache
27-
def _find_pyproject_toml_path(search_path: Path) -> Path | None:
28-
"""Find the pyproject.toml file that applies to the search path.
29-
30-
The search is done ascending through the folders tree until a pyproject.toml
31-
file is found in the same folder. If the root '/' is reached, None is returned.
32-
"""
33-
if search_path.is_file():
34-
search_path = search_path.parent
35-
36-
for parent in (search_path, *search_path.parents):
37-
candidate = parent / "pyproject.toml"
38-
if candidate.is_file():
39-
return candidate
40-
41-
return None
42-
43-
4427
@cache
4528
def _parse_pyproject(pyproject_path: Path) -> _ConfigOptions | None:
46-
"""Extract and validate the mdformat options from the pyproject.toml file.
29+
"""Extract and validate the mdformat options from the ``pyproject.toml`` file.
30+
31+
The options are searched inside a ``[tool.mdformat]`` section within the TOML file,
32+
and they are validated using the default functions from ``mdformat._conf``.
4733
48-
The options are searched inside a [tool.mdformat] key within the toml file,
49-
and they are validated using the default functions from `mdformat._conf`.
34+
If no ``[tool.mdformat]`` section is found, ``None`` is returned.
5035
"""
5136
with pyproject_path.open(mode="rb") as pyproject_file:
52-
content = tomllib.load(pyproject_file)
37+
try:
38+
content = tomllib.load(pyproject_file)
39+
except tomllib.TOMLDecodeError as e:
40+
raise mdformat._conf.InvalidConfError(f"Invalid TOML syntax: {e}")
5341

5442
options = content.get("tool", {}).get("mdformat")
55-
if options is not None:
56-
mdformat._conf._validate_keys(options, pyproject_path)
57-
mdformat._conf._validate_values(options, pyproject_path)
43+
if options is None:
44+
return None
45+
46+
mdformat._conf._validate_keys(options, pyproject_path)
47+
mdformat._conf._validate_values(options, pyproject_path)
5848

5949
return options
6050

6151

52+
_orig_read_toml_opts = mdformat._conf.read_toml_opts
53+
54+
6255
@cache
63-
def read_toml_opts(conf_dir: Path) -> tuple[dict, Path | None]:
64-
"""Alternative read_toml_opts that reads from pyproject.toml instead of .mdformat.toml.
56+
def patched_read_toml_opts(search_path: Path) -> tuple[dict, Path | None]:
57+
"""Patched version of ``mdformat._conf.read_toml_opts``.
58+
59+
Search the first ``.mdformat.toml`` or ``pyproject.toml`` file in the provided
60+
``search_path`` folder or its parents.
61+
62+
The search is done ascending through the folders tree until a ``.mdformat.toml`` or a
63+
``pyproject.toml`` file is found. If the root ``/`` is reached, ``None`` is returned.
6564
66-
Notice that if `.mdformat.toml` exists it is ignored.
65+
``.mdformat.toml`` takes precedence over ``pyproject.toml`` if both are present in the
66+
same folder. In that case, a warning is logged to inform the user that the
67+
``pyproject.toml`` file has been ignored.
68+
69+
``pyproject.toml`` files without a ``[tool.mdformat]`` section are ignored.
70+
71+
This behavior mimics the one from Ruff, as described in `issues#17
72+
<https://github.com/csala/mdformat-pyproject/issues/17>`_.
6773
"""
68-
pyproject_path = _find_pyproject_toml_path(conf_dir)
69-
if pyproject_path:
70-
pyproject_opts = _parse_pyproject(pyproject_path)
71-
else:
72-
pyproject_opts = {}
74+
if search_path.is_file():
75+
search_path = search_path.parent
7376

74-
return pyproject_opts, pyproject_path
77+
for parent in (search_path, *search_path.parents):
78+
# Try to find a pyproject.toml file first, with a [tool.mdformat] section.
79+
pyproject_options = None
80+
pyproject_path = parent / "pyproject.toml"
81+
if pyproject_path.is_file():
82+
pyproject_options = _parse_pyproject(pyproject_path)
83+
84+
# Read options from .mdformat.toml if present.
85+
mdformat_path = parent / ".mdformat.toml"
86+
if mdformat_path.is_file():
87+
# If both pyproject.toml and .mdformat.toml are present, the latter takes
88+
# precedence, but we warn the user that pyproject.toml is being ignored.
89+
if pyproject_options is not None:
90+
print_paragraphs(
91+
(
92+
f"Warning: ignoring mdformat options from {pyproject_path} "
93+
f"since {mdformat_path} is present.",
94+
)
95+
)
96+
97+
# Return options from .mdformat.toml using the original function, even if
98+
# they are empty.
99+
return _orig_read_toml_opts(mdformat_path.parent)
100+
101+
# Return options from pyproject.toml if .mdformat.toml is not present.
102+
elif pyproject_options is not None:
103+
return pyproject_options, pyproject_path
104+
105+
# No config file found, return empty options.
106+
return {}, None
75107

76108

77109
def update_mdit(mdit: markdown_it.MarkdownIt) -> None:
78-
"""No-op, since this plugin only monkey patches and does not modify mdit."""
110+
"""No-op, since this plugin only monkey patches and does not modify ``mdit``."""
79111
pass
80112

81113

82114
RENDERERS: dict[str, Render] = {}
83115

84116
# Monkey patch mdformat._conf to use our own read_toml_opts version
85-
mdformat._conf.read_toml_opts = read_toml_opts
117+
mdformat._cli.read_toml_opts = patched_read_toml_opts
File renamed without changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Test file
2+
3+
This repository contains 2 separate configuration files with contradictory
4+
settings:
5+
6+
1. The `tests/mdformat_toml/.mdformat.toml`, which defines a `wrap` length of 80
7+
and sets `number` to `false`, meaning that numbered lists would be written
8+
starting with `1.` on all the rows.
9+
1. The `pyproject.toml`, which should be used after the `mdformat-pyproject`
10+
plugin is installed, and which defines wrap length of 99 and sets `number` to
11+
`true`, meaning that numbered lists would be written starting with
12+
consecutive numbers.
13+
14+
This file is written according to the rules specified in the `.mdformat.toml`
15+
file placed alongside it, and serves as a valid test to see if the plugin is
16+
working as expected and honoring `.mdformat.toml` when found.

tests/pre-commit-test.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
This repository contains 2 separate configuration files with contradictory settings:
44

5-
1. The `.mdformat.toml`, which should be used by default by `mdformat`, and which defines a `wrap`
6-
length of 80 and sets `number` to `false`, meaning that numbered lists would be written starting
7-
with `1.` on all the rows.
5+
1. The `tests/mdformat_toml/.mdformat.toml`, which defines a `wrap` length of 80 and sets `number`
6+
to `false`, meaning that numbered lists would be written starting with `1.` on all the rows.
87
2. The `pyproject.toml`, which should be used after the `mdformat-pyproject` plugin is installed,
98
and which defines wrap length of 99 and sets `number` to `true`, meaning that numbered lists
109
would be written starting with consecutive numbers.

0 commit comments

Comments
 (0)