-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.rs
More file actions
123 lines (108 loc) · 4.16 KB
/
build.rs
File metadata and controls
123 lines (108 loc) · 4.16 KB
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
// build.rs — generates a compact, one-line-per-section spec from SPEC.md at compile time.
// `ilo help ai` / `ilo -ai` embeds this via include_str!(concat!(env!("OUT_DIR"), "/spec_ai.txt")).
fn main() {
println!("cargo:rerun-if-changed=SPEC.md");
let spec = std::fs::read_to_string("SPEC.md").expect("SPEC.md not found");
let compact = compact_spec(&spec);
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set by Cargo");
std::fs::write(std::path::Path::new(&out_dir).join("spec_ai.txt"), compact)
.expect("failed to write spec_ai.txt");
}
/// Compress the spec into one line per `## Section`.
/// - Table headers + separator rows are dropped; data rows become `key=value` tokens.
/// - Bullet points are joined with `;`.
/// - `### Subsection` becomes an inline `[Subsection]` label.
/// - Code fence markers, blank lines, and `---` dividers are stripped.
/// - Everything within a section is joined with ` ` and emitted as `SECTION: content`.
fn compact_spec(src: &str) -> String {
// Split into (heading, content_lines) sections.
// The preamble (before first ## heading) gets an empty heading.
let mut sections: Vec<(String, Vec<String>)> = vec![("".into(), vec![])];
for line in src.lines() {
let trimmed = line.trim();
if let Some(h) = trimmed.strip_prefix("## ") {
sections.push((h.to_uppercase(), vec![]));
} else {
sections
.last_mut()
.expect("sections always non-empty")
.1
.push(trimmed.to_string());
}
}
let mut out = String::new();
for (heading, raw_lines) in sections {
let tokens = compress_section(&raw_lines);
if tokens.is_empty() {
continue;
}
if heading.is_empty() {
out.push_str(&tokens);
} else {
out.push_str(&heading);
out.push_str(": ");
out.push_str(&tokens);
}
out.push('\n');
}
out
}
/// Compress a section's lines into a single string.
fn compress_section(lines: &[String]) -> String {
#[derive(PartialEq)]
enum TableState {
NotInTable,
InHeader, // first data row seen, separator not yet seen
InData, // past the separator row — real data rows
}
let mut items: Vec<String> = Vec::new();
let mut table_state = TableState::NotInTable;
for line in lines {
let t = line.as_str();
// Blank lines, horizontal rules, and code-fence markers are noise.
if t.is_empty() || t == "---" || t.starts_with("```") {
continue;
}
if let Some(sub) = t.strip_prefix("### ") {
// Subsection heading inline.
table_state = TableState::NotInTable;
items.push(format!("[{sub}]"));
continue;
}
if t.starts_with('|') {
let is_sep = t.chars().all(|c| matches!(c, '|' | '-' | ':' | ' '));
if is_sep {
// Separator row: marks end of header, start of data.
table_state = TableState::InData;
continue;
}
match table_state {
TableState::NotInTable => {
// First row of a new table = the header row — skip it.
table_state = TableState::InHeader;
}
TableState::InHeader => {
// Still before the separator (unusual: two header rows?) — skip.
}
TableState::InData => {
// Real data row: extract cells.
let cells: Vec<&str> = t
.split('|')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect();
items.push(cells.join("="));
}
}
continue;
}
// Non-table line — reset table state.
table_state = TableState::NotInTable;
if let Some(bullet) = t.strip_prefix("- ") {
items.push(bullet.to_string());
} else {
items.push(t.to_string());
}
}
items.join(" ")
}