use crate::{
error::Result,
habitica::{HabiticaTask, HabiticaTaskStatus, HabiticaTaskType},
taskwarrior::{Task, TaskDifficulty, TaskStatus, TaskType},
};
pub fn taskwarrior_to_habitica(
tw_task: &Task,
note_content: Option<&str>,
) -> Result<Option<HabiticaTask>> {
if !tw_task.status.should_sync_to_habitica() {
return Ok(None);
}
let (completed, _status) = match tw_task.status {
TaskStatus::Pending | TaskStatus::Waiting => (false, HabiticaTaskStatus::Pending),
TaskStatus::Completed => (true, HabiticaTaskStatus::Completed),
TaskStatus::Deleted | TaskStatus::Recurring => return Ok(None),
};
let task_type = match tw_task.task_type() {
TaskType::Todo => HabiticaTaskType::Todo,
TaskType::Daily => HabiticaTaskType::Daily,
_ => HabiticaTaskType::Todo, };
Ok(Some(HabiticaTask {
id: tw_task.habitica_uuid,
text: tw_task.description.clone(),
notes: note_content.unwrap_or("").to_string(),
task_type,
priority: tw_task.difficulty().to_habitica_priority(),
completed,
date: tw_task.due,
updated_at: tw_task.modified,
is_due: false, }))
}
pub fn habitica_to_taskwarrior(
h_task: &HabiticaTask,
existing_tw_task: Option<&Task>,
) -> Result<Task> {
let status = match h_task.effective_status() {
HabiticaTaskStatus::Pending => TaskStatus::Pending,
HabiticaTaskStatus::Completed => TaskStatus::Completed,
};
let difficulty = TaskDifficulty::from_habitica_priority(h_task.priority);
let task_type = match h_task.task_type {
HabiticaTaskType::Todo => TaskType::Todo,
HabiticaTaskType::Daily => TaskType::Daily,
HabiticaTaskType::Habit => TaskType::Habit,
HabiticaTaskType::Reward => TaskType::Reward,
};
let (uuid, extra, annotations) = if let Some(existing) = existing_tw_task {
(
existing.uuid,
existing.extra.clone(),
existing.annotations.clone(),
)
} else {
(uuid::Uuid::new_v4(), serde_json::Map::new(), None)
};
Ok(Task {
uuid,
description: h_task.text.clone(),
status,
modified: h_task.updated_at,
due: h_task.date,
annotations,
habitica_uuid: h_task.id,
habitica_difficulty: Some(difficulty),
habitica_task_type: Some(task_type),
extra,
})
}
pub fn update_taskwarrior_from_habitica(tw_task: &mut Task, h_task: &HabiticaTask) -> Result<()> {
tw_task.description = h_task.text.clone();
tw_task.due = h_task.date;
tw_task.modified = h_task.updated_at;
tw_task.habitica_uuid = h_task.id;
tw_task.habitica_difficulty = Some(TaskDifficulty::from_habitica_priority(h_task.priority));
let task_type = match h_task.task_type {
HabiticaTaskType::Todo => TaskType::Todo,
HabiticaTaskType::Daily => TaskType::Daily,
HabiticaTaskType::Habit => TaskType::Habit,
HabiticaTaskType::Reward => TaskType::Reward,
};
tw_task.habitica_task_type = Some(task_type);
tw_task.status = match (h_task.effective_status(), tw_task.status) {
(HabiticaTaskStatus::Pending, TaskStatus::Waiting) => TaskStatus::Waiting,
(HabiticaTaskStatus::Pending, _) => TaskStatus::Pending,
(HabiticaTaskStatus::Completed, _) => TaskStatus::Completed,
};
Ok(())
}
pub fn tasks_are_equivalent(tw_task: &Task, h_task: &HabiticaTask) -> bool {
if tw_task.description != h_task.text {
return false;
}
if tw_task.due != h_task.date {
return false;
}
if tw_task.difficulty().to_habitica_priority() != h_task.priority {
return false;
}
let tw_type = match tw_task.task_type() {
TaskType::Todo => HabiticaTaskType::Todo,
TaskType::Daily => HabiticaTaskType::Daily,
TaskType::Habit => HabiticaTaskType::Habit,
TaskType::Reward => HabiticaTaskType::Reward,
};
if tw_type != h_task.task_type {
return false;
}
let tw_completed = tw_task.status.is_completed();
if tw_completed != h_task.completed {
return false;
}
if tw_task.habitica_uuid != h_task.id {
return false;
}
true
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::bool_assert_comparison)]
mod tests {
use chrono::Utc;
use super::*;
fn test_tw_task() -> Task {
Task {
uuid: uuid::Uuid::new_v4(),
description: "Test task".to_string(),
status: TaskStatus::Pending,
modified: Some(Utc::now()),
due: None,
annotations: None,
habitica_uuid: Some(uuid::Uuid::new_v4()),
habitica_difficulty: Some(TaskDifficulty::Easy),
habitica_task_type: Some(TaskType::Todo),
extra: serde_json::Map::new(),
}
}
fn test_h_task() -> HabiticaTask {
HabiticaTask {
id: Some(uuid::Uuid::new_v4()),
text: "Test task".to_string(),
notes: String::new(),
task_type: HabiticaTaskType::Todo,
priority: 1.0,
completed: false,
date: None,
updated_at: Some(Utc::now()),
is_due: false,
}
}
#[test]
fn test_taskwarrior_to_habitica_pending() {
let tw_task = test_tw_task();
let h_task = taskwarrior_to_habitica(&tw_task, None).unwrap().unwrap();
assert_eq!(h_task.text, tw_task.description);
assert_eq!(h_task.completed, false);
assert_eq!(h_task.priority, 1.0);
}
#[test]
fn test_taskwarrior_to_habitica_completed() {
let mut tw_task = test_tw_task();
tw_task.status = TaskStatus::Completed;
let h_task = taskwarrior_to_habitica(&tw_task, None).unwrap().unwrap();
assert_eq!(h_task.completed, true);
}
#[test]
fn test_taskwarrior_to_habitica_deleted() {
let mut tw_task = test_tw_task();
tw_task.status = TaskStatus::Deleted;
let result = taskwarrior_to_habitica(&tw_task, None).unwrap();
assert!(result.is_none());
}
#[test]
fn test_habitica_to_taskwarrior() {
let h_task = test_h_task();
let tw_task = habitica_to_taskwarrior(&h_task, None).unwrap();
assert_eq!(tw_task.description, h_task.text);
assert_eq!(tw_task.status, TaskStatus::Pending);
assert_eq!(tw_task.habitica_uuid, h_task.id);
}
#[test]
fn test_tasks_are_equivalent() {
let tw_task = test_tw_task();
let h_task = taskwarrior_to_habitica(&tw_task, None).unwrap().unwrap();
assert!(tasks_are_equivalent(&tw_task, &h_task));
}
#[test]
fn test_tasks_not_equivalent_different_text() {
let tw_task = test_tw_task();
let mut h_task = taskwarrior_to_habitica(&tw_task, None).unwrap().unwrap();
h_task.text = "Different text".to_string();
assert!(!tasks_are_equivalent(&tw_task, &h_task));
}
}