diff --git a/src/commands/generate.rs b/src/commands/generate.rs index cca7ed2..1bfc6d5 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -185,6 +185,9 @@ pub(crate) fn generate(command: &Command) -> Status { std::fs::create_dir_all(&new_path).unwrap(); } + let command = + utils::formater::handle_placeholders(&meta.get_command(), &given_name, meta.clone()); + if utils::template_handler::generate_template( &format!(".templates/{}", template_name), &new_path, @@ -199,6 +202,23 @@ pub(crate) fn generate(command: &Command) -> Status { } meta.generate_snippets(); log!("Files generated successfully."); + + if !command.trim().is_empty() { + log!("Executing Command: {}", command); + + match utils::functions::execute_user_command(command) { + Ok(result) => { + log!("{}", result); + } + Err(e) => { + if let Some(stderr) = e.to_string().lines().next() { + log!("{}", stderr); + } + log!("Error executing command: {}", e); + } + } + } + Status::ok() } else { Status::error("Files could not be generated.".to_string()) diff --git a/src/commands/new.rs b/src/commands/new.rs index e53e8a5..dd53aef 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -31,6 +31,12 @@ pub(crate) fn definition() -> Command { ".".to_string(), "Provide a path for the new template.".to_string(), )); + + new_command.add_flag(Flag::new_value_flag( + vec!["command".to_string(), "cmd".to_string()], + ".".to_string(), + "Provide a command that will be executed after the template is created. This command will run in the template's directory.".to_string(), + )); new_command } @@ -58,6 +64,7 @@ pub(crate) fn new(command: &Command) -> Status { template_name.clone(), command.get_value_flag("description").clone(), command.get_value_flag("path").clone(), + command.get_value_flag("command").clone(), ), ) .unwrap(); diff --git a/src/data.rs b/src/data.rs index d332456..a61b54b 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,13 +1,19 @@ use crate::placeholder_storage::get_all_placeholders; /// Get the content for a new templify file -pub fn templify_file_blank(name: String, description: String, path: String) -> String { +pub fn templify_file_blank( + name: String, + description: String, + path: String, + command: String, +) -> String { let content = format!("# This is the '{}' template # This file is used by templify to generate new files from this template. # You can use the following variables in this file: #description: The description of the template #path: The path where the file should be generated based on the project root (you can also use placeholders here) +#command: The command that will be executed after the template is created. This command will run in the template's directory. #vars: # Define a variable placeholders that can be used in the file content # - package # Variable Placeholder # - subdir(src) # Variable Placeholder with default value @@ -23,9 +29,12 @@ pub fn templify_file_blank(name: String, description: String, path: String) -> S # IMPORTANT: Lines starting with a . are auto generated and should not be changed. description: {} -path: {} -", name, description, path); - +path: {}{} +", name, description, path, if command.trim().is_empty() || command.trim() == "." { + String::new() + } else { + format!("\ncommand: {}", command) + }); content.to_string() } diff --git a/src/types/flag.rs b/src/types/flag.rs index 74f85d4..45b92b2 100644 --- a/src/types/flag.rs +++ b/src/types/flag.rs @@ -55,10 +55,32 @@ impl Flag { return Status::error(format!("Missing value for flag: -{}", name)); } - let mut v = args[i + 1].clone(); - if v.starts_with('-') { + let mut j = i + 1; + let first_val = &args[j]; + + if first_val.starts_with('-') { return Status::error(format!("Missing value for flag: -{}", name)); } + let mut v; + if first_val.starts_with('\'') || first_val.starts_with('"') { + let quote_char = first_val.chars().next(); + v = first_val[1..].to_string(); // Skip opening quote + v.push(' '); + j += 1; + // Iterate until closing quote + while j < args.len() { + if args[j].ends_with(quote_char.unwrap()) { + v.push_str(&args[j][..args[j].len() - 1]); // Add without closing quote + break; + } else { + v.push_str(&args[j]); + v.push(' '); + } + j += 1; + } + } else { + v = args[j].clone(); + } if v.starts_with('/') { v = v[1..].to_string(); @@ -66,8 +88,8 @@ impl Flag { self.value = v; // remove the flag and its value from the arguments - args.remove(i); - args.remove(i); + let range = i..=j; + args.drain(range); } return Status::ok(); } diff --git a/src/types/template_meta.rs b/src/types/template_meta.rs index 5d496da..13d7dfa 100644 --- a/src/types/template_meta.rs +++ b/src/types/template_meta.rs @@ -21,6 +21,7 @@ impl TemplateMeta { map.insert("description".to_string(), "".to_string()); map.insert("path".to_string(), ".".to_string()); map.insert(".source".to_string(), "".to_string()); + map.insert("command".to_string(), "".to_string()); let mut file_path = format!(".templates/{}/.templify.yaml", template_name); if !std::path::Path::new(&file_path).exists() { @@ -135,4 +136,9 @@ impl TemplateMeta { pub fn get_source(&self) -> String { self.map[".source"].clone() } + + /// Returns the command that is specified in the template meta information. + pub fn get_command(&self) -> String { + self.map["command"].clone() + } } diff --git a/src/utils/functions.rs b/src/utils/functions.rs index 49c2905..0214561 100644 --- a/src/utils/functions.rs +++ b/src/utils/functions.rs @@ -1,5 +1,7 @@ use crate::{logger, types::status::Status}; use chrono::Datelike; +use std::io::{Error, ErrorKind}; +use std::process::Command; use std::{io::Write, path::Path}; /// Check if templify is initialized in the current project @@ -122,3 +124,25 @@ pub(crate) fn handle_log_file(file: String) -> Status { logger::add_logger_entity_closure("file-log".to_string(), file_logger, file_logger_error); Status::ok() } + +/// Execute Command +pub fn execute_user_command(command: String) -> Result { + let (shell, flag) = if cfg!(target_os = "windows") { + ("cmd.exe", "/C") + } else { + ("sh", "-c") + }; + + let output = Command::new(shell).arg(flag).arg(command).output()?; + + if !output.status.success() { + let exit_code = output.status.code().unwrap_or(-1); + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(Error::new( + ErrorKind::Other, + format!("Command failed with exit code {}: {}", exit_code, stderr), + )); + } + + String::from_utf8(output.stdout).map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string())) +} diff --git a/tests/command_tests/generate_test.rs b/tests/command_tests/generate_test.rs index a4e78c6..ea4c18f 100644 --- a/tests/command_tests/generate_test.rs +++ b/tests/command_tests/generate_test.rs @@ -9,8 +9,8 @@ pub fn test() { utils::run_failure("tpy generate"); // test name matching and -strict flag - utils::run_successfully("tpy new Component -path src/$$name$$/subdir"); - utils::run_successfully("tpy new Command -path src/commands/subdir"); + utils::run_successfully( "tpy new Component -path src/$$name$$/subdir -command \"echo Generated template with name $$name$$\""); + utils::run_successfully("tpy new Command -path src/commands/subdir -command \'echo Generated template with name $$name$$\'"); utils::run_failure("tpy generate comp"); // Missing name utils::run_failure("tpy generate com test"); // not unique @@ -45,6 +45,7 @@ pub fn test() { .check_all_exists(); utils::run_successfully("tpy generate comp foo"); + log::contains_line("Generated template with name foo"); fs::dir("src") .dir("foo") @@ -109,6 +110,8 @@ pub fn test() { // test -force flag utils::run_successfully("tpy generate comm FOO"); + log::contains_line("Generated template with name FOO"); + fs::dir("src") .dir("commands") .dir("subdir")