Experimenting with more structured ways to handle command-line input/output in Rust
use std::collections::HashMap;
use std::path::PathBuf;

use icu_locale::Locale;
use miette::Diagnostic;
use thiserror::Error;
use wax::{BuildError, Glob, WalkEntry, WalkError};

#[derive(Diagnostic, Debug, Error)]
#[error(transparent)]
pub enum Error {
    Build(#[from] BuildError),
    Walk(#[from] WalkError),
    #[error("Directory could not be matched by glob in macro attribute")]
    NoMatchesInEntry {
        path: PathBuf,
        complete_match: String,
    },
    #[error("Glob did not match any valid files")]
    NoMatchesInGlob,
    #[error("Invalid locale identifier")]
    InvalidLocale {
        path: PathBuf,
        locale: String,
        source: icu_locale::ParseError,
    },
}

pub fn locales(attribute: &syn::LitStr) -> Result<HashMap<Locale, PathBuf>, Error> {
    // Read the fluent file at the given path
    let manifest_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
    let attribute_glob = attribute.value();

    let mut locales = HashMap::new();

    let glob = Glob::new(&attribute_glob)?;
    let entries = glob
        .walk(&manifest_root)
        .collect::<Result<Vec<WalkEntry>, WalkError>>()?;

    // Emit all glob-related diagnostics
    for diagnostic in glob.diagnose() {
        eprintln!("{diagnostic:?}");
    }

    for entry in &entries {
        // TODO: this assumes that the locale is the first capture
        let captured_locale = if let Some(captured) = entry.matched().get(1) {
            captured
        } else {
            return Err(Error::NoMatchesInEntry {
                path: entry.path().to_path_buf(),
                complete_match: entry.matched().complete().to_string(),
            });
        };

        // Captured directories may have a suffix of `/`
        let stripped_locale = captured_locale.strip_suffix('/').unwrap_or(captured_locale);
        let locale =
            Locale::try_from_str(stripped_locale).map_err(|source| Error::InvalidLocale {
                path: entry.path().to_path_buf(),
                locale: stripped_locale.to_string(),
                source,
            })?;

        // Insert this locale (and make sure it's unique!)
        let previous_value = locales.insert(locale, entry.path().to_path_buf());
        assert!(previous_value.is_none());
    }

    // Make sure there was at least one file matched
    if locales.is_empty() {
        return Err(Error::NoMatchesInGlob);
    }

    Ok(locales)
}