Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/commands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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())
Expand Down
7 changes: 7 additions & 0 deletions src/commands/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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();
Expand Down
17 changes: 13 additions & 4 deletions src/data.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
}

Expand Down
30 changes: 26 additions & 4 deletions src/types/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,41 @@ 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();
}
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();
}
Expand Down
6 changes: 6 additions & 0 deletions src/types/template_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
}
}
24 changes: 24 additions & 0 deletions src/utils/functions.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<String, std::io::Error> {
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()))
}
7 changes: 5 additions & 2 deletions tests/command_tests/generate_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
Loading