1macro_rules! doc {
4 ($path:literal) => {
5 concat!(env!("CARGO_MANIFEST_DIR"), $path)
6 };
7}
8
9pub const LLMS_TXT: &str = concat!(
10 include_str!(doc!("/documentation/guide/00_intro.md")),
11 "\n\n---\n\n",
12 include_str!(doc!("/documentation/guide/05_method.md")),
13 "\n\n---\n\n",
14 include_str!(doc!("/documentation/guide/10_syntax.md")),
15 "\n\n---\n\n",
16 include_str!(doc!("/documentation/guide/20_composition.md")),
17 "\n\n---\n\n",
18 include_str!(doc!("/documentation/guide/30_data.md")),
19 "\n\n---\n\n",
20 include_str!(doc!("/documentation/guide/40_units.md")),
21 "\n\n---\n\n",
22 include_str!(doc!("/documentation/guide/50_rules.md")),
23 "\n\n---\n\n",
24 include_str!(doc!("/documentation/guide/60_veto.md")),
25 "\n\n---\n\n",
26 include_str!(doc!("/documentation/guide/70_anti_patterns.md")),
27 "\n\n---\n\n",
28 include_str!(doc!("/documentation/guide/80_footer.md")),
29);
30
31pub const EVALUATE_GUIDE: &str = include_str!(doc!("/documentation/evaluate_guide.md"));
32
33const METHOD: &str = include_str!(doc!("/documentation/guide/05_method.md"));
34const SYNTAX: &str = include_str!(doc!("/documentation/guide/10_syntax.md"));
35const COMPOSITION: &str = include_str!(doc!("/documentation/guide/20_composition.md"));
36const DATA: &str = include_str!(doc!("/documentation/guide/30_data.md"));
37const UNITS: &str = include_str!(doc!("/documentation/guide/40_units.md"));
38const RULES: &str = include_str!(doc!("/documentation/guide/50_rules.md"));
39const VETO: &str = include_str!(doc!("/documentation/guide/60_veto.md"));
40const ANTI_PATTERNS: &str = include_str!(doc!("/documentation/guide/70_anti_patterns.md"));
41
42pub const EXAMPLE_01_COFFEE_ORDER: &str =
43 include_str!(doc!("/documentation/examples/01_coffee_order.lemma"));
44pub const EXAMPLE_02_LIBRARY_FEES: &str =
45 include_str!(doc!("/documentation/examples/02_library_fees.lemma"));
46pub const EXAMPLE_03_RECIPE_SCALING: &str =
47 include_str!(doc!("/documentation/examples/03_recipe_scaling.lemma"));
48pub const EXAMPLE_04_MEMBERSHIP_BENEFITS: &str =
49 include_str!(doc!("/documentation/examples/04_membership_benefits.lemma"));
50pub const EXAMPLE_05_WEATHER_CLOTHING: &str =
51 include_str!(doc!("/documentation/examples/05_weather_clothing.lemma"));
52pub const EXAMPLE_NL_TAX_NET_SALARY: &str =
53 include_str!(doc!("/documentation/examples/nl/tax/net_salary.lemma"));
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum GuideTopic {
59 Method,
60 Syntax,
61 Data,
62 Rules,
63 Units,
64 Veto,
65 Composition,
66 AntiPatterns,
67 Evaluate,
68 Full,
69}
70
71impl GuideTopic {
72 pub const ALL: &[GuideTopic] = &[
73 GuideTopic::Method,
74 GuideTopic::Syntax,
75 GuideTopic::Data,
76 GuideTopic::Rules,
77 GuideTopic::Units,
78 GuideTopic::Veto,
79 GuideTopic::Composition,
80 GuideTopic::AntiPatterns,
81 GuideTopic::Evaluate,
82 GuideTopic::Full,
83 ];
84
85 pub const VALID_LIST: &str =
86 "method, syntax, data, rules, units, veto, composition, anti_patterns, evaluate, full";
87
88 pub fn as_str(self) -> &'static str {
89 match self {
90 GuideTopic::Method => "method",
91 GuideTopic::Syntax => "syntax",
92 GuideTopic::Data => "data",
93 GuideTopic::Rules => "rules",
94 GuideTopic::Units => "units",
95 GuideTopic::Veto => "veto",
96 GuideTopic::Composition => "composition",
97 GuideTopic::AntiPatterns => "anti_patterns",
98 GuideTopic::Evaluate => "evaluate",
99 GuideTopic::Full => "full",
100 }
101 }
102
103 pub fn parse(name: &str) -> Option<Self> {
104 Self::ALL.iter().copied().find(|t| t.as_str() == name)
105 }
106
107 pub fn section_text(self) -> &'static str {
109 match self {
110 GuideTopic::Method => METHOD,
111 GuideTopic::Syntax => SYNTAX,
112 GuideTopic::Data => DATA,
113 GuideTopic::Rules => RULES,
114 GuideTopic::Units => UNITS,
115 GuideTopic::Veto => VETO,
116 GuideTopic::Composition => COMPOSITION,
117 GuideTopic::AntiPatterns => ANTI_PATTERNS,
118 GuideTopic::Evaluate => EVALUATE_GUIDE,
119 GuideTopic::Full => LLMS_TXT,
120 }
121 }
122}
123
124pub struct ExampleResource {
126 pub path: &'static str,
127 pub body: &'static str,
128}
129
130pub const EXAMPLE_RESOURCES: &[ExampleResource] = &[
131 ExampleResource {
132 path: "01_coffee_order.lemma",
133 body: EXAMPLE_01_COFFEE_ORDER,
134 },
135 ExampleResource {
136 path: "02_library_fees.lemma",
137 body: EXAMPLE_02_LIBRARY_FEES,
138 },
139 ExampleResource {
140 path: "03_recipe_scaling.lemma",
141 body: EXAMPLE_03_RECIPE_SCALING,
142 },
143 ExampleResource {
144 path: "04_membership_benefits.lemma",
145 body: EXAMPLE_04_MEMBERSHIP_BENEFITS,
146 },
147 ExampleResource {
148 path: "05_weather_clothing.lemma",
149 body: EXAMPLE_05_WEATHER_CLOTHING,
150 },
151 ExampleResource {
152 path: "nl/tax/net_salary.lemma",
153 body: EXAMPLE_NL_TAX_NET_SALARY,
154 },
155];
156
157pub fn example_by_path(path: &str) -> Option<&'static str> {
158 EXAMPLE_RESOURCES
159 .iter()
160 .find(|e| e.path == path)
161 .map(|e| e.body)
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use std::fs;
168 use std::path::PathBuf;
169
170 fn guide_dir() -> PathBuf {
171 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("documentation/guide")
172 }
173
174 fn concat_guide_fragments() -> String {
175 let mut entries = fs::read_dir(guide_dir())
176 .expect("BUG: documentation/guide/ must exist")
177 .filter_map(|entry| {
178 let entry = entry.ok()?;
179 let path = entry.path();
180 if path.extension()?.to_str()? == "md"
181 && !path.file_stem()?.to_str()?.ends_with("_improved")
182 {
183 Some(path)
184 } else {
185 None
186 }
187 })
188 .collect::<Vec<_>>();
189 entries.sort();
190 let mut content = String::new();
191 for (i, path) in entries.iter().enumerate() {
192 if i > 0 {
193 content.push_str("\n\n---\n\n");
194 }
195 content.push_str(
196 &fs::read_to_string(path)
197 .unwrap_or_else(|e| panic!("BUG: read {}: {e}", path.display())),
198 );
199 }
200 content
201 }
202
203 #[test]
204 fn guide_topic_parse_round_trip() {
205 for topic in GuideTopic::ALL {
206 assert_eq!(GuideTopic::parse(topic.as_str()), Some(*topic));
207 }
208 assert!(GuideTopic::parse("temporal").is_none());
209 assert!(GuideTopic::parse("").is_none());
210 }
211
212 #[test]
213 fn full_guide_matches_concatenated_fragments() {
214 assert_eq!(GuideTopic::Full.section_text(), concat_guide_fragments());
215 }
216
217 #[test]
218 fn full_guide_does_not_embed_evaluate_guide() {
219 assert!(!GuideTopic::Full
220 .section_text()
221 .contains("**Evaluating loaded specs**"));
222 }
223}