Skip to content

Commit 0988c9c

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 0988c9c

3 files changed

Lines changed: 191 additions & 1 deletion

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Describe what the mod does.
2626
2727
[line]
2828
29+
<!-- generated:start -->
2930
[size=4][b]Requirements[/b][/size]
3031
3132
[list]
@@ -59,6 +60,7 @@ Describe what the mod does.
5960
[*]Compatible with Skyrim SE and AE.
6061
[*]No ESP/ESL required.
6162
[/list]
63+
<!-- generated:end -->
6264
6365
[line]
6466

scripts/generate-nexus-page.py

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

0 commit comments

Comments
 (0)