From 7b2217886ca938e85594e11af589eb7308136a42 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 17 Mar 2026 09:19:36 +0100 Subject: [PATCH] added auto discovery and bumped version --- Cargo.toml.out | 33 ++++++++++++++ src/cli.rs | 4 +- src/main.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 Cargo.toml.out diff --git a/Cargo.toml.out b/Cargo.toml.out new file mode 100644 index 0000000..8a1a38f --- /dev/null +++ b/Cargo.toml.out @@ -0,0 +1,33 @@ +[[bin]] +name = "mould" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.102" +crossterm = "0.29.0" +dirs = "6.0.0" +env_logger = "0.11.9" +log = "0.4.29" +ratatui = "0.30.0" +serde_json = "1.0.149" +serde_yaml = "0.9.34" +thiserror = "2.0.18" +toml = "1.0.6" +tui-input = "0.15.0" + +[dependencies.clap] +features = ["derive"] +version = "4.6.0" + +[dependencies.serde] +features = ["derive"] +version = "1.0.228" + +[dev-dependencies] +tempfile = "3.27.0" + +[package] +authors = ["Nils Pukropp "] +edition = 2024 +name = "mould" +version = "0.3.0" diff --git a/src/cli.rs b/src/cli.rs index 0e8751f..9932add 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,8 +12,8 @@ use std::path::PathBuf; )] pub struct Cli { /// The input template file (e.g., .env.example, config.json.template, docker-compose.yml) - #[arg(required = true, value_name = "INPUT_FILE")] - pub input: PathBuf, + #[arg(required = false, value_name = "INPUT_FILE")] + pub input: Option, /// Optional output file. If not provided, it will be inferred. #[arg(short, long, value_name = "OUTPUT_FILE")] diff --git a/src/main.rs b/src/main.rs index 222991a..c1f1eb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,8 @@ use ratatui::{Terminal, backend::CrosstermBackend}; fn determine_output_path(input: &Path) -> PathBuf { let file_name = input.file_name().unwrap_or_default().to_string_lossy(); - if file_name == ".env.example" { + // Standard mappings + if file_name == ".env.example" || file_name == ".env.template" { return input.with_file_name(".env"); } @@ -37,11 +38,36 @@ fn determine_output_path(input: &Path) -> PathBuf { return input.with_file_name("compose.override.yaml"); } - if file_name.ends_with(".example.json") { - return input.with_file_name(file_name.replace(".example.json", ".json")); + // Pattern-based mappings + if let Some(base) = file_name.strip_suffix(".env.example") { + return input.with_file_name(format!("{}.env", base)); } - if file_name.ends_with(".template.json") { - return input.with_file_name(file_name.replace(".template.json", ".json")); + if let Some(base) = file_name.strip_suffix(".env.template") { + return input.with_file_name(format!("{}.env", base)); + } + if let Some(base) = file_name.strip_suffix(".example.json") { + return input.with_file_name(format!("{}.json", base)); + } + if let Some(base) = file_name.strip_suffix(".template.json") { + return input.with_file_name(format!("{}.json", base)); + } + if let Some(base) = file_name.strip_suffix(".example.yml") { + return input.with_file_name(format!("{}.yml", base)); + } + if let Some(base) = file_name.strip_suffix(".template.yml") { + return input.with_file_name(format!("{}.yml", base)); + } + if let Some(base) = file_name.strip_suffix(".example.yaml") { + return input.with_file_name(format!("{}.yaml", base)); + } + if let Some(base) = file_name.strip_suffix(".template.yaml") { + return input.with_file_name(format!("{}.yaml", base)); + } + if let Some(base) = file_name.strip_suffix(".example.toml") { + return input.with_file_name(format!("{}.toml", base)); + } + if let Some(base) = file_name.strip_suffix(".template.toml") { + return input.with_file_name(format!("{}.toml", base)); } input.with_extension(format!( @@ -50,6 +76,60 @@ fn determine_output_path(input: &Path) -> PathBuf { )) } +/// Discovers common configuration template files in the current directory. +fn find_input_file() -> Option { + let candidates = [ + ".env.example", + "compose.yml", + "docker-compose.yml", + ".env.template", + "compose.yaml", + "docker-compose.yaml", + ]; + + // Priority 1: Exact matches for well-known defaults + for name in &candidates { + let path = PathBuf::from(name); + if path.exists() { + return Some(path); + } + } + + // Priority 2: Pattern matches + if let Ok(entries) = std::fs::read_dir(".") { + let mut fallback = None; + for entry in entries.flatten() { + let name = entry.file_name(); + let name_str = name.to_string_lossy(); + + if name_str.ends_with(".env.example") + || name_str.ends_with(".env.template") + || name_str.ends_with(".example.json") + || name_str.ends_with(".template.json") + || name_str.ends_with(".example.yml") + || name_str.ends_with(".template.yml") + || name_str.ends_with(".example.yaml") + || name_str.ends_with(".template.yaml") + || name_str.ends_with(".example.toml") + || name_str.ends_with(".template.toml") + { + // Prefer .env.* or compose.* if multiple matches + if name_str.contains(".env") || name_str.contains("compose") { + return Some(entry.path()); + } + if fallback.is_none() { + fallback = Some(entry.path()); + } + } + } + if let Some(path) = fallback { + return Some(path); + } + } + + None +} + fn main() -> anyhow::Result<()> { let args = cli::parse(); @@ -64,11 +144,27 @@ fn main() -> anyhow::Result<()> { .format_timestamp(None) .init(); - let input_path = args.input; - if !input_path.exists() { - error!("Input file not found: {}", input_path.display()); - return Err(MouldError::FileNotFound(input_path.display().to_string()).into()); - } + let input_path = match args.input { + Some(path) => { + if !path.exists() { + error!("Input file not found: {}", path.display()); + return Err(MouldError::FileNotFound(path.display().to_string()).into()); + } + path + } + None => match find_input_file() { + Some(path) => { + info!("Discovered template: {}", path.display()); + path + } + None => { + error!("No template file provided and none discovered in current directory."); + println!("Usage: mould "); + println!("Supported defaults: .env.example, compose.yml, docker-compose.yml, etc."); + return Err(MouldError::FileNotFound("None".to_string()).into()); + } + }, + }; info!("Input: {}", input_path.display());