printenv2 0.0.3

A printenv rewrite in Rust
use crate::args::{ColorMode, EscapeMode};
use crate::env::{Env, RecordPair};
use crate::platform_ext::u8_vec_to_string;
use crate::AppResult;
use colored::{ColoredString, Colorize};
use std::collections::HashMap;
use std::io::Write;

pub struct Printer<'a> {
    pub null: bool,
    pub color: ColorMode,
    pub escape: EscapeMode,
    pub variables: Option<&'a Vec<String>>,
}

impl<'a> Default for Printer<'a> {
    fn default() -> Self {
        Self {
            null: false,
            color: ColorMode::Auto,
            escape: EscapeMode::No,
            variables: None,
        }
    }
}

enum FormatField {
    Key,
    Value,
}

impl<'a> Printer<'a> {
    fn format(&self, bytes: &[u8], field: &FormatField) -> ColoredString {
        match u8_vec_to_string(bytes) {
            Ok(string) => {
                let string = {
                    if self.escape == EscapeMode::No {
                        string
                    } else {
                        Self::escape(&string)
                    }
                };
                match field {
                    FormatField::Key => string.yellow(),
                    FormatField::Value => string.bright_white(),
                }
            }
            Err(string) => string.red(),
        }
    }

    fn escape(string: &str) -> String {
        string
            .chars()
            .map(|char| {
                if char.is_control() {
                    char.escape_default().collect()
                } else {
                    char.to_string()
                }
            })
            .collect()
    }

    fn print_with_variables(&self, env: &Env, variables: &[String]) -> AppResult<Vec<u8>> {
        let mut output = Vec::new();
        let mut dict: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
        for RecordPair(key, value) in env.iter() {
            assert_eq!(dict.insert(key.clone(), value.clone()), None);
        }

        for variable in variables {
            if let Some(value) = dict.get(variable.as_bytes()) {
                write!(
                    &mut output,
                    "{value}{separator}",
                    value = self.format(value, &FormatField::Value),
                    separator = if self.null { "\0" } else { "\n" },
                )?;
            }
        }
        Ok(output)
    }

    fn print_without_variables(&self, env: &Env) -> AppResult<Vec<u8>> {
        let mut output = Vec::new();
        let equal_sign = "=";
        for record_result in env.iter() {
            write!(
                &mut output,
                "{key}{equal_sign}{value}{separator}",
                key = self.format(&record_result.0, &FormatField::Key),
                equal_sign = equal_sign.white(),
                value = self.format(&record_result.1, &FormatField::Value),
                separator = if self.null { "\0" } else { "\n" },
            )?;
        }
        Ok(output)
    }

    pub fn print(&self, env: &Env) -> AppResult<Vec<u8>> {
        if self.color == ColorMode::Never {
            colored::control::set_override(false);
        }
        self.variables.map_or_else(
            || self.print_without_variables(env),
            |variables| self.print_with_variables(env, variables),
        )
    }
}

#[cfg(test)]
mod tests {
    use super::Printer;

    #[test]
    fn escape() {
        let cases = vec![("String", "String"), ("a\nb", "a\\nb"), ("中文", "中文")];
        for case in cases {
            assert_eq!(Printer::escape(case.0), case.1);
        }
    }
}