Skip to content

Commit 151a6ef

Browse files
committed
refactor: Move modules under internal folder, re-expose API from top-level
1 parent 962c9b3 commit 151a6ef

10 files changed

Lines changed: 770 additions & 673 deletions

File tree

src/griffe2md/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,57 @@
66
from __future__ import annotations
77

88
from griffe2md._internal.cli import get_parser, main
9+
from griffe2md._internal.config import CONFIG_FILE_PATHS, ConfigDict, default_config, load_config
10+
from griffe2md._internal.main import (
11+
prepare_context,
12+
prepare_env,
13+
render_object_docs,
14+
render_package_docs,
15+
write_package_docs,
16+
)
17+
from griffe2md._internal.rendering import (
18+
Order,
19+
do_any,
20+
do_as_attributes_section,
21+
do_as_classes_section,
22+
do_as_functions_section,
23+
do_as_modules_section,
24+
do_filter_objects,
25+
do_format_attribute,
26+
do_format_code,
27+
do_format_signature,
28+
do_heading,
29+
do_order_members,
30+
do_split_path,
31+
from_private_package,
32+
order_map,
33+
)
934

10-
__all__: list[str] = ["get_parser", "main"]
35+
__all__: list[str] = [
36+
"CONFIG_FILE_PATHS",
37+
"ConfigDict",
38+
"Order",
39+
"default_config",
40+
"do_any",
41+
"do_as_attributes_section",
42+
"do_as_classes_section",
43+
"do_as_functions_section",
44+
"do_as_modules_section",
45+
"do_filter_objects",
46+
"do_format_attribute",
47+
"do_format_code",
48+
"do_format_signature",
49+
"do_heading",
50+
"do_order_members",
51+
"do_split_path",
52+
"from_private_package",
53+
"get_parser",
54+
"load_config",
55+
"main",
56+
"order_map",
57+
"prepare_context",
58+
"prepare_env",
59+
"render_object_docs",
60+
"render_package_docs",
61+
"write_package_docs",
62+
]

src/griffe2md/_internal/cli.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"""Module that contains the command line application."""
2-
31
# Why does this file exist, and why not put this in `__main__`?
42
#
53
# You might be tempted to import things from `__main__` later,
@@ -17,17 +15,17 @@
1715
import sys
1816
from typing import Any
1917

20-
from griffe2md import debug
21-
from griffe2md.config import load_config
22-
from griffe2md.main import write_package_docs
18+
from griffe2md._internal import debug
19+
from griffe2md._internal.config import load_config
20+
from griffe2md._internal.main import write_package_docs
2321

2422

2523
class _DebugInfo(argparse.Action):
2624
def __init__(self, nargs: int | str | None = 0, **kwargs: Any) -> None:
2725
super().__init__(nargs=nargs, **kwargs)
2826

2927
def __call__(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002
30-
debug.print_debug_info()
28+
debug._print_debug_info()
3129
sys.exit(0)
3230

3331

@@ -40,7 +38,7 @@ def get_parser() -> argparse.ArgumentParser:
4038
parser = argparse.ArgumentParser(prog="griffe2md")
4139
parser.add_argument("package", help="The package to output Markdown docs for.")
4240
parser.add_argument("-o", "--output", default=None, help="File to write to. Default: stdout.")
43-
parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug.get_version()}")
41+
parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug._get_version()}")
4442
parser.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.")
4543
return parser
4644

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"""Load configuration."""
2-
31
from __future__ import annotations
42

53
import logging
@@ -16,13 +14,14 @@
1614
if TYPE_CHECKING:
1715
from re import Pattern
1816

19-
logger = logging.getLogger(__name__)
17+
_logger = logging.getLogger(__name__)
2018

2119
CONFIG_FILE_PATHS = (
2220
Path(".config/griffe2md.toml"),
2321
Path("config/griffe2md.toml"),
2422
Path("pyproject.toml"),
2523
)
24+
"""Paths to default configuration files."""
2625

2726

2827
def _locate_config_file() -> Path | None:
@@ -41,7 +40,7 @@ def load_config() -> ConfigDict | None:
4140
if not (config_path := _locate_config_file()):
4241
return None
4342

44-
logger.debug("Loading config from %s", config_path)
43+
_logger.debug("Loading config from %s", config_path)
4544

4645
with config_path.open("rb") as f:
4746
config = tomllib.load(f)
@@ -254,3 +253,4 @@ class ConfigDict(TypedDict):
254253
"show_docstring_functions": True,
255254
"show_docstring_modules": True,
256255
}
256+
"""Default configuration values."""

