zspell 0.3.3

Native Rust library for spellchecking, with a command line interface
Documentation
use std::borrow::Borrow;
use std::sync::Arc;

use hashbrown::Equivalent;

use super::parser::ParsedPersonalMeta;
use super::rule::AfxRule;
use crate::morph::MorphInfo;
use crate::parser_affix::ParsedRule;

/// Additional information attached to an entry in a dictionary
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Meta {
    stem: Arc<String>,
    source: Source,
}

impl Meta {
    pub(crate) fn new(stem_rc: Arc<String>, source: Source) -> Self {
        Self {
            stem: stem_rc,
            source,
        }
    }

    /// Return the stem of a word. Prefers the stem from the morph info if it is available
    pub fn stem(&self) -> &str {
        // If we have a dictionary source, check if we have a stem `MorphInfo`
        // and return it
        if let Source::Dict(morphvec) = &self.source {
            if let Some(stem) = morphvec.iter().find_map(|morph| {
                if let MorphInfo::Stem(st) = morph.borrow() {
                    Some(st)
                } else {
                    None
                }
            }) {
                return stem.as_str();
            }
        }

        &self.stem
    }

    pub fn source(&self) -> &Source {
        &self.source
    }
}

/// Source information
#[allow(clippy::box_collection)]
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Source {
    /// this meta came from an affix and has a full affix rule
    Affix(Arc<AfxRule>),
    /// this meta came from a .dic file, only contains morphinfo
    Dict(Box<Vec<Arc<MorphInfo>>>),
    /// this meta came from the personal dictionary
    /// String is the "friend" word
    Personal(Box<PersonalMeta>),
    /// The source is a raw text file with no additional metadata
    Raw,
}

impl Source {
    /// Add morphinfo, if any, to a vector
    pub fn push_morphs<'a>(&'a self, dest: &mut Vec<&'a MorphInfo>) {
        match self {
            // Unsure how to handle nesting types. Maybe need rule group number
            // in Affix source
            Source::Affix(_) => todo!(),
            Source::Dict(v) => v.iter().for_each(|val| dest.push(val)),
            Source::Personal(pm) => pm.morph.iter().for_each(|val| dest.push(val)),
            Source::Raw => (),
        }
    }
}

/// Representation of meta info for a personal dictionary
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PersonalMeta {
    friend: Option<Arc<String>>,
    morph: Vec<Arc<MorphInfo>>,
}

impl PersonalMeta {
    pub fn new(friend: Option<Arc<String>>, morph: Vec<Arc<MorphInfo>>) -> Self {
        Self { friend, morph }
    }
}

#[cfg(test)]
mod tests {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};

    use super::*;

    fn calculate_hash<T: Hash>(t: &T) -> u64 {
        let mut s = DefaultHasher::new();
        t.hash(&mut s);
        s.finish()
    }

    // FIXME
    // #[test]
    // fn check_hashes() {
    //     // validate that our owned & borrowed types have the same hash
    //     let owned = Extra {
    //         stem: "abcd".to_string(),
    //         source: Source::Personal(Box::new(PersonalMeta {
    //             friend: Some("efgh".to_owned()),
    //             morph: vec![MorphInfo::DerivPfx("xyz".to_owned())],
    //         })),
    //     };

    //     let meta = match owned.source {
    //         Source::Personal(ref m) => m,
    //         _ => panic!(),
    //     };

    //     let borrowed = ExtraBorrowed {
    //         stem: &owned.stem,
    //         source: SourceBorrowed::Personal {
    //             friend: meta.friend.as_ref(),
    //             morph: &meta.morph,
    //         },
    //     };

    //     let h1 = calculate_hash(&owned);
    //     let h2 = calculate_hash(&borrowed);

    //     assert_eq!(h1, h2);
    //     assert_eq!(&owned, &borrowed);
    //     assert_eq!(owned, borrowed.to_owned());
    // }
}