shvproto 6.1.1

Rust implementation of the SHV protocol
Documentation
#![allow(clippy::indexing_slicing, reason = "Lots of indexing here")]
use log::LevelFilter;

pub fn parse_log_verbosity<'a>(verbosity: &'a str, module_path: &'a str) -> Vec<(Option<&'a str>, LevelFilter)> {
    verbosity
        .split(',')
        .map(|module_level_str| {
            let (name, level) = module_level_str
                .split_once('=')
                .unwrap_or((module_level_str, "D"));
            let level = if level.is_empty() { "D" } else { level };
            let module = if name.is_empty() {
                None
            } else if name == "." {
                Some(module_path)
            } else {
                Some(name)
            };
            let level = match level {
                "O" => LevelFilter::Off,
                "E" => LevelFilter::Error,
                "W" => LevelFilter::Warn,
                "I" => LevelFilter::Info,
                "D" => LevelFilter::Debug,
                _ => LevelFilter::Trace,
            };
            (module, level)
        })
        .collect::<Vec<_>>()
}

pub fn hex_array(data: &[u8]) -> String {
    let mut ret = "[".to_string();
    for b in data {
        if ret.len() > 1 {
            ret += ",";
        }
        ret += &format!("0x{b:02x}");
    }
    ret += "]";
    ret
}
pub fn hex_dump(data: &[u8]) -> String {
    let mut ret = String::default();
    let mut hex_line = String::default();
    let mut char_line = String::default();
    let box_size = (data.len() / 16 + 1) * 16 + 1;
    for i in 0..box_size {
        let byte = if i < data.len() { Some(data[i]) } else { None };
        if i % 16 == 0 {
            ret += &hex_line;
            ret += &char_line;
            if byte.is_some() {
                if i > 0 {
                    ret += "\n";
                }
                ret += &format!("{i:04x} ");
            }
            hex_line.clear();
            char_line.clear();
        }
        let hex_str = byte.map_or_else(|| "   ".to_string(), |b| format!("{b:02x} "));
        let c_str = byte.map_or_else(|| " ".to_string(), |b| {
            let c = b as char;
            let c = if c >= ' ' && c < (127 as char) { c } else { '.' };
            c.to_string()
        });
        hex_line += &hex_str;
        char_line += &c_str;
    }
    ret
}

#[cfg(test)]
mod tests {
    use std::iter::zip;

    use log::LevelFilter;

    use super::parse_log_verbosity;

    #[test]
    fn log_verbosity() {
        let current_module = module_path!();
        let data = vec![
            ("", vec![(None, LevelFilter::Debug)]),
            (".", vec![(Some(current_module), LevelFilter::Debug)]),
            (".=I", vec![(Some(current_module), LevelFilter::Info)]),
            ("mod", vec![(Some("mod"), LevelFilter::Debug)]),
            ("foo,bar", vec![(Some("foo"), LevelFilter::Debug), (Some("bar"), LevelFilter::Debug)]),
            ("foo::bar=I", vec![(Some("foo::bar"), LevelFilter::Info)]),
            ("=W,foo", vec![(None, LevelFilter::Warn), (Some("foo"), LevelFilter::Debug)]),
            ("foo=I,bar=E", vec![(Some("foo"), LevelFilter::Info), (Some("bar"), LevelFilter::Error)]),
            ("foo=O", vec![(Some("foo"), LevelFilter::Off)]),
        ];
        for (verbosity, expected_result) in data {
            let result = parse_log_verbosity(verbosity, current_module);
            assert_eq!(result.len(), expected_result.len(), "verbosity: `{verbosity}`");
            for ((module, filter), (module_expected, filter_expected)) in zip(result, expected_result) {
                assert!(module == module_expected && filter == filter_expected, "verbosity: `{verbosity}`");
            }
        }
    }
}