src/griffe2md/_internal/main.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
from __future__ import annotations
2+
3+
import re
4+
import sys
5+
from pathlib import Path
6+
from typing import IO, TYPE_CHECKING, cast
7+
8+
import mdformat
9+
from griffe import GriffeLoader, Parser
10+
from jinja2 import Environment, FileSystemLoader
11+
12+
from griffe2md._internal import rendering
13+
from griffe2md._internal.config import ConfigDict, default_config
14+
15+
if TYPE_CHECKING:
16+
from griffe import Object
17+
18+
19+
def _output(text: str, to: IO | str | None = None) -> None:
20+
if isinstance(to, str):
21+
with open(to, "w") as output:
22+
output.write(text)
23+
else:
24+
if to is None:
25+
to = sys.stdout
26+
to.write(text)
27+
28+
29+
def prepare_context(obj: Object, config: ConfigDict | None = None) -> dict:
30+
"""Prepare Jinja context.
31+
32+
Parameters:
33+
obj: A Griffe object.
34+
config: The configuration options.
35+
36+
Returns:
37+
The Jinja context.
38+
"""
39+
config = cast("ConfigDict", {**default_config, **(config or {})})
40+
if config["filters"]:
41+
config["filters"] = [
42+
(re.compile(filtr.lstrip("!")), filtr.startswith("!")) if isinstance(filtr, str) else filtr
43+
for filtr in config["filters"]
44+
]
45+
46+
heading_level = config["heading_level"]
47+
try:
48+
config["members_order"] = rendering.Order(config["members_order"]).value
49+
except ValueError as error:
50+
choices = "', '".join(item.value for item in rendering.Order)
51+
raise ValueError(
52+
f"Unknown members_order '{config['members_order']}', choose between '{choices}'.",
53+
) from error
54+
55+
summary = config["summary"]
56+
if summary is True:
57+
config["summary"] = {
58+
"attributes": True,
59+
"functions": True,
60+
"classes": True,
61+
"modules": True,
62+
}
63+
elif summary is False:
64+
config["summary"] = {
65+
"attributes": False,
66+
"functions": False,
67+
"classes": False,
68+
"modules": False,
69+
}
70+
else:
71+
config["summary"] = {
72+
"attributes": summary.get("attributes", False),
73+
"functions": summary.get("functions", False),
74+
"classes": summary.get("classes", False),
75+
"modules": summary.get("modules", False),
76+
}
77+
78+
return {
79+
"config": config,
80+
obj.kind.value: obj,
81+
"heading_level": heading_level,
82+
"root": True,
83+
}
84+
85+
86+
def prepare_env(env: Environment | None = None) -> Environment:
87+
"""Prepare Jinja environment.
88+
89+
Parameters:
90+
env: A Jinja environment.
91+
92+
Returns:
93+
The Jinja environment.
94+
"""
95+
env = env or Environment(
96+
autoescape=False, # noqa: S701
97+
loader=FileSystemLoader([Path(__file__).parent.parent / "templates"]),
98+
auto_reload=False,
99+
)
100+
env.filters["any"] = rendering.do_any
101+
env.filters["heading"] = rendering.do_heading
102+
env.filters["as_attributes_section"] = rendering.do_as_attributes_section
103+
env.filters["as_classes_section"] = rendering.do_as_classes_section
104+
env.filters["as_functions_section"] = rendering.do_as_functions_section
105+
env.filters["as_modules_section"] = rendering.do_as_modules_section
106+
env.filters["filter_objects"] = rendering.do_filter_objects
107+
env.filters["format_code"] = rendering.do_format_code
108+
env.filters["format_signature"] = rendering.do_format_signature
109+
env.filters["format_attribute"] = rendering.do_format_attribute
110+
env.filters["order_members"] = rendering.do_order_members
111+
env.filters["split_path"] = rendering.do_split_path
112+
env.filters["stash_crossref"] = lambda ref, length: ref
113+
env.filters["from_private_package"] = rendering.from_private_package
114+
115+
return env
116+
117+
118+
def render_object_docs(obj: Object, config: ConfigDict | None = None) -> str:
119+
"""Render docs for a given object.
120+
121+
Parameters:
122+
obj: The Griffe object to render docs for.
123+
config: The rendering configuration.
124+
125+
Returns:
126+
Markdown.
127+
"""
128+
env = prepare_env()
129+
context = prepare_context(obj, config)
130+
rendered = env.get_template(f"{obj.kind.value}.md.jinja").render(**context)
131+
return mdformat.text(rendered)
132+
133+
134+
def render_package_docs(package: str, config: ConfigDict | None = None) -> str:
135+
"""Render docs for a given package.
136+
137+
Parameters:
138+
package: The package (name) to render docs for.
139+
config: The rendering configuration.
140+
141+
Returns:
142+
Markdown.
143+
"""
144+
config = cast("ConfigDict", {**default_config, **(config or {})})
145+
parser = config["docstring_style"] and Parser(config["docstring_style"])
146+
loader = GriffeLoader(docstring_parser=parser)
147+
module = loader.load(package)
148+
loader.resolve_aliases(external=True)
149+
return render_object_docs(module, config) # type: ignore[arg-type]
150+
151+
152+
def write_package_docs(
153+
package: str,
154+
config: ConfigDict | None = None,
155+
output: IO | str | None = None,
156+
) -> None:
157+
"""Write docs for a given package to a file or stdout.
158+
159+
Parameters:
160+
package: The package to render docs for.
161+
config: The rendering configuration.
162+
output: The file to write to.
163+
"""
164+
_output(render_package_docs(package, config), to=output)

0 commit comments

Comments
 (0)