rgen_cli_lib/cmds/
list.rs1use rayon::prelude::*;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4use rgen_utils::error::Result;
5use walkdir::WalkDir;
6
7static mut TEMPLATE_CACHE: Option<Arc<std::collections::HashMap<PathBuf, TemplateInfo>>> = None;
9static CACHE_INIT: std::sync::Once = std::sync::Once::new();
10
11#[derive(Debug, Clone)]
12struct TemplateInfo {
13 relative_path: String,
14 output_path: Option<String>,
15 variables: Vec<(String, String)>,
16 rdf_files: usize,
17 sparql_queries: usize,
18}
19
20pub fn run() -> Result<()> {
21 let templates_dir = find_templates_directory()?;
22
23 println!("Available templates:");
24 println!("===================");
25
26 let template_infos = get_cached_template_infos(&templates_dir)?;
28
29 if template_infos.is_empty() {
30 println!("No templates found in {}", templates_dir.display());
31 } else {
32 template_infos.par_iter().for_each(|(_, info)| {
34 display_template_info_cached(info);
35 });
36
37 println!("\nTotal: {} template(s)", template_infos.len());
38 }
39
40 Ok(())
41}
42
43fn find_templates_directory() -> Result<PathBuf> {
44 let possible_paths = [
46 PathBuf::from("templates"),
47 PathBuf::from("examples"),
48 PathBuf::from("../templates"),
49 PathBuf::from("../examples"),
50 ];
51
52 for path in &possible_paths {
53 if path.exists() && path.is_dir() {
54 return Ok(path.clone());
55 }
56 }
57
58 Err(rgen_utils::error::Error::new(
59 "No templates directory found. Please ensure you're in a project with templates.",
60 ))
61}
62
63fn get_cached_template_infos(
64 templates_dir: &Path,
65) -> Result<Arc<std::collections::HashMap<PathBuf, TemplateInfo>>> {
66 CACHE_INIT.call_once(|| unsafe {
67 TEMPLATE_CACHE = Some(Arc::new(std::collections::HashMap::new()));
68 });
69
70 unsafe {
71 if let Some(cache) = (*&raw const TEMPLATE_CACHE).as_ref() {
72 if !cache.is_empty() {
73 return Ok(cache.clone());
74 }
75 }
76 }
77
78 let mut template_infos = std::collections::HashMap::new();
80
81 let template_paths: Vec<_> = WalkDir::new(templates_dir)
83 .follow_links(true)
84 .into_iter()
85 .filter_map(|e| e.ok())
86 .filter(|entry| entry.path().extension().is_some_and(|ext| ext == "tmpl"))
87 .map(|entry| entry.path().to_path_buf())
88 .collect();
89
90 let parsed_infos: Vec<_> = template_paths
92 .par_iter()
93 .filter_map(|template_path| parse_template_info(template_path, templates_dir).ok())
94 .collect();
95
96 for (path, info) in parsed_infos {
98 template_infos.insert(path, info);
99 }
100
101 let cache = Arc::new(template_infos);
102 unsafe {
103 TEMPLATE_CACHE = Some(cache.clone());
104 }
105
106 Ok(cache)
107}
108
109fn parse_template_info(template_path: &Path, base_dir: &Path) -> Result<(PathBuf, TemplateInfo)> {
110 let relative_path = template_path
111 .strip_prefix(base_dir)
112 .unwrap_or(template_path)
113 .to_string_lossy()
114 .to_string();
115
116 let content = std::fs::read_to_string(template_path)?;
117
118 if let Some(frontmatter) = extract_frontmatter(&content) {
119 let output_path = frontmatter
120 .get("to")
121 .and_then(|v| v.as_str())
122 .map(|s| s.to_string());
123
124 let variables: Vec<(String, String)> = frontmatter
125 .get("vars")
126 .and_then(|v| v.as_object())
127 .map(|vars_map| {
128 vars_map
129 .iter()
130 .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
131 .collect()
132 })
133 .unwrap_or_default();
134
135 let rdf_files = frontmatter
136 .get("rdf")
137 .and_then(|v| v.as_array())
138 .map(|arr| arr.len())
139 .unwrap_or(0);
140
141 let sparql_queries = frontmatter
142 .get("sparql")
143 .and_then(|v| v.as_array())
144 .map(|arr| arr.len())
145 .unwrap_or(0);
146
147 Ok((
148 template_path.to_path_buf(),
149 TemplateInfo {
150 relative_path,
151 output_path,
152 variables,
153 rdf_files,
154 sparql_queries,
155 },
156 ))
157 } else {
158 Ok((
159 template_path.to_path_buf(),
160 TemplateInfo {
161 relative_path,
162 output_path: None,
163 variables: Vec::new(),
164 rdf_files: 0,
165 sparql_queries: 0,
166 },
167 ))
168 }
169}
170
171fn display_template_info_cached(info: &TemplateInfo) {
172 println!("\nš {}", info.relative_path);
173
174 if let Some(output) = &info.output_path {
175 println!(" Output: {}", output);
176 }
177
178 if !info.variables.is_empty() {
179 println!(" Variables:");
180 for (key, value) in &info.variables {
181 println!(" {}: {}", key, value);
182 }
183 }
184
185 if info.rdf_files > 0 {
186 println!(" RDF files: {}", info.rdf_files);
187 }
188
189 if info.sparql_queries > 0 {
190 println!(" SPARQL queries: {}", info.sparql_queries);
191 }
192}
193
194fn extract_frontmatter(content: &str) -> Option<serde_json::Value> {
195 if content.starts_with("---\n") {
197 if let Some(end_marker) = content[4..].find("\n---\n") {
198 let yaml_content = &content[4..4 + end_marker];
199 serde_yaml::from_str::<serde_json::Value>(yaml_content).ok()
200 } else {
201 None
202 }
203 } else {
204 None
205 }
206}