polib 0.3.0

Read, manipulate and store translation data in GNU gettext PO format.
Documentation
/// The plural form resolution rule of the target language.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CatalogPluralRules {
    /// Total number of plural forms, including singular form.
    pub nplurals: usize,
    /// The plural form resolution expression in the function of n.
    pub expr: String,
}

impl Default for CatalogPluralRules {
    fn default() -> Self {
        Self {
            nplurals: 1,
            expr: String::from("0"),
        }
    }
}

/// Error type when parsing an invalid plural rules.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PluralRulesError {
    message: String,
}

impl From<&str> for PluralRulesError {
    fn from(s: &str) -> Self {
        Self {
            message: s.to_string(),
        }
    }
}

impl From<String> for PluralRulesError {
    fn from(s: String) -> Self {
        Self { message: s }
    }
}

impl From<std::num::ParseIntError> for PluralRulesError {
    fn from(value: std::num::ParseIntError) -> Self {
        Self {
            message: format!("cannot parse nplurals: {}", value),
        }
    }
}

impl std::fmt::Display for PluralRulesError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "invalid plural rules: {}", self.message)
    }
}

impl std::error::Error for PluralRulesError {}

impl CatalogPluralRules {
    /// Parse a plural resolution rules from string form stored in PO file.
    pub fn parse(rules: &str) -> Result<Self, PluralRulesError> {
        let mut nplurals: Option<usize> = None;
        let mut expr: Option<&str> = None;
        for rule in rules.split(';') {
            let rule = rule.trim();
            if rule.is_empty() {
                continue;
            }
            if let Some((key, value)) = rule.split_once('=') {
                match key {
                    "nplurals" => {
                        nplurals = Some(value.parse()?);
                    }
                    "plural" => {
                        expr = Some(value);
                    }
                    unrecognized => {
                        return Err(PluralRulesError::from(format!(
                            "unrecognized entry {}",
                            unrecognized
                        )));
                    }
                }
            } else {
                return Err(PluralRulesError::from(format!("cannot parse {}", rule)));
            }
        }
        if let (Some(nplurals), Some(expr)) = (nplurals, expr) {
            if nplurals == 0 {
                Err(PluralRulesError::from("nplurals equals to zero"))
            } else if expr.is_empty() {
                Err(PluralRulesError::from("plural rule expression is empty"))
            } else {
                Ok(CatalogPluralRules {
                    nplurals,
                    expr: String::from(expr),
                })
            }
        } else if nplurals.is_none() {
            Err(PluralRulesError::from("nplurals does not exist"))
        } else if expr.is_none() {
            Err(PluralRulesError::from(
                "plural rule expression does not exist",
            ))
        } else {
            std::unreachable!();
        }
    }

    /// Dump the plural resolution rules to string form to write to a PO file.
    pub fn dump(&self) -> String {
        format!("nplurals={}; plural={};", self.nplurals, self.expr)
    }
}