aoc-input-build 0.1.2

AoC helper that automatically downloads inputs. For use as a build dependency.
Documentation
use std::{fmt::Display, io, sync::LazyLock};

use icu_calendar::Iso;
use icu_datetime::{
    DateTimeFormatter,
    fieldsets::{self, YMDT},
};
use icu_time::{TimeZoneInfo, ZonedDateTime, zone::models::AtTime};
use jiff::tz::TimeZone;
use jiff_icu::ConvertFrom;
use locale_config::Locale;

#[derive(Debug)]
pub enum Error {
    IO(String, io::Error),
    Request(String, ureq::Error),
    Date(i8, Box<jiff::Zoned>),
}

impl Error {
    pub fn fatal(&self) -> bool {
        match self {
            Self::IO(_, _) => true,
            Self::Request(_, _) => true,
            Self::Date(_, _) => false,
        }
    }
}

fn localized_formatter() -> DateTimeFormatter<YMDT> {
    static LOCALE: LazyLock<icu_locale::Locale> = LazyLock::new(|| {
        Locale::current()
            .tags_for("time")
            .next()
            .map_or_else(|| "en-US".to_string(), |l| l.as_ref().to_owned())
            .parse()
            .expect("Failed to parse locale")
    });

    DateTimeFormatter::try_new((&*LOCALE).into(), fieldsets::YMDT::short())
        .expect("Failed to construct date time formatter")
}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::IO(file, error) => write!(f, "IO error: '{error}' when accessing '{file}'"),
            Self::Request(url, error) => {
                write!(f, "HTTP error: '{error}' when fetching '{url}'")
            }
            Self::Date(day, release) => {
                let local_tz = TimeZone::system();
                let icu_zdt = ZonedDateTime::<Iso, TimeZoneInfo<AtTime>>::convert_from(
                    &release.with_time_zone(local_tz),
                );
                let formatter = localized_formatter();

                write!(
                    f,
                    "trying to access day {day} input before it is ready on {}",
                    formatter.format(&icu_zdt)
                )
            }
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::IO(_, error) => Some(error),
            Self::Request(_, error) => Some(error),
            Self::Date(_, _) => None,
        }
    }
}

pub fn cargo_error<T>(res: Result<T, Error>) -> Option<T> {
    match res {
        Ok(t) => Some(t),
        Err(e) => {
            if e.fatal() {
                println!("cargo::error={}", e);
            } else {
                println!("cargo::warning={}", e);
            }
            None
        }
    }
}