Switch to a command-line API closer to TaskWarrior

* Use a parser (rather than clap) to process the command line
* Outline some generic support for filtering, reporting, modifying, etc.
* Break argument parsing strictly from invocation, to allow independent testing
This commit is contained in:
Dustin J. Mitchell
2020-12-03 06:58:10 +00:00
parent 87bb829634
commit 2c579b9f01
45 changed files with 1720 additions and 1072 deletions

View File

@@ -0,0 +1,34 @@
use crate::argparse::{DescriptionMod, Modification};
use failure::Fallible;
use taskchampion::{Replica, Status};
pub(crate) fn execute(replica: &mut Replica, modification: Modification) -> Fallible<()> {
let description = match modification.description {
DescriptionMod::Set(ref s) => s.clone(),
_ => "(no description)".to_owned(),
};
let t = replica.new_task(Status::Pending, description).unwrap();
println!("added task {}", t.get_uuid());
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::invocation::cmd::test::test_replica;
#[test]
fn test_add() {
let mut replica = test_replica();
let modification = Modification {
description: DescriptionMod::Set("my description".to_owned()),
..Default::default()
};
execute(&mut replica, modification).unwrap();
// check that the task appeared..
let task = replica.get_working_set_task(1).unwrap().unwrap();
assert_eq!(task.get_description(), "my description");
assert_eq!(task.get_status(), Status::Pending);
}
}

View File

@@ -0,0 +1,21 @@
use failure::Fallible;
use taskchampion::Replica;
pub(crate) fn execute(replica: &mut Replica) -> Fallible<()> {
replica.gc()?;
println!("garbage collected.");
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::invocation::cmd::test::test_replica;
#[test]
fn test_gc() {
let mut replica = test_replica();
execute(&mut replica).unwrap();
// this mostly just needs to not fail!
}
}

View File

@@ -0,0 +1,28 @@
use failure::Fallible;
pub(crate) fn execute(command_name: String, summary: bool) -> Fallible<()> {
println!(
"TaskChampion {}: Personal task-tracking",
env!("CARGO_PKG_VERSION")
);
if !summary {
println!();
println!("USAGE: {} [args]\n(help output TODO)", command_name); // TODO
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_summary() {
execute("task".to_owned(), true).unwrap();
}
#[test]
fn test_long() {
execute("task".to_owned(), false).unwrap();
}
}

View File

@@ -0,0 +1,49 @@
use crate::argparse::Filter;
use crate::invocation::filtered_tasks;
use crate::table;
use failure::Fallible;
use prettytable::{cell, row, Table};
use taskchampion::Replica;
pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fallible<()> {
for task in filtered_tasks(replica, &filter)? {
let uuid = task.get_uuid();
let mut t = Table::new();
t.set_format(table::format());
if debug {
t.set_titles(row![b->"key", b->"value"]);
for (k, v) in task.get_taskmap().iter() {
t.add_row(row![k, v]);
}
} else {
t.add_row(row![b->"Uuid", uuid]);
if let Some(i) = replica.get_working_set_index(uuid)? {
t.add_row(row![b->"Id", i]);
}
t.add_row(row![b->"Description", task.get_description()]);
t.add_row(row![b->"Status", task.get_status()]);
t.add_row(row![b->"Active", task.is_active()]);
}
t.printstd();
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::invocation::cmd::test::test_replica;
#[test]
fn test_info() {
let mut replica = test_replica();
let filter = Filter {
..Default::default()
};
let debug = false;
execute(&mut replica, filter, debug).unwrap();
// output is to stdout, so this is as much as we can check
}
}

View File

@@ -0,0 +1,45 @@
use crate::argparse::Report;
use crate::invocation::filtered_tasks;
use crate::table;
use failure::Fallible;
use prettytable::{cell, row, Table};
use taskchampion::Replica;
pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> {
let mut t = Table::new();
t.set_format(table::format());
t.set_titles(row![b->"id", b->"act", b->"description"]);
for task in filtered_tasks(replica, &report.filter)? {
let uuid = task.get_uuid();
let mut id = uuid.to_string();
if let Some(i) = replica.get_working_set_index(&uuid)? {
id = i.to_string();
}
let active = match task.is_active() {
true => "*",
false => "",
};
t.add_row(row![id, active, task.get_description()]);
}
t.printstd();
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::argparse::Filter;
use crate::invocation::cmd::test::test_replica;
#[test]
fn test_list() {
let mut replica = test_replica();
let report = Report {
filter: Filter {
..Default::default()
},
};
execute(&mut replica, report).unwrap();
// output is to stdout, so this is as much as we can check
}
}

View File

@@ -0,0 +1,13 @@
//! Responsible for executing commands as parsed by [`crate::argparse`].
pub(crate) mod add;
pub(crate) mod gc;
pub(crate) mod help;
pub(crate) mod info;
pub(crate) mod list;
pub(crate) mod modify;
pub(crate) mod sync;
pub(crate) mod version;
#[cfg(test)]
mod test;

View File

@@ -0,0 +1,49 @@
use crate::argparse::{Filter, Modification};
use crate::invocation::{apply_modification, filtered_tasks};
use failure::Fallible;
use taskchampion::Replica;
pub(crate) fn execute(
replica: &mut Replica,
filter: Filter,
modification: Modification,
) -> Fallible<()> {
for task in filtered_tasks(replica, &filter)? {
let mut task = task.into_mut(replica);
apply_modification(&mut task, &modification)?;
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::argparse::DescriptionMod;
use crate::invocation::cmd::test::test_replica;
use taskchampion::Status;
#[test]
fn test_modify() {
let mut replica = test_replica();
let task = replica
.new_task(Status::Pending, "old description".to_owned())
.unwrap();
let filter = Filter {
..Default::default()
};
let modification = Modification {
description: DescriptionMod::Set("new description".to_owned()),
..Default::default()
};
execute(&mut replica, filter, modification).unwrap();
// check that the task appeared..
let task = replica.get_task(task.get_uuid()).unwrap().unwrap();
assert_eq!(task.get_description(), "new description");
assert_eq!(task.get_status(), Status::Pending);
}
}

View File

@@ -0,0 +1,26 @@
use failure::Fallible;
use taskchampion::{server::Server, Replica};
pub(crate) fn execute(replica: &mut Replica, server: &mut Box<dyn Server>) -> Fallible<()> {
replica.sync(server)?;
println!("sync complete.");
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::invocation::cmd::test::{test_replica, test_server};
use tempdir::TempDir;
#[test]
fn test_add() {
let mut replica = test_replica();
let server_dir = TempDir::new("test").unwrap();
let mut server = test_server(&server_dir);
// this just has to not fail -- the details of the actual sync are
// tested thoroughly in the taskchampion crate
execute(&mut replica, &mut server).unwrap();
}
}

View File

@@ -0,0 +1,14 @@
use taskchampion::{server, taskstorage, Replica, ServerConfig};
use tempdir::TempDir;
pub(super) fn test_replica() -> Replica {
let storage = taskstorage::InMemoryStorage::new();
Replica::new(Box::new(storage))
}
pub(super) fn test_server(dir: &TempDir) -> Box<dyn server::Server> {
server::from_config(ServerConfig::Local {
server_dir: dir.path().to_path_buf(),
})
.unwrap()
}

View File

@@ -0,0 +1,16 @@
use failure::Fallible;
pub(crate) fn execute() -> Fallible<()> {
println!("TaskChampion {}", env!("CARGO_PKG_VERSION"));
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_version() {
execute().unwrap();
}
}