adnv 1.5.0

CLI that tracks, checks and auto-updates infrastructure dependency versions across Terraform, Helm, Docker, Git and npm
Documentation
use chrono::{DateTime, Local};
use clap::{Parser, builder::TypedValueParser};
use miette::Result;
use regex::Regex;

use adnv::{Config, ops::check};

// `StringValueParser::try_map` hands us an owned `String`, so the signature is
// clap's, not ours.
#[allow(clippy::needless_pass_by_value)]
pub fn parse_datetime(value: String) -> Result<DateTime<Local>, String> {
    use chrono::NaiveDate;

    if let Ok(datetime) = value.parse::<DateTime<Local>>() {
        Ok(datetime)
    } else {
        let date = value
            .parse::<NaiveDate>()
            .map_err(|err| format!("valid RFC3339-formatted date or datetime: {err}"))?;
        Ok(date
            .and_hms_opt(0, 0, 0)
            .unwrap()
            .and_local_timezone(Local)
            .unwrap())
    }
}

#[derive(Debug, Parser)]
pub struct Command {
    /// Defines the duration of the search window. Default 24h
    #[arg(
        short,
        long,
        env = "ADNV_CHECK_LAST",
        conflicts_with = "since",
        help_heading = "Filtering options"
    )]
    last: Option<humantime::Duration>,

    /// Defines the search window to since a specified date (formatted using RFC3339)
    #[arg(
        short = 'S',
        long,
        env = "ADNV_CHECK_SINCE",
        conflicts_with = "last",
        value_parser = clap::builder::StringValueParser::new().try_map(parse_datetime),
        help_heading = "Filtering options"
    )]
    since: Option<DateTime<Local>>,

    /// Output to alternative backends (this flag can be repeated to output to multiple backends)
    #[arg(
        short,
        long = "output",
        env = "ADNV_CHECK_OUTPUTS",
        value_name = "OUTPUT_NAME",
        value_delimiter = ','
    )]
    outputs: Vec<String>,

    /// Defines a filter to keep only versions that match the regex
    #[arg(short, long, env = "ADNV_CHECK_INCLUDE", value_name = "INCLUDE_REGEX")]
    include: Option<Regex>,

    /// Defines a filter to keep only versions that don't match the regex
    #[arg(short, long, env = "ADNV_CHECK_EXCLUDE", value_name = "EXCLUDE_REGEX")]
    exclude: Option<Regex>,

    /// External sources from git. If filled, it will be checked instead of config file
    sources: Vec<check::Source>,
}

impl Command {
    pub async fn run(self, config: Option<&Config>) -> Result<()> {
        let sources = match self.sources.as_slice() {
            [] => config.map_or(&[][..], |config| config.check.sources.as_slice()),
            sources => {
                if config.is_some() {
                    tracing::warn!("Configuration file has not been read");
                }

                sources
            }
        };

        let outputs = config
            .map(|config| config.check.outputs.clone())
            .unwrap_or_default();

        adnv::ops::check::run(
            check::Options {
                last: self.last.map(Into::into),
                since: self.since,
                outputs: self.outputs,
                include: self.include,
                exclude: self.exclude,
            },
            sources,
            outputs,
        )
        .await?;

        Ok(())
    }
}