Skip to content

Commit 0eff00e

Browse files
committed
Assume the "run" command as a default
1 parent 26746df commit 0eff00e

File tree

2 files changed

+101
-59
lines changed

2 files changed

+101
-59
lines changed

src/args.rs

Lines changed: 89 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,36 @@ pub fn parse() -> Result<Options, ArgsError> {
2222
/// (*all* arguments, including binary name).
2323
#[inline]
2424
pub fn parse_from_argv<I, T>(argv: I) -> Result<Options, ArgsError>
25-
where I: IntoIterator<Item=T>, T: Into<OsString>
25+
where I: IntoIterator<Item=T>, T: Clone + Into<OsString>
2626
{
27-
let matches = create_parser().get_matches_from(argv);
27+
let argv: Vec<_> = argv.into_iter().collect();
28+
29+
// We allow `gisht JohnDoe/foo` to be an alias of `gisht run JohnDoe/foo`.
30+
// To support this, some preprocessing on the arguments has to be done
31+
// in order to pick the parser with or without subcommands.
32+
let parser = {
33+
// Determine whether the first non-flag argument is one of the gist commands.
34+
let first_arg = argv.iter().skip(1)
35+
.map(|arg| {
36+
let arg: OsString = arg.clone().into();
37+
arg.into_string().unwrap_or_else(|_| String::new())
38+
})
39+
.find(|arg| !arg.starts_with("-"))
40+
.unwrap_or_else(|| String::new());
41+
42+
// If it is, use the full argument parser which recognizes those commands.
43+
match Command::from_str(&first_arg) {
44+
Ok(_) => create_full_parser(),
45+
Err(_) => {
46+
// If it's not, the parser will already have "run" command baked in.
47+
let mut parser = create_parser_base();
48+
parser = configure_run_gist_parser(parser);
49+
parser
50+
},
51+
}
52+
};
53+
54+
let matches = parser.get_matches_from(argv);
2855
Options::try_from(matches)
2956
}
3057

@@ -37,10 +64,10 @@ pub struct Options {
3764
/// Corresponds to the number of times the -v flag has been passed.
3865
/// If -q has been used instead, this will be negative.
3966
pub verbosity: isize,
40-
/// Gist command that's been issued, if any.
41-
pub command: Option<Command>,
42-
/// URI to the gist to operate on, if any.
43-
pub gist: Option<gist::Uri>,
67+
/// Gist command that's been issued.
68+
pub command: Command,
69+
/// URI to the gist to operate on.
70+
pub gist: gist::Uri,
4471
/// Arguments to the gist, if any.
4572
/// This is only used if command == Some(Command::Run).
4673
pub gist_args: Option<Vec<String>>,
@@ -60,19 +87,22 @@ impl<'a> TryFrom<ArgMatches<'a>> for Options {
6087
let verbose_count = matches.occurrences_of(OPT_VERBOSE) as isize;
6188
let quiet_count = matches.occurrences_of(OPT_QUIET) as isize;
6289

63-
// Command may be optionally provided, alongside the gist argument.
64-
let (subcmd, submatches) = matches.subcommand();
65-
let command = Command::from_str(subcmd).ok();
66-
let gist = match submatches.and_then(|m| m.value_of(ARG_GIST)) {
67-
Some(g) => Some(try!(gist::Uri::from_str(g))),
68-
_ => None,
69-
};
90+
// Command may be optionally provided.
91+
// If it isn't, it means the "run" default was used, and so all the arguments
92+
// are arguments to `gisht run`.
93+
let (cmd, cmd_matches) = matches.subcommand();
94+
let cmd_matches = cmd_matches.unwrap_or(&matches);
95+
let command = Command::from_str(cmd).unwrap_or(Command::Run);
96+
97+
// Parse out the gist URI argument.
98+
let gist = try!(gist::Uri::from_str(
99+
cmd_matches.value_of(ARG_GIST).unwrap()
100+
));
70101

71102
// For the "run" command, arguments may be provided.
72-
let mut gist_args = submatches
73-
.and_then(|m| m.values_of(ARG_GIST_ARGV))
103+
let mut gist_args = cmd_matches.values_of(ARG_GIST_ARGV)
74104
.map(|argv| argv.map(|v| v.to_owned()).collect());
75-
if command == Some(Command::Run) && gist_args.is_none() {
105+
if command == Command::Run && gist_args.is_none() {
76106
gist_args = Some(vec![]);
77107
}
78108

@@ -159,8 +189,34 @@ const OPT_VERBOSE: &'static str = "verbose";
159189
const OPT_QUIET: &'static str = "quiet";
160190

161191

162-
/// Create the argument parser.
163-
fn create_parser<'p>() -> Parser<'p> {
192+
/// Create the full argument parser.
193+
/// This parser accepts the entire gamut of the application's arguments and flags.
194+
fn create_full_parser<'p>() -> Parser<'p> {
195+
let parser = create_parser_base();
196+
197+
parser
198+
.setting(AppSettings::SubcommandRequiredElseHelp)
199+
.setting(AppSettings::VersionlessSubcommands)
200+
201+
.subcommand(configure_run_gist_parser(
202+
SubCommand::with_name(Command::Run.name())
203+
.about("Run the specified gist")))
204+
.subcommand(SubCommand::with_name(Command::Which.name())
205+
.about("Output the path to gist's binary")
206+
.arg(gist_arg("Gist to locate")))
207+
.subcommand(SubCommand::with_name(Command::Print.name())
208+
.about("Print the source code of gist's binary")
209+
.arg(gist_arg("Gist to print")))
210+
.subcommand(SubCommand::with_name(Command::Open.name())
211+
.about("Open the gist's webpage")
212+
.arg(gist_arg("Gist to open")))
213+
}
214+
215+
/// Create the "base" argument parser object.
216+
///
217+
/// This base contains all the shared configuration (like the application name)
218+
/// and the flags shared by all gist subcommands.
219+
fn create_parser_base<'p>() -> Parser<'p> {
164220
let mut parser = Parser::new(APP_NAME);
165221
if let Some(version) = option_env!("CARGO_PKG_VERSION") {
166222
parser = parser.version(version);
@@ -169,8 +225,6 @@ fn create_parser<'p>() -> Parser<'p> {
169225
.about(APP_DESC)
170226

171227
.setting(AppSettings::ArgRequiredElseHelp)
172-
.setting(AppSettings::SubcommandRequiredElseHelp)
173-
.setting(AppSettings::VersionlessSubcommands)
174228
.setting(AppSettings::UnifiedHelpMessage)
175229
.setting(AppSettings::DeriveDisplayOrder)
176230

@@ -187,28 +241,22 @@ fn create_parser<'p>() -> Parser<'p> {
187241
.help("Decrease logging verbosity"))
188242
.help_short("H")
189243
.version_short("V")
244+
}
190245

191-
.subcommand(SubCommand::with_name(Command::Run.name())
192-
.about("Run the specified gist")
193-
.arg(gist_arg("Gist to run"))
194-
// This argument spec is capturing everything after the gist URI,
195-
// allowing for the arguments to be passed to the gist itself.
196-
.arg(Arg::with_name(ARG_GIST_ARGV)
197-
.required(false)
198-
.multiple(true)
199-
.use_delimiter(false)
200-
.help("Optional arguments passed to the gist")
201-
.value_name("ARGS"))
202-
.setting(AppSettings::TrailingVarArg))
203-
.subcommand(SubCommand::with_name(Command::Which.name())
204-
.about("Output the path to gist's binary")
205-
.arg(gist_arg("Gist to locate")))
206-
.subcommand(SubCommand::with_name(Command::Print.name())
207-
.about("Print the source code of gist's binary")
208-
.arg(gist_arg("Gist to print")))
209-
.subcommand(SubCommand::with_name(Command::Open.name())
210-
.about("Open the gist's webpage")
211-
.arg(gist_arg("Gist to open")))
246+
/// Configure a parser for the "run" command.
247+
/// This is also used when there is no command given.
248+
fn configure_run_gist_parser<'p>(parser: Parser<'p>) -> Parser<'p> {
249+
parser
250+
.arg(gist_arg("Gist to run"))
251+
// This argument spec is capturing everything after the gist URI,
252+
// allowing for the arguments to be passed to the gist itself.
253+
.arg(Arg::with_name(ARG_GIST_ARGV)
254+
.required(false)
255+
.multiple(true)
256+
.use_delimiter(false)
257+
.help("Optional arguments passed to the gist")
258+
.value_name("ARGS"))
259+
.setting(AppSettings::TrailingVarArg)
212260
}
213261

214262
/// Create the GIST argument to various gist subcommands.

src/main.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,20 @@ fn main() {
8686
}
8787
}
8888

89-
if let Some(cmd) = opts.command {
90-
let gist_uri = opts.gist.unwrap();
91-
debug!("Gist {} specified as the argument", gist_uri);
92-
93-
let gist = Gist::from_uri(gist_uri.clone());
94-
if !gist.is_local() {
95-
if let Err(err) = gist_uri.host().download_gist(&gist) {
96-
error!("Failed to download gist {}: {}", gist.uri, err);
97-
exit(exitcode::EX_IOERR);
98-
}
89+
debug!("Gist {} specified as the argument", opts.gist);
90+
let gist = Gist::from_uri(opts.gist.clone());
91+
if !gist.is_local() {
92+
if let Err(err) = opts.gist.host().download_gist(&gist) {
93+
error!("Failed to download gist {}: {}", gist.uri, err);
94+
exit(exitcode::EX_IOERR);
9995
}
96+
}
10097

101-
match cmd {
102-
args::Command::Run => run_gist(&gist, opts.gist_args.as_ref().unwrap()),
103-
args::Command::Which => print_binary_path(&gist),
104-
args::Command::Print => print_gist(&gist),
105-
_ => unimplemented!(),
106-
}
107-
} else {
108-
debug!("No gist command specified -- exiting.");
98+
match opts.command {
99+
args::Command::Run => run_gist(&gist, opts.gist_args.as_ref().unwrap()),
100+
args::Command::Which => print_binary_path(&gist),
101+
args::Command::Print => print_gist(&gist),
102+
_ => unimplemented!(),
109103
}
110104
}
111105

0 commit comments

Comments
 (0)