Universes are really an optimization of filtering tasks, so let's define them there, and derive them from the set of conditions. This means that complex filters might get missed and end up doing a full task scan, but that's probably OK. Note that this does not fix the working-set API issues (#108 and #123).
745 lines
22 KiB
Rust
745 lines
22 KiB
Rust
use super::args::*;
|
|
use super::{ArgList, DescriptionMod, Filter, Modification};
|
|
use crate::usage;
|
|
use nom::{branch::alt, combinator::*, sequence::*, IResult};
|
|
use taskchampion::Status;
|
|
|
|
// IMPLEMENTATION NOTE:
|
|
//
|
|
// For each variant of Subcommand, there is a private, empty type of the same name with a `parse`
|
|
// method and a `get_usage` method. The parse methods may handle several subcommands, but always
|
|
// produce the variant of the same name as the type.
|
|
//
|
|
// This organization helps to gather the parsing and usage information into
|
|
// comprehensible chunks of code, to ensure that everything is documented.
|
|
|
|
/// A subcommand is the specific operation that the CLI should execute.
|
|
#[derive(Debug, PartialEq)]
|
|
pub(crate) enum Subcommand {
|
|
/// Display the tool version
|
|
Version,
|
|
|
|
/// Display the help output
|
|
Help {
|
|
/// Give the summary help (fitting on a few lines)
|
|
summary: bool,
|
|
},
|
|
|
|
/// Add a new task
|
|
Add {
|
|
modification: Modification,
|
|
},
|
|
|
|
/// Modify existing tasks
|
|
Modify {
|
|
filter: Filter,
|
|
modification: Modification,
|
|
},
|
|
|
|
/// Lists (reports)
|
|
Report {
|
|
/// The name of the report to show
|
|
report_name: String,
|
|
|
|
/// Additional filter terms beyond those in the report
|
|
filter: Filter,
|
|
},
|
|
|
|
/// Per-task information (typically one task)
|
|
Info {
|
|
filter: Filter,
|
|
debug: bool,
|
|
},
|
|
|
|
/// Basic operations without args
|
|
Gc,
|
|
Sync,
|
|
}
|
|
|
|
impl Subcommand {
|
|
pub(super) fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
all_consuming(alt((
|
|
Version::parse,
|
|
Help::parse,
|
|
Add::parse,
|
|
Modify::parse,
|
|
Info::parse,
|
|
Gc::parse,
|
|
Sync::parse,
|
|
// This must come last since it accepts arbitrary report names
|
|
Report::parse,
|
|
)))(input)
|
|
}
|
|
|
|
pub(super) fn get_usage(u: &mut usage::Usage) {
|
|
Version::get_usage(u);
|
|
Help::get_usage(u);
|
|
Add::get_usage(u);
|
|
Modify::get_usage(u);
|
|
Info::get_usage(u);
|
|
Gc::get_usage(u);
|
|
Sync::get_usage(u);
|
|
Report::get_usage(u);
|
|
}
|
|
}
|
|
|
|
struct Version;
|
|
|
|
impl Version {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(_: &str) -> Result<Subcommand, ()> {
|
|
Ok(Subcommand::Version)
|
|
}
|
|
map_res(
|
|
alt((
|
|
arg_matching(literal("version")),
|
|
arg_matching(literal("--version")),
|
|
)),
|
|
to_subcommand,
|
|
)(input)
|
|
}
|
|
|
|
fn get_usage(u: &mut usage::Usage) {
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "version",
|
|
syntax: "version",
|
|
summary: "Show the TaskChampion version",
|
|
description: "Show the version of the TaskChampion binary",
|
|
});
|
|
}
|
|
}
|
|
|
|
struct Help;
|
|
|
|
impl Help {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(input: &str) -> Result<Subcommand, ()> {
|
|
Ok(Subcommand::Help {
|
|
summary: input == "-h",
|
|
})
|
|
}
|
|
map_res(
|
|
alt((
|
|
arg_matching(literal("help")),
|
|
arg_matching(literal("--help")),
|
|
arg_matching(literal("-h")),
|
|
)),
|
|
to_subcommand,
|
|
)(input)
|
|
}
|
|
|
|
fn get_usage(_u: &mut usage::Usage) {}
|
|
}
|
|
|
|
struct Add;
|
|
|
|
impl Add {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(input: (&str, Modification)) -> Result<Subcommand, ()> {
|
|
Ok(Subcommand::Add {
|
|
modification: input.1,
|
|
})
|
|
}
|
|
map_res(
|
|
pair(arg_matching(literal("add")), Modification::parse),
|
|
to_subcommand,
|
|
)(input)
|
|
}
|
|
|
|
fn get_usage(u: &mut usage::Usage) {
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "add",
|
|
syntax: "add [modification]",
|
|
summary: "Add a new task",
|
|
description: "
|
|
Add a new, pending task to the list of tasks. The modification must include a
|
|
description.",
|
|
});
|
|
}
|
|
}
|
|
|
|
struct Modify;
|
|
|
|
impl Modify {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(input: (Filter, &str, Modification)) -> Result<Subcommand, ()> {
|
|
let filter = input.0;
|
|
let mut modification = input.2;
|
|
|
|
match input.1 {
|
|
"prepend" => {
|
|
if let DescriptionMod::Set(s) = modification.description {
|
|
modification.description = DescriptionMod::Prepend(s)
|
|
}
|
|
}
|
|
"append" => {
|
|
if let DescriptionMod::Set(s) = modification.description {
|
|
modification.description = DescriptionMod::Append(s)
|
|
}
|
|
}
|
|
"start" => modification.active = Some(true),
|
|
"stop" => modification.active = Some(false),
|
|
"done" => modification.status = Some(Status::Completed),
|
|
_ => {}
|
|
}
|
|
|
|
Ok(Subcommand::Modify {
|
|
filter,
|
|
modification,
|
|
})
|
|
}
|
|
map_res(
|
|
tuple((
|
|
Filter::parse,
|
|
alt((
|
|
arg_matching(literal("modify")),
|
|
arg_matching(literal("prepend")),
|
|
arg_matching(literal("append")),
|
|
arg_matching(literal("start")),
|
|
arg_matching(literal("stop")),
|
|
arg_matching(literal("done")),
|
|
)),
|
|
Modification::parse,
|
|
)),
|
|
to_subcommand,
|
|
)(input)
|
|
}
|
|
|
|
fn get_usage(u: &mut usage::Usage) {
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "modify",
|
|
syntax: "[filter] modify [modification]",
|
|
summary: "Modify tasks",
|
|
description: "
|
|
Modify all tasks matching the filter.",
|
|
});
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "prepend",
|
|
syntax: "[filter] prepend [modification]",
|
|
summary: "Prepend task description",
|
|
description: "
|
|
Modify all tasks matching the filter by inserting the given description before each
|
|
task's description.",
|
|
});
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "append",
|
|
syntax: "[filter] append [modification]",
|
|
summary: "Append task description",
|
|
description: "
|
|
Modify all tasks matching the filter by adding the given description to the end
|
|
of each task's description.",
|
|
});
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "start",
|
|
syntax: "[filter] start [modification]",
|
|
summary: "Start tasks",
|
|
description: "
|
|
Start all tasks matching the filter, additionally applying any given modifications."
|
|
});
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "stop",
|
|
syntax: "[filter] stop [modification]",
|
|
summary: "Stop tasks",
|
|
description: "
|
|
Stop all tasks matching the filter, additionally applying any given modifications.",
|
|
});
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "done",
|
|
syntax: "[filter] done [modification]",
|
|
summary: "Mark tasks as completed",
|
|
description: "
|
|
Mark all tasks matching the filter as completed, additionally applying any given
|
|
modifications.",
|
|
});
|
|
}
|
|
}
|
|
|
|
struct Report;
|
|
|
|
impl Report {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(filter: Filter, report_name: &str) -> Result<Subcommand, ()> {
|
|
Ok(Subcommand::Report {
|
|
filter,
|
|
report_name: report_name.to_owned(),
|
|
})
|
|
}
|
|
// allow the filter expression before or after the report name
|
|
alt((
|
|
map_res(pair(arg_matching(report_name), Filter::parse), |input| {
|
|
to_subcommand(input.1, input.0)
|
|
}),
|
|
map_res(pair(Filter::parse, arg_matching(report_name)), |input| {
|
|
to_subcommand(input.0, input.1)
|
|
}),
|
|
// default to a "next" report
|
|
map_res(Filter::parse, |input| to_subcommand(input, "next")),
|
|
))(input)
|
|
}
|
|
|
|
fn get_usage(u: &mut usage::Usage) {
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "report",
|
|
syntax: "[filter] [report-name] *or* [report-name] [filter]",
|
|
summary: "Show a report",
|
|
description: "
|
|
Show the named report, including only tasks matching the filter",
|
|
});
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "next",
|
|
syntax: "[filter]",
|
|
summary: "Show the 'next' report",
|
|
description: "
|
|
Show the report named 'next', including only tasks matching the filter",
|
|
});
|
|
}
|
|
}
|
|
|
|
struct Info;
|
|
|
|
impl Info {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(input: (Filter, &str)) -> Result<Subcommand, ()> {
|
|
let debug = input.1 == "debug";
|
|
Ok(Subcommand::Info {
|
|
filter: input.0,
|
|
debug,
|
|
})
|
|
}
|
|
map_res(
|
|
pair(
|
|
Filter::parse,
|
|
alt((
|
|
arg_matching(literal("info")),
|
|
arg_matching(literal("debug")),
|
|
)),
|
|
),
|
|
to_subcommand,
|
|
)(input)
|
|
}
|
|
|
|
fn get_usage(u: &mut usage::Usage) {
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "info",
|
|
syntax: "[filter] info",
|
|
summary: "Show tasks",
|
|
description: " Show information about all tasks matching the fiter.",
|
|
});
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "debug",
|
|
syntax: "[filter] debug",
|
|
summary: "Show task debug details",
|
|
description: " Show all key/value properties of the tasks matching the fiter.",
|
|
});
|
|
}
|
|
}
|
|
|
|
struct Gc;
|
|
|
|
impl Gc {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(_: &str) -> Result<Subcommand, ()> {
|
|
Ok(Subcommand::Gc)
|
|
}
|
|
map_res(arg_matching(literal("gc")), to_subcommand)(input)
|
|
}
|
|
|
|
fn get_usage(u: &mut usage::Usage) {
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "gc",
|
|
syntax: "gc",
|
|
summary: "Perform 'garbage collection'",
|
|
description: "
|
|
Perform 'garbage collection'. This refreshes the list of pending tasks
|
|
and their short id's.",
|
|
});
|
|
}
|
|
}
|
|
|
|
struct Sync;
|
|
|
|
impl Sync {
|
|
fn parse(input: ArgList) -> IResult<ArgList, Subcommand> {
|
|
fn to_subcommand(_: &str) -> Result<Subcommand, ()> {
|
|
Ok(Subcommand::Sync)
|
|
}
|
|
map_res(arg_matching(literal("sync")), to_subcommand)(input)
|
|
}
|
|
|
|
fn get_usage(u: &mut usage::Usage) {
|
|
u.subcommands.push(usage::Subcommand {
|
|
name: "sync",
|
|
syntax: "sync",
|
|
summary: "Synchronize this replica",
|
|
description: "
|
|
Synchronize this replica locally or against a remote server, as configured.
|
|
|
|
Synchronization is a critical part of maintaining the task database, and should
|
|
be done regularly, even if only locally. It is typically run in a crontask.",
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use crate::argparse::Condition;
|
|
|
|
const EMPTY: Vec<&str> = vec![];
|
|
|
|
#[test]
|
|
fn test_version() {
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["version"]).unwrap(),
|
|
(&EMPTY[..], Subcommand::Version)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dd_version() {
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["--version"]).unwrap(),
|
|
(&EMPTY[..], Subcommand::Version)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d_h() {
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["-h"]).unwrap(),
|
|
(&EMPTY[..], Subcommand::Help { summary: true })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_help() {
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["help"]).unwrap(),
|
|
(&EMPTY[..], Subcommand::Help { summary: false })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dd_help() {
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["--help"]).unwrap(),
|
|
(&EMPTY[..], Subcommand::Help { summary: false })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_description() {
|
|
let subcommand = Subcommand::Add {
|
|
modification: Modification {
|
|
description: DescriptionMod::Set(s!("foo")),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["add", "foo"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_description_multi() {
|
|
let subcommand = Subcommand::Add {
|
|
modification: Modification {
|
|
description: DescriptionMod::Set(s!("foo bar")),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["add", "foo", "bar"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_modify_description_multi() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
description: DescriptionMod::Set(s!("foo bar")),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "modify", "foo", "bar"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_append() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
description: DescriptionMod::Append(s!("foo bar")),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "append", "foo", "bar"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_prepend() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
description: DescriptionMod::Prepend(s!("foo bar")),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "prepend", "foo", "bar"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_done() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
status: Some(Status::Completed),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "done"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_done_modify() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
description: DescriptionMod::Set(s!("now-finished")),
|
|
status: Some(Status::Completed),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "done", "now-finished"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_start() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
active: Some(true),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "start"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_start_modify() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
active: Some(true),
|
|
description: DescriptionMod::Set(s!("mod")),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "start", "mod"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_stop() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
active: Some(false),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "stop"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_stop_modify() {
|
|
let subcommand = Subcommand::Modify {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(123)])],
|
|
},
|
|
modification: Modification {
|
|
description: DescriptionMod::Set(s!("mod")),
|
|
active: Some(false),
|
|
..Default::default()
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["123", "stop", "mod"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_report() {
|
|
let subcommand = Subcommand::Report {
|
|
filter: Default::default(),
|
|
report_name: "myreport".to_owned(),
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["myreport"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_report_filter_before() {
|
|
let subcommand = Subcommand::Report {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![
|
|
TaskId::WorkingSetId(12),
|
|
TaskId::WorkingSetId(13),
|
|
])],
|
|
},
|
|
report_name: "foo".to_owned(),
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["12,13", "foo"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_report_filter_after() {
|
|
let subcommand = Subcommand::Report {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![
|
|
TaskId::WorkingSetId(12),
|
|
TaskId::WorkingSetId(13),
|
|
])],
|
|
},
|
|
report_name: "foo".to_owned(),
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["foo", "12,13"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_report_filter_next() {
|
|
let subcommand = Subcommand::Report {
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![
|
|
TaskId::WorkingSetId(12),
|
|
TaskId::WorkingSetId(13),
|
|
])],
|
|
},
|
|
report_name: "next".to_owned(),
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["12,13"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_report_next() {
|
|
let subcommand = Subcommand::Report {
|
|
filter: Filter {
|
|
..Default::default()
|
|
},
|
|
report_name: "next".to_owned(),
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv![]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_info_filter() {
|
|
let subcommand = Subcommand::Info {
|
|
debug: false,
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![
|
|
TaskId::WorkingSetId(12),
|
|
TaskId::WorkingSetId(13),
|
|
])],
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["12,13", "info"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_filter() {
|
|
let subcommand = Subcommand::Info {
|
|
debug: true,
|
|
filter: Filter {
|
|
conditions: vec![Condition::IdList(vec![TaskId::WorkingSetId(12)])],
|
|
},
|
|
};
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["12", "debug"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gc() {
|
|
let subcommand = Subcommand::Gc;
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["gc"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gc_extra_args() {
|
|
assert!(Subcommand::parse(argv!["gc", "foo"]).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sync() {
|
|
let subcommand = Subcommand::Sync;
|
|
assert_eq!(
|
|
Subcommand::parse(argv!["sync"]).unwrap(),
|
|
(&EMPTY[..], subcommand)
|
|
);
|
|
}
|
|
}
|