use super::{SECS_PER_DAY, Url, opts};
use anyhow::Result;
use std::path::PathBuf;
use termcolor::{Color, ColorSpec, WriteColor};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum RepoStatus<'a, T> {
Uncloneable(Url<'a>),
Unnamed,
Success(Url<'a>, T),
Unassociated(Url<'a>),
Nonexistent(Url<'a>),
Archived(Url<'a>),
}
impl<'a, T> RepoStatus<'a, T> {
pub fn as_success(&self) -> Option<(Url<'a>, &T)> {
match self {
Self::Uncloneable(_)
| Self::Unnamed
| Self::Unassociated(_)
| Self::Nonexistent(_)
| Self::Archived(_) => None,
Self::Success(url, value) => Some((*url, value)),
}
}
#[allow(dead_code)]
pub fn is_success(&self) -> bool {
self.as_success().is_some()
}
pub fn is_failure(&self) -> bool {
self.as_success().is_none()
}
pub fn erase_url(self) -> RepoStatus<'static, T> {
match self {
Self::Uncloneable(_) => RepoStatus::Uncloneable(Url::default()),
Self::Unnamed => RepoStatus::Unnamed,
Self::Success(_, value) => RepoStatus::Success(Url::default(), value),
Self::Unassociated(_) => RepoStatus::Unassociated(Url::default()),
Self::Nonexistent(_) => RepoStatus::Nonexistent(Url::default()),
Self::Archived(_) => RepoStatus::Archived(Url::default()),
}
}
pub fn leak_url(self) -> RepoStatus<'static, T> {
match self {
Self::Uncloneable(url) => RepoStatus::Uncloneable(url.leak()),
Self::Unnamed => RepoStatus::Unnamed,
Self::Success(url, value) => RepoStatus::Success(url.leak(), value),
Self::Unassociated(url) => RepoStatus::Unassociated(url.leak()),
Self::Nonexistent(url) => RepoStatus::Nonexistent(url.leak()),
Self::Archived(url) => RepoStatus::Archived(url.leak()),
}
}
pub fn map<U>(self, f: impl Fn(T) -> U) -> RepoStatus<'a, U> {
match self {
Self::Uncloneable(url) => RepoStatus::Uncloneable(url),
Self::Unnamed => RepoStatus::Unnamed,
Self::Success(url, value) => RepoStatus::Success(url, f(value)),
Self::Unassociated(url) => RepoStatus::Unassociated(url),
Self::Nonexistent(url) => RepoStatus::Nonexistent(url),
Self::Archived(url) => RepoStatus::Archived(url),
}
}
#[allow(clippy::panic)]
pub fn map_failure<U>(self) -> RepoStatus<'a, U> {
self.map(|_| panic!("unexpected `RepoStatus::Success`"))
}
}
impl<'a, T, E> RepoStatus<'a, Result<T, E>> {
pub fn transpose(self) -> Result<RepoStatus<'a, T>, E> {
match self {
Self::Uncloneable(url) => Ok(RepoStatus::Uncloneable(url)),
Self::Unnamed => Ok(RepoStatus::Unnamed),
Self::Success(url, Ok(value)) => Ok(RepoStatus::Success(url, value)),
Self::Success(_, Err(error)) => Err(error),
Self::Unassociated(url) => Ok(RepoStatus::Unassociated(url)),
Self::Nonexistent(url) => Ok(RepoStatus::Nonexistent(url)),
Self::Archived(url) => Ok(RepoStatus::Archived(url)),
}
}
}
const SATURATION_MULTIPLIER: u64 = 3;
impl RepoStatus<'_, u64> {
pub fn color(&self) -> Option<Color> {
let age = match self {
Self::Uncloneable(_) | Self::Unnamed => {
return Some(Color::Rgb(u8::MAX, u8::MAX, 0));
}
Self::Success(_, age) => age,
Self::Unassociated(_) | Self::Nonexistent(_) | Self::Archived(_) => {
return Some(Color::Rgb(u8::MAX, 0, 0));
}
};
let age_in_days = age / SECS_PER_DAY;
let Some(max_age_excess) = age_in_days.checked_sub(opts::get().max_age) else {
debug_assert!(false);
return None;
};
let subtrahend_u64 = if opts::get().max_age == 0 {
u64::MAX
} else {
(max_age_excess * u64::from(u8::MAX)) / (SATURATION_MULTIPLIER * opts::get().max_age)
};
Some(Color::Rgb(
u8::MAX,
u8::MAX.saturating_sub(u8::try_from(subtrahend_u64).unwrap_or(u8::MAX)),
0,
))
}
#[cfg_attr(dylint_lib = "try_io_result", allow(try_io_result))]
pub fn write(&self, stream: &mut impl WriteColor) -> std::io::Result<()> {
match self {
Self::Uncloneable(url) => {
write_url(stream, *url)?;
write!(stream, " is uncloneable")?;
Ok(())
}
Self::Unnamed => write!(stream, "no repository"),
Self::Success(url, age) => {
write_url(stream, *url)?;
write!(stream, " updated ")?;
stream.set_color(ColorSpec::new().set_fg(self.color()))?;
write!(stream, "{}", age / SECS_PER_DAY)?;
stream.set_color(ColorSpec::new().set_fg(None))?;
write!(stream, " days ago")?;
Ok(())
}
Self::Unassociated(url) => {
write!(stream, "not in ")?;
write_url(stream, *url)?;
Ok(())
}
Self::Nonexistent(url) => {
write_url(stream, *url)?;
write!(stream, " does not exist")?;
Ok(())
}
Self::Archived(url) => {
write_url(stream, *url)?;
write!(stream, " archived")?;
Ok(())
}
}
}
}
#[allow(clippy::wrong_self_convention)]
impl RepoStatus<'_, ()> {
pub fn to_archival_status_string(&self) -> String {
let s = match self {
RepoStatus::Success(_, ()) => "unarchived",
RepoStatus::Archived(_) => "archived",
_ => "other",
};
s.to_owned()
}
pub fn to_existence_string(&self) -> String {
let s = match self {
RepoStatus::Success(_, ()) => "exists",
RepoStatus::Nonexistent(_) => "does not exist",
_ => "other",
};
s.to_owned()
}
}
impl RepoStatus<'_, PathBuf> {
pub fn to_membership_string(&self) -> String {
let s = match self {
RepoStatus::Success(_, _) => "member",
RepoStatus::Unassociated(_) => "non-member",
_ => "other",
};
s.to_owned()
}
}
#[cfg_attr(dylint_lib = "try_io_result", allow(try_io_result))]
fn write_url(stream: &mut impl WriteColor, url: Url) -> std::io::Result<()> {
stream.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?;
write!(stream, "{url}")?;
stream.set_color(ColorSpec::new().set_fg(None))?;
Ok(())
}