use std::collections::{hash_map::Entry, HashMap};
use sha2::{Digest, Sha256};
use unicode_width::UnicodeWidthStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StringLength {
StripAnsi,
Unicode,
}
pub type MemoizedLenMap = HashMap<String, u16>;
mod to_from_string_impl {
use super::*;
impl std::str::FromStr for StringLength {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"strip_ansi" => Ok(Self::StripAnsi),
"unicode" => Ok(Self::Unicode),
_ => Err(format!("Invalid StringLength variant: {}", s)),
}
}
}
impl std::fmt::Display for StringLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::StripAnsi => write!(f, "strip_ansi"),
Self::Unicode => write!(f, "unicode"),
}
}
}
}
impl StringLength {
pub fn calculate(&self, input: &str, memoized_len_map: &mut MemoizedLenMap) -> u16 {
match self {
StringLength::Unicode => UnicodeWidthStr::width(input) as u16,
StringLength::StripAnsi => match memoized_len_map.entry(input.to_string()) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let stripped_input = strip_ansi::strip_ansi(input);
let stripped_input: &str = stripped_input.as_ref();
let length = UnicodeWidthStr::width(stripped_input) as u16;
entry.insert(length);
length
}
},
}
}
pub fn calculate_sha256(text: &str) -> u32 {
let mut hasher = Sha256::new();
hasher.update(text);
let result = hasher.finalize();
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&result.as_slice()[..4]);
u32::from_le_bytes(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::timed;
#[test]
fn test_sha256() {
let input = "foo";
let (hash, duration) = timed!({
let hash = StringLength::calculate_sha256(input);
assert_eq!(hash, 1806968364);
hash
});
println!("Execution time - string_length(Sha256): {:?}", duration);
assert_eq!(hash, 1806968364);
}
#[test]
fn test_strip_ansi_esc_seq_len_cache_speedup() {
let input = "\u{1b}[31mfoo\u{1b}[0m";
let memoized_len_map = &mut MemoizedLenMap::new();
assert!(!memoized_len_map.contains_key(input));
let (_, duration_uncached) = timed!({
let len = StringLength::StripAnsi.calculate(input, memoized_len_map);
assert_eq!(len, 3);
assert!(memoized_len_map.contains_key(input));
});
println!(
"Execution time - U string_length(StripAnsi): {:?}",
duration_uncached
);
let (_, duration_cached) = timed!({
let len = StringLength::StripAnsi.calculate(input, memoized_len_map);
assert_eq!(len, 3);
assert!(memoized_len_map.contains_key(input));
});
println!(
"Execution time - C string_length(StripAnsi): {:?}",
duration_cached
);
}
#[test]
fn test_unicode_string_len_no_cache() {
let input = "foo";
let memoized_len_map = &mut MemoizedLenMap::new();
assert!(!memoized_len_map.contains_key(input));
let (_, duration_uncached) = timed!({
let len = StringLength::Unicode.calculate(input, memoized_len_map);
assert_eq!(len, 3);
assert!(!memoized_len_map.contains_key(input));
});
println!(
"Execution time - U string_length(Unicode): {:?}",
duration_uncached
);
}
}