mkdocs-nav-numbering-plugin 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mkdocs_nav_numbering_plugin/__init__.py +3 -0
- mkdocs_nav_numbering_plugin/nav_numbering.py +169 -0
- mkdocs_nav_numbering_plugin-0.1.0.dist-info/METADATA +68 -0
- mkdocs_nav_numbering_plugin-0.1.0.dist-info/RECORD +8 -0
- mkdocs_nav_numbering_plugin-0.1.0.dist-info/WHEEL +5 -0
- mkdocs_nav_numbering_plugin-0.1.0.dist-info/entry_points.txt +2 -0
- mkdocs_nav_numbering_plugin-0.1.0.dist-info/licenses/LICENSE +21 -0
- mkdocs_nav_numbering_plugin-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
from mkdocs.config import config_options
|
|
5
|
+
from mkdocs.plugins import BasePlugin
|
|
6
|
+
from mkdocs.structure.nav import Navigation, Section, Page, Link
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NavNumberingPlugin(BasePlugin):
|
|
10
|
+
"""
|
|
11
|
+
MkDocs plugin to add hierarchical numbering to navigation items and page headings.
|
|
12
|
+
|
|
13
|
+
Configuration options:
|
|
14
|
+
enabled: Enable/disable the plugin (default: true)
|
|
15
|
+
nav_depth: Maximum depth for nav numbering, 0 = unlimited (default: 0)
|
|
16
|
+
heading_depth: Maximum depth for heading numbering within pages, 0 = unlimited (default: 0)
|
|
17
|
+
number_nav: Add numbers to navigation items (default: true)
|
|
18
|
+
number_headings: Add numbers to headings within pages (default: true)
|
|
19
|
+
number_h1: Add number to the first h1 (page title) (default: true)
|
|
20
|
+
separator: Separator between number parts (default: ".")
|
|
21
|
+
exclude: List of page paths to exclude from numbering (default: [])
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
config_scheme = (
|
|
25
|
+
("enabled", config_options.Type(bool, default=True)),
|
|
26
|
+
("nav_depth", config_options.Type(int, default=0)),
|
|
27
|
+
("heading_depth", config_options.Type(int, default=0)),
|
|
28
|
+
("number_nav", config_options.Type(bool, default=True)),
|
|
29
|
+
("number_headings", config_options.Type(bool, default=True)),
|
|
30
|
+
("number_h1", config_options.Type(bool, default=True)),
|
|
31
|
+
("separator", config_options.Type(str, default=".")),
|
|
32
|
+
("exclude", config_options.Type(list, default=[])),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
# Maps page src_uri -> nav number, e.g. "Manual/Model/Model_Intro.md" -> "1.2.1"
|
|
37
|
+
self.page_numbers: Dict[str, str] = {}
|
|
38
|
+
|
|
39
|
+
def on_nav(self, nav: Navigation, config, files) -> Navigation:
|
|
40
|
+
"""
|
|
41
|
+
Assign numbers to all nav items based on their position, including groups.
|
|
42
|
+
"""
|
|
43
|
+
if not self.config["enabled"] or not self.config["number_nav"]:
|
|
44
|
+
return nav
|
|
45
|
+
|
|
46
|
+
nav_depth = self.config["nav_depth"]
|
|
47
|
+
separator = self.config["separator"]
|
|
48
|
+
exclude = self.config["exclude"]
|
|
49
|
+
|
|
50
|
+
def walk(items, prefix: List[int]) -> None:
|
|
51
|
+
for idx, item in enumerate(items, start=1):
|
|
52
|
+
number_parts = prefix + [idx]
|
|
53
|
+
current_depth = len(number_parts)
|
|
54
|
+
|
|
55
|
+
# Check if we should number at this depth
|
|
56
|
+
should_number = nav_depth == 0 or current_depth <= nav_depth
|
|
57
|
+
number_str = separator.join(map(str, number_parts))
|
|
58
|
+
|
|
59
|
+
# Section (group) – e.g. "Manual", "Model Environment"
|
|
60
|
+
if isinstance(item, Section):
|
|
61
|
+
if should_number:
|
|
62
|
+
item.title = f"{number_str} {item.title}"
|
|
63
|
+
# For sections, prefix continues for children
|
|
64
|
+
walk(item.children, number_parts)
|
|
65
|
+
|
|
66
|
+
# Page – actual Markdown file
|
|
67
|
+
elif isinstance(item, Page):
|
|
68
|
+
# Check if page is excluded
|
|
69
|
+
src_uri = item.file.src_uri if item.file else None
|
|
70
|
+
is_excluded = src_uri and any(
|
|
71
|
+
src_uri.endswith(exc) or exc in src_uri for exc in exclude
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if should_number and not is_excluded:
|
|
75
|
+
item.title = f"{number_str} {item.title}"
|
|
76
|
+
# Map src_uri -> number so we can use it in on_page_markdown
|
|
77
|
+
if src_uri and not is_excluded:
|
|
78
|
+
self.page_numbers[src_uri] = number_str
|
|
79
|
+
|
|
80
|
+
# Link or other types – optionally number them or skip
|
|
81
|
+
elif isinstance(item, Link):
|
|
82
|
+
if should_number:
|
|
83
|
+
item.title = f"{number_str} {item.title}"
|
|
84
|
+
else:
|
|
85
|
+
# Unknown / custom nav item – just recurse if it has children
|
|
86
|
+
children = getattr(item, "children", None)
|
|
87
|
+
if children:
|
|
88
|
+
walk(children, number_parts)
|
|
89
|
+
|
|
90
|
+
# Start top-level at 1,2,3,... (tabs like Introduction, Manual, Tutorials)
|
|
91
|
+
walk(nav.items, [])
|
|
92
|
+
|
|
93
|
+
return nav
|
|
94
|
+
|
|
95
|
+
def on_page_markdown(self, markdown: str, page: Page, config, files) -> str:
|
|
96
|
+
"""
|
|
97
|
+
Prepend the nav number to headings inside each page.
|
|
98
|
+
- The first h1 (page title) gets the base_number (e.g., 3.1.1)
|
|
99
|
+
- Subsequent h2, h3, etc. get sub-numbers (e.g., 3.1.1.1, 3.1.1.2)
|
|
100
|
+
"""
|
|
101
|
+
if not self.config["enabled"] or not self.config["number_headings"]:
|
|
102
|
+
return markdown
|
|
103
|
+
|
|
104
|
+
src_uri = page.file.src_uri if page and page.file else None
|
|
105
|
+
base_number = self.page_numbers.get(src_uri)
|
|
106
|
+
if not base_number:
|
|
107
|
+
return markdown
|
|
108
|
+
|
|
109
|
+
heading_depth = self.config["heading_depth"]
|
|
110
|
+
number_h1 = self.config["number_h1"]
|
|
111
|
+
separator = self.config["separator"]
|
|
112
|
+
base_depth = len(base_number.split(separator))
|
|
113
|
+
|
|
114
|
+
# Track whether we've seen the first h1 (page title)
|
|
115
|
+
first_h1_seen = [False] # Use list to allow mutation in nested function
|
|
116
|
+
|
|
117
|
+
# Track heading counters for h2+ (sub-sections within the page)
|
|
118
|
+
# h2 -> counter[0], h3 -> counter[1], etc.
|
|
119
|
+
counters: List[int] = []
|
|
120
|
+
|
|
121
|
+
def repl(match: re.Match) -> str:
|
|
122
|
+
hashes = match.group("hashes")
|
|
123
|
+
title = match.group("title").strip()
|
|
124
|
+
level = len(hashes) # "#"=1, "##"=2, etc.
|
|
125
|
+
|
|
126
|
+
# Avoid double-numbering if already numbered
|
|
127
|
+
if re.match(r"^\d+(\.\d+)*\s+", title):
|
|
128
|
+
return match.group(0)
|
|
129
|
+
|
|
130
|
+
# First h1 is the page title - use base_number directly
|
|
131
|
+
if level == 1:
|
|
132
|
+
if not first_h1_seen[0]:
|
|
133
|
+
first_h1_seen[0] = True
|
|
134
|
+
if number_h1:
|
|
135
|
+
return f"{hashes} {base_number} {title}"
|
|
136
|
+
return match.group(0)
|
|
137
|
+
else:
|
|
138
|
+
# Additional h1s in the same page - handle gracefully
|
|
139
|
+
counters.clear()
|
|
140
|
+
if number_h1:
|
|
141
|
+
return f"{hashes} {base_number} {title}"
|
|
142
|
+
return match.group(0)
|
|
143
|
+
|
|
144
|
+
# For h2 and below, add sub-numbering relative to base_number
|
|
145
|
+
# Adjust level: h2 -> index 0, h3 -> index 1, etc.
|
|
146
|
+
adjusted_level = level - 1 # h2=1, h3=2, etc.
|
|
147
|
+
|
|
148
|
+
# Check depth limit (total depth = base_depth + adjusted_level)
|
|
149
|
+
total_depth = base_depth + adjusted_level
|
|
150
|
+
if heading_depth > 0 and total_depth > heading_depth:
|
|
151
|
+
return match.group(0)
|
|
152
|
+
|
|
153
|
+
# Resize counters to current adjusted level
|
|
154
|
+
while len(counters) < adjusted_level:
|
|
155
|
+
counters.append(0)
|
|
156
|
+
while len(counters) > adjusted_level:
|
|
157
|
+
counters.pop()
|
|
158
|
+
|
|
159
|
+
counters[adjusted_level - 1] += 1
|
|
160
|
+
|
|
161
|
+
# Build full number: base_number + sub-counters
|
|
162
|
+
rel = separator.join(str(c) for c in counters[:adjusted_level])
|
|
163
|
+
full_number = f"{base_number}{separator}{rel}"
|
|
164
|
+
|
|
165
|
+
return f"{hashes} {full_number} {title}"
|
|
166
|
+
|
|
167
|
+
heading_pattern = re.compile(r"^(?P<hashes>#{1,6})\s+(?P<title>.+)$", re.MULTILINE)
|
|
168
|
+
markdown = heading_pattern.sub(repl, markdown)
|
|
169
|
+
return markdown
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mkdocs-nav-numbering-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MkDocs plugin to add hierarchical numbering to nav and page headings
|
|
5
|
+
Author: ZMT Zurich MedTech AG
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/your-org/mkdocs-nav-numbering-plugin
|
|
8
|
+
Project-URL: Repository, https://github.com/your-org/mkdocs-nav-numbering-plugin
|
|
9
|
+
Project-URL: Issues, https://github.com/your-org/mkdocs-nav-numbering-plugin/issues
|
|
10
|
+
Keywords: mkdocs,plugin,navigation,numbering,documentation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Plugins
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Documentation
|
|
19
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: mkdocs>=1.4
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# mkdocs-nav-numbering-plugin
|
|
27
|
+
|
|
28
|
+
MkDocs plugin that adds hierarchical numbering to navigation items and page headings.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Number nav sections, pages, and links (optional)
|
|
33
|
+
- Number page headings based on nav position
|
|
34
|
+
- Configurable depth limits for nav and headings
|
|
35
|
+
- Configurable separator
|
|
36
|
+
- Exclude specific pages
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install mkdocs-nav-numbering-plugin
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
plugins:
|
|
48
|
+
- nav-numbering:
|
|
49
|
+
nav_depth: 4
|
|
50
|
+
heading_depth: 5
|
|
51
|
+
number_h1: true
|
|
52
|
+
number_nav: true
|
|
53
|
+
number_headings: true
|
|
54
|
+
separator: "."
|
|
55
|
+
exclude:
|
|
56
|
+
- index.md
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Options
|
|
60
|
+
|
|
61
|
+
- `enabled` (bool, default: true)
|
|
62
|
+
- `nav_depth` (int, default: 0) — 0 means unlimited
|
|
63
|
+
- `heading_depth` (int, default: 0) — 0 means unlimited
|
|
64
|
+
- `number_nav` (bool, default: true)
|
|
65
|
+
- `number_headings` (bool, default: true)
|
|
66
|
+
- `number_h1` (bool, default: true)
|
|
67
|
+
- `separator` (str, default: ".")
|
|
68
|
+
- `exclude` (list, default: [])
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
mkdocs_nav_numbering_plugin/__init__.py,sha256=McZEZwPtatKF0PvPVKMD7_nBeaOjdHNME6tQNwNIt8M,80
|
|
2
|
+
mkdocs_nav_numbering_plugin/nav_numbering.py,sha256=zRiG9_AZdLKe21pnb_b_vQSw8LkWpL8HK6UdY14WoPc,7263
|
|
3
|
+
mkdocs_nav_numbering_plugin-0.1.0.dist-info/licenses/LICENSE,sha256=o8hK_UrJ1kd51VuQ5ZP4d8gwK5WPlt6Zg5vO-T42eoo,1078
|
|
4
|
+
mkdocs_nav_numbering_plugin-0.1.0.dist-info/METADATA,sha256=Erwhh43scMWp4QOY-uGORxkA-5TPRs88iZfNoqqoDew,2005
|
|
5
|
+
mkdocs_nav_numbering_plugin-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
mkdocs_nav_numbering_plugin-0.1.0.dist-info/entry_points.txt,sha256=NnO7O_09mMyAHFIspg2zmsfri_m7nt8Km8xZK0tgvtQ,80
|
|
7
|
+
mkdocs_nav_numbering_plugin-0.1.0.dist-info/top_level.txt,sha256=UcRwMarClerK-Iuj0RooOAq7IVhn_oRmQRiCZgSCfN4,28
|
|
8
|
+
mkdocs_nav_numbering_plugin-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZMT Zurich MedTech AG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mkdocs_nav_numbering_plugin
|