From 3fc0415a0e08b90ef32d8e7379deabe46695e302 Mon Sep 17 00:00:00 2001 From: Nathanael DEMACON Date: Fri, 29 Mar 2024 14:16:14 +0100 Subject: [PATCH 1/4] fix: pass config file to ssh and handle multiple config files Signed-off-by: Nathanael DEMACON --- Cargo.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 ++ src/main.rs | 6 +++++- src/ssh.rs | 43 +++++++++++++++++++++++++++++++++++++++++-- src/ui.rs | 5 ++++- 5 files changed, 103 insertions(+), 6 deletions(-) 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"); From bb1616945841723fd3dd739f081307c53544a9be Mon Sep 17 00:00:00 2001 From: Nathanael DEMACON Date: Fri, 29 Mar 2024 14:20:37 +0100 Subject: [PATCH 2/4] refactor: fix linter issues Signed-off-by: Nathanael DEMACON --- src/ssh.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/ssh.rs b/src/ssh.rs index 725959b..221f3ff 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -29,26 +29,25 @@ impl Host { /// # Panics /// /// Will panic if the regex cannot be compiled. - pub fn run_command_template<'a>( + pub fn run_command_template( &self, pattern: &str, - config_paths: &Vec, + config_paths: &[String], ) -> anyhow::Result<()> { let handlebars = Handlebars::new(); 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(); + template_data["config_file"] = if config_paths.len() == 1 { + json!(config_paths[0]) + } else { + 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); + temp_file = Some(new_temp_file); - json!(temp_file_path) - } + json!(temp_file_path) }; let rendered_command = handlebars.render_template(pattern, &template_data)?; @@ -72,13 +71,13 @@ impl Host { } } -fn single_config_file(config_paths: &Vec) -> anyhow::Result { +fn single_config_file(config_paths: &[String]) -> anyhow::Result { let temp_file = NamedTempFile::with_prefix("sshs-")?; // Include all config files let includes = config_paths .iter() - .map(|path| format!("Include {}", path)) + .map(|path| format!("Include {path}")) .join("\n"); // Write to temporary file From a2bd15094250297b3077b6c7f0b1784e4637af80 Mon Sep 17 00:00:00 2001 From: Nathanael DEMACON Date: Fri, 29 Mar 2024 14:28:18 +0100 Subject: [PATCH 3/4] refactor: use `serde_json::to_value` Signed-off-by: Nathanael DEMACON --- src/ssh.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ssh.rs b/src/ssh.rs index 221f3ff..9231adf 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -2,7 +2,6 @@ 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; @@ -38,16 +37,16 @@ impl Host { let mut temp_file = None; - let mut template_data = json!(&self); + let mut template_data = serde_json::to_value(self)?; template_data["config_file"] = if config_paths.len() == 1 { - json!(config_paths[0]) + serde_json::to_value(&config_paths[0])? } else { 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) + serde_json::to_value(temp_file_path)? }; let rendered_command = handlebars.render_template(pattern, &template_data)?; From 516856163e6b258418c59cf6ce319b0655dbb374 Mon Sep 17 00:00:00 2001 From: Nathanael DEMACON Date: Fri, 29 Mar 2024 14:56:40 +0100 Subject: [PATCH 4/4] refactor: clean up Signed-off-by: Nathanael DEMACON --- src/ssh.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ssh.rs b/src/ssh.rs index 9231adf..1551139 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -35,19 +35,17 @@ impl Host { ) -> anyhow::Result<()> { let handlebars = Handlebars::new(); - let mut temp_file = None; + let temp_file = (config_paths.len() >= 2) + .then(|| single_config_file(config_paths)) + .transpose()?; let mut template_data = serde_json::to_value(self)?; - template_data["config_file"] = if config_paths.len() == 1 { - serde_json::to_value(&config_paths[0])? - } else { - 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); - - serde_json::to_value(temp_file_path)? - }; + // If there are multiple config files, use the temporary file, otherwise use the first one + template_data["config_file"] = temp_file + .as_ref() + .map_or(&config_paths[0] as &str, |f| f.path().to_str().unwrap()) + .into(); let rendered_command = handlebars.render_template(pattern, &template_data)?;