use super::ast::{ConfigAst, ConfigNode, DirectiveData, HostBlockData, Separator};
use toride_ssh_core::Result;
pub fn add_host(ast: &mut ConfigAst, name: &str, directives: Vec<(String, String)>) -> Result<()> {
if find_host_index(ast, name).is_some() {
return Err(toride_ssh_core::Error::DuplicateHost(name.to_owned()));
}
let nodes: Vec<ConfigNode> = directives
.into_iter()
.map(|(key, value)| {
ConfigNode::Directive(Box::new(DirectiveData {
keyword: key,
separator: Separator::Space,
value,
comment: None,
indent: String::new(),
}))
})
.collect();
let block = ConfigNode::HostBlock(Box::new(HostBlockData {
header: format!("Host {name}"),
patterns: vec![name.to_owned()],
nodes,
}));
let insert_pos = ast
.nodes
.iter()
.rposition(|n| matches!(n, ConfigNode::HostBlock(_) | ConfigNode::MatchBlock(_)))
.map_or(ast.nodes.len(), |i| i + 1);
if insert_pos > 0 {
let prev_is_blank = matches!(ast.nodes.get(insert_pos - 1), Some(ConfigNode::BlankLine));
if !prev_is_blank {
ast.nodes.insert(insert_pos, ConfigNode::BlankLine);
ast.nodes.insert(insert_pos + 1, block);
return Ok(());
}
}
ast.nodes.insert(insert_pos, block);
Ok(())
}
pub fn remove_host(ast: &mut ConfigAst, name: &str) -> Result<()> {
let idx = find_host_index(ast, name)
.ok_or_else(|| toride_ssh_core::Error::HostNotFound(name.to_owned()))?;
ast.nodes.remove(idx);
if idx > 0 && matches!(ast.nodes.get(idx - 1), Some(ConfigNode::BlankLine)) {
ast.nodes.remove(idx - 1);
}
Ok(())
}
pub fn rename_host(ast: &mut ConfigAst, old_name: &str, new_name: &str) -> Result<()> {
let idx = find_host_index(ast, old_name)
.ok_or_else(|| toride_ssh_core::Error::HostNotFound(old_name.to_owned()))?;
if find_host_index(ast, new_name).is_some() {
return Err(toride_ssh_core::Error::DuplicateHost(new_name.to_owned()));
}
if let ConfigNode::HostBlock(b) = &mut ast.nodes[idx] {
b.header = format!("Host {new_name}");
b.patterns = vec![new_name.to_owned()];
}
Ok(())
}
#[allow(dead_code, reason = "public API, not exercised in-workspace")]
pub fn add_directive_to_host(
ast: &mut ConfigAst,
name: &str,
key: &str,
value: &str,
) -> Result<()> {
let idx = find_host_index(ast, name)
.ok_or_else(|| toride_ssh_core::Error::HostNotFound(name.to_owned()))?;
if let Some(nodes) = ast.nodes[idx].as_host_block_mut() {
nodes.push(ConfigNode::Directive(Box::new(DirectiveData {
keyword: key.to_owned(),
separator: Separator::Space,
value: value.to_owned(),
comment: None,
indent: String::new(),
})));
}
Ok(())
}
#[allow(dead_code, reason = "public API, not exercised in-workspace")]
pub fn remove_directive_from_host(ast: &mut ConfigAst, name: &str, key: &str) -> Result<()> {
let idx = find_host_index(ast, name)
.ok_or_else(|| toride_ssh_core::Error::HostNotFound(name.to_owned()))?;
let key_lower = key.to_lowercase();
if let Some(nodes) = ast.nodes[idx].as_host_block_mut() {
nodes.retain(|node| {
if let Some((k, _)) = node.as_directive() {
!k.eq_ignore_ascii_case(&key_lower)
} else {
true
}
});
}
Ok(())
}
pub(crate) fn find_host_index(ast: &ConfigAst, name: &str) -> Option<usize> {
ast.nodes.iter().position(|node| {
if let ConfigNode::HostBlock(b) = node {
b.patterns.iter().any(|p| p == name)
} else {
false
}
})
}
#[cfg(test)]
#[path = "editor.test.rs"]
mod tests;