typst-upgrade 1.7.0

A tool to upgrade typst packages
use std::{fmt::Display, sync::OnceLock};

use clap::ValueEnum;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiffChoice {
    Short,
    Full,
    None,
}

impl ValueEnum for DiffChoice {
    fn value_variants<'a>() -> &'a [Self] {
        &[Self::Short, Self::Full, Self::None]
    }

    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
        Some(match self {
            Self::Short => clap::builder::PossibleValue::new("short"),
            Self::Full => clap::builder::PossibleValue::new("full"),
            Self::None => clap::builder::PossibleValue::new("none"),
        })
    }
}

impl Display for DiffChoice {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.to_possible_value()
            .expect("no values are skipped")
            .get_name()
            .fmt(f)
    }
}

static DIFF_CHOICE: OnceLock<DiffChoice> = OnceLock::new();

pub fn init(diff: DiffChoice) {
    DIFF_CHOICE.set(diff).unwrap();
}

#[cfg_attr(coverage_nightly, coverage(off))]
pub fn show(old: &str, new: &str) {
    show_difflines(old, new, *DIFF_CHOICE.get().unwrap(), |res| match res {
        diff::Result::Left(l) => {
            diff!(del "{}", l);
        }
        diff::Result::Both(l, _) => {
            diff!("{}", l);
        }
        diff::Result::Right(r) => {
            diff!(add "{}", r);
        }
    })
}

fn show_difflines(old: &str, new: &str, diff: DiffChoice, out: impl Fn(diff::Result<&str>)) {
    for line in diff::lines(old, new) {
        match line {
            diff::Result::Left(l) if diff != DiffChoice::None => out(diff::Result::Left(l)),
            diff::Result::Both(l, r) if diff == DiffChoice::Full => out(diff::Result::Both(l, r)),
            diff::Result::Right(r) if diff != DiffChoice::None => out(diff::Result::Right(r)),
            _ => (),
        }
    }
}

#[cfg(test)]
mod test {
    use clap::ValueEnum;

    use super::DiffChoice;

    #[test]
    fn variants() {
        let variants: &[DiffChoice] = DiffChoice::value_variants();
        assert_eq!(
            variants,
            &[DiffChoice::Short, DiffChoice::Full, DiffChoice::None]
        );
    }

    #[test]
    fn parse() {
        for diff in ["short", "full", "none"] {
            let choice: DiffChoice = clap::ValueEnum::from_str(diff, false).unwrap();
            assert_eq!(choice.to_string(), diff);
        }
    }

    #[test]
    fn init() {
        super::init(DiffChoice::Short);
        assert!(
            std::panic::catch_unwind(|| {
                super::init(DiffChoice::Full);
            })
            .is_err()
        );
        assert!(
            std::panic::catch_unwind(|| {
                super::init(DiffChoice::None);
            })
            .is_err()
        );
    }

    #[test]
    fn show() {
        let old = "line1\nline2\nline3";
        let new = "line1\nline2\nline4";
        super::show_difflines(old, new, DiffChoice::None, |_| unreachable!());
        super::show_difflines(old, new, DiffChoice::Short, |res| match res {
            diff::Result::Left(l) => assert_eq!(l, "line3"),
            diff::Result::Both(_, _) => unreachable!(),
            diff::Result::Right(r) => assert_eq!(r, "line4"),
        });
        super::show_difflines(old, new, DiffChoice::Full, |res| match res {
            diff::Result::Left(l) => assert_eq!(l, "line3"),
            diff::Result::Both(l, _) => assert!(matches!(l, "line1" | "line2")),
            diff::Result::Right(r) => assert_eq!(r, "line4"),
        });
    }
}