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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
// Template markers for code generation
const TEMPLATE_DOCUMENT_BEGIN: &str = "--- TEMPLATE DOCUMENT BEGIN ---";
const TEMPLATE_DOCUMENT_END: &str = "--- TEMPLATE DOCUMENT END ---";
const TEMPLATE_FUNC_BEGIN: &str = "--- TEMPLATE FUNC BEGIN ---";
const TEMPLATE_FUNC_END: &str = "--- TEMPLATE FUNC END ---";
const TEMPLATE_LIST_BEGIN: &str = "--- TEMPLATE LIST BEGIN ---";
const TEMPLATE_LIST_END: &str = "--- TEMPLATE LIST END ---";
// Template parameter patterns for substitution
const PARAM_DOCUMENT_PATH: &str = "{{DOCUMENT_PATH}}";
const PARAM_DOCUMENT_CONSTANT_NAME: &str = "{{DOCUMENT_CONSTANT_NAME}}";
const PARAM_DOCUMENT_CONTENT: &str = "{{DOCUMENT_CONTENT}}";
const PARAM_DOCUMENT_PATH_SNAKE_CASE: &str = "{{DOCUMENT_PATH_SNAKE_CASE}}";
fn main() -> io::Result<()> {
println!("cargo:rerun-if-changed=src/docs.rs.template");
println!("cargo:rerun-if-changed=../../docs/Documents");
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("docs.rs");
// Read all markdown files from docs directory recursively
let docs_dir = Path::new("../../docs/Documents");
let mut documents = Vec::new();
if docs_dir.exists() {
collect_markdown_files(&docs_dir, &mut documents)?;
}
// Read template file
let template_path = Path::new("src/docs.rs.template");
let template_content = fs::read_to_string(template_path)?;
// Extract template sections preserving original indentation
let document_template = template_content
.split(TEMPLATE_DOCUMENT_BEGIN)
.nth(1)
.and_then(|s| s.split(TEMPLATE_DOCUMENT_END).next())
.unwrap_or("")
.trim_start_matches('\n')
.trim_end_matches('\n');
let match_arm_template = template_content
.split(TEMPLATE_FUNC_BEGIN)
.nth(1)
.and_then(|s| s.split(TEMPLATE_FUNC_END).next())
.unwrap_or("")
.trim_start_matches('\n')
.trim_end_matches('\n');
// Generate document blocks and match arms
let mut document_blocks = String::new();
let mut match_arms = String::new();
let mut list_items = String::new();
for (relative_path, content) in &documents {
// Calculate parameters for template substitution
let document_path = format!("./docs/Documents/{}", relative_path);
// Generate constant name from relative path
let document_constant_name = relative_path
.replace('/', "_")
.replace('-', "_")
.replace(".md", "")
.replace(".txt", "")
.to_uppercase();
// Generate snake_case name for function matching
let document_path_snake_case = relative_path
.replace('/', "_")
.replace('-', "_")
.replace(".md", "")
.replace(".txt", "")
.to_lowercase();
// Replace template parameters in document block preserving indentation
let document_block = document_template
.replace(PARAM_DOCUMENT_PATH, &document_path)
.replace(PARAM_DOCUMENT_CONSTANT_NAME, &document_constant_name)
.replace(PARAM_DOCUMENT_CONTENT, content.trim())
.replace("r#\"\"#", &format!("r#\"{}\"#", content.trim()));
document_blocks.push_str(&document_block);
document_blocks.push_str("\n\n");
// Replace template parameters in match arm preserving indentation
let match_arm = match_arm_template
.replace(PARAM_DOCUMENT_PATH_SNAKE_CASE, &document_path_snake_case)
.replace(PARAM_DOCUMENT_CONSTANT_NAME, &document_constant_name);
match_arms.push_str(&match_arm);
match_arms.push_str("\n");
// Generate list item for documents() function
let list_item = format!(" \"{}\".to_string(),", document_path_snake_case);
list_items.push_str(&list_item);
list_items.push_str("\n");
}
// Remove trailing newline from the last list item
if !list_items.is_empty() {
list_items.pop();
}
// Build final output by replacing template sections
let mut output = String::new();
// Add header before document blocks
if let Some(header) = template_content.split(TEMPLATE_DOCUMENT_BEGIN).next() {
output.push_str(header.trim());
output.push_str("\n\n");
}
// Add document blocks
output.push_str(&document_blocks);
// Add function section
if let Some(func_section) = template_content.split(TEMPLATE_FUNC_BEGIN).next() {
if let Some(rest) = func_section.split(TEMPLATE_DOCUMENT_END).nth(1) {
output.push_str(rest.trim());
output.push_str("\n");
}
}
// Add match arms
output.push_str(&match_arms);
// Add list items for documents() function
if let Some(list_section) = template_content.split(TEMPLATE_LIST_BEGIN).next() {
if let Some(rest) = list_section.split(TEMPLATE_FUNC_END).nth(1) {
output.push_str(rest.trim());
output.push_str("\n");
}
}
output.push_str(&list_items);
// Add footer
if let Some(footer) = template_content.split(TEMPLATE_LIST_END).nth(1) {
// Preserve original indentation in footer
output.push_str(footer);
}
// Write generated file
let mut file = fs::File::create(&dest_path)?;
file.write_all(output.as_bytes())?;
// Copy to src directory for development
let src_dest_path = Path::new("src/docs.rs");
fs::write(src_dest_path, output)?;
Ok(())
}
fn collect_markdown_files(dir: &Path, documents: &mut Vec<(String, String)>) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
collect_markdown_files(&path, documents)?;
} else if path
.extension()
.map_or(false, |ext| ext == "md" || ext == "txt")
{
if let Ok(relative_path) = path.strip_prefix("../../docs/Documents") {
if let Some(relative_path_str) = relative_path.to_str() {
let content = fs::read_to_string(&path)?;
documents.push((
relative_path_str.trim_start_matches('/').to_string(),
content,
));
}
}
}
}
Ok(())
}
|