swh 0.2.1

A CLI for Switch Hosts
Documentation
use crate::core::kdl::config::Config;
use crate::core::kdl::host::Host;
use regex::Regex;
use std::fmt;
use std::fmt::Formatter;
use std::net::IpAddr;
use std::str::FromStr;
use std::string::ParseError;

#[derive(Debug, Clone)]
pub struct HostEntry {
    pub ip: Option<IpAddr>,
    pub name: Option<String>,
    pub aliases: Option<Vec<String>>,
    pub comment: Option<String>,
}

impl HostEntry {
    pub fn empty() -> Self {
        Self {
            ip: None,
            name: None,
            aliases: None,
            comment: None,
        }
    }

    pub fn comment(comment: &str) -> Self {
        Self {
            ip: None,
            name: None,
            aliases: None,
            comment: Some(comment.to_string()),
        }
    }
}

impl PartialEq for HostEntry {
    fn eq(&self, other: &Self) -> bool {
        self.ip == other.ip
            && self.name == other.name
            && self.aliases == other.aliases
            && self.comment == other.comment
    }
}

impl fmt::Display for HostEntry {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        if self.ip.is_none() && self.comment.is_some() {
            write!(f, "# {}", self.comment.as_ref().unwrap())
        } else if self.ip.is_some() {
            f.write_fmt(format_args!(
                "{}\t{}",
                self.ip.unwrap(),
                self.name.as_ref().unwrap()
            ))?;
            if self.aliases.is_some() {
                write!(
                    f,
                    "\t{}",
                    self.aliases.as_ref().unwrap_or(&vec![]).join(" ")
                )?;
            }
            if self.comment.is_some() {
                write!(f, "\t# {}", self.comment.as_ref().unwrap())?;
            }
            Ok(())
        } else {
            write!(f, "")
        }
    }
}

impl FromStr for HostEntry {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let comment = Regex::new(r"^#\s*(?P<comment>.+)\s*$").unwrap();
        let entry =
            Regex::new(r"^(?P<ip>.+?)\s+(?P<name>.+?)(\s+(?P<aliases>[^#]+))?(#\s*(?P<c>.*))?$")
                .unwrap();
        if comment.is_match(s) {
            Ok(comment
                .captures(s)
                .map(|cap| {
                    HostEntry::comment(cap.name("comment").map(|t| t.as_str().trim()).unwrap_or(""))
                })
                .unwrap())
        } else if entry.is_match(s) {
            let caps = entry.captures(s).unwrap();
            let ip_str = caps.name("ip").map(|t| t.as_str()).unwrap();

            let ip: Option<IpAddr> = match ip_str.parse() {
                Ok(x) => Some(x),
                _ => None,
            };

            let name = caps.name("name").map(|t| String::from(t.as_str().trim()));
            let alias = caps
                .name("aliases")
                .map(|t| String::from(t.as_str().trim()));
            let alias_vec: Option<Vec<String>> = alias.map(|a| {
                a.split_whitespace()
                    .map(String::from)
                    .collect::<Vec<String>>()
            });
            let comment = caps
                .name("comment")
                .map(|t| String::from(t.as_str().trim()));
            Ok(HostEntry {
                ip,
                name,
                aliases: alias_vec,
                comment,
            })
        } else {
            Ok(HostEntry::empty())
        }
    }
}

impl From<Config> for Vec<HostEntry> {
    fn from(config: Config) -> Self {
        if config.enabled_envs().is_empty() {
            return vec![];
        }
        let mut v = Vec::new();
        v.push(HostEntry::comment(super::host_file::SWH_CONTENT_START));
        for env in config.enabled_envs().iter() {
            for host in env.hosts.to_vec().iter() {
                let entry: HostEntry = host.to_owned().into();
                v.push(entry);
            }
        }
        v.push(HostEntry::comment(super::host_file::SWH_CONTENT_END));
        v
    }
}

impl From<Host> for HostEntry {
    fn from(host: Host) -> Self {
        Self {
            ip: Some(host.ip.parse::<IpAddr>().unwrap()),
            name: Some(host.name.clone()),
            aliases: Some(host.aliases()),
            comment: None,
        }
    }
}