prmt 0.5.0

Ultra-fast, customizable shell prompt generator with zero-copy parsing
Documentation
use crate::error::{PromptError, Result};
use crate::module_trait::{Module, ModuleContext};
use std::env;

pub struct EnvModule;

impl Default for EnvModule {
    fn default() -> Self {
        Self::new()
    }
}

impl EnvModule {
    pub fn new() -> Self {
        Self
    }
}

impl Module for EnvModule {
    fn render(&self, format: &str, _context: &ModuleContext) -> Result<Option<String>> {
        if format.is_empty() {
            return Err(PromptError::InvalidFormat {
                module: "env".to_string(),
                format: format.to_string(),
                valid_formats: "Provide an environment variable name, e.g., {env:blue:USER}"
                    .to_string(),
            });
        }

        match env::var_os(format) {
            None => Ok(None),
            Some(value) if value.is_empty() => Ok(None),
            Some(value) => Ok(Some(value.to_string_lossy().into_owned())),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serial_test::serial;
    use std::env;
    use std::ffi::OsString;

    struct EnvVarGuard {
        key: String,
        original: Option<OsString>,
    }

    impl EnvVarGuard {
        fn set(key: &str, value: &str) -> Self {
            let original = env::var_os(key);
            unsafe {
                env::set_var(key, value);
            }
            Self {
                key: key.to_string(),
                original,
            }
        }

        fn unset(key: &str) -> Self {
            let original = env::var_os(key);
            unsafe {
                env::remove_var(key);
            }
            Self {
                key: key.to_string(),
                original,
            }
        }
    }

    impl Drop for EnvVarGuard {
        fn drop(&mut self) {
            if let Some(value) = &self.original {
                unsafe {
                    env::set_var(&self.key, value);
                }
            } else {
                unsafe {
                    env::remove_var(&self.key);
                }
            }
        }
    }

    #[test]
    #[serial]
    fn renders_value_when_variable_is_present() {
        let module = EnvModule::new();
        let _guard = EnvVarGuard::set("PRMT_TEST_ENV_PRESENT", "zenpie");

        let value = module
            .render("PRMT_TEST_ENV_PRESENT", &ModuleContext::default())
            .unwrap();

        assert_eq!(value, Some("zenpie".to_string()));
    }

    #[test]
    #[serial]
    fn returns_none_when_variable_missing() {
        let module = EnvModule::new();
        let _guard = EnvVarGuard::unset("PRMT_TEST_ENV_MISSING");

        let value = module
            .render("PRMT_TEST_ENV_MISSING", &ModuleContext::default())
            .unwrap();

        assert_eq!(value, None);
    }

    #[test]
    #[serial]
    fn returns_none_when_variable_empty() {
        let module = EnvModule::new();
        let _guard = EnvVarGuard::set("PRMT_TEST_ENV_EMPTY", "");

        let value = module
            .render("PRMT_TEST_ENV_EMPTY", &ModuleContext::default())
            .unwrap();

        assert_eq!(value, None);
    }

    #[test]
    fn errors_when_format_missing() {
        let module = EnvModule::new();
        let err = module.render("", &ModuleContext::default()).unwrap_err();

        match err {
            PromptError::InvalidFormat { module, .. } => assert_eq!(module, "env"),
            other => panic!("expected invalid format error, got {other:?}"),
        }
    }
}