complish 0.0.1

Core library for project-aware task management with git integration
Documentation
use std::{
  cmp::Ordering,
  fmt::{Display, Formatter, Result as FmtResult},
  str::FromStr,
};

use eyre::{Error, Result, eyre};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
  Done,
  InProgress,
  #[default]
  Todo,
}

impl Display for TaskStatus {
  fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
    match self {
      Self::Done => write!(f, "done"),
      Self::InProgress => write!(f, "in_progress"),
      Self::Todo => write!(f, "todo"),
    }
  }
}

impl FromStr for TaskStatus {
  type Err = Error;

  fn from_str(s: &str) -> Result<Self> {
    match s.to_lowercase().as_str() {
      "done" => Ok(Self::Done),
      "in_progress" => Ok(Self::InProgress),
      "todo" => Ok(Self::Todo),
      _ => Err(eyre!(format!("invalid task status: {}", s))),
    }
  }
}

impl Ord for TaskStatus {
  fn cmp(&self, other: &Self) -> Ordering {
    let self_priority = match self {
      TaskStatus::InProgress => 0,
      TaskStatus::Todo => 1,
      TaskStatus::Done => 2,
    };

    let other_priority = match other {
      TaskStatus::InProgress => 0,
      TaskStatus::Todo => 1,
      TaskStatus::Done => 2,
    };

    self_priority.cmp(&other_priority)
  }
}

impl PartialOrd for TaskStatus {
  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    Some(self.cmp(other))
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  mod display {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_displays_correctly() {
      assert_eq!(TaskStatus::Done.to_string(), "done");
      assert_eq!(TaskStatus::InProgress.to_string(), "in_progress");
      assert_eq!(TaskStatus::Todo.to_string(), "todo");
    }
  }

  mod from_str {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_parses_correctly() {
      assert_eq!(TaskStatus::from_str("done").unwrap(), TaskStatus::Done);
      assert_eq!(
        TaskStatus::from_str("in_progress").unwrap(),
        TaskStatus::InProgress
      );
      assert_eq!(TaskStatus::from_str("todo").unwrap(), TaskStatus::Todo);
    }

    #[test]
    fn it_errors_on_invalid_input() {
      assert!(TaskStatus::from_str("invalid").is_err());
    }

    #[test]
    fn it_parses_case_insensitively() {
      for str in &["TODO", "Todo", "ToDo"] {
        assert_eq!(TaskStatus::from_str(str).unwrap(), TaskStatus::Todo);
      }
    }
  }

  mod ordering {
    use super::*;

    #[test]
    fn it_orders_correctly() {
      assert!(TaskStatus::InProgress < TaskStatus::Todo);
      assert!(TaskStatus::Todo < TaskStatus::Done);
      assert!(TaskStatus::Done > TaskStatus::InProgress);
    }
  }
}