diff --git a/Cargo.lock b/Cargo.lock index 5cbe22ae5..67d2f7bd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4994,6 +4994,7 @@ dependencies = [ "base64", "dirs-next", "filedescriptor", + "filenamegen", "k9", "log", "portable-pty", diff --git a/docs/changelog.md b/docs/changelog.md index 98602a6d1..ca96b632a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,7 @@ usually the best available version. As features stabilize some brief notes about them will accumulate here. * Fixed: ssh client would read `/etc/ssh/config` rather than the proper `/etc/ssh/ssh_config` +* Updated: ssh client now processes `Include` statements in ssh config * x11: support for [VoidSymbol](config/keys.md#voidsymbol) in key assignments. Thanks to [@digitallyserviced](https://github.com/digitallyserviced)! [#759](https://github.com/wez/wezterm/pull/759) * Fixed: UTF8-encoded-C1 control codes were not always recognized as control codes, and could result in a panic when later attempting to update the line. [#768](https://github.com/wez/wezterm/issues/768) * Fixed: `wezterm cli split-pane` didn't use the current working dir of the source pane. [#766](https://github.com/wez/wezterm/issues/766) diff --git a/wezterm-ssh/Cargo.toml b/wezterm-ssh/Cargo.toml index 7b1347deb..ac0f9081b 100644 --- a/wezterm-ssh/Cargo.toml +++ b/wezterm-ssh/Cargo.toml @@ -15,6 +15,7 @@ anyhow = "1.0" base64 = "0.13" dirs-next = "2.0" filedescriptor = { version="0.8", path = "../filedescriptor" } +filenamegen = "0.2" log = "0.4" portable-pty = { version="0.5", path = "../pty" } regex = "1" diff --git a/wezterm-ssh/src/config.rs b/wezterm-ssh/src/config.rs index 359152c4b..e3a0169ff 100644 --- a/wezterm-ssh/src/config.rs +++ b/wezterm-ssh/src/config.rs @@ -140,10 +140,70 @@ struct ParsedConfigFile { } impl ParsedConfigFile { - fn parse(s: &str) -> Self { + fn parse(s: &str, cwd: Option<&Path>) -> Self { let mut options = ConfigMap::new(); let mut groups = vec![]; + Self::parse_impl(s, cwd, &mut options, &mut groups); + + Self { options, groups } + } + + fn do_include( + pattern: &str, + cwd: Option<&Path>, + options: &mut ConfigMap, + groups: &mut Vec, + ) { + match filenamegen::Glob::new(&pattern) { + Ok(g) => { + match cwd + .as_ref() + .map(|p| p.to_path_buf()) + .or_else(|| std::env::current_dir().ok()) + { + Some(cwd) => { + for path in g.walk(&cwd) { + let path = if path.is_absolute() { + path + } else { + cwd.join(path) + }; + match std::fs::read_to_string(&path) { + Ok(data) => { + Self::parse_impl(&data, Some(&cwd), options, groups); + } + Err(err) => { + log::error!( + "error expanding `Include {}`: unable to open {}: {:#}", + pattern, + path.display(), + err + ); + } + } + } + } + None => { + log::error!( + "error expanding `Include {}`: unable to determine cwd", + pattern + ); + } + } + } + Err(err) => { + log::error!("error expanding `Include {}`: {:#}", pattern, err); + } + } + } + + fn parse_impl( + s: &str, + cwd: Option<&Path>, + options: &mut ConfigMap, + groups: &mut Vec, + ) { for line in s.lines() { let line = line.trim(); if line.is_empty() || line.starts_with('#') { @@ -177,6 +237,11 @@ impl ParsedConfigFile { patterns } + if k == "include" { + Self::do_include(v, cwd, options, groups); + continue; + } + if k == "host" { let patterns = parse_pattern_list(v); groups.push(MatchGroup { @@ -253,8 +318,6 @@ impl ParsedConfigFile { } } } - - Self { options, groups } } /// Apply configuration values that match the specified hostname to target, @@ -337,14 +400,15 @@ impl Config { /// and add that to the list of configs. pub fn add_config_string(&mut self, config_string: &str) { self.config_files - .push(ParsedConfigFile::parse(config_string)); + .push(ParsedConfigFile::parse(config_string, None)); } /// Open `path`, read its contents and parse it as an `ssh_config` file, /// adding that to the list of configs pub fn add_config_file>(&mut self, path: P) { - if let Ok(data) = std::fs::read_to_string(path) { - self.config_files.push(ParsedConfigFile::parse(&data)); + if let Ok(data) = std::fs::read_to_string(path.as_ref()) { + self.config_files + .push(ParsedConfigFile::parse(&data, path.as_ref().parent())); } }