dictgen 0.5.3

Compile-time case-insensitive map
Documentation
#[cfg(feature = "codegen")]
pub struct OrderedMapGen<'g> {
    pub(crate) r#gen: crate::DictGen<'g>,
    pub(crate) unicase: bool,
    pub(crate) unicode: bool,
}

#[cfg(feature = "codegen")]
impl OrderedMapGen<'_> {
    pub fn unicase(mut self, yes: bool) -> Self {
        self.unicase = yes;
        self
    }

    pub fn unicode(mut self, yes: bool) -> Self {
        self.unicode = yes;
        self
    }

    pub fn write<W: std::io::Write, V: std::fmt::Display>(
        &self,
        file: &mut W,
        data: impl Iterator<Item = (impl AsRef<str>, V)>,
    ) -> Result<(), std::io::Error> {
        let mut data: Vec<_> = data.collect();
        data.sort_unstable_by_key(|v| unicase::UniCase::new(v.0.as_ref().to_owned()));

        let name = self.r#gen.name;
        let key_type = self.key_type();
        let value_type = self.r#gen.value_type;

        let mut smallest = usize::MAX;
        let mut largest = usize::MIN;

        writeln!(
            file,
            "pub static {name}: dictgen::OrderedMap<{key_type}, {value_type}> = dictgen::OrderedMap {{"
        )?;
        writeln!(file, "    keys: &[")?;
        for (key, _value) in data.iter() {
            let key = key.as_ref();
            smallest = std::cmp::min(smallest, key.len());
            largest = std::cmp::max(largest, key.len());

            let key = self.key_new(key);

            writeln!(file, "      {key},")?;
        }
        if largest == 0 {
            smallest = 0;
        }
        writeln!(file, "    ],")?;
        writeln!(file, "    values: &[")?;
        for (_key, value) in data.iter() {
            writeln!(file, "      {value},")?;
        }
        writeln!(file, "    ],")?;
        writeln!(file, "    range: {smallest}..={largest},")?;
        writeln!(file, "}};")?;

        Ok(())
    }

    fn key_type(&self) -> &'static str {
        match (self.unicase, self.unicode) {
            (true, true) => "dictgen::InsensitiveStr<'static>",
            (true, false) => "dictgen::InsensitiveAscii<'static>",
            (false, _) => "&'static str",
        }
    }

    fn key_new(&self, key: &str) -> String {
        match (self.unicase, self.unicode) {
            (true, true) => {
                if key.is_ascii() {
                    format!("dictgen::InsensitiveStr::Ascii({key:?})")
                } else {
                    format!("dictgen::InsensitiveStr::Unicode({key:?})")
                }
            }
            (true, false) => format!("dictgen::InsensitiveAscii({key:?})"),
            (false, _) => format!("{key:?}"),
        }
    }
}

pub struct OrderedMap<K: 'static, V: 'static> {
    pub keys: &'static [K],
    pub values: &'static [V],
    pub range: core::ops::RangeInclusive<usize>,
}

impl<V> OrderedMap<crate::InsensitiveStr<'_>, V> {
    #[inline]
    pub fn find(&self, word: &'_ unicase::UniCase<&str>) -> Option<&'static V> {
        if self.range.contains(&word.len()) {
            self.keys
                .binary_search_by_key(word, |key| key.convert())
                .map(|i| &self.values[i])
                .ok()
        } else {
            None
        }
    }
}

impl<V> OrderedMap<crate::InsensitiveAscii<'_>, V> {
    #[inline]
    pub fn find(&self, word: &'_ unicase::Ascii<&str>) -> Option<&'static V> {
        if self.range.contains(&word.len()) {
            self.keys
                .binary_search_by_key(word, |key| key.convert())
                .map(|i| &self.values[i])
                .ok()
        } else {
            None
        }
    }
}

impl<V> OrderedMap<&str, V> {
    #[inline]
    pub fn find(&self, word: &'_ &str) -> Option<&'static V> {
        if self.range.contains(&word.len()) {
            self.keys.binary_search(word).map(|i| &self.values[i]).ok()
        } else {
            None
        }
    }
}