mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00
Support multiple exit codes
..with more specific error enums.
This commit is contained in:
parent
2345a57940
commit
bb7130f960
23 changed files with 112 additions and 34 deletions
|
@ -1,6 +1,5 @@
|
|||
use super::args::*;
|
||||
use super::{ArgList, Subcommand};
|
||||
use anyhow::bail;
|
||||
use nom::{combinator::*, sequence::*, Err, IResult};
|
||||
|
||||
/// A command is the overall command that the CLI should execute.
|
||||
|
@ -29,13 +28,22 @@ impl Command {
|
|||
}
|
||||
|
||||
/// Parse a command from the given list of strings.
|
||||
pub fn from_argv(argv: &[&str]) -> anyhow::Result<Command> {
|
||||
pub fn from_argv(argv: &[&str]) -> Result<Command, crate::Error> {
|
||||
match Command::parse(argv) {
|
||||
Ok((&[], cmd)) => Ok(cmd),
|
||||
Ok((trailing, _)) => bail!("command line has trailing arguments: {:?}", trailing),
|
||||
Ok((trailing, _)) => Err(crate::Error::for_arguments(format!(
|
||||
"command line has trailing arguments: {:?}",
|
||||
trailing
|
||||
))),
|
||||
Err(Err::Incomplete(_)) => unreachable!(),
|
||||
Err(Err::Error(e)) => bail!("command line not recognized: {:?}", e),
|
||||
Err(Err::Failure(e)) => bail!("command line not recognized: {:?}", e),
|
||||
Err(Err::Error(e)) => Err(crate::Error::for_arguments(format!(
|
||||
"command line not recognized: {:?}",
|
||||
e
|
||||
))),
|
||||
Err(Err::Failure(e)) => Err(crate::Error::for_arguments(format!(
|
||||
"command line not recognized: {:?}",
|
||||
e
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use std::process::exit;
|
||||
|
||||
pub fn main() {
|
||||
if let Err(err) = taskchampion_cli::main() {
|
||||
eprintln!("{:?}", err);
|
||||
exit(1);
|
||||
match taskchampion_cli::main() {
|
||||
Ok(_) => exit(0),
|
||||
Err(e) => {
|
||||
eprintln!("{:?}", e);
|
||||
exit(e.exit_status());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
59
cli/src/errors.rs
Normal file
59
cli/src/errors.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use taskchampion::Error as TcError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Command-Line Syntax Error: {0}")]
|
||||
Arguments(String),
|
||||
|
||||
#[error(transparent)]
|
||||
TaskChampion(#[from] TcError),
|
||||
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Construct a new command-line argument error
|
||||
pub(crate) fn for_arguments<S: ToString>(msg: S) -> Self {
|
||||
Error::Arguments(msg.to_string())
|
||||
}
|
||||
|
||||
/// Determine the exit status for this error, as documented.
|
||||
pub fn exit_status(&self) -> i32 {
|
||||
match *self {
|
||||
Error::Arguments(_) => 3,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
let err: anyhow::Error = err.into();
|
||||
Error::Other(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use anyhow::anyhow;
|
||||
|
||||
#[test]
|
||||
fn test_exit_status() {
|
||||
let mut err: Error;
|
||||
|
||||
err = anyhow!("uhoh").into();
|
||||
assert_eq!(err.exit_status(), 1);
|
||||
|
||||
err = Error::Arguments("uhoh".to_string());
|
||||
assert_eq!(err.exit_status(), 3);
|
||||
|
||||
err = std::io::Error::last_os_error().into();
|
||||
assert_eq!(err.exit_status(), 1);
|
||||
|
||||
err = TcError::Database("uhoh".to_string()).into();
|
||||
assert_eq!(err.exit_status(), 1);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ pub(crate) fn execute<W: WriteColor>(
|
|||
w: &mut W,
|
||||
replica: &mut Replica,
|
||||
modification: Modification,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
let description = match modification.description {
|
||||
DescriptionMod::Set(ref s) => s.clone(),
|
||||
_ => "(no description)".to_owned(),
|
||||
|
|
|
@ -6,7 +6,7 @@ pub(crate) fn execute<W: WriteColor>(
|
|||
w: &mut W,
|
||||
config_operation: ConfigOperation,
|
||||
settings: &Settings,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
match config_operation {
|
||||
ConfigOperation::Set(key, value) => {
|
||||
let filename = settings.set(&key, &value)?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use taskchampion::Replica;
|
||||
use termcolor::WriteColor;
|
||||
|
||||
pub(crate) fn execute<W: WriteColor>(w: &mut W, replica: &mut Replica) -> anyhow::Result<()> {
|
||||
pub(crate) fn execute<W: WriteColor>(w: &mut W, replica: &mut Replica) -> Result<(), crate::Error> {
|
||||
log::debug!("rebuilding working set");
|
||||
replica.rebuild_working_set(true)?;
|
||||
writeln!(w, "garbage collected.")?;
|
||||
|
|
|
@ -5,7 +5,7 @@ pub(crate) fn execute<W: WriteColor>(
|
|||
w: &mut W,
|
||||
command_name: String,
|
||||
summary: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
let usage = Usage::new();
|
||||
usage.write_help(w, command_name.as_ref(), summary)?;
|
||||
Ok(())
|
||||
|
|
|
@ -10,7 +10,7 @@ pub(crate) fn execute<W: WriteColor>(
|
|||
replica: &mut Replica,
|
||||
filter: Filter,
|
||||
debug: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
let working_set = replica.working_set()?;
|
||||
|
||||
for task in filtered_tasks(replica, &filter)? {
|
||||
|
|
|
@ -8,7 +8,7 @@ pub(crate) fn execute<W: WriteColor>(
|
|||
replica: &mut Replica,
|
||||
filter: Filter,
|
||||
modification: Modification,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
for task in filtered_tasks(replica, &filter)? {
|
||||
let mut task = task.into_mut(replica);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ pub(crate) fn execute<W: WriteColor>(
|
|||
settings: &Settings,
|
||||
report_name: String,
|
||||
filter: Filter,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
display_report(w, replica, settings, report_name, filter)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ pub(crate) fn execute<W: WriteColor>(
|
|||
w: &mut W,
|
||||
replica: &mut Replica,
|
||||
server: &mut Box<dyn Server>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
replica.sync(server)?;
|
||||
writeln!(w, "sync complete.")?;
|
||||
Ok(())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use termcolor::{ColorSpec, WriteColor};
|
||||
|
||||
pub(crate) fn execute<W: WriteColor>(w: &mut W) -> anyhow::Result<()> {
|
||||
pub(crate) fn execute<W: WriteColor>(w: &mut W) -> Result<(), crate::Error> {
|
||||
write!(w, "TaskChampion ")?;
|
||||
w.set_color(ColorSpec::new().set_bold(true))?;
|
||||
writeln!(w, "{}", env!("CARGO_PKG_VERSION"))?;
|
||||
|
|
|
@ -19,7 +19,7 @@ use report::display_report;
|
|||
|
||||
/// Invoke the given Command in the context of the given settings
|
||||
#[allow(clippy::needless_return)]
|
||||
pub(crate) fn invoke(command: Command, settings: Settings) -> anyhow::Result<()> {
|
||||
pub(crate) fn invoke(command: Command, settings: Settings) -> Result<(), crate::Error> {
|
||||
log::debug!("command: {:?}", command);
|
||||
log::debug!("settings: {:?}", settings);
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ pub(super) fn display_report<W: WriteColor>(
|
|||
settings: &Settings,
|
||||
report_name: String,
|
||||
filter: Filter,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), crate::Error> {
|
||||
let mut t = Table::new();
|
||||
let working_set = replica.working_set()?;
|
||||
|
||||
|
|
|
@ -38,23 +38,26 @@ use std::string::FromUtf8Error;
|
|||
mod macros;
|
||||
|
||||
mod argparse;
|
||||
mod errors;
|
||||
mod invocation;
|
||||
mod settings;
|
||||
mod table;
|
||||
mod usage;
|
||||
|
||||
pub(crate) use errors::Error;
|
||||
use settings::Settings;
|
||||
|
||||
/// The main entry point for the command-line interface. This builds an Invocation
|
||||
/// from the particulars of the operating-system interface, and then executes it.
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
pub fn main() -> Result<(), Error> {
|
||||
env_logger::init();
|
||||
|
||||
// parse the command line into a vector of &str, failing if
|
||||
// there are invalid utf-8 sequences.
|
||||
let argv: Vec<String> = std::env::args_os()
|
||||
.map(|oss| String::from_utf8(oss.into_vec()))
|
||||
.collect::<Result<_, FromUtf8Error>>()?;
|
||||
.collect::<Result<_, FromUtf8Error>>()
|
||||
.map_err(|_| Error::for_arguments("arguments must be valid utf-8"))?;
|
||||
let argv: Vec<&str> = argv.iter().map(|s| s.as_ref()).collect();
|
||||
|
||||
// parse the command line
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue