diff --git a/Cargo.lock b/Cargo.lock index 55afb19..2358cd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,22 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -375,6 +391,12 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "lock_api" version = "0.4.11" @@ -591,6 +613,19 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -631,9 +666,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -716,10 +751,12 @@ dependencies = [ "ratatui", "regex", "serde", + "serde_json", "shellexpand", "shlex", "strum", "strum_macros", + "tempfile", "tui-input", "unicode-width", ] @@ -790,6 +827,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.58" diff --git a/Cargo.toml b/Cargo.toml index 3d4696f..418056f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,11 @@ itertools = "0.12.1" ratatui = "0.26.1" regex = { version = "1.10.3", default-features = false, features = ["std"] } serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.115" shellexpand = "3.1.0" shlex = "1.3.0" strum = "0.26.1" strum_macros = "0.26.1" +tempfile = "3.10.1" tui-input = "0.8.0" unicode-width = "0.1.11" diff --git a/src/main.rs b/src/main.rs index 5acba1b..a9c72c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,11 @@ struct Args { sort: bool, /// Handlebars template of the command to execute - #[arg(short, long, default_value = "ssh \"{{{name}}}\"")] + #[arg( + short, + long, + default_value = "ssh \"{{{name}}}\" -F \"{{{config_file}}}\"" + )] template: String, /// Exit after ending the SSH session diff --git a/src/ssh.rs b/src/ssh.rs index cc79745..725959b 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -2,8 +2,10 @@ use anyhow::anyhow; use handlebars::Handlebars; use itertools::Itertools; use serde::Serialize; +use serde_json::json; use std::collections::VecDeque; use std::process::Command; +use tempfile::NamedTempFile; use crate::ssh_config::{self, parser_error::ParseError, HostVecExt}; @@ -27,9 +29,29 @@ impl Host { /// # Panics /// /// Will panic if the regex cannot be compiled. - pub fn run_command_template(&self, pattern: &str) -> anyhow::Result<()> { + pub fn run_command_template<'a>( + &self, + pattern: &str, + config_paths: &Vec, + ) -> anyhow::Result<()> { let handlebars = Handlebars::new(); - let rendered_command = handlebars.render_template(pattern, &self)?; + + let mut temp_file = None; + + let mut template_data = json!(&self); + template_data["config_file"] = match config_paths.len() { + 1 => json!(config_paths[0]), + _ => { + let new_temp_file = single_config_file(config_paths)?; + let temp_file_path = new_temp_file.path().to_str().unwrap().to_string(); + + temp_file = Some(new_temp_file); + + json!(temp_file_path) + } + }; + + let rendered_command = handlebars.render_template(pattern, &template_data)?; println!("Running command: {rendered_command}"); @@ -40,6 +62,8 @@ impl Host { let command = args.pop_front().ok_or(anyhow!("Failed to get command"))?; let status = Command::new(command).args(args).spawn()?.wait()?; + drop(temp_file); + if !status.success() { std::process::exit(status.code().unwrap_or(1)); } @@ -48,6 +72,21 @@ impl Host { } } +fn single_config_file(config_paths: &Vec) -> anyhow::Result { + let temp_file = NamedTempFile::with_prefix("sshs-")?; + + // Include all config files + let includes = config_paths + .iter() + .map(|path| format!("Include {}", path)) + .join("\n"); + + // Write to temporary file + std::fs::write(temp_file.path(), includes)?; + + Ok(temp_file) +} + #[derive(Debug)] pub enum ParseConfigError { Io(std::io::Error), diff --git a/src/ui.rs b/src/ui.rs index 3ee6e63..9076124 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -179,7 +179,10 @@ impl App { restore_terminal(terminal).expect("Failed to restore terminal"); - host.run_command_template(&self.config.command_template)?; + host.run_command_template( + &self.config.command_template, + &self.config.config_paths, + )?; setup_terminal(terminal).expect("Failed to setup terminal");