use std::io::{self, BufRead, Write};
use crate::error::AppError;
use crate::io::cli_print::show_table;
use crate::io::{cli_print::list_table, storage::TaskStore};
use crate::task::Priority;
use crate::time::parse_deadline_input;
use crate::todo::{
SortBy, TaskUpdate, add_task, complete_task, delete_task, incomplete_task, list_tasks,
show_details, undo_task_apply, undo_task_preview,
};
pub fn add(
content: String,
store: &TaskStore,
description: Option<String>,
deadline: Option<String>,
priority: Option<Priority>,
) -> Result<(), AppError> {
let parsed_deadline = match deadline {
Some(s) => Some(parse_deadline_input(&s)?),
None => None,
};
add_task(store, content, description, parsed_deadline, priority)
}
pub fn list(store: &TaskStore, sort: Option<SortBy>) -> Result<(), AppError> {
let tasks_list = list_tasks(store, sort)?;
match tasks_list {
None => {
println!("+_+ No tasks");
Ok(())
}
Some(tasks) => {
println!("{}", list_table(&tasks));
Ok(())
}
}
}
pub fn show(no: usize, store: &TaskStore) -> Result<(), AppError> {
let task_show = show_details(no, store)?;
match task_show {
None => {
println!("+_+ No tasks");
Ok(())
}
Some(task) => {
println!("{}", show_table(&task, no));
println!("-Description-");
match task.description() {
None => println!("+_+ No description"),
Some(desc) => println!("{}", desc),
}
Ok(())
}
}
}
pub fn done(no: usize, store: &TaskStore) -> Result<(), AppError> {
complete_task(no, store)
}
pub fn undone(no: usize, store: &TaskStore) -> Result<(), AppError> {
incomplete_task(no, store)
}
pub fn delete(no: usize, store: &TaskStore) -> Result<(), AppError> {
delete_task(no, store)
}
pub fn change(
no: usize,
store: &TaskStore,
content: Option<String>,
description: Option<Option<String>>,
deadline: Option<Option<String>>,
priority: Option<Option<Priority>>,
) -> Result<(), AppError> {
let deadline = match deadline {
None => None,
Some(None) => Some(None),
Some(Some(t)) => Some(Some(parse_deadline_input(&t)?)),
};
let taskupdate = TaskUpdate::new(content, description, deadline, priority);
taskupdate.change_task(no, store)
}
pub fn undo(store: &TaskStore, yes: bool) -> Result<(), AppError> {
let backup_tasks = match undo_task_preview(store) {
Ok(tasks) => tasks,
Err(AppError::NothingToUndo) => {
println!("+_+ Nothing to undo");
return Ok(());
}
Err(e) => return Err(e),
};
println!("The list will be restored to:");
println!("{}", list_table(&backup_tasks));
if !yes && !confirm(&mut io::stdin().lock()) {
println!(">_< Undo cancelled.");
return Ok(());
}
undo_task_apply(store)?;
println!("Undo >>>");
Ok(())
}
fn confirm(read: &mut dyn BufRead) -> bool {
print!("Confirm undo? [y/N] ");
let _ = io::stdout().flush();
let mut input = String::new();
match read.read_line(&mut input) {
Ok(0) => false,
Ok(_) => matches!(input.trim().to_lowercase().as_str(), "y" | "yes"),
Err(_) => false,
}
}
#[cfg(test)]
mod commands_test {
use super::*;
use crate::time::to_utc;
use chrono::NaiveDateTime;
use std::{fs, process};
fn temp_path(name: &str) -> String {
std::env::temp_dir()
.join(format!("{}_rstodo_test_{}.json", process::id(), name))
.to_string_lossy()
.to_string()
}
fn set_test_task(path: &TaskStore) {
add(
"task1".into(),
path,
Some("desc1".into()),
Some("2000-01-01T12:00:00".into()),
Some(Priority::High),
)
.unwrap();
add("task2".into(), path, None, None, None).unwrap();
add(
"task3".into(),
path,
None,
Some("2000-01-02T08:00:00".into()),
Some(Priority::Medium),
)
.unwrap();
}
#[test]
fn test_add() {
let file = temp_path("add");
let path = TaskStore::new(Some(file.clone()));
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
add(
"test_add1".to_string(),
&path,
Some("about_assert_add_test".to_string()),
Some("2000-01-01T12:00:00".to_string()),
Some(Priority::High),
)
.unwrap();
let tasks = path.load().unwrap();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].id(), 1);
assert_eq!(tasks[0]._content(), "test_add1");
assert_eq!(
tasks[0].description(),
Some("about_assert_add_test".to_string())
);
assert_eq!(tasks[0].priority(), Priority::High);
assert!(!tasks[0]._completed());
let expected = to_utc(
&NaiveDateTime::parse_from_str("2000-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S").unwrap(),
)
.unwrap();
assert_eq!(tasks[0].deadline(), Some(expected));
add("test_add2".to_string(), &path, None, None, None).unwrap();
let tasks = path.load().unwrap();
assert_eq!(tasks.len(), 2);
assert_eq!(tasks[1].id(), 2);
assert_eq!(tasks[1]._content(), "test_add2".to_string());
assert_eq!(tasks[1].priority(), Priority::Low);
assert!(tasks[1].deadline().is_none());
assert!(tasks[1].description().is_none());
assert!(!tasks[1]._completed());
add(
"test_add3".to_string(),
&path,
None,
Some("2000-01-02".to_string()),
None,
)
.unwrap();
let tasks = path.load().unwrap();
assert_eq!(tasks.len(), 3);
assert_eq!(tasks[2].id(), 3);
assert_eq!(tasks[2]._content(), "test_add3".to_string());
assert_eq!(tasks[2].priority(), Priority::Low);
assert!(tasks[2].description().is_none());
assert!(!tasks[2]._completed());
let expected = to_utc(
&NaiveDateTime::parse_from_str("2000-01-02T23:59:59", "%Y-%m-%dT%H:%M:%S").unwrap(),
)
.unwrap();
assert_eq!(tasks[2].deadline(), Some(expected));
assert!(
add(
"test_add3".to_string(),
&path,
None,
Some("not-a-date".to_string()),
None
)
.is_err()
);
assert_eq!(path.load().unwrap().len(), 3);
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
}
#[test]
fn test_list_show() {
let file = temp_path("list_show");
let path = TaskStore::new(Some(file.clone()));
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
assert!(list(&path, None).is_ok());
set_test_task(&path);
assert!(list(&path, Some(SortBy::Priority)).is_ok());
println!();
assert!(list(&path, Some(SortBy::Deadline)).is_ok());
println!();
assert!(show(2, &path).is_ok());
println!();
assert!(show(0, &path).is_err());
assert!(show(99, &path).is_err());
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
}
#[test]
fn test_delete() {
let file = temp_path("delete");
let path = TaskStore::new(Some(file.clone()));
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
assert!(delete(1, &path).is_err());
set_test_task(&path);
delete(2, &path).unwrap();
let tasks = path.load().unwrap();
assert_eq!(tasks.len(), 2);
assert_eq!(tasks[0]._content(), "task1".to_string());
assert_eq!(tasks[1]._content(), "task3".to_string());
assert!(delete(0, &path).is_err());
assert!(delete(99, &path).is_err());
assert_eq!(path.load().unwrap().len(), 2);
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
}
#[test]
fn test_change() {
let file = temp_path("change");
let path = TaskStore::new(Some(file.clone()));
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
assert!(change(1, &path, None, None, None, None).is_err());
assert!(change(1, &path, Some("change_task1".to_string()), None, None, None).is_err());
set_test_task(&path);
change(
1,
&path,
Some("change_task2".to_string()),
Some(Some("new_desc2".to_string())),
None,
None,
)
.unwrap();
let tasks = path.load().unwrap();
assert_eq!(tasks[0]._content(), "change_task2".to_string());
assert_eq!(tasks[0].description(), Some("new_desc2".to_string()));
change(1, &path, None, Some(None), None, None).unwrap();
assert!(path.load().unwrap()[0].description().is_none());
change(1, &path, None, None, Some(None), None).unwrap();
assert!(path.load().unwrap()[0].deadline().is_none());
change(
1,
&path,
None,
None,
Some(Some("2000-2-1".to_string())),
None,
)
.unwrap();
let expected = to_utc(
&NaiveDateTime::parse_from_str("2000-02-01T23:59:59", "%Y-%m-%dT%H:%M:%S").unwrap(),
)
.unwrap();
assert_eq!(path.load().unwrap()[0].deadline(), Some(expected));
change(3, &path, None, None, None, Some(None)).unwrap();
assert_eq!(path.load().unwrap()[2].priority(), Priority::Low);
change(3, &path, None, None, None, Some(Some(Priority::Medium))).unwrap();
assert_eq!(path.load().unwrap()[2].priority(), Priority::Medium);
let tasks = path.load().unwrap();
assert!(
change(
1,
&path,
None,
None,
Some(Some("not-a-date".to_string())),
None
)
.is_err()
);
assert_eq!(path.load().unwrap(), tasks);
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
}
#[test]
fn test_sort() {
let file = temp_path("sort");
let path = TaskStore::new(Some(file.clone()));
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
set_test_task(&path);
list(&path, Some(SortBy::Deadline)).unwrap();
let tasks = path.load().unwrap();
let expected = to_utc(
&NaiveDateTime::parse_from_str("2000-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S").unwrap(),
)
.unwrap();
assert_eq!(tasks[0].deadline(), Some(expected));
assert!(tasks[2].deadline().is_none());
list(&path, Some(SortBy::Priority)).unwrap();
let tasks = path.load().unwrap();
assert_eq!(tasks[0].priority(), Priority::High);
assert_eq!(tasks[1].priority(), Priority::Medium);
assert_eq!(tasks[2].priority(), Priority::Low);
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
}
#[test]
fn test_done_undone() {
let file = temp_path("done_undone");
let path = TaskStore::new(Some(file.clone()));
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
set_test_task(&path);
assert!(done(0, &path).is_err());
assert!(done(99, &path).is_err());
done(1, &path).unwrap();
assert!(path.load().unwrap()[0]._completed());
undone(1, &path).unwrap();
assert!(!path.load().unwrap()[0]._completed());
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
}
#[test]
fn test_undo() {
let file = temp_path("test_undo");
let path = TaskStore::new(Some(file.clone()));
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
set_test_task(&path);
add("undo_test_content1".to_string(), &path, None, None, None).unwrap();
assert_eq!(path.load().unwrap()[3]._content(), "undo_test_content1");
assert_eq!(path.load_backup().unwrap()[1]._content(), "task2");
assert_eq!(path.load_backup().unwrap()[2]._content(), "task3");
assert_eq!(path.load_backup().unwrap().len(), 3);
undo(&path, true).unwrap();
assert_eq!(path.load().unwrap()[1]._content(), "task2");
assert_eq!(path.load().unwrap()[2]._content(), "task3");
assert_eq!(path.load().unwrap().len(), 3);
let _ = fs::remove_file(path.main_path());
let _ = fs::remove_file(path.backup_path());
}
}