Skip to content

Commit 2db013e

Browse files
Documentation: generate page structure
1 parent ade055b commit 2db013e

File tree

4 files changed

+230
-7
lines changed

4 files changed

+230
-7
lines changed

Doc/doxygen.cfg

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,8 @@ RECURSIVE = YES
10971097
# run.
10981098

10991099
# Doxygen crashes while parsing SamplerBlueNoiseErrorDistribution_128x128_OptimizedFor_2d2d2d2d_1spp.cpp
1100-
EXCLUDE = DiligentFX/PostProcess/Common/src/PostFXContext.cpp \
1100+
EXCLUDE = DiligentCore/BuildTools/Android \
1101+
DiligentFX/PostProcess/Common/src/PostFXContext.cpp \
11011102
DiligentSamples/Android \
11021103
DiligentSamples/Samples/Asteroids/SDK \
11031104
DiligentFX/Shaders/PostProcess/SuperResolution/private/fsr1 \
@@ -1124,7 +1125,9 @@ EXCLUDE_SYMLINKS = NO
11241125
EXCLUDE_PATTERNS = */build/* \
11251126
*/ThirdParty/* \
11261127
*/.*/* \
1127-
*/DiligentSamples/*/assets/*
1128+
*/Tests/* \
1129+
*/DiligentSamples/*/assets/* \
1130+
*/__pycache__/*
11281131

11291132
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
11301133
# (namespaces, classes, functions, etc.) that should be excluded from the

Doc/md_filter.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import re
44

5-
from md_utils import compute_page_id
5+
from md_utils import get_project_root, compute_page_id
66

77
def replace_special_symbols(text):
88
# Define your symbol mappings using Unicode escape sequences.
@@ -82,10 +82,7 @@ def repl(match):
8282

8383

8484
def process_content(input_filepath, lines):
85-
# Determine the directory of this script.
86-
script_dir = os.path.dirname(os.path.abspath(__file__))
87-
# The project root is one level up.
88-
root_dir = os.path.dirname(script_dir)
85+
root_dir = get_project_root()
8986

9087
page_id = compute_page_id(root_dir, input_filepath)
9188
output_lines = []

Doc/md_pages.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import os
2+
import sys
3+
import fnmatch
4+
from md_utils import get_project_root, compute_page_id
5+
#import json
6+
7+
# Directories to exclude explicitly (relative to the project root).
8+
EXCLUDED_DIRS = [
9+
"Doc",
10+
"Media",
11+
"DiligentCore/BuildTools/Android",
12+
"DiligentSamples/Android",
13+
"DiligentSamples/Samples/Asteroids/SDK",
14+
"DiligentFX/Shaders/PostProcess/SuperResolution/private/fsr1",
15+
"DiligentTools/NativeApp/Android",
16+
"Tests"
17+
]
18+
19+
# Additional exclusion patterns.
20+
ADDITIONAL_EXCLUDE_PATTERNS = [
21+
"build/*",
22+
"*/build/*",
23+
"*/ThirdParty/*",
24+
".*/*",
25+
"*/.*/*",
26+
"DiligentSamples/*/assets/*",
27+
"*/Tests/*",
28+
"*/__pycache__/*"
29+
]
30+
31+
def should_exclude(rel_dir):
32+
"""
33+
Return True if the given relative directory (relative to the project root)
34+
should be excluded based on explicit directories, additional patterns,
35+
or if any directory component starts with a dot.
36+
"""
37+
# Normalize to forward slashes.
38+
norm_rel_dir = rel_dir.replace(os.sep, '/')
39+
40+
# Check explicit exclusions.
41+
for ex in EXCLUDED_DIRS:
42+
if norm_rel_dir == ex or norm_rel_dir.startswith(ex + '/'):
43+
return True
44+
45+
# Check additional patterns.
46+
for pattern in ADDITIONAL_EXCLUDE_PATTERNS:
47+
if fnmatch.fnmatch(norm_rel_dir, pattern) or fnmatch.fnmatch(norm_rel_dir + '/', pattern):
48+
return True
49+
50+
return False
51+
52+
def build_md_tree(root_dir, rel_dir=""):
53+
"""
54+
Recursively build a tree structure starting at root_dir.
55+
56+
Parameters:
57+
- root_dir: the absolute path to the project root.
58+
- rel_dir: the relative directory path from the root (default is "", meaning the root itself).
59+
60+
Returns:
61+
A dictionary node with keys:
62+
"path": the relative path of the node,
63+
"files": a sorted list of Markdown file names in that directory,
64+
"subdirs": a list of child nodes (for subdirectories that contain Markdown files),
65+
or None if neither the current directory nor any of its subdirectories contain Markdown files.
66+
"""
67+
current_path = os.path.join(root_dir, rel_dir)
68+
69+
# Skip if the current relative directory should be excluded.
70+
if rel_dir and should_exclude(rel_dir):
71+
return None
72+
73+
try:
74+
entries = os.listdir(current_path)
75+
except Exception as e:
76+
# If we cannot list the directory, skip it.
77+
return None
78+
79+
files = []
80+
subdirs = []
81+
for entry in entries:
82+
full_path = os.path.join(current_path, entry)
83+
if os.path.isfile(full_path) and entry.lower().endswith('.md'):
84+
files.append(entry)
85+
elif os.path.isdir(full_path):
86+
subdirs.append(entry)
87+
88+
# Recursively build nodes for subdirectories.
89+
children = []
90+
for d in sorted(subdirs):
91+
child_rel = os.path.join(rel_dir, d) if rel_dir else d
92+
child_node = build_md_tree(root_dir, child_rel)
93+
if child_node is not None:
94+
children.append(child_node)
95+
96+
# If there are no markdown files here and no children with markdown files, skip this directory.
97+
if not files and not children:
98+
return None
99+
100+
return {
101+
"path": rel_dir,
102+
"files": sorted(files),
103+
"subdirs": children
104+
}
105+
106+
107+
def clean_level1(name):
108+
"""If a level-1 folder name starts with 'Diligent' (case-insensitive), remove that prefix."""
109+
if name.lower().startswith("diligent"):
110+
return name[len("Diligent"):]
111+
return name
112+
113+
114+
def compute_container(node, root_dir, level = 0):
115+
"""
116+
Recursively process the tree node and compute its container ID and page text.
117+
118+
Rules:
119+
- If the node contains exactly one Markdown file and no subdirectories,
120+
then its page text is empty (the file's page will be used as a subpage)
121+
and the function returns that file's page ID.
122+
- Otherwise, a container page is generated. Its page text is a block
123+
that starts with a \page definition (using a synthetic container ID and
124+
a title) and then lists as subpages all container IDs returned from:
125+
* Each Markdown file in the node
126+
* Each child node (from recursive invocation)
127+
The container ID is stored in node["container_id"] and the page text in node["page_text"].
128+
The function returns the container ID.
129+
"""
130+
files = node.get("files", [])
131+
children = node.get("subdirs", [])
132+
rel_path = node.get("path", "") # relative path from the project root
133+
134+
# Case 1: Leaf node (exactly one Markdown file and no subdirectories).
135+
if len(files) == 1 and not children:
136+
md_file = files[0]
137+
full_path = os.path.join(root_dir, rel_path, md_file) if rel_path else os.path.join(root_dir, md_file)
138+
file_id = compute_page_id(root_dir, full_path)
139+
node["container_id"] = file_id
140+
node["page_text"] = "" # no container page text for a single-file node
141+
return file_id
142+
143+
# Case 2: Container node.
144+
# Create a synthetic container ID.
145+
if rel_path == "":
146+
container_id = "root"
147+
title = "User Guide"
148+
else:
149+
container_id = "dir_" + rel_path.replace(os.sep, "_")
150+
title = os.path.basename(rel_path)
151+
if level == 1:
152+
title = clean_level1(title)
153+
node["container_id"] = container_id
154+
155+
# Gather subpage IDs from local Markdown files.
156+
subpage_ids = []
157+
for f in files:
158+
full_path = os.path.join(root_dir, rel_path, f) if rel_path else os.path.join(root_dir, f)
159+
file_id = compute_page_id(root_dir, full_path)
160+
subpage_ids.append(file_id)
161+
162+
# Recurse into each child node.
163+
for child in children:
164+
child_id = compute_container(child, root_dir, level + 1)
165+
subpage_ids.append(child_id)
166+
167+
# Construct the page text for this container node.
168+
# It defines a page with the container ID and title, and lists subpages.
169+
lines = []
170+
lines.append("/**\n")
171+
lines.append(f" * \\page {container_id} {title}\n")
172+
for sp in subpage_ids:
173+
lines.append(f" *\n")
174+
lines.append(f" * \\subpage {sp}\n")
175+
lines.append(" */\n\n")
176+
page_text = "".join(lines)
177+
node["page_text"] = page_text
178+
179+
return container_id
180+
181+
def write_pages(node, out_stream):
182+
"""
183+
Recursively traverse the tree and write out the page text for each node,
184+
if non-empty.
185+
"""
186+
if node.get("page_text"):
187+
out_stream.write(node["page_text"])
188+
for child in node.get("subdirs", []):
189+
write_pages(child, out_stream)
190+
191+
192+
def main():
193+
project_root = get_project_root()
194+
output_file = sys.argv[1] if len(sys.argv) >= 2 else None
195+
196+
tree = build_md_tree(project_root)
197+
if tree is None:
198+
sys.stderr.write("No Markdown files found in the project.\n")
199+
return
200+
201+
#print(json.dumps(tree, indent=4))
202+
203+
compute_container(tree, project_root)
204+
205+
if output_file:
206+
out_stream = open(output_file, 'w', encoding='utf-8')
207+
else:
208+
out_stream = sys.stdout
209+
write_pages(tree, out_stream)
210+
211+
if __name__ == "__main__":
212+
main()

Doc/md_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,14 @@ def compute_page_id(root_dir, input_filepath):
1616
# Replace path separators with underscores.
1717
page_id = rel_path_no_ext.replace(os.sep, "_")
1818
return page_id
19+
20+
def get_project_root():
21+
"""
22+
Get the project root directory.
23+
24+
The project root is defined as the directory containing the script.
25+
"""
26+
# Determine the directory of this script.
27+
script_dir = os.path.dirname(os.path.abspath(__file__))
28+
# The project root is one level up.
29+
return os.path.dirname(script_dir)

0 commit comments

Comments
 (0)