printenv2 0.0.3

A printenv rewrite in Rust
use crate::args::KeyOrder;
use crate::platform_ext;
use std::cmp::Ordering;
use std::slice::Iter;

#[derive(Eq, Debug)]
pub struct RecordPair(pub Vec<u8>, pub Vec<u8>);

impl PartialEq<Self> for RecordPair {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq(&other.0)
    }
}

impl PartialOrd<Self> for RecordPair {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for RecordPair {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.cmp(&other.0)
    }
}

#[derive(Debug, PartialEq)]
pub struct Env(pub Vec<RecordPair>);

impl Env {
    pub fn iter(&self) -> Iter<'_, RecordPair> {
        self.0.iter()
    }

    pub fn sort_by_key(&mut self, key_order: KeyOrder) {
        // Sort results if needed
        match key_order {
            KeyOrder::Asc => {
                self.0.sort();
            }
            KeyOrder::Desc => {
                self.0.sort_by(|a, b| b.cmp(a));
            }
        }
    }
}

#[cfg(remote_env)]
pub mod remote {
    use crate::AppResult;
    pub fn get_environment_string(pid: u32) -> AppResult<Vec<u8>> {
        #[cfg(target_os = "linux")]
        {
            crate::remote_linux_procfs::get_environment_string(pid)
        }

        #[cfg(unix_kvm)]
        {
            crate::remote_unix_kvm::get_environment_string(pid)
        }

        #[cfg(target_family = "windows")]
        {
            crate::remote_windows::get_environment_string(pid)
        }
    }

    #[test]
    fn test_get_environment_string() {
        use crate::args::ColorMode;
        use crate::printer::Printer;

        let actual = get_environment_string(std::process::id()).unwrap();
        let expected = super::Env::new();
        let printer = Printer {
            null: true,
            color: ColorMode::Never,
            ..Default::default()
        };
        assert_eq!(actual, printer.print(&expected).unwrap());
    }
}

fn parse_record_pair(record: &[u8]) -> Option<RecordPair> {
    record
        .iter()
        .position(|c| b'=' == *c)
        .map(|i| RecordPair((&record[..i]).to_vec(), (&record[i + 1..]).to_vec()))
}

impl From<Vec<u8>> for Env {
    fn from(env_string: Vec<u8>) -> Self {
        Self(
            env_string
                .split(|c| *c == 0)
                .filter_map(parse_record_pair)
                .collect(),
        )
    }
}

impl Env {
    pub fn new() -> Self {
        Self(
            std::env::vars_os()
                .map(|(key, value)| {
                    RecordPair(
                        platform_ext::os_string_to_u8_vec(&key),
                        platform_ext::os_string_to_u8_vec(&value),
                    )
                })
                .collect(),
        )
    }
}

#[cfg(test)]
mod test {
    use super::{Env, RecordPair};

    #[test]
    fn parse_records_by_env_string() {
        let cases = vec![(
            b"a=b\0c=d\0",
            Env(vec![
                RecordPair(b"a".to_vec(), b"b".to_vec()),
                RecordPair(b"c".to_vec(), b"d".to_vec()),
            ]),
        )];

        for case in cases {
            let env_obj = Env::from(case.0.to_vec());
            assert_eq!(env_obj, case.1);
        }
    }
}