use std::{collections::BTreeMap, fs, io};
pub type HostMap = BTreeMap<String, String>;
pub fn parse_ssh_config<S: AsRef<str>>(config: S) -> Result<HostMap, io::Error> {
let config = config.as_ref();
let mut map = BTreeMap::new();
let mut token = String::new();
let mut line = vec![];
let mut skip_line = false;
let mut stanza = String::new();
let mut key = true;
for c in config.chars() {
if skip_line {
if c == '\n' {
skip_line = false;
}
continue;
}
if c == '#' {
skip_line = true;
line.push(token);
token = String::new();
} else if key && (c == ' ' || c == '=') {
if !token.is_empty() {
line.push(token);
token = String::new();
key = false;
}
} else if c == '\n' {
if !token.is_empty() {
line.push(token);
token = String::new();
key = true;
}
if line.is_empty() {
continue;
} else if line.len() != 2 {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("can't parse line: {:?}", line),
));
} else {
match line[0].as_ref() {
"Host" => stanza = line[1].clone(),
"Hostname" => {
if stanza.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("can't parse line: {:?}", line),
));
}
if stanza != "*" {
map.insert(stanza.clone(), line[1].clone());
}
stanza.clear();
}
_ => {}
}
line.clear();
}
} else {
token.push(c);
}
}
Ok(map)
}
pub fn load_ssh_config() -> Result<HostMap, io::Error> {
let path = format!("{}/.ssh/config", env!("HOME"));
parse_ssh_config(&fs::read_to_string(path)?)
}