complish 0.0.1

Core library for project-aware task management with git integration
Documentation
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct TaskWorkLog {
  pub ended_at: Option<DateTime<Utc>>,
  pub note: Option<String>,
  pub source: Option<String>,
  pub started_at: DateTime<Utc>,
}

impl TaskWorkLog {
  pub fn new() -> Self {
    Self {
      ended_at: None,
      note: None,
      source: None,
      started_at: Utc::now(),
    }
  }

  pub fn add_note(&mut self, note: impl Into<String>) {
    self.note = Some(note.into());
  }

  pub fn add_source(&mut self, source: impl Into<String>) {
    self.source = Some(source.into());
  }

  pub fn duration(&self) -> Option<Duration> {
    self.ended_at.map(|ended_at| ended_at - self.started_at)
  }

  pub fn stop(&mut self) {
    if self.ended_at.is_none() {
      self.ended_at = Some(Utc::now());
    }
  }

  #[must_use]
  pub fn with_note(mut self, note: impl Into<String>) -> Self {
    self.note = Some(note.into());
    self
  }

  #[must_use]
  pub fn with_source(mut self, source: impl Into<String>) -> Self {
    self.source = Some(source.into());
    self
  }
}

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

  mod new {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_creates_a_new_work_log() {
      let work_log = TaskWorkLog::new();

      assert_eq!(work_log.ended_at, None);
      assert_eq!(work_log.note, None);
      assert_eq!(work_log.source, None);
      assert!(work_log.started_at.timestamp() > 0);
    }
  }

  mod add_note {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_adds_a_note_to_the_worklog() {
      let mut work_log = TaskWorkLog::new();
      work_log.add_note("a test note");

      assert_eq!(work_log.note.unwrap(), "a test note");
    }
  }

  mod add_source {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_adds_a_source_to_the_worklog() {
      let mut work_log = TaskWorkLog::new();
      work_log.add_source("git");

      assert_eq!(work_log.source.unwrap(), "git");
    }
  }

  mod duration {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_the_duration_of_the_work_log() {
      let now = Utc::now();
      let mut work_log = TaskWorkLog::new();
      work_log.started_at = now - Duration::minutes(10);
      work_log.ended_at = Some(now);

      assert_eq!(work_log.duration().unwrap(), Duration::minutes(10));
    }

    #[test]
    fn it_returns_none_if_the_work_log_has_not_been_ended() {
      let work_log = TaskWorkLog::new();

      assert_eq!(work_log.duration(), None);
    }
  }

  mod stop {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_sets_the_ended_at_to_now() {
      let mut work_log = TaskWorkLog::new();
      work_log.stop();

      assert!(work_log.ended_at.is_some());
    }

    #[test]
    fn it_does_not_set_the_ended_at_if_it_has_already_been_set() {
      let mut work_log = TaskWorkLog::new();
      let ended_at = Utc::now() - Duration::minutes(10);
      work_log.ended_at = Some(ended_at);
      work_log.stop();

      assert_eq!(work_log.ended_at.unwrap(), ended_at);
    }
  }

  mod with_note {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_creates_a_new_work_log_with_the_given_note() {
      let work_log = TaskWorkLog::new().with_note("a test note");

      assert_eq!(work_log.note.unwrap(), "a test note");
    }
  }

  mod with_source {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_creates_a_new_work_log_with_the_given_source() {
      let work_log = TaskWorkLog::new().with_source("git");

      assert_eq!(work_log.source.unwrap(), "git");
    }
  }
}