summaryrefslogtreecommitdiff
path: root/gen/gen_override_renderer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gen/gen_override_renderer.rs')
-rw-r--r--gen/gen_override_renderer.rs188
1 files changed, 188 insertions, 0 deletions
diff --git a/gen/gen_override_renderer.rs b/gen/gen_override_renderer.rs
new file mode 100644
index 0000000..2ac97bd
--- /dev/null
+++ b/gen/gen_override_renderer.rs
@@ -0,0 +1,188 @@
+use std::{collections::HashSet, path::PathBuf};
+
+use regex::Regex;
+use tokio::fs;
+
+use crate::r#gen::{
+ constants::{
+ COMMANDS_PATH, OVERRIDE_RENDERER_ENTRY, OVERRIDE_RENDERER_ENTRY_TEMPLATE, TEMPLATE_END,
+ TEMPLATE_START,
+ },
+ resolve_types::resolve_type_paths,
+};
+
+pub async fn generate_override_renderer(repo_root: &PathBuf) {
+ let template_path = repo_root.join(OVERRIDE_RENDERER_ENTRY_TEMPLATE);
+ let output_path = repo_root.join(OVERRIDE_RENDERER_ENTRY);
+ let all_possible_types = collect_all_possible_types(&PathBuf::from(COMMANDS_PATH)).await;
+
+ // Read the template
+ let template = tokio::fs::read_to_string(&template_path).await.unwrap();
+
+ // Extract the template section from the template content
+ const MATCH_MARKER: &str = "// MATCHING";
+
+ let template_start_index = template
+ .find(TEMPLATE_START)
+ .ok_or("Template start marker not found")
+ .unwrap();
+ let template_end_index = template
+ .find(TEMPLATE_END)
+ .ok_or("Template end marker not found")
+ .unwrap();
+
+ let template_slice = &template[template_start_index..template_end_index + TEMPLATE_END.len()];
+ let renderer_template = template_slice
+ .trim_start_matches(TEMPLATE_START)
+ .trim_end_matches(TEMPLATE_END)
+ .trim_matches('\n');
+
+ // Generate the match arms for each renderer
+ let match_arms: String = all_possible_types
+ .iter()
+ .map(|type_name| {
+ let name = type_name.split("::").last().unwrap_or(type_name);
+ renderer_template
+ .replace("JVOutputTypeName", name)
+ .replace("JVOutputType", type_name)
+ .trim_matches('\n')
+ .to_string()
+ })
+ .collect::<Vec<String>>()
+ .join("\n");
+
+ // Replace the template section with the generated match arms
+ let final_content = template
+ .replace(renderer_template, "")
+ .replace(TEMPLATE_START, "")
+ .replace(TEMPLATE_END, "")
+ .replace(MATCH_MARKER, &match_arms)
+ .lines()
+ .filter(|line| !line.trim().is_empty())
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ // Write the generated code
+ tokio::fs::write(output_path, final_content).await.unwrap();
+}
+
+pub async fn collect_all_possible_types(dir: &PathBuf) -> HashSet<String> {
+ let mut all_types = HashSet::new();
+ let mut dirs_to_visit = vec![dir.clone()];
+
+ while let Some(current_dir) = dirs_to_visit.pop() {
+ let entries_result = fs::read_dir(&current_dir).await;
+ if entries_result.is_err() {
+ continue;
+ }
+
+ let mut entries = entries_result.unwrap();
+
+ loop {
+ let entry_result = entries.next_entry().await;
+ if entry_result.is_err() {
+ break;
+ }
+
+ let entry_opt = entry_result.unwrap();
+ if entry_opt.is_none() {
+ break;
+ }
+
+ let entry = entry_opt.unwrap();
+ let path = entry.path();
+
+ if path.is_dir() {
+ dirs_to_visit.push(path);
+ continue;
+ }
+
+ let is_rs_file = path.extension().map(|ext| ext == "rs").unwrap_or(false);
+
+ if !is_rs_file {
+ continue;
+ }
+
+ let code_result = fs::read_to_string(&path).await;
+ if code_result.is_err() {
+ continue;
+ }
+
+ let code = code_result.unwrap();
+ let types_opt = resolve_type_paths(&code, get_output_types(&code).unwrap());
+
+ if let Some(types) = types_opt {
+ for type_name in types {
+ all_types.insert(type_name);
+ }
+ }
+ }
+ }
+
+ all_types
+}
+
+pub fn get_output_types(code: &String) -> Option<Vec<String>> {
+ let mut output_types = Vec::new();
+
+ // Find all cmd_output! macros
+ let cmd_output_re = Regex::new(r"cmd_output!\s*\(\s*[^,]+,\s*([^)]+)\s*\)").ok()?;
+ for cap in cmd_output_re.captures_iter(code) {
+ let type_name = cap[1].trim();
+ output_types.push(type_name.to_string());
+ }
+
+ Some(output_types)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_get_output_types() {
+ const SITUATION: &str = "
+ use crate::{
+ cmd_output,
+ cmds::out::{
+ JVCustomOutput, JVCustomOutput2
+ },
+ systems::cmd::{
+ cmd_system::JVCommandContext,
+ errors::{CmdExecuteError, CmdPrepareError},
+ workspace_reader::LocalWorkspaceReader,
+ },
+ };
+ use cmd_system_macros::exec;
+ use other::cmds::output::JVCustomOutputOutside;
+
+ async fn exec() -> Result<(), CmdExecuteError> {
+ cmd_output!(output, JVCustomOutput)
+ cmd_output!(output, JVCustomOutput2)
+ cmd_output!(output, JVCustomOutputNotExist)
+ cmd_output!(output, JVCustomOutputOutside)
+ }
+ ";
+
+ let result = get_output_types(&SITUATION.to_string());
+ assert!(result.is_some(), "Parse failed");
+ let result = result.unwrap();
+ let expected = vec![
+ "JVCustomOutput".to_string(),
+ "JVCustomOutput2".to_string(),
+ "JVCustomOutputNotExist".to_string(),
+ "JVCustomOutputOutside".to_string(),
+ ];
+ assert_eq!(result, expected);
+
+ let result = resolve_type_paths(&SITUATION.to_string(), expected);
+ assert!(result.is_some(), "Parse failed");
+ let result = result.unwrap();
+ let expected = vec![
+ "crate::cmds::out::JVCustomOutput".to_string(),
+ "crate::cmds::out::JVCustomOutput2".to_string(),
+ "other::cmds::output::JVCustomOutputOutside".to_string(),
+ ];
+ assert_eq!(result, expected);
+ }
+}