use a generic Write instance for command output

This commit is contained in:
Dustin J. Mitchell
2020-12-20 19:45:24 -05:00
parent 6b550e7516
commit 7d17740ca8
13 changed files with 154 additions and 46 deletions

1
Cargo.lock generated
View File

@@ -2274,6 +2274,7 @@ dependencies = [
"prettytable-rs", "prettytable-rs",
"taskchampion", "taskchampion",
"tempdir", "tempdir",
"termcolor",
"textwrap 0.12.1", "textwrap 0.12.1",
] ]

View File

@@ -12,6 +12,7 @@ log = "^0.4.11"
nom = "*" nom = "*"
prettytable-rs = "^0.8.0" prettytable-rs = "^0.8.0"
textwrap = "0.12.1" textwrap = "0.12.1"
termcolor = "1.1.2"
[dependencies.config] [dependencies.config]
default-features = false default-features = false

View File

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

View File

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

View File

@@ -1,24 +1,31 @@
use crate::usage::Usage; use crate::usage::Usage;
use failure::Fallible; use failure::Fallible;
use std::io; use termcolor::WriteColor;
pub(crate) fn execute(command_name: String, summary: bool) -> Fallible<()> { pub(crate) fn execute<W: WriteColor>(
w: &mut W,
command_name: String,
summary: bool,
) -> Fallible<()> {
let usage = Usage::new(); let usage = Usage::new();
usage.write_help(io::stdout(), command_name, summary)?; usage.write_help(w, command_name, summary)?;
Ok(()) Ok(())
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::invocation::cmd::test::*;
#[test] #[test]
fn test_summary() { fn test_summary() {
execute("task".to_owned(), true).unwrap(); let mut w = test_writer();
execute(&mut w, "task".to_owned(), true).unwrap();
} }
#[test] #[test]
fn test_long() { fn test_long() {
execute("task".to_owned(), false).unwrap(); let mut w = test_writer();
execute(&mut w, "task".to_owned(), false).unwrap();
} }
} }

View File

@@ -4,8 +4,14 @@ use crate::table;
use failure::Fallible; use failure::Fallible;
use prettytable::{cell, row, Table}; use prettytable::{cell, row, Table};
use taskchampion::Replica; use taskchampion::Replica;
use termcolor::WriteColor;
pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fallible<()> { pub(crate) fn execute<W: WriteColor>(
w: &mut W,
replica: &mut Replica,
filter: Filter,
debug: bool,
) -> Fallible<()> {
for task in filtered_tasks(replica, &filter)? { for task in filtered_tasks(replica, &filter)? {
let uuid = task.get_uuid(); let uuid = task.get_uuid();
@@ -25,7 +31,7 @@ pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fal
t.add_row(row![b->"Status", task.get_status()]); t.add_row(row![b->"Status", task.get_status()]);
t.add_row(row![b->"Active", task.is_active()]); t.add_row(row![b->"Active", task.is_active()]);
} }
t.printstd(); t.print(w)?;
} }
Ok(()) Ok(())
@@ -34,16 +40,22 @@ pub(crate) fn execute(replica: &mut Replica, filter: Filter, debug: bool) -> Fal
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::invocation::cmd::test::test_replica; use crate::invocation::cmd::test::*;
use taskchampion::Status;
#[test] #[test]
fn test_info() { fn test_info() {
let mut w = test_writer();
let mut replica = test_replica(); let mut replica = test_replica();
replica
.new_task(Status::Pending, "my task".to_owned())
.unwrap();
let filter = Filter { let filter = Filter {
..Default::default() ..Default::default()
}; };
let debug = false; let debug = false;
execute(&mut replica, filter, debug).unwrap(); execute(&mut w, &mut replica, filter, debug).unwrap();
// output is to stdout, so this is as much as we can check assert!(w.into_string().contains("my task"));
} }
} }

View File

