use anyhow::Result;
#[derive(Debug, PartialEq)]
pub(super) enum Line {
Empty,
Host {
line_number: usize,
args: Vec<String>,
},
Match {
line_number: usize,
args: Vec<String>,
},
Include {
line_number: usize,
args: Vec<String>,
},
Generic {
line_number: usize,
keyword: String,
args: Vec<String>,
},
}
pub(super) fn split_args(input: &str) -> Result<Vec<String>> {
let mut i = 0;
let input: Vec<char> = input.chars().collect();
let mut output = Vec::<String>::new();
while i < input.len() {
if input[i] == ' ' || input[i] == '\t' {
i += 1;
continue;
}
if input[i] == '#' {
break; }
let mut current_arg = String::new();
let mut quote_state: char = '\0';
while i < input.len() {
let ch = input[i];
match (ch, quote_state) {
('\\', _) => {
let next = input.get(i + 1);
match next {
Some(nn @ ('\'' | '\"' | '\\')) => {
current_arg.push(*nn);
i += 1;
}
Some(_) | None => current_arg.push(ch), }
}
(' ' | '\t', '\0') => break, (q @ ('\'' | '\"'), '\0') => quote_state = q, (q1, q2) if q1 == q2 => quote_state = '\0', (c, _) => current_arg.push(c), }
i += 1;
}
anyhow::ensure!(quote_state == '\0', "unterminated quote");
output.push(current_arg);
i += 1;
}
Ok(output)
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod test {
use anyhow::{Context, Result, anyhow};
use assertables::{assert_contains_as_result, assert_eq_as_result};
use crate::config::ssh::split_args;
#[test]
fn arg_splitting() -> Result<()> {
for (input, expected) in [
("", vec![]),
("a", vec!["a"]),
(" a b ", vec!["a", "b"]),
(" a b # c d", vec!["a", "b"]),
(r#"a\ \' \"b"#, vec!["a\\", "'", "\"b"]),
(r#""a b" 'c d'"#, vec!["a b", "c d"]),
(r#""a \"b" '\'c d'"#, vec!["a \"b", "'c d"]),
] {
let msg = || format!("input \"{input}\" failed");
assert_eq_as_result!(split_args(input).with_context(msg)?, expected)
.map_err(|e| anyhow!(e))
.with_context(msg)?;
}
for (input, expected_msg) in [
("aaa\"bbb", "unterminated quote"),
("'", "unterminated quote"),
] {
let err = split_args(input).unwrap_err();
assert_contains_as_result!(err.to_string(), expected_msg)
.map_err(|e| anyhow!(e))
.with_context(|| format!("input \"{input}\" failed"))?;
}
Ok(())
}
}