|
1 | 1 | """Main plugin module.""" |
2 | 2 |
|
3 | | -from __future__ import annotations |
4 | | - |
| 3 | +import pathlib |
5 | 4 | import sys |
6 | 5 | from functools import cache |
7 | | -from pathlib import Path |
| 6 | +from typing import TYPE_CHECKING, MutableMapping, Optional, Sequence, Tuple, Union |
8 | 7 |
|
9 | 8 | import markdown_it |
10 | 9 | import mdformat |
11 | 10 |
|
12 | | -TYPE_CHECKING = False |
13 | 11 | if TYPE_CHECKING: |
14 | | - from collections.abc import MutableMapping, Sequence |
15 | | - |
16 | 12 | from mdformat.renderer.typing import Render |
17 | 13 |
|
18 | | - _ConfigOptions = MutableMapping[str, int | str | Sequence[str]] |
19 | | - |
20 | | - |
21 | 14 | if sys.version_info >= (3, 11): |
22 | 15 | import tomllib |
23 | 16 | else: |
24 | 17 | import tomli as tomllib |
25 | 18 |
|
26 | 19 |
|
27 | | -@cache |
28 | | -def _search_config_file(search_path: Path) -> Path | None: |
29 | | - """Search the first ``.mdformat.toml`` or ``pyproject.toml`` file in the provided |
30 | | - ``search_path`` folder or its parents. |
31 | | -
|
32 | | - The search is done ascending through the folders tree until a ``.mdformat.toml`` or a |
33 | | - ``pyproject.toml`` file is found. If the root ``/`` is reached, ``None`` is returned. |
| 20 | +_ConfigOptions = MutableMapping[str, Union[int, str, Sequence[str]]] |
34 | 21 |
|
35 | | - ``.mdformat.toml`` takes precedence over ``pyproject.toml`` if both are present in the |
36 | | - same folder. |
37 | 22 |
|
38 | | - ``pyproject.toml`` files without a ``[tool.mdformat]`` section are ignored. |
| 23 | +@cache |
| 24 | +def _find_pyproject_toml_path(search_path: pathlib.Path) -> Optional[pathlib.Path]: |
| 25 | + """Find the pyproject.toml file that applies to the search path. |
39 | 26 |
|
40 | | - This behavior mimics the one from Ruff, as described in `issues#17 |
41 | | - <https://github.com/csala/mdformat-pyproject/issues/17>`_. |
| 27 | + The search is done ascending through the folders tree until a pyproject.toml |
| 28 | + file is found in the same folder. If the root '/' is reached, None is returned. |
42 | 29 | """ |
43 | 30 | if search_path.is_file(): |
44 | 31 | search_path = search_path.parent |
45 | 32 |
|
46 | 33 | for parent in (search_path, *search_path.parents): |
47 | | - for filename in (".mdformat.toml", "pyproject.toml"): |
48 | | - candidate = parent / filename |
49 | | - if candidate.is_file(): |
50 | | - # If we found a pyproject.toml, only return it if it contains |
51 | | - # a [tool.mdformat] section. |
52 | | - if candidate.name == "pyproject.toml": |
53 | | - options = _parse_pyproject(candidate) |
54 | | - if options is None: |
55 | | - continue |
56 | | - |
57 | | - return candidate |
| 34 | + candidate = parent / "pyproject.toml" |
| 35 | + if candidate.is_file(): |
| 36 | + return candidate |
58 | 37 |
|
59 | 38 | return None |
60 | 39 |
|
61 | 40 |
|
62 | 41 | @cache |
63 | | -def _parse_pyproject(pyproject_path: Path) -> _ConfigOptions | None: |
64 | | - """Extract and validate the mdformat options from the ``pyproject.toml`` file. |
| 42 | +def _parse_pyproject(pyproject_path: pathlib.Path) -> Optional[_ConfigOptions]: |
| 43 | + """Extract and validate the mdformat options from the pyproject.toml file. |
65 | 44 |
|
66 | | - The options are searched inside a ``[tool.mdformat]`` section within the TOML file, |
67 | | - and they are validated using the default functions from ``mdformat._conf``. |
68 | | -
|
69 | | - If no ``[tool.mdformat]`` section is found, ``None`` is returned. |
| 45 | + The options are searched inside a [tool.mdformat] key within the toml file, |
| 46 | + and they are validated using the default functions from `mdformat._conf`. |
70 | 47 | """ |
71 | 48 | with pyproject_path.open(mode="rb") as pyproject_file: |
72 | | - try: |
73 | | - content = tomllib.load(pyproject_file) |
74 | | - except tomllib.TOMLDecodeError as e: |
75 | | - raise mdformat._conf.InvalidConfError(f"Invalid TOML syntax: {e}") |
| 49 | + content = tomllib.load(pyproject_file) |
76 | 50 |
|
77 | 51 | options = content.get("tool", {}).get("mdformat") |
78 | | - if options is None: |
79 | | - return None |
80 | | - |
81 | | - mdformat._conf._validate_keys(options, pyproject_path) |
82 | | - mdformat._conf._validate_values(options, pyproject_path) |
| 52 | + if options is not None: |
| 53 | + mdformat._conf._validate_keys(options, pyproject_path) |
| 54 | + mdformat._conf._validate_values(options, pyproject_path) |
83 | 55 |
|
84 | 56 | return options |
85 | 57 |
|
86 | 58 |
|
87 | | -_orig_read_toml_opts = mdformat._conf.read_toml_opts |
88 | | - |
89 | | - |
90 | 59 | @cache |
91 | | -def patched_read_toml_opts(conf_dir: Path) -> tuple[MutableMapping, Path | None]: |
92 | | - """Patched version of ``mdformat._conf.read_toml_opts``. |
| 60 | +def read_toml_opts( |
| 61 | + conf_dir: pathlib.Path, |
| 62 | +) -> Tuple[MutableMapping, Optional[pathlib.Path]]: |
| 63 | + """Alternative read_toml_opts that reads from pyproject.toml instead of .mdformat.toml. |
93 | 64 |
|
94 | | - Tries to read options from ``pyproject.toml`` first before falling back to |
95 | | - ``.mdformat.toml``. |
| 65 | + Notice that if `.mdformat.toml` exists it is ignored. |
96 | 66 | """ |
97 | | - config_file = _search_config_file(conf_dir) |
98 | | - |
99 | | - if config_file: |
100 | | - # We found a .mdformat.toml, use the original function directly. |
101 | | - if config_file.name == ".mdformat.toml": |
102 | | - return _orig_read_toml_opts(config_file.parent) |
103 | | - |
104 | | - # Otherwise, we found a pyproject.toml, try to parse it. |
105 | | - options = _parse_pyproject(config_file) |
106 | | - if options: |
107 | | - return options, config_file |
| 67 | + pyproject_path = _find_pyproject_toml_path(conf_dir) |
| 68 | + if pyproject_path: |
| 69 | + pyproject_opts = _parse_pyproject(pyproject_path) |
| 70 | + else: |
| 71 | + pyproject_opts = {} |
108 | 72 |
|
109 | | - # No config file found, return empty options. |
110 | | - return {}, None |
| 73 | + return pyproject_opts, pyproject_path |
111 | 74 |
|
112 | 75 |
|
113 | 76 | def update_mdit(mdit: markdown_it.MarkdownIt) -> None: |
114 | | - """No-op, since this plugin only monkey patches and does not modify ``mdit``.""" |
| 77 | + """No-op, since this plugin only monkey patches and does not modify mdit.""" |
115 | 78 | pass |
116 | 79 |
|
117 | 80 |
|
118 | | -RENDERERS: MutableMapping[str, Render] = {} |
| 81 | +RENDERERS: MutableMapping[str, "Render"] = {} |
119 | 82 |
|
120 | | -# Monkey patch mdformat._conf to use our own read_toml_opts version. |
121 | | -mdformat._conf.read_toml_opts = patched_read_toml_opts |
| 83 | +# Monkey patch mdformat._conf to use our own read_toml_opts version |
| 84 | +mdformat._conf.read_toml_opts = read_toml_opts |
0 commit comments