@@ -4,8 +4,13 @@ use crate::table;
use failure::Fallible; use failure::Fallible;
use prettytable::{cell, row, Table}; use prettytable::{cell, row, Table};
use taskchampion::Replica; use taskchampion::Replica;
use termcolor::WriteColor;
pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> { pub(crate) fn execute<W: WriteColor>(
w: &mut W,
replica: &mut Replica,
report: Report,
) -> Fallible<()> {
let mut t = Table::new(); let mut t = Table::new();
t.set_format(table::format()); t.set_format(table::format());
t.set_titles(row![b->"id", b->"act", b->"description"]); t.set_titles(row![b->"id", b->"act", b->"description"]);
@@ -21,7 +26,7 @@ pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> {
}; };
t.add_row(row![id, active, task.get_description()]); t.add_row(row![id, active, task.get_description()]);
} }
t.printstd(); t.print(w)?;
Ok(()) Ok(())
} }
@@ -29,17 +34,23 @@ pub(crate) fn execute(replica: &mut Replica, report: Report) -> Fallible<()> {
mod test { mod test {
use super::*; use super::*;
use crate::argparse::Filter; use crate::argparse::Filter;
use crate::invocation::cmd::test::test_replica; use crate::invocation::cmd::test::*;
use taskchampion::Status;
#[test] #[test]
fn test_list() { fn test_list() {
let mut w = test_writer();
let mut replica = test_replica(); let mut replica = test_replica();
replica
.new_task(Status::Pending, "my task".to_owned())
.unwrap();
let report = Report { let report = Report {
filter: Filter { filter: Filter {
..Default::default() ..Default::default()
}, },
}; };
execute(&mut replica, report).unwrap(); execute(&mut w, &mut replica, report).unwrap();
// output is to stdout, so this is as much as we can check assert!(w.into_string().contains("my task"));
} }
} }

View File

@@ -2,8 +2,10 @@ use crate::argparse::{Filter, Modification};
use crate::invocation::{apply_modification, filtered_tasks}; use crate::invocation::{apply_modification, filtered_tasks};
use failure::Fallible; use failure::Fallible;
use taskchampion::Replica; use taskchampion::Replica;
use termcolor::WriteColor;
pub(crate) fn execute( pub(crate) fn execute<W: WriteColor>(
w: &mut W,
replica: &mut Replica, replica: &mut Replica,
filter: Filter, filter: Filter,
modification: Modification, modification: Modification,
@@ -11,7 +13,7 @@ pub(crate) fn execute(
for task in filtered_tasks(replica, &filter)? { for task in filtered_tasks(replica, &filter)? {
let mut task = task.into_mut(replica); let mut task = task.into_mut(replica);
apply_modification(&mut task, &modification)?; apply_modification(w, &mut task, &modification)?;
} }
Ok(()) Ok(())
@@ -22,10 +24,12 @@ mod test {
use super::*; use super::*;
use crate::argparse::DescriptionMod; use crate::argparse::DescriptionMod;
use crate::invocation::cmd::test::test_replica; use crate::invocation::cmd::test::test_replica;
use crate::invocation::cmd::test::*;
use taskchampion::Status; use taskchampion::Status;
#[test] #[test]
fn test_modify() { fn test_modify() {
let mut w = test_writer();
let mut replica = test_replica(); let mut replica = test_replica();
let task = replica let task = replica
@@ -39,11 +43,16 @@ mod test {
description: DescriptionMod::Set("new description".to_owned()), description: DescriptionMod::Set("new description".to_owned()),
..Default::default() ..Default::default()
}; };
execute(&mut replica, filter, modification).unwrap(); execute(&mut w, &mut replica, filter, modification).unwrap();
// check that the task appeared.. // check that the task appeared..
let task = replica.get_task(task.get_uuid()).unwrap().unwrap(); let task = replica.get_task(task.get_uuid()).unwrap().unwrap();
assert_eq!(task.get_description(), "new description"); assert_eq!(task.get_description(), "new description");
assert_eq!(task.get_status(), Status::Pending); assert_eq!(task.get_status(), Status::Pending);
assert_eq!(
w.into_string(),
format!("modified task {}\n", task.get_uuid())
);
} }
} }

View File

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

View File

@@ -1,3 +1,4 @@
use std::io;
use taskchampion::{server, taskstorage, Replica, ServerConfig}; use taskchampion::{server, taskstorage, Replica, ServerConfig};
use tempdir::TempDir; use tempdir::TempDir;
@@ -12,3 +13,38 @@ pub(super) fn test_server(dir: &TempDir) -> Box<dyn server::Server> {
}) })
.unwrap() .unwrap()
} }
pub(super) struct TestWriter {
data: Vec<u8>,
}
impl TestWriter {
pub(super) fn into_string(self) -> String {
String::from_utf8(self.data).unwrap()
}
}
impl io::Write for TestWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.data.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.data.flush()
}
}
impl termcolor::WriteColor for TestWriter {
fn supports_color(&self) -> bool {
false
}
fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> {
Ok(())
}
fn reset(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(super) fn test_writer() -> TestWriter {
TestWriter { data: vec![] }
}

View File

@@ -1,16 +1,23 @@
use failure::Fallible; use failure::Fallible;
use termcolor::{ColorSpec, WriteColor};
pub(crate) fn execute() -> Fallible<()> { pub(crate) fn execute<W: WriteColor>(w: &mut W) -> Fallible<()> {
println!("TaskChampion {}", env!("CARGO_PKG_VERSION")); write!(w, "TaskChampion ")?;
w.set_color(ColorSpec::new().set_bold(true))?;
write!(w, "{}\n", env!("CARGO_PKG_VERSION"))?;
w.reset()?;
Ok(()) Ok(())
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::invocation::cmd::test::*;
#[test] #[test]
fn test_version() { fn test_version() {
execute().unwrap(); let mut w = test_writer();
execute(&mut w).unwrap();
assert!(w.into_string().starts_with("TaskChampion "));
} }
} }

View File

@@ -4,6 +4,7 @@ use crate::argparse::{Command, Subcommand};
use config::Config; use config::Config;
use failure::Fallible; use failure::Fallible;
use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Uuid}; use taskchampion::{server, Replica, ReplicaConfig, ServerConfig, Uuid};
use termcolor::{ColorChoice, StandardStream};
mod cmd; mod cmd;
mod filter; mod filter;
@@ -17,6 +18,8 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> {
log::debug!("command: {:?}", command); log::debug!("command: {:?}", command);
log::debug!("settings: {:?}", settings); log::debug!("settings: {:?}", settings);
let mut w = StandardStream::stdout(ColorChoice::Auto);
// This function examines the command and breaks out the necessary bits to call one of the // This function examines the command and breaks out the necessary bits to call one of the
// `execute` functions in a submodule of `cmd`. // `execute` functions in a submodule of `cmd`.
@@ -26,11 +29,11 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> {
Command { Command {
subcommand: Subcommand::Help { summary }, subcommand: Subcommand::Help { summary },
command_name, command_name,
} => return cmd::help::execute(command_name, summary), } => return cmd::help::execute(&mut w, command_name, summary),
Command { Command {
subcommand: Subcommand::Version, subcommand: Subcommand::Version,
.. ..
} => return cmd::version::execute(), } => return cmd::version::execute(&mut w),
_ => {} _ => {}
}; };
@@ -39,7 +42,7 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> {
Command { Command {
subcommand: Subcommand::Add { modification }, subcommand: Subcommand::Add { modification },
.. ..
} => return cmd::add::execute(&mut replica, modification), } => return cmd::add::execute(&mut w, &mut replica, modification),
Command { Command {
subcommand: subcommand:
@@ -48,29 +51,29 @@ pub(crate) fn invoke(command: Command, settings: Config) -> Fallible<()> {
modification, modification,
}, },
.. ..
} => return cmd::modify::execute(&mut replica, filter, modification), } => return cmd::modify::execute(&mut w, &mut replica, filter, modification),
Command { Command {
subcommand: Subcommand::List { report }, subcommand: Subcommand::List { report },
.. ..
} => return cmd::list::execute(&mut replica, report), } => return cmd::list::execute(&mut w, &mut replica, report),
Command { Command {
subcommand: Subcommand::Info { filter, debug }, subcommand: Subcommand::Info { filter, debug },
.. ..
} => return cmd::info::execute(&mut replica, filter, debug), } => return cmd::info::execute(&mut w, &mut replica, filter, debug),
Command { Command {
subcommand: Subcommand::Gc, subcommand: Subcommand::Gc,
.. ..
} => return cmd::gc::execute(&mut replica), } => return cmd::gc::execute(&mut w, &mut replica),
Command { Command {
subcommand: Subcommand::Sync, subcommand: Subcommand::Sync,
.. ..
} => { } => {
let mut server = get_server(&settings)?; let mut server = get_server(&settings)?;
return cmd::sync::execute(&mut replica, &mut server); return cmd::sync::execute(&mut w, &mut replica, &mut server);
} }
// handled in the first match, but here to ensure this match is exhaustive // handled in the first match, but here to ensure this match is exhaustive

View File

@@ -1,9 +1,14 @@
use crate::argparse::{DescriptionMod, Modification}; use crate::argparse::{DescriptionMod, Modification};
use failure::Fallible; use failure::Fallible;
use taskchampion::TaskMut; use taskchampion::TaskMut;
use termcolor::WriteColor;
/// Apply the given modification /// Apply the given modification
pub(super) fn apply_modification(task: &mut TaskMut, modification: &Modification) -> Fallible<()> { pub(super) fn apply_modification<W: WriteColor>(
w: &mut W,
task: &mut TaskMut,
modification: &Modification,
) -> Fallible<()> {
match modification.description { match modification.description {
DescriptionMod::Set(ref description) => task.set_description(description.clone())?, DescriptionMod::Set(ref description) => task.set_description(description.clone())?,
DescriptionMod::Prepend(ref description) => { DescriptionMod::Prepend(ref description) => {
@@ -27,7 +32,7 @@ pub(super) fn apply_modification(task: &mut TaskMut, modification: &Modification
task.stop()?; task.stop()?;
} }
println!("modified task {}", task.get_uuid()); write!(w, "modified task {}\n", task.get_uuid())?;
Ok(()) Ok(())
} }