ferrocat-po 1.3.0

Performance-first PO parsing, serialization, and catalog update primitives for ferrocat.
Documentation
use std::collections::{BTreeMap, BTreeSet};

use super::{ApiError, NormalizedParsedCatalog};

pub(super) fn index_catalogs<'a>(
    catalogs: &'a [&'a NormalizedParsedCatalog],
    label: &str,
) -> Result<BTreeMap<String, &'a NormalizedParsedCatalog>, ApiError> {
    let mut index = BTreeMap::new();
    for catalog in catalogs {
        let locale = catalog
            .parsed_catalog()
            .locale
            .as_deref()
            .filter(|locale| !locale.trim().is_empty())
            .ok_or_else(|| {
                ApiError::InvalidArguments(format!(
                    "{label} requires every catalog to declare a locale"
                ))
            })?;
        if index.insert(locale.to_owned(), *catalog).is_some() {
            return Err(ApiError::InvalidArguments(format!(
                "{label} received duplicate catalog locale {locale:?}"
            )));
        }
    }
    Ok(index)
}

pub(super) fn select_target_locales(
    catalog_index: &BTreeMap<String, &NormalizedParsedCatalog>,
    source_locale: &str,
    requested_locales: &[&str],
    label: &str,
) -> Result<Vec<String>, ApiError> {
    if requested_locales.is_empty() {
        return Ok(catalog_index
            .keys()
            .filter(|locale| locale.as_str() != source_locale)
            .cloned()
            .collect());
    }

    let mut seen = BTreeSet::new();
    let mut locales = Vec::new();
    for locale in requested_locales {
        if *locale == source_locale || !seen.insert((*locale).to_owned()) {
            continue;
        }
        if !catalog_index.contains_key(*locale) {
            return Err(ApiError::InvalidArguments(format!(
                "{label} did not receive requested locale {locale:?}"
            )));
        }
        locales.push((*locale).to_owned());
    }
    Ok(locales)
}