-
Notifications
You must be signed in to change notification settings - Fork 81
Expand file tree
/
Copy pathhooks.py
More file actions
144 lines (112 loc) · 4.64 KB
/
Copy pathhooks.py
File metadata and controls
144 lines (112 loc) · 4.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""
MkDocs hooks for Ibexa developer documentation.
Automatically keeps the llmstxt plugin's ``sections`` config in sync with the ``nav`` defined in ``mkdocs.yml``
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mkdocs.config.defaults import MkDocsConfig
FRONTMATTER_EDITION_DISPLAY = {
"lts-update": "LTS Update",
"experience": "Experience",
"commerce": "Commerce",
"headless": "Headless",
}
def on_config(config: "MkDocsConfig") -> None:
"""Populate llmstxt sections from nav before the build starts.
The llmstxt plugin reads ``config.sections`` in its ``on_files`` event,
which fires after ``on_config``, so injecting here is the right place.
"""
nav = config.get("nav")
if not nav:
return
llmstxt = config["plugins"].get("llmstxt")
if llmstxt is None:
return
import sys
_here = Path(__file__).parent
if str(_here) not in sys.path:
sys.path.insert(0, str(_here))
from update_llmstxt_config import convert_nav_to_llmstxt_sections
docs_dir = Path(config["docs_dir"])
llmstxt.config.sections = convert_nav_to_llmstxt_sections(nav, docs_dir)
def on_page_content(html: str, *, page: "Page", config: "MkDocsConfig", **kwargs) -> None:
"""Reformat the Markdown content generated by the llmstxt plugin for this page.
Hooks run after plugins for every event, so at this point the llmstxt
plugin has already generated Markdown and stored it in ``_md_pages``.
We reformat here so the plugin writes the corrected content in its own
``on_post_build`` (which also runs before ours, for the same reason).
"""
llmstxt = config["plugins"].get("llmstxt")
if llmstxt is None:
return
src_uri = page.file.src_uri
page_info = llmstxt._md_pages.get(src_uri)
if page_info is None:
return
content = page_info.content
content = _inject_edition_badges(content, page, config)
reformatted = _renumber_ordered_lists(content)
if reformatted != content:
llmstxt._md_pages[src_uri] = page_info._replace(content=reformatted)
elif content != page_info.content:
llmstxt._md_pages[src_uri] = page_info._replace(content=content)
def on_post_build(*, config: "MkDocsConfig", **kwargs) -> None:
"""No-op — reformatting is done per-page in on_page_content."""
def _inject_edition_badges(content: str, page: "Page", config: "MkDocsConfig") -> str:
"""Insert 'Editions: X, Y' line after the first h1 heading, from frontmatter."""
src_path = Path(config["docs_dir"]) / page.file.src_path
try:
from mkdocs.utils import meta as mkdocs_meta
with open(src_path, encoding="utf-8") as f:
raw = f.read()
_, frontmatter = mkdocs_meta.get_data(raw)
except Exception:
return content
def _to_list(value):
if isinstance(value, list):
return value
if isinstance(value, str):
return value.split()
return []
edition = frontmatter.get("edition")
editions = frontmatter.get("editions") or []
all_editions = _to_list(edition) + _to_list(editions)
display = [FRONTMATTER_EDITION_DISPLAY.get(e, e) for e in all_editions if e]
if not display:
return content
badge_line = "Editions: " + ", ".join(display)
# Insert after the first h1 (# ...) line
lines = content.split("\n")
for i, line in enumerate(lines):
if line.startswith("# "):
lines.insert(i + 1, "")
lines.insert(i + 2, badge_line)
return "\n".join(lines)
return badge_line + "\n\n" + content
def _renumber_ordered_lists(content: str) -> str:
"""Replace repeated '1.' list markers with sequential numbers (1. 2. 3. ...)."""
lines = content.split("\n")
result = []
counters: list[int] = [] # stack of counters per indent level
for line in lines:
m = re.match(r'^(\s*)1\. (.*)$', line)
if m:
indent = len(m.group(1))
level = indent // 2 # assume 2-space indent per level
# Trim deeper levels from the stack
while len(counters) > level + 1:
counters.pop()
# Extend stack if we went deeper
while len(counters) <= level:
counters.append(0)
counters[level] += 1
result.append(f"{m.group(1)}{counters[level]}. {m.group(2)}")
else:
# Both blank lines and non-list lines reset the counter stack,
# since a blank line always separates distinct markdown lists.
counters.clear()
result.append(line)
return "\n".join(result)