extern crate anyhow;
extern crate home;
extern crate serde;
extern crate terminal_size;
extern crate toml;
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::{
collections::VecDeque,
fs::{create_dir_all, remove_file, rename, OpenOptions},
io::{prelude::*, BufReader, Write},
path::PathBuf,
};
fn get_terminal_width() -> usize {
#[cfg(not(any(test, feature = "integration")))]
if let Some((terminal_size::Width(w), _)) = terminal_size::terminal_size() {
return w as usize;
}
80 as usize
}
const KO_BASE_DIR: &'static str = ".kanorg.d";
const KO_CFG_FILE: &'static str = "config";
const KO_ACTIVE_TASKS_DIR: &'static str = "active.d";
const KO_ARCHIVED_TASKS_DIR: &'static str = "archive.d";
#[derive(Deserialize, Serialize, Debug)]
struct Workflows {
backlog: VecDeque<u16>,
todo: VecDeque<u16>,
doing: VecDeque<u16>,
done: VecDeque<u16>,
last_task: u16,
}
impl Workflows {
pub fn last_task(&self) -> u16 {
self.last_task
}
pub fn workflow(&self, workflow_name: &str) -> Option<&VecDeque<u16>> {
match workflow_name {
"backlog" => Some(&self.backlog),
"todo" => Some(&self.todo),
"doing" => Some(&self.doing),
"done" => Some(&self.done),
_ => None,
}
}
pub fn workflow_mut(&mut self, workflow_name: &str) -> Option<&mut VecDeque<u16>> {
match workflow_name {
"backlog" => Some(&mut self.backlog),
"todo" => Some(&mut self.todo),
"doing" => Some(&mut self.doing),
"done" => Some(&mut self.done),
_ => None,
}
}
pub fn find_workflow_name(&self, task: &u16) -> Option<&str> {
if self.backlog.contains(task) {
Some("backlog")
} else if self.todo.contains(task) {
Some("todo")
} else if self.doing.contains(task) {
Some("doing")
} else if self.done.contains(task) {
Some("done")
} else {
None
}
}
pub fn main_max_len(&self) -> usize {
match vec![self.todo.len(), self.doing.len(), self.done.len()]
.iter()
.max()
{
None => 0,
Some(max_len) => max_len.clone(),
}
}
pub fn remove_task(&mut self, task: u16, workflow_name: &str) -> Result<()> {
match self.workflow_mut(workflow_name) {
Some(workflow) => match workflow.iter().position(|t| t == &task) {
Some(pos) => {
workflow.remove(pos).expect(&format!(
"The index `{}` is out of bounds from: `{:?}.",
pos, workflow,
));
Ok(())
}
None => {
return Err(anyhow!(
"Couldn't not find the task `{}` in the workflow `{}`.",
task,
workflow_name
))
}
},
None => {
return Err(anyhow!(
"Couldn't remove task `{}` because the workflow `{:?}` does not exist.",
task,
workflow_name
))
}
}
}
fn add_task(&mut self, task: u16, workflow_name: &str) -> Result<Option<u16>> {
match workflow_name {
"done" => {
self.done.push_front(task);
if self.done.len() > 5 {
return Ok(self.done.pop_back());
}
}
"backlog" | "todo" | "doing" => {
self.workflow_mut(workflow_name).unwrap().push_front(task)
}
_ => {
return Err(anyhow!(
"No workflow named `{}`. It has to be one of backlog, todo, doing or done.",
workflow_name
))
}
}
Ok(None)
}
pub fn add_new_task(&mut self, workflow_name: &str) -> Result<Option<u16>> {
self.last_task += 1;
self.add_task(self.last_task, workflow_name)
}
pub fn move_task(&mut self, task: u16, from: &str, to: &str) -> Result<Option<u16>> {
self.remove_task(task, from)?;
self.add_task(task, to)
}
}
pub struct KanOrgBoard {
workflows: Workflows,
base_dir: PathBuf,
}
impl KanOrgBoard {
fn search_config_file(hints: &[&PathBuf]) -> Result<PathBuf> {
let relative_cfg_file = format!("{}/{}", KO_BASE_DIR, KO_CFG_FILE);
for search_dir in hints {
let config_file = search_dir.join(&relative_cfg_file);
if config_file.is_file() {
return Ok(config_file);
}
}
Err(anyhow!(
"No configuration file found in any of the following paths: {:#?}\nPlease \
consider creating a new configuration with the `create` subcommand.",
hints
))
}
fn save_config(&self) -> Result<()> {
let config_file = self.base_dir.join(KO_CFG_FILE);
OpenOptions::new()
.write(true)
.truncate(true)
.open(&config_file)?
.write_all(toml::to_string(&self.workflows)?.as_bytes())?;
Ok(())
}
fn read_first_line_cleaned(file_path: PathBuf) -> Result<String> {
let mut first_line: String = String::new();
BufReader::new(OpenOptions::new().read(true).open(file_path)?)
.read_line(&mut first_line)?;
Ok(first_line.strip_prefix("# ").unwrap().trim().to_owned())
}
fn format_task_title(&self, task: Option<&u16>, max_size: usize) -> Result<String> {
match task {
Some(task_id) => Ok(format!(
"{1:>4} {2:<.0$}",
max_size - 6,
task_id,
Self::read_first_line_cleaned(
self.base_dir
.join(KO_ACTIVE_TASKS_DIR)
.join(task_id.to_string())
)?
)),
None => Ok(String::new()),
}
}
#[cfg(not(any(test, feature = "integration")))]
fn edit_file_interactivelly(file_path: PathBuf) -> Result<()> {
let editor = std::env::var("EDITOR").unwrap_or("vi".to_owned());
if let Err(err) = std::process::Command::new(&editor).arg(&file_path).status() {
return match err.kind() {
std::io::ErrorKind::NotFound => {
Err(anyhow!("Could not find the editor `{}`.", editor))
}
_ => Err(anyhow!(
"There was a problem editing the file `{}`: \n\t{}",
file_path.to_str().unwrap(),
err
)),
};
}
Ok(())
}
pub fn new(current_working_dir: &PathBuf) -> Result<Self> {
let config_file =
Self::search_config_file(&[current_working_dir, &home::home_dir().unwrap()])?;
let base_dir = config_file.parent().unwrap().to_path_buf();
create_dir_all(base_dir.join(KO_ACTIVE_TASKS_DIR))?;
create_dir_all(base_dir.join(KO_ARCHIVED_TASKS_DIR))?;
let mut contents: String = String::new();
OpenOptions::new()
.read(true)
.open(&config_file)?
.read_to_string(&mut contents)?;
let workflows: Workflows = match toml::from_str(&contents) {
Ok(file_contents) => file_contents,
Err(err) => {
return Err(anyhow!(
"Could not properly parse the config file {:?}:\n\t{}",
config_file,
err
))
}
};
Ok(Self {
workflows: workflows,
base_dir: base_dir,
})
}
pub fn create(target_dir: &str) -> Result<()> {
let target_base_dir = PathBuf::from(target_dir).canonicalize()?.join(KO_BASE_DIR);
create_dir_all(target_base_dir.join(KO_ACTIVE_TASKS_DIR))?;
create_dir_all(target_base_dir.join(KO_ARCHIVED_TASKS_DIR))?;
let config_file = target_base_dir.join(KO_CFG_FILE);
if !config_file.is_file() {
OpenOptions::new()
.write(true)
.read(true)
.create(true)
.open(&config_file)?
.write_all(
"backlog = []\ntodo = []\ndoing = []\ndone = []\nlast_task = 0\n".as_bytes(),
)?;
}
Ok(())
}
pub fn show<W: Write>(&self, workflow_name: Option<&str>, writer: &mut W) -> Result<()> {
let terminal_columns = get_terminal_width();
if let Some(single_workflow) = workflow_name {
let selected_workflow_name = single_workflow.to_owned().to_uppercase();
let selected_workflow = self.workflows.workflow(single_workflow);
if let None = selected_workflow {
return Err(anyhow!(
"Workflow `{}` does not exist.",
selected_workflow_name
));
}
writeln!(writer, "{}", selected_workflow_name)?;
for task in selected_workflow.unwrap().iter() {
writeln!(
writer,
"{}",
self.format_task_title(Some(task), terminal_columns)?
)?;
}
} else {
let column_gap: usize = (terminal_columns - (3 + 1)) / 3;
let backlog_workflow = self.workflows.workflow("backlog").unwrap();
let mut backlog_iter = backlog_workflow.iter();
let mut todo_iter = self.workflows.workflow("todo").unwrap().iter();
let mut doing_iter = self.workflows.workflow("doing").unwrap().iter();
let mut done_iter = self.workflows.workflow("done").unwrap().iter();
writeln!(
writer,
"|{:^3$}|{:^3$}|{:^3$}|",
"TODO", "DOING", "DONE", column_gap
)?;
writeln!(writer, "|{0}|{0}|{0}|", str::repeat("-", column_gap))?;
for _ in 0..self.workflows.main_max_len() {
writeln!(
writer,
"|{:3$}|{:3$}|{:3$}|",
self.format_task_title(todo_iter.next(), column_gap)?,
self.format_task_title(doing_iter.next(), column_gap)?,
self.format_task_title(done_iter.next(), column_gap)?,
column_gap,
)?;
}
writeln!(writer, "\nBACKLOG")?;
let backlog_len = backlog_workflow.len();
let backlog_max_len = if backlog_len <= 5 { backlog_len } else { 5 };
for _ in 0..backlog_max_len {
writeln!(
writer,
"{}",
self.format_task_title(backlog_iter.next(), terminal_columns)?
)?;
}
if backlog_len > 5 {
writeln!(
writer,
"WARNING: The backlog has been trimmed. Run `ko show backlog` to see \
all the backlog tasks.",
)?;
}
}
Ok(())
}
pub fn add(&mut self, title: &str, workflow_name: Option<&str>) -> Result<()> {
let task_title: String = title.to_owned();
let selected_workflow_name = workflow_name.unwrap_or("backlog");
let active_tasks_dir = self.base_dir.join(KO_ACTIVE_TASKS_DIR);
let archive_tasks_dir = self.base_dir.join(KO_ARCHIVED_TASKS_DIR);
if let Some(bench_task) = self.workflows.add_new_task(selected_workflow_name)? {
rename(
active_tasks_dir.join(bench_task.to_string()),
archive_tasks_dir.join(bench_task.to_string()),
)?;
}
let last_task_path = active_tasks_dir.join(self.workflows.last_task().to_string());
match OpenOptions::new()
.create(true)
.write(true)
.open(&last_task_path)
{
Ok(mut writing_file) => {
if let Err(err) = writing_file.write_all(format!("# {}\n", task_title).as_bytes()) {
return Err(anyhow!(
"There was a problem writing the file {:?}: {}",
last_task_path,
err
));
}
}
Err(err) => {
return Err(anyhow!(
"There was a problem opening the file {:?}: {}",
last_task_path,
err
))
}
}
#[cfg(not(any(test, feature = "integration")))]
Self::edit_file_interactivelly(last_task_path)?;
self.save_config()?;
Ok(())
}
pub fn relocate(&mut self, task: &str, workflow_name: Option<&str>) -> Result<()> {
let selected_task: u16 = match task.parse() {
Ok(value) => value,
Err(err) => return Err(anyhow!("Couldn't parse `{}` into u16: {:?}", task, err)),
};
let from_workflow = match self.workflows.find_workflow_name(&selected_task) {
Some(found_workflow_name) => found_workflow_name.to_owned(),
None => {
return Err(anyhow!(
"Couldn't find the task `{}` in any active workflow.",
task
))
}
};
let to_workflow = workflow_name.unwrap_or("backlog");
if from_workflow == to_workflow {
println!(
"Task `{}` is already is in the workflow `{}`.",
task, to_workflow
);
return Ok(());
}
let active_tasks_dir = self.base_dir.join(KO_ACTIVE_TASKS_DIR);
let archive_tasks_dir = self.base_dir.join(KO_ARCHIVED_TASKS_DIR);
match self
.workflows
.move_task(selected_task, &from_workflow, &to_workflow)
{
Ok(benck_task_opt) => {
if let Some(bench_task) = benck_task_opt {
rename(
active_tasks_dir.join(bench_task.to_string()),
archive_tasks_dir.join(bench_task.to_string()),
)?;
}
}
Err(err) => {
return Err(anyhow!(
"There was a problem moving the task `{}` from `{}` to `{}`:\n\t{}",
task,
from_workflow,
to_workflow,
err
))
}
}
println!(
"Moved task `{}` from workflow `{}` to `{}`.",
task, from_workflow, to_workflow
);
self.save_config()?;
Ok(())
}
pub fn edit(&self, task: &str) -> Result<()> {
let selected_task: u16 = match task.parse() {
Ok(value) => value,
Err(err) => return Err(anyhow!("Couldn't parse `{}` into u16: {:?}", task, err)),
};
if let None = self.workflows.find_workflow_name(&selected_task) {
return Err(anyhow!(
"Couldn't find the task `{}` in any active workflow.",
task
));
}
#[cfg(not(any(test, feature = "integration")))]
Self::edit_file_interactivelly(
self.base_dir
.join(format!("{}/{}", KO_ACTIVE_TASKS_DIR, selected_task)),
)?;
Ok(())
}
pub fn delete(&mut self, task: &str) -> Result<()> {
let selected_task: u16 = match task.parse() {
Ok(value) => value,
Err(err) => return Err(anyhow!("Couldn't parse `{}` into u16: {:?}", task, err)),
};
let contained_in_workflow = match self.workflows.find_workflow_name(&selected_task) {
Some(found_workflow_name) => found_workflow_name.to_owned(),
None => {
return Err(anyhow!(
"Couldn't find the task `{}` in any active workflow.",
task
))
}
};
self.workflows
.remove_task(selected_task, &contained_in_workflow)?;
remove_file(
self.base_dir
.join(format!("{}/{}", KO_ACTIVE_TASKS_DIR, selected_task)),
)?;
self.save_config()?;
Ok(())
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
mod workflows {
use super::*;
#[test]
fn last_task() {
let w: Workflows = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert_eq!(w.last_task(), 12u16);
}
#[test]
fn workflow() {
let w: Workflows = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert_eq!(
w.workflow("backlog").unwrap().as_slices().0,
&[12u16, 11u16, 10u16, 9u16][..]
);
assert!(w.workflow("haha greetings").is_none());
}
#[test]
fn workflow_mut() {
let mut w: Workflows = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
let backlog = w.workflow_mut("backlog").unwrap();
assert_eq!(backlog.as_slices().0, &[12u16, 11u16, 10u16, 9u16][..]);
backlog.push_back(1000u16);
assert_eq!(
backlog.as_slices().0,
&[12u16, 11u16, 10u16, 9u16, 1000u16][..]
);
assert!(w.workflow_mut("haha greetings").is_none());
}
#[test]
fn find_workflow_name() {
let w = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert_eq!(w.find_workflow_name(&4u16), Some("done"));
assert_eq!(w.find_workflow_name(&200u16), None);
}
#[test]
fn main_max_len() {
let w = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert_eq!(w.main_max_len(), 5);
let w = Workflows {
backlog: VecDeque::new(),
todo: VecDeque::new(),
doing: VecDeque::new(),
done: VecDeque::new(),
last_task: 0,
};
assert_eq!(w.main_max_len(), 0);
}
#[test]
fn remove_task() {
let mut w = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert!(w.remove_task(4, "done").is_ok());
assert!(!w.done.contains(&4u16));
assert!(w.remove_task(4, "none").is_err());
assert!(w.remove_task(2000, "backlog").is_err());
}
#[test]
fn add_task() {
let mut w = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert!(w.add_task(1000, "doing").is_ok());
assert!(w.doing.contains(&1000u16));
}
#[test]
fn add_new_task() {
let mut w = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert!(!w.done.contains(&13u16));
assert!(w.add_new_task("done").is_ok());
assert!(w.done.contains(&13u16));
assert_eq!(w.last_task, 13u16);
}
#[test]
fn move_task() {
let mut w = Workflows {
backlog: vec![12, 11, 10, 9].into_iter().collect(),
todo: vec![8].into_iter().collect(),
doing: vec![7, 6].into_iter().collect(),
done: vec![5, 4, 3, 2, 1].into_iter().collect(),
last_task: 12,
};
assert!(w.move_task(10u16, "backlog", "todo").is_ok());
assert!(!w.backlog.contains(&10u16));
assert!(w.todo.contains(&10u16));
assert!(w.move_task(1000u16, "todo", "done").is_err());
}
}
mod kanorgboard {
extern crate fs_extra;
extern crate tempfile;
use super::*;
fn setup_workspace(copy_example_config: bool) -> tempfile::TempDir {
let temp_dir =
tempfile::tempdir().expect("There was a problem creating test temporary file.");
if copy_example_config {
fs_extra::copy_items(
&[PathBuf::from(env!("PWD"))
.join("examples")
.join(KO_BASE_DIR)],
temp_dir.path(),
&fs_extra::dir::CopyOptions::new(),
)
.expect("There was a problem copying test files.");
}
temp_dir
}
#[test]
fn search_config_file() -> Result<()> {
let tmpkeep = setup_workspace(true);
let temp_dir = tmpkeep.path();
let found_file = KanOrgBoard::search_config_file(&[&temp_dir.to_path_buf()])
.expect("Error, the config file could not be found.");
assert_eq!(
found_file,
temp_dir.join(format!("{}/{}", KO_BASE_DIR, KO_CFG_FILE))
);
Ok(())
}
#[test]
fn save_config() {
let tmpkeep = setup_workspace(false);
let temp_dir_base = tmpkeep.path().join(KO_BASE_DIR);
create_dir_all(&temp_dir_base)
.expect("There was an error creating the test setup dirs");
let temp_config_file = temp_dir_base.join(KO_CFG_FILE);
assert!(OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&temp_config_file)
.is_ok());
let kanorg_saving = KanOrgBoard {
workflows: Workflows {
backlog: vec![5].into_iter().collect(),
todo: vec![4, 3].into_iter().collect(),
doing: vec![2].into_iter().collect(),
done: vec![1].into_iter().collect(),
last_task: 5,
},
base_dir: temp_dir_base,
}
.save_config();
assert!(kanorg_saving.is_ok());
let mut file_contents = String::new();
assert!(OpenOptions::new()
.read(true)
.open(&temp_config_file)
.unwrap()
.read_to_string(&mut file_contents)
.is_ok());
assert_eq!(
file_contents,
"backlog = [5]\ntodo = [4, 3]\ndoing = [2]\ndone = [1]\nlast_task = 5\n"
);
}
#[test]
fn read_first_line_cleaned() {
let tmpkeep = setup_workspace(false);
let temp_file_path = tmpkeep.path().join("some_file");
assert!(OpenOptions::new()
.create(true)
.write(true)
.open(&temp_file_path)
.unwrap()
.write_all("# This is a test file\n\nthe description\n".as_bytes())
.is_ok());
let first_line = KanOrgBoard::read_first_line_cleaned(temp_file_path);
assert!(first_line.is_ok());
assert_eq!(first_line.unwrap(), "This is a test file".to_owned());
}
#[test]
fn format_task_title() {
let tmpkeep = setup_workspace(true);
let k = KanOrgBoard::new(&tmpkeep.path().to_path_buf());
assert!(k.is_ok());
let format_title = k.unwrap().format_task_title(Some(&8u16), 50 as usize);
assert!(format_title.is_ok());
assert_eq!(
format_title.unwrap(),
" 8 This is the task 8 sample title".to_owned(),
);
}
#[test]
fn create() {
let tmpkeep = setup_workspace(false);
let temp_dir = tmpkeep.path().to_path_buf();
let temp_base_dir = temp_dir.join(KO_BASE_DIR);
assert!(!&temp_base_dir.exists());
assert!(KanOrgBoard::create(&temp_dir.to_str().unwrap()).is_ok());
assert!(temp_base_dir.join(KO_CFG_FILE).is_file());
assert!(temp_base_dir.join(KO_ACTIVE_TASKS_DIR).is_dir());
assert!(temp_base_dir.join(KO_ARCHIVED_TASKS_DIR).is_dir());
}
#[test]
fn show() {
let tmpkeep = setup_workspace(true);
let temp_dir = tmpkeep.path().to_path_buf();
let k = KanOrgBoard::new(&temp_dir);
assert!(k.is_ok());
let expected_output = "\
| TODO | DOING | DONE |\n\
|-------------------------|-------------------------|-------------------------|\n\
| 18 This is the task 18 | 14 This is the task 14 | 12 This is the task 12 |\n\
| 17 This is the task 17 | 13 This is the task 13 | 11 This is the task 11 |\n\
| 16 This is the task 16 | | 10 This is the task 10 |\n\
| 15 This is the task 15 | | 9 This is the task 9 |\n\
| | | 8 This is the task 8 |\n\
\n\
BACKLOG\n \
25 This is the task 25 sample title\n \
24 This is the task 24 sample title\n \
23 This is the task 23 sample title\n \
22 This is the task 22 sample title\n \
21 This is the task 21 sample title\n\
WARNING: The backlog has been trimmed. Run `ko show backlog` to see all the \
backlog tasks.\n";
let mut stdout_capture = std::io::Cursor::new(Vec::new());
assert!(k.unwrap().show(None, &mut stdout_capture).is_ok());
stdout_capture.seek(std::io::SeekFrom::Start(0)).unwrap();
assert_eq!(
expected_output,
String::from_utf8(stdout_capture.into_inner())
.expect("There was a problem obtaining the output from the function"),
);
}
#[test]
fn show_single() {
let tmpkeep = setup_workspace(true);
let temp_dir = tmpkeep.path().to_path_buf();
let k = KanOrgBoard::new(&temp_dir);
assert!(k.is_ok());
let expected_output = "\
DONE\n \
12 This is the task 12 sample title\n \
11 This is the task 11 sample title\n \
10 This is the task 10 sample title\n \
9 This is the task 9 sample title\n \
8 This is the task 8 sample title\n\
";
let mut stdout_capture = std::io::Cursor::new(Vec::new());
assert!(k.unwrap().show(Some("done"), &mut stdout_capture).is_ok());
stdout_capture.seek(std::io::SeekFrom::Start(0)).unwrap();
assert_eq!(
expected_output,
String::from_utf8(stdout_capture.into_inner())
.expect("There was a problem obtaining the output from the function"),
);
}
#[test]
fn add() {
let tmpkeep = setup_workspace(true);
let temp_dir = tmpkeep.path().to_path_buf();
let mut k = KanOrgBoard::new(&temp_dir)
.expect("Something unwanted ocurred creating KanOrgBoard object.");
assert!(k.add("some task title", Some("doing")).is_ok());
let last_task = k.workflows.last_task;
assert!(temp_dir
.join(format!(
"{}/{}/{}",
KO_BASE_DIR, KO_ACTIVE_TASKS_DIR, last_task,
))
.is_file());
}
#[test]
fn add_overflow() {
let tmpkeep = setup_workspace(true);
let temp_dir = tmpkeep.path().to_path_buf();
let mut k = KanOrgBoard::new(&temp_dir).expect("Error creating the kanboard.");
let oldest_done_task = k.workflows.done[4];
assert!(k.add("some task title", Some("done")).is_ok());
assert!(temp_dir
.join(format!(
"{}/{}/{}",
KO_BASE_DIR, KO_ARCHIVED_TASKS_DIR, oldest_done_task,
))
.is_file());
}
#[test]
fn relocate() {
let tmpkeep = setup_workspace(true);
let temp_dir = tmpkeep.path().to_path_buf();
let mut k = KanOrgBoard::new(&temp_dir).expect("Error creating the kanboard.");
assert!(k.workflows.todo.contains(&17u16));
assert!(!k.workflows.doing.contains(&17u16));
assert!(k.relocate("17", Some("doing")).is_ok());
assert!(!k.workflows.todo.contains(&17u16));
assert!(k.workflows.doing.contains(&17u16));
assert!(k.relocate("17", None).is_ok());
assert!(!k.workflows.doing.contains(&17u16));
assert!(k.workflows.backlog.contains(&17u16));
assert!(k.relocate("juanito", None).is_err());
assert!(k.relocate("12223", None).is_err())
}
#[test]
fn delete() {
let tmpkeep = setup_workspace(true);
let temp_dir = tmpkeep.path().to_path_buf();
let target_file =
temp_dir.join(format!("{}/{}/{}", KO_BASE_DIR, KO_ACTIVE_TASKS_DIR, "13"));
let mut k = KanOrgBoard::new(&temp_dir).expect("Error creating the kanboard.");
assert!(target_file.is_file());
assert!(k.delete("13").is_ok());
assert!(!target_file.is_file());
}
}
}