use super::{Context, Module}; use std::ffi::OsString; use crate::config::RootModuleConfig; use crate::configs::hostname::HostnameConfig; use crate::formatter::StringFormatter; /// Creates a module with the system hostname /// /// Will display the hostname if all of the following criteria are met: /// - hostname.disabled is absent or false /// - hostname.ssh_only is false OR the user is currently connected as an SSH session (`$SSH_CONNECTION`) pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("hostname"); let config: HostnameConfig = HostnameConfig::try_load(module.config); let ssh_connection = context.get_env("SSH_CONNECTION"); if config.ssh_only && ssh_connection.is_none() { return None; } let os_hostname: OsString = gethostname::gethostname(); let host = match os_hostname.into_string() { Ok(host) => host, Err(bad) => { log::warn!("hostname is not valid UTF!\n{:?}", bad); return None; } }; //rustc doesn't let you do an "if" and an "if let" in the same if statement // if this changes in the future this can become a lot cleaner let host = if !config.trim_at.is_empty() { if let Some(index) = host.find(config.trim_at) { host.split_at(index).0 } else { host.as_ref() } } else { host.as_ref() }; let parsed = StringFormatter::new(config.format).and_then(|formatter| { formatter .map_style(|variable| match variable { "style" => Some(Ok(config.style)), _ => None, }) .map(|variable| match variable { "hostname" => Some(Ok(host)), _ => None, }) .parse(None) }); module.set_segments(match parsed { Ok(segments) => segments, Err(error) => { log::warn!("Error in module `hostname`:\n{}", error); return None; } }); Some(module) } #[cfg(test)] mod tests { use crate::test::ModuleRenderer; use ansi_term::{Color, Style}; use std::io; macro_rules! get_hostname { () => { if let Some(hostname) = gethostname::gethostname().into_string().ok() { hostname } else { println!( "hostname was not tested because gethostname failed! \ This could be caused by your hostname containing invalid UTF." ); return Ok(()); } }; } #[test] fn ssh_only_false() -> io::Result<()> { let hostname = get_hostname!(); let actual = ModuleRenderer::new("hostname") .config(toml::toml! { [hostname] ssh_only = false trim_at = "" }) .collect(); let expected = Some(format!("{} in ", style().paint(hostname))); assert_eq!(expected, actual); Ok(()) } #[test] fn no_ssh() -> io::Result<()> { let actual = ModuleRenderer::new("hostname") .config(toml::toml! { [hostname] ssh_only = true }) .collect(); let expected = None; assert_eq!(expected, actual); Ok(()) } #[test] fn ssh() -> io::Result<()> { let hostname = get_hostname!(); let actual = ModuleRenderer::new("hostname") .config(toml::toml! { [hostname] ssh_only = true trim_at = "" }) .env("SSH_CONNECTION", "something") .collect(); let expected = Some(format!("{} in ", style().paint(hostname))); assert_eq!(expected, actual); Ok(()) } #[test] fn no_trim_at() -> io::Result<()> { let hostname = get_hostname!(); let actual = ModuleRenderer::new("hostname") .config(toml::toml! { [hostname] ssh_only = false trim_at = "" }) .collect(); let expected = Some(format!("{} in ", style().paint(hostname))); assert_eq!(expected, actual); Ok(()) } #[test] fn trim_at() -> io::Result<()> { let hostname = get_hostname!(); let (remainder, trim_at) = hostname.split_at(1); let actual = ModuleRenderer::new("hostname") .config(toml::toml! { [hostname] ssh_only = false trim_at = trim_at }) .collect(); let expected = Some(format!("{} in ", style().paint(remainder))); assert_eq!(expected, actual); Ok(()) } fn style() -> Style { Color::Green.bold().dimmed() } }