1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ValueType {
9 Str,
10 Integer,
11 Bool,
12 ArrayOfStrings,
13 Table,
15}
16
17impl ValueType {
18 pub fn display_name(self) -> &'static str {
19 match self {
20 ValueType::Str => "string",
21 ValueType::Integer => "integer",
22 ValueType::Bool => "boolean",
23 ValueType::ArrayOfStrings => "array of strings",
24 ValueType::Table => "table",
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct KeyDef {
32 pub name: &'static str,
33 pub value_type: ValueType,
34 pub required: bool,
35 pub description: &'static str,
36 pub known_values: &'static [&'static str],
38}
39
40#[derive(Debug, Clone)]
42pub struct SectionDef {
43 pub name: &'static str,
44 pub description: &'static str,
45 pub is_array_table: bool,
47 pub is_free_form: bool,
49 pub keys: &'static [KeyDef],
50}
51
52pub static SECTIONS: &[SectionDef] = &[
54 SectionDef {
55 name: "project",
56 description: "Project metadata and configuration.",
57 is_array_table: false,
58 is_free_form: false,
59 keys: &[
60 KeyDef {
61 name: "name",
62 value_type: ValueType::Str,
63 required: true,
64 description: "The project name. Must be non-empty.",
65 known_values: &[],
66 },
67 KeyDef {
68 name: "version",
69 value_type: ValueType::Str,
70 required: true,
71 description: "The project version (semver recommended, e.g. \"0.1.0\").",
72 known_values: &[],
73 },
74 KeyDef {
75 name: "entry",
76 value_type: ValueType::Str,
77 required: false,
78 description: "Entry script for `shape` with no args (project mode). E.g. \"src/main.shape\".",
79 known_values: &[],
80 },
81 KeyDef {
82 name: "authors",
83 value_type: ValueType::ArrayOfStrings,
84 required: false,
85 description: "List of project authors.",
86 known_values: &[],
87 },
88 KeyDef {
89 name: "shape-version",
90 value_type: ValueType::Str,
91 required: false,
92 description: "Required Shape language version (e.g. \">=0.5.0\").",
93 known_values: &[],
94 },
95 KeyDef {
96 name: "license",
97 value_type: ValueType::Str,
98 required: false,
99 description: "SPDX license identifier (e.g. \"MIT\", \"Apache-2.0\").",
100 known_values: &[
101 "MIT",
102 "Apache-2.0",
103 "GPL-3.0",
104 "BSD-3-Clause",
105 "ISC",
106 "Unlicense",
107 ],
108 },
109 KeyDef {
110 name: "repository",
111 value_type: ValueType::Str,
112 required: false,
113 description: "URL of the project's source repository.",
114 known_values: &[],
115 },
116 ],
117 },
118 SectionDef {
119 name: "modules",
120 description: "Module resolution configuration.",
121 is_array_table: false,
122 is_free_form: false,
123 keys: &[KeyDef {
124 name: "paths",
125 value_type: ValueType::ArrayOfStrings,
126 required: false,
127 description: "Additional directories to search for modules (relative to project root).",
128 known_values: &[],
129 }],
130 },
131 SectionDef {
132 name: "dependencies",
133 description: "Project dependencies. Each key is a package name, value is a version string or detailed table.",
134 is_array_table: false,
135 is_free_form: true,
136 keys: &[],
137 },
138 SectionDef {
139 name: "dev-dependencies",
140 description: "Development-only dependencies (not included in production builds).",
141 is_array_table: false,
142 is_free_form: true,
143 keys: &[],
144 },
145 SectionDef {
146 name: "build",
147 description: "Build configuration.",
148 is_array_table: false,
149 is_free_form: false,
150 keys: &[
151 KeyDef {
152 name: "target",
153 value_type: ValueType::Str,
154 required: false,
155 description: "Build target: \"bytecode\" or \"native\".",
156 known_values: &["bytecode", "native"],
157 },
158 KeyDef {
159 name: "opt_level",
160 value_type: ValueType::Integer,
161 required: false,
162 description: "Optimization level (0-3). Higher is more optimized.",
163 known_values: &["0", "1", "2", "3"],
164 },
165 KeyDef {
166 name: "output",
167 value_type: ValueType::Str,
168 required: false,
169 description: "Output directory for build artifacts.",
170 known_values: &[],
171 },
172 ],
173 },
174 SectionDef {
175 name: "extensions",
176 description: "Extension module libraries to load. Each `[[extensions]]` entry defines one module.",
177 is_array_table: true,
178 is_free_form: false,
179 keys: &[
180 KeyDef {
181 name: "name",
182 value_type: ValueType::Str,
183 required: true,
184 description: "Name of the extension module.",
185 known_values: &[],
186 },
187 KeyDef {
188 name: "path",
189 value_type: ValueType::Str,
190 required: true,
191 description: "Path to the shared library (.so/.dylib/.dll).",
192 known_values: &[],
193 },
194 KeyDef {
195 name: "config",
196 value_type: ValueType::Table,
197 required: false,
198 description: "Module-specific configuration table.",
199 known_values: &[],
200 },
201 ],
202 },
203];
204
205pub fn find_section(name: &str) -> Option<&'static SectionDef> {
207 SECTIONS.iter().find(|s| s.name == name)
208}
209
210pub fn find_key(section_name: &str, key_name: &str) -> Option<&'static KeyDef> {
212 find_section(section_name).and_then(|s| s.keys.iter().find(|k| k.name == key_name))
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_find_section() {
221 assert!(find_section("project").is_some());
222 assert!(find_section("dependencies").is_some());
223 assert!(find_section("nonexistent").is_none());
224 }
225
226 #[test]
227 fn test_find_key() {
228 let key = find_key("project", "name");
229 assert!(key.is_some());
230 let key = key.unwrap();
231 assert!(key.required);
232 assert_eq!(key.value_type, ValueType::Str);
233 }
234
235 #[test]
236 fn test_all_sections_present() {
237 let names: Vec<&str> = SECTIONS.iter().map(|s| s.name).collect();
238 assert!(names.contains(&"project"));
239 assert!(names.contains(&"modules"));
240 assert!(names.contains(&"dependencies"));
241 assert!(names.contains(&"dev-dependencies"));
242 assert!(names.contains(&"build"));
243 assert!(names.contains(&"extensions"));
244 }
245
246 #[test]
247 fn test_extensions_is_array_table() {
248 let ext = find_section("extensions").unwrap();
249 assert!(ext.is_array_table);
250 }
251
252 #[test]
253 fn test_dependencies_is_free_form() {
254 let deps = find_section("dependencies").unwrap();
255 assert!(deps.is_free_form);
256 let dev_deps = find_section("dev-dependencies").unwrap();
257 assert!(dev_deps.is_free_form);
258 }
259}