complish 0.0.1

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

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Project {
  pub completed_at: Option<DateTime<Utc>>,
  pub created_at: DateTime<Utc>,
  pub description: Option<String>,
  pub id: u32,
  pub key: String,
  pub name: String,
  pub task_ids: Vec<u32>,
  pub updated_at: DateTime<Utc>,
}

impl Project {
  pub fn new(id: u32, name: impl Into<String>) -> Self {
    let now = Utc::now();
    let name = name.into();

    let key = if name.len() >= 3 {
      name[0..3].to_uppercase()
    } else {
      name.to_uppercase()
    };

    Self {
      completed_at: None,
      created_at: now,
      description: None,
      id,
      key,
      name,
      task_ids: Vec::new(),
      updated_at: now,
    }
  }

  pub fn add_desc(&mut self, description: impl Into<String>) {
    self.add_description(description);
  }

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

  pub fn add_task(&mut self, task_id: u32) {
    if !self.task_ids.contains(&task_id) {
      self.task_ids.push(task_id);
      self.touch();
    }
  }

  pub fn complete(&mut self) {
    if !self.is_complete() {
      self.completed_at = Some(Utc::now());
      self.touch();
    }
  }

  pub fn contains_task(&self, task_id: u32) -> bool {
    self.task_ids.contains(&task_id)
  }

  pub fn is_complete(&self) -> bool {
    self.completed_at.is_some()
  }

  pub fn is_empty(&self) -> bool {
    self.task_ids.is_empty()
  }

  pub fn len(&self) -> usize {
    self.task_ids.len()
  }

  pub fn remove_task(&mut self, task_id: u32) {
    self.task_ids.retain(|id| *id != task_id);
    self.touch();
  }

  pub fn touch(&mut self) {
    self.updated_at = Utc::now();
  }

  #[must_use = "this method returns a new instance with an updated description"]
  pub fn with_desc(self, description: impl Into<String>) -> Self {
    self.with_description(description)
  }

  #[must_use = "this method returns a new instance with an updated description"]
  pub fn with_description(mut self, description: impl Into<String>) -> Self {
    self.description = Some(description.into());
    self
  }

  #[must_use = "this method returns a new instance with an updated key"]
  pub fn with_key(mut self, key: impl Into<String>) -> Self {
    self.key = key.into();
    self
  }

  #[must_use = "this method returns a new instance with an updated task list"]
  pub fn with_tasks(mut self, task_ids: Vec<u32>) -> Self {
    self.task_ids = task_ids;
    self
  }
}