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, Duration, Local};
use itertools::Itertools;

use super::{Options, VersionedSource};
use crate::sources::list::Tag;

#[allow(dead_code)]
pub fn process_result(sources: &[VersionedSource], options: &Options) -> Vec<VersionedSource> {
    let since = compute_since_date(options);
    sources
        .iter()
        .filter_map(|source| process_versioned_source(source, since))
        .collect::<Vec<VersionedSource>>()
}

pub fn compute_since_date(options: &Options) -> DateTime<Local> {
    let now = Local::now();

    match (options.since, options.last) {
        (None, None) => now - Duration::days(1),
        (None, Some(duration)) => {
            now - Duration::from_std(duration).unwrap_or_else(|_| Duration::days(365 * 100))
        }
        (Some(since), _) => since,
    }
}

fn process_versioned_source(
    source: &VersionedSource,
    since: DateTime<Local>,
) -> Option<VersionedSource> {
    let versions_filtered: Vec<Tag> = source
        .versions
        .iter()
        .filter_map(|tag| tag.created_at().map(|created_at| (tag, created_at)))
        .filter(|(_, created_at)| **created_at > since)
        .sorted_by(|(_, created_at_a), (_, created_at_b)| Ord::cmp(created_at_b, created_at_a))
        .map(|(tag, _)| tag.clone())
        .collect();

    if versions_filtered.is_empty() {
        None
    } else {
        Some(VersionedSource {
            source: source.source.clone(),
            versions: versions_filtered,
        })
    }
}

#[cfg(test)]
mod test {
    use chrono::{Duration, Local};

    use super::super::{Source, VersionedSource};

    fn mock1() -> VersionedSource {
        VersionedSource {
            source: Source {
                label: None,
                source: "git+https://github.com/foo/bar#tag=v1.2".parse().unwrap(),
                include: None,
                exclude: None,
            },
            versions: vec![
                tag!("2.0.1").with_created_at(chrono::Local::now()),
                tag!("1.0.0").with_created_at(chrono::Local::now() - chrono::Duration::days(2)),
                tag!("2.0.0").with_created_at(chrono::Local::now() - chrono::Duration::hours(2)),
                tag!("0.1.0").with_created_at(chrono::Local::now() - chrono::Duration::weeks(2)),
            ],
        }
    }

    fn mock2() -> VersionedSource {
        VersionedSource {
            source: Source {
                label: Some("adnv cli".to_string()),
                source: "git+https://gitlab.com/adnap/tools/adnv".parse().unwrap(),
                include: None,
                exclude: None,
            },
            versions: vec![
                tag!("1.0.0").with_created_at(chrono::Local::now() - chrono::Duration::weeks(1)),
            ],
        }
    }

    #[test]
    fn test_succeeds_without_options() {
        let mock1 = mock1();
        let mock2 = mock2();
        let sources = vec![mock1.clone(), mock2];

        let result = super::process_result(
            &sources,
            &super::Options {
                last: None,
                since: None,
                include: None,
                exclude: None,
                outputs: vec![],
            },
        );
        let wanted = vec![VersionedSource {
            source: mock1.source,
            versions: vec![mock1.versions[0].clone(), mock1.versions[2].clone()],
        }];
        pretty_assertions::assert_eq!(wanted, result);
    }

    #[test]
    fn test_succeeds_with_last_10_days_ago() {
        let mock1 = mock1();
        let mock2 = mock2();
        let sources = [mock1.clone(), mock2.clone()];

        let result = super::process_result(
            &sources,
            &super::Options {
                last: Some(Duration::days(10).to_std().unwrap()),
                since: None,
                include: None,
                exclude: None,
                outputs: vec![],
            },
        );
        let wanted = vec![
            VersionedSource {
                source: mock1.source,
                versions: vec![
                    mock1.versions[0].clone(),
                    mock1.versions[2].clone(),
                    mock1.versions[1].clone(),
                ],
            },
            VersionedSource {
                source: mock2.source,
                versions: vec![mock2.versions[0].clone()],
            },
        ];
        pretty_assertions::assert_eq!(wanted, result);
    }

    #[test]
    fn test_succeeds_with_since_3_weeks_ago() {
        let mock1 = mock1();
        let mock2 = mock2();
        let sources = [mock1.clone(), mock2.clone()];

        let result = super::process_result(
            &sources,
            &super::Options {
                last: None,
                since: Some(Local::now() - Duration::weeks(3)),
                include: None,
                exclude: None,
                outputs: vec![],
            },
        );
        let wanted = vec![
            VersionedSource {
                source: mock1.source,
                versions: vec![
                    mock1.versions[0].clone(),
                    mock1.versions[2].clone(),
                    mock1.versions[1].clone(),
                    mock1.versions[3].clone(),
                ],
            },
            mock2,
        ];
        pretty_assertions::assert_eq!(wanted, result);
    }
}