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,62 @@
use super::args::*;
use super::{ArgList, Subcommand};
use failure::{format_err, Fallible};
use nom::{combinator::*, sequence::*, Err, IResult};
/// A command is the overall command that the CLI should execute.
///
/// It consists of some information common to all commands and a `Subcommand` identifying the
/// particular kind of behavior desired.
#[derive(Debug, PartialEq)]
pub(crate) struct Command {
pub(crate) command_name: String,
pub(crate) subcommand: Subcommand,
}
impl Command {
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Command> {
fn to_command(input: (&str, Subcommand)) -> Result<Command, ()> {
let command = Command {
command_name: input.0.to_owned(),
subcommand: input.1,
};
Ok(command)
}
map_res(
all_consuming(tuple((arg_matching(any), Subcommand::parse))),
to_command,
)(input)
}
/// Parse a command from the given list of strings.
pub fn from_argv(argv: &[&str]) -> Fallible<Command> {
match Command::parse(argv) {
Ok((&[], cmd)) => Ok(cmd),
Ok((trailing, _)) => Err(format_err!(
"command line has trailing arguments: {:?}",
trailing
)),
Err(Err::Incomplete(_)) => unreachable!(),
Err(Err::Error(e)) => Err(format_err!("command line not recognized: {:?}", e)),
Err(Err::Failure(e)) => Err(format_err!("command line not recognized: {:?}", e)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
// NOTE: most testing of specific subcommands is handled in `subcommand.rs`.
#[test]
fn test_version() {
assert_eq!(
Command::from_argv(argv!["task", "version"]).unwrap(),
Command {
subcommand: Subcommand::Version,
command_name: "task".to_owned(),
}
);
}
}