use std::collections::BTreeSet; pub fn parse_ssh_config_hosts(config: &str) -> BTreeSet<String> { let mut hosts = BTreeSet::new(); let mut needs_another_line = false; for line in config.lines() { let line = line.trim_start(); if let Some(line) = line.strip_prefix("Host") { match line.chars().next() { Some('\\') => { needs_another_line = true; } Some('\n' | '\r') => { needs_another_line = false; } Some(c) if c.is_whitespace() => { parse_hosts_from(line, &mut hosts); } Some(_) | None => { needs_another_line = false; } }; if needs_another_line { parse_hosts_from(line, &mut hosts); needs_another_line = line.trim_end().ends_with('\\'); } else { needs_another_line = false; } } else if needs_another_line { needs_another_line = line.trim_end().ends_with('\\'); parse_hosts_from(line, &mut hosts); } else { needs_another_line = false; } } hosts} fn parse_hosts_from(line: &str, hosts: &mut BTreeSet<String>) { hosts.extend( line.split_whitespace() .filter(|field| !field.starts_with("!")) .filter(|field| !field.contains("*")) .filter(|field| !field.is_empty()) .map(|field| field.to_owned()), );} #[cfg(test)]mod tests { use super::*; #[test] fn test_thank_you_bjorn3() { let hosts = " Host * AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/id_ed25519 Host whatever.* User another Host !not_this User not_me Host something HostName whatever.tld Host linux bsd host3 User bjorn Host rpi user rpi hostname rpi.local Host \ somehost \ anotherhost Hostname 192.168.3.3"; let expected_hosts = BTreeSet::from_iter([ "something".to_owned(), "linux".to_owned(), "host3".to_owned(), "bsd".to_owned(), "rpi".to_owned(), "somehost".to_owned(), "anotherhost".to_owned(), ]); assert_eq!(expected_hosts, parse_ssh_config_hosts(hosts)); }}
use std::collections::BTreeSet;
pub fn parse_ssh_config_hosts(config: &str) -> BTreeSet<String> {
let mut hosts = BTreeSet::new();
let mut needs_another_line = false;
for line in config.lines() {
let line = line.trim_start();
if let Some(line) = line.strip_prefix("Host") {
match line.chars().next() {
Some('\\') => {
needs_another_line = true;
}
Some('\n' | '\r') => {
needs_another_line = false;
}
Some(c) if c.is_whitespace() => {
parse_hosts_from(line, &mut hosts);
}
Some(_) | None => {
needs_another_line = false;
}
};
if needs_another_line {
parse_hosts_from(line, &mut hosts);
needs_another_line = line.trim_end().ends_with('\\');
} else {
needs_another_line = false;
}
} else if needs_another_line {
needs_another_line = line.trim_end().ends_with('\\');
parse_hosts_from(line, &mut hosts);
} else {
needs_another_line = false;
}
}
hosts
}
fn parse_hosts_from(line: &str, hosts: &mut BTreeSet<String>) {
hosts.extend(
line.split_whitespace()
.filter(|field| !field.starts_with("!"))
.filter(|field| !field.contains("*"))
.filter(|field| !field.is_empty())
.map(|field| field.to_owned()),
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_thank_you_bjorn3() {
let hosts = "
Host *
AddKeysToAgent yes
UseKeychain yes
IdentityFile ~/.ssh/id_ed25519
Host whatever.*
User another
Host !not_this
User not_me
Host something
HostName whatever.tld
Host linux bsd host3
User bjorn
Host rpi
user rpi
hostname rpi.local
Host \
somehost \
anotherhost
Hostname 192.168.3.3";
let expected_hosts = BTreeSet::from_iter([
"something".to_owned(),
"linux".to_owned(),
"host3".to_owned(),
"bsd".to_owned(),
"rpi".to_owned(),
"somehost".to_owned(),
"anotherhost".to_owned(),
]);
assert_eq!(expected_hosts, parse_ssh_config_hosts(hosts));
}
}