use std::fmt;
use colored::Colorize;
use serde::{
de::{Deserializer, Error as SerdeDeError},
Deserialize, Serialize,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Satisfying {
Changed(String, String),
Done,
NoChange(String),
}
impl fmt::Display for Satisfying {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Changed(from, to) => write!(
f,
"{}: {} => {}",
"changed".yellow(),
from.yellow().dimmed(),
to.yellow()
),
Self::Done => write!(f, "{}", "done".blue()),
Self::NoChange(s) => write!(f, "{}: {}", "nochange".green(), s.green()),
}
}
}
impl From<&Satisfying> for String {
fn from(source: &Satisfying) -> Self {
Self::from(match source {
Satisfying::Changed(_, _) => "changed",
Satisfying::Done => "done",
Satisfying::NoChange(_) => "nochange",
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Status {
InProgress,
Pending,
Satisfying(Satisfying),
Unsatisfying(Unsatisfying),
Waiting,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InProgress => write!(f, "{}", "inprogress".cyan()),
Self::Pending => write!(f, "{}", "pending".white()),
Self::Satisfying(s) => write!(f, "{}", s),
Self::Unsatisfying(u) => write!(f, "{}", u),
Self::Waiting => write!(f, "{}", "waiting".white()),
}
}
}
impl From<&Status> for String {
fn from(source: &Status) -> Self {
match source {
Status::InProgress => Self::from("inprogress"),
Status::Pending => Self::from("pending"),
Status::Satisfying(s) => Self::from(s),
Status::Unsatisfying(u) => Self::from(u),
Status::Waiting => Self::from("waiting"),
}
}
}
impl<'de> Deserialize<'de> for Status {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = match toml::value::Value::deserialize(deserializer)? {
toml::Value::String(s) => match s.as_str() {
"blocked" => Status::Unsatisfying(Unsatisfying::Blocked),
"changed" => Status::Satisfying(Satisfying::Changed(String::new(), String::new())),
"done" => Status::Satisfying(Satisfying::Done),
"error" => Status::Unsatisfying(Unsatisfying::Error),
"inprogress" => Status::InProgress,
"nochange" => Status::Satisfying(Satisfying::NoChange(String::new())),
"pending" => Status::Pending,
"skipped" => Status::Unsatisfying(Unsatisfying::Skipped),
"waiting" => Status::Waiting,
_ => {
return Err(SerdeDeError::custom("invalid status"));
}
},
_ => {
return Err(SerdeDeError::custom(
"must provide a string to convert to status",
));
}
};
Ok(value)
}
}
impl Serialize for Status {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(String::from(self).as_str())
}
}
impl Status {
pub fn changed(before: String, after: String) -> Self {
if before == after {
Self::no_change(before)
} else {
Self::Satisfying(Satisfying::Changed(before, after))
}
}
pub fn is_satisfying(&self) -> bool {
matches!(&self, Self::Satisfying(_))
}
#[cfg(test)]
pub fn is_settled(&self) -> bool {
matches!(&self, Self::Satisfying(_) | Self::Unsatisfying(_))
}
pub fn no_change(before: String) -> Self {
Self::Satisfying(Satisfying::NoChange(before))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Unsatisfying {
Blocked,
Error,
Skipped,
}
impl fmt::Display for Unsatisfying {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Blocked => write!(f, "{}", "blocked".red().dimmed()),
Self::Error => write!(f, "{}", "error".red()),
Self::Skipped => write!(f, "{}", "skipped".blue()),
}
}
}
impl From<&Unsatisfying> for String {
fn from(source: &Unsatisfying) -> Self {
Self::from(match source {
Unsatisfying::Blocked => "blocked",
Unsatisfying::Error => "error",
Unsatisfying::Skipped => "skipped",
})
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
#[test]
fn from_status_for_string() {
let input = Status::Satisfying(Satisfying::Done);
assert_eq!(String::from(&input), "done");
}
#[test]
fn from_toml() {
let input = r#"blocked = "blocked"
changed = "changed"
done = "done"
error = "error"
inprogress = "inprogress"
nochange = "nochange"
pending = "pending"
skipped = "skipped"
waiting = "waiting"
"#;
let want: BTreeMap<&str, Status> = BTreeMap::from([
("blocked", Status::Unsatisfying(Unsatisfying::Blocked)),
(
"changed",
Status::Satisfying(Satisfying::Changed(String::new(), String::new())),
),
("done", Status::Satisfying(Satisfying::Done)),
("error", Status::Unsatisfying(Unsatisfying::Error)),
("inprogress", Status::InProgress),
(
"nochange",
Status::Satisfying(Satisfying::NoChange(String::new())),
),
("pending", Status::Pending),
("skipped", Status::Unsatisfying(Unsatisfying::Skipped)),
("waiting", Status::Waiting),
]);
let got = toml::from_str::<BTreeMap<&str, Status>>(input).expect("unable to deserialize");
assert_eq!(got, want);
}
#[test]
fn into_toml() {
let input: BTreeMap<&str, Status> = BTreeMap::from([
("blocked", Status::Unsatisfying(Unsatisfying::Blocked)),
(
"changed",
Status::Satisfying(Satisfying::Changed(String::new(), String::new())),
),
("done", Status::Satisfying(Satisfying::Done)),
("error", Status::Unsatisfying(Unsatisfying::Error)),
("inprogress", Status::InProgress),
(
"nochange",
Status::Satisfying(Satisfying::NoChange(String::new())),
),
("pending", Status::Pending),
("skipped", Status::Unsatisfying(Unsatisfying::Skipped)),
("waiting", Status::Waiting),
]);
let want = r#"blocked = "blocked"
changed = "changed"
done = "done"
error = "error"
inprogress = "inprogress"
nochange = "nochange"
pending = "pending"
skipped = "skipped"
waiting = "waiting"
"#;
let got = toml::to_string(&input).expect("unable to serialize");
assert_eq!(got, want);
}
}