Skip to content

Commit 6f35adf

Browse files
committed
feat(docs): add nexus page BBCode generator
- README.md: add user-facing Requirements, Installation, Compatibility block wrapped in nexus:start/end markers before dev Prerequisites - docs/nexus-page.md: wrap generated sections in generated:start/end markers - scripts/generate-nexus-page.py: extract nexus block from README, convert Markdown to BBCode, inject into nexus-page.md template, print to stdout
1 parent 221a514 commit 6f35adf

3 files changed

Lines changed: 199 additions & 6 deletions

File tree

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,33 @@ Supports building on **Linux** (cross-compilation via `clang-cl` + [xwin](https:
77

88
---
99

10-
## Prerequisites
10+
<!-- nexus:start -->
11+
## Requirements
12+
13+
- [SKSE64](https://skse.silverlock.org/)
14+
- [Address Library for SKSE Plugins](https://www.nexusmods.com/skyrimspecialedition/mods/32444)
15+
16+
## Installation
17+
18+
**Mod manager (recommended):**
19+
1. Install the requirements above.
20+
2. Install ExampleMod via your mod manager.
21+
3. Launch Skyrim via SKSE.
22+
23+
**Manual:**
24+
1. Install the requirements above.
25+
2. Copy `ExampleMod.dll` to `Data\SKSE\Plugins\`.
26+
3. Launch Skyrim via SKSE.
27+
28+
## Compatibility
29+
30+
- Compatible with Skyrim SE and AE.
31+
- No ESP/ESL required.
32+
<!-- nexus:end -->
33+
34+
---
35+
36+
## Prerequisites (Developer)
1137

1238
### All platforms
1339
- [Git](https://git-scm.com/)

docs/nexus-page.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
2121
[line]
2222
23-
[size=4][b]Overview[/b][/size]
23+
[size=4][b][color=#B8953E]Overview[/color][/b][/size]
2424
2525
Describe what the mod does.
2626
2727
[line]
2828
29-
[size=4][b]Requirements[/b][/size]
29+
<!-- generated:start -->
30+
[size=4][b][color=#B8953E]Requirements[/color][/b][/size]
3031
3132
[list]
3233
[*][url=https://skse.silverlock.org/]SKSE64[/url]
@@ -35,7 +36,7 @@ Describe what the mod does.
3536
3637
[line]
3738
38-
[size=4][b]Installation[/b][/size]
39+
[size=4][b][color=#B8953E]Installation[/color][/b][/size]
3940
4041
[b]Mod manager (recommended):[/b]
4142
[list=1]
@@ -53,16 +54,17 @@ Describe what the mod does.
5354
5455
[line]
5556
56-
[size=4][b]Compatibility[/b][/size]
57+
[size=4][b][color=#B8953E]Compatibility[/color][/b][/size]
5758
5859
[list]
5960
[*]Compatible with Skyrim SE and AE.
6061
[*]No ESP/ESL required.
6162
[/list]
63+
<!-- generated:end -->
6264
6365
[line]
6466
65-
[size=4][b]Credits[/b][/size]
67+
[size=4][b][color=#B8953E]Credits[/color][/b][/size]
6668
6769
[list]
6870
[*][url=https://skse.silverlock.org/]SKSE[/url] by the SKSE Team

scripts/generate-nexus-page.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env python3
2+
"""Generate nexus-page BBCode from README.md user-facing content.
3+
4+
Reads the block between <!-- nexus:start --> and <!-- nexus:end --> in README.md,
5+
converts it to BBCode, substitutes it into the <!-- generated:start/end --> region
6+
of docs/nexus-page.md in memory, and prints the result to stdout.
7+
8+
Usage:
9+
python scripts/generate-nexus-page.py
10+
python scripts/generate-nexus-page.py | xclip -selection clipboard
11+
"""
12+
13+
import argparse
14+
import re
15+
import sys
16+
from pathlib import Path
17+
18+
README = Path(__file__).parent.parent / "README.md"
19+
NEXUS_TEMPLATE = Path(__file__).parent.parent / "docs" / "nexus-page.md"
20+
21+
NEXUS_START = "<!-- nexus:start -->"
22+
NEXUS_END = "<!-- nexus:end -->"
23+
GEN_START = "<!-- generated:start -->"
24+
GEN_END = "<!-- generated:end -->"
25+
BBCODE_FENCE_OPEN = "```bbcode"
26+
BBCODE_FENCE_CLOSE = "```"
27+
28+
29+
def extract_block(text: str, start_marker: str, end_marker: str) -> str:
30+
start = text.find(start_marker)
31+
if start == -1:
32+
sys.exit(f"error: marker not found: {start_marker!r}")
33+
end = text.find(end_marker, start + len(start_marker))
34+
if end == -1:
35+
sys.exit(f"error: marker not found: {end_marker!r}")
36+
return text[start + len(start_marker):end].strip()
37+
38+
39+
def md_to_bbcode(md: str) -> str:
40+
lines = md.splitlines()
41+
output: list[str] = []
42+
i = 0
43+
first_heading = True
44+
45+
while i < len(lines):
46+
line = lines[i]
47+
48+
# ## Heading
49+
if line.startswith("## "):
50+
heading = line[3:].strip()
51+
if not first_heading:
52+
output.append("")
53+
output.append("[line]")
54+
output.append("")
55+
first_heading = False
56+
output.append(f"[size=4][b][color=#B8953E]{heading}[/color][/b][/size]")
57+
i += 1
58+
continue
59+
60+
# Unordered list block
61+
if line.startswith("- "):
62+
items: list[str] = []
63+
while i < len(lines) and lines[i].startswith("- "):
64+
items.append(convert_inline(lines[i][2:].strip()))
65+
i += 1
66+
output.append("[list]")
67+
for item in items:
68+
output.append(f"[*]{item}")
69+
output.append("[/list]")
70+
continue
71+
72+
# Ordered list block — may be preceded by a bold label line
73+
if re.match(r"\d+\. ", line):
74+
items = []
75+
while i < len(lines) and re.match(r"\d+\. ", lines[i]):
76+
items.append(convert_inline(re.sub(r"^\d+\. ", "", lines[i])))
77+
i += 1
78+
output.append("[list=1]")
79+
for item in items:
80+
output.append(f"[*]{item}")
81+
output.append("[/list]")
82+
continue
83+
84+
# Bold label (e.g. **Mod manager (recommended):**)
85+
if re.match(r"\*\*.+\*\*", line):
86+
output.append(convert_inline(line))
87+
i += 1
88+
continue
89+
90+
# Blank line
91+
if line.strip() == "":
92+
output.append("")
93+
i += 1
94+
continue
95+
96+
# Plain paragraph (pass through inline conversion)
97+
stripped = line.strip()
98+
if stripped and not stripped.startswith("<!--"):
99+
output.append(convert_inline(line))
100+
101+
i += 1
102+
103+
# Collapse consecutive blank lines to single blank
104+
result = re.sub(r"\n{3,}", "\n\n", "\n".join(output))
105+
return result.strip()
106+
107+
108+
def convert_inline(text: str) -> str:
109+
# [text](url) → [url=url]text[/url]
110+
# Note: link text containing ] or URLs containing unbalanced ) are not supported.
111+
text = re.sub(r"\[([^\]]+)\]\(([^)]*(?:\([^)]*\)[^)]*)*)\)", r"[url=\2]\1[/url]", text)
112+
# **text** → [b]text[/b]
113+
text = re.sub(r"\*\*(.+?)\*\*", r"[b]\1[/b]", text)
114+
# `code` → [font=Courier New]code[/font]
115+
text = re.sub(r"`([^`]+)`", r"[font=Courier New]\1[/font]", text)
116+
return text
117+
118+
119+
def extract_bbcode_fence(text: str) -> str:
120+
start = text.find(BBCODE_FENCE_OPEN)
121+
if start == -1:
122+
sys.exit(f"error: {BBCODE_FENCE_OPEN!r} fence not found in nexus-page.md")
123+
start = text.index("\n", start) + 1
124+
end = text.find(f"\n{BBCODE_FENCE_CLOSE}", start)
125+
if end == -1:
126+
sys.exit("error: closing ``` fence not found in nexus-page.md")
127+
return text[start:end]
128+
129+
130+
def inject_generated(bbcode_content: str, generated: str) -> str:
131+
start = bbcode_content.find(GEN_START)
132+
if start == -1:
133+
sys.exit(f"error: marker not found: {GEN_START!r}")
134+
end = bbcode_content.find(GEN_END, start + len(GEN_START))
135+
if end == -1:
136+
sys.exit(f"error: marker not found: {GEN_END!r}")
137+
return (
138+
bbcode_content[:start]
139+
+ GEN_START + "\n"
140+
+ generated + "\n"
141+
+ bbcode_content[end:]
142+
)
143+
144+
145+
def main() -> None:
146+
argparse.ArgumentParser(
147+
description=__doc__,
148+
formatter_class=argparse.RawDescriptionHelpFormatter,
149+
).parse_args()
150+
151+
readme = README.read_text(encoding="utf-8")
152+
template = NEXUS_TEMPLATE.read_text(encoding="utf-8")
153+
154+
md_block = extract_block(readme, NEXUS_START, NEXUS_END)
155+
bbcode_generated = md_to_bbcode(md_block)
156+
157+
bbcode_content = extract_bbcode_fence(template)
158+
result = inject_generated(bbcode_content, bbcode_generated)
159+
result = result.replace(GEN_START + "\n", "").replace("\n" + GEN_END, "")
160+
161+
print(result)
162+
163+
164+
if __name__ == "__main__":
165+
main()

0 commit comments

Comments
 (0)