1use anyhow::Result;
2use glob::glob;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone)]
9pub struct PackConventions {
10 pub template_patterns: &'static [&'static str],
11 pub rdf_patterns: &'static [&'static str],
12 pub query_patterns: &'static [&'static str],
13 pub shape_patterns: &'static [&'static str],
14}
15
16impl Default for PackConventions {
17 fn default() -> Self {
18 Self {
19 template_patterns: &["templates/**/*.tmpl", "templates/**/*.tera"],
20 rdf_patterns: &[
21 "templates/**/graphs/*.ttl",
22 "templates/**/graphs/*.rdf",
23 "templates/**/graphs/*.jsonld"
24 ],
25 query_patterns: &["templates/**/queries/*.rq", "templates/**/queries/*.sparql"],
26 shape_patterns: &[
27 "templates/**/graphs/shapes/*.shacl.ttl",
28 "templates/**/shapes/*.ttl"
29 ],
30 }
31 }
32}
33
34#[derive(Debug, Clone, Deserialize, Serialize)]
36pub struct RpackManifest {
37 #[serde(rename = "rpack")]
38 pub metadata: RpackMetadata,
39 #[serde(default)]
40 pub dependencies: BTreeMap<String, String>,
41 #[serde(default)]
42 pub templates: TemplatesConfig,
43 #[serde(default)]
44 pub macros: MacrosConfig,
45 #[serde(default)]
46 pub rdf: RdfConfig,
47 #[serde(default)]
48 pub queries: QueriesConfig,
49 #[serde(default)]
50 pub shapes: ShapesConfig,
51 #[serde(default)]
52 pub preset: PresetConfig,
53}
54
55#[derive(Debug, Clone, Deserialize, Serialize)]
57pub struct RpackMetadata {
58 pub id: String,
59 pub name: String,
60 pub version: String,
61 pub description: String,
62 pub license: String,
63 pub rgen_compat: String,
64}
65
66#[derive(Debug, Clone, Deserialize, Serialize, Default)]
68pub struct TemplatesConfig {
69 #[serde(default)]
71 pub patterns: Vec<String>,
72 #[serde(default)]
74 pub includes: Vec<String>,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize, Default)]
79pub struct MacrosConfig {
80 #[serde(default)]
81 pub paths: Vec<String>,
82}
83
84#[derive(Debug, Clone, Deserialize, Serialize, Default)]
86pub struct RdfConfig {
87 #[serde(default)]
88 pub base: Option<String>,
89 #[serde(default)]
90 pub prefixes: BTreeMap<String, String>,
91 #[serde(default)]
93 pub patterns: Vec<String>,
94 #[serde(default)]
96 pub inline: Vec<String>,
97}
98
99#[derive(Debug, Clone, Deserialize, Serialize, Default)]
101pub struct QueriesConfig {
102 #[serde(default)]
104 pub patterns: Vec<String>,
105 #[serde(default)]
106 pub aliases: BTreeMap<String, String>,
107}
108
109#[derive(Debug, Clone, Deserialize, Serialize, Default)]
111pub struct ShapesConfig {
112 #[serde(default)]
114 pub patterns: Vec<String>,
115}
116
117#[derive(Debug, Clone, Deserialize, Serialize, Default)]
119pub struct PresetConfig {
120 #[serde(default)]
121 pub config: Option<PathBuf>,
122 #[serde(default)]
123 pub vars: BTreeMap<String, String>,
124}
125
126fn discover_files(base_path: &Path, patterns: &[&str]) -> Result<Vec<PathBuf>> {
128 let mut files = Vec::new();
129 for pattern in patterns {
130 let full_pattern = base_path.join(pattern);
131 for entry in glob(&full_pattern.to_string_lossy())? {
132 files.push(entry?);
133 }
134 }
135 files.sort(); Ok(files)
137}
138
139impl RpackManifest {
140 pub fn load_from_file(path: &PathBuf) -> Result<Self> {
142 let content = std::fs::read_to_string(path)?;
143 let manifest: RpackManifest = toml::from_str(&content)?;
144 Ok(manifest)
145 }
146
147 pub fn discover_templates(&self, base_path: &Path) -> Result<Vec<PathBuf>> {
149 let patterns = if self.templates.patterns.is_empty() {
150 PackConventions::default().template_patterns
151 } else {
152 &self
153 .templates
154 .patterns
155 .iter()
156 .map(|s| s.as_str())
157 .collect::<Vec<_>>()
158 };
159
160 discover_files(base_path, patterns)
161 }
162
163 pub fn discover_rdf_files(&self, base_path: &Path) -> Result<Vec<PathBuf>> {
165 let patterns = if self.rdf.patterns.is_empty() {
166 PackConventions::default().rdf_patterns
167 } else {
168 &self
169 .rdf
170 .patterns
171 .iter()
172 .map(|s| s.as_str())
173 .collect::<Vec<_>>()
174 };
175
176 discover_files(base_path, patterns)
177 }
178
179 pub fn discover_query_files(&self, base_path: &Path) -> Result<Vec<PathBuf>> {
181 let patterns = if self.queries.patterns.is_empty() {
182 PackConventions::default().query_patterns
183 } else {
184 &self
185 .queries
186 .patterns
187 .iter()
188 .map(|s| s.as_str())
189 .collect::<Vec<_>>()
190 };
191
192 discover_files(base_path, patterns)
193 }
194
195 pub fn discover_shape_files(&self, base_path: &Path) -> Result<Vec<PathBuf>> {
197 let patterns = if self.shapes.patterns.is_empty() {
198 PackConventions::default().shape_patterns
199 } else {
200 &self
201 .shapes
202 .patterns
203 .iter()
204 .map(|s| s.as_str())
205 .collect::<Vec<_>>()
206 };
207
208 discover_files(base_path, patterns)
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use std::io::Write;
216 use tempfile::NamedTempFile;
217
218 #[test]
219 fn test_manifest_parsing() {
220 let toml_content = r#"
221[rpack]
222id = "io.rgen.rust.cli-subcommand"
223name = "Rust CLI subcommand"
224version = "0.1.0"
225description = "Generate clap subcommands"
226license = "MIT"
227rgen_compat = ">=0.1 <0.2"
228
229[dependencies]
230"io.rgen.macros.std" = "^0.1"
231
232[templates]
233patterns = ["cli/subcommand/*.tmpl"]
234includes = ["macros/**/*.tera"]
235
236[rdf]
237base = "http://example.org/"
238prefixes.ex = "http://example.org/"
239patterns = ["templates/**/graphs/*.ttl"]
240inline = ["@prefix ex: <http://example.org/> . ex:Foo a ex:Type ."]
241
242[queries]
243patterns = ["../queries/*.rq"]
244aliases.component_by_name = "../queries/component_by_name.rq"
245
246[shapes]
247patterns = ["../shapes/*.ttl"]
248
249[preset]
250config = "../preset/rgen.toml"
251vars = { author = "Acme", license = "MIT" }
252"#;
253
254 let manifest: RpackManifest = toml::from_str(toml_content).unwrap();
255
256 assert_eq!(manifest.metadata.id, "io.rgen.rust.cli-subcommand");
257 assert_eq!(manifest.metadata.name, "Rust CLI subcommand");
258 assert_eq!(manifest.metadata.version, "0.1.0");
259 assert_eq!(manifest.templates.patterns.len(), 1);
260 assert_eq!(manifest.rdf.patterns.len(), 1);
261 assert_eq!(manifest.queries.aliases.len(), 1);
262 }
263
264 #[test]
265 fn test_manifest_load_from_file() {
266 let mut temp_file = NamedTempFile::new().unwrap();
267 let toml_content = r#"
268[rpack]
269id = "test"
270name = "Test"
271version = "0.1.0"
272description = "Test"
273license = "MIT"
274rgen_compat = ">=0.1 <0.2"
275"#;
276 temp_file.write_all(toml_content.as_bytes()).unwrap();
277
278 let manifest = RpackManifest::load_from_file(&temp_file.path().to_path_buf()).unwrap();
279 assert_eq!(manifest.metadata.id, "test");
280 }
281}