Skip to content

Commit

Permalink
feat(Completions): one can now generate a bash completions script at …
Browse files Browse the repository at this point in the history
…compile time

By using a build.rs "build script" one can now generate a bash completions script which allows tab
completions for the entire program, to include, subcommands, options, everything!

See the documentation for full examples and details.

Closes #376
  • Loading branch information
kbknapp committed Jul 1, 2016
1 parent 3a000d6 commit e75b6c7
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 7 deletions.
10 changes: 9 additions & 1 deletion src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
mod settings;
#[macro_use]
mod macros;
mod parser;
pub mod parser;
mod meta;
mod help;

Expand All @@ -27,6 +27,7 @@ use app::parser::Parser;
use app::help::Help;
use errors::Error;
use errors::Result as ClapResult;
use shell::Shell;

/// Used to create a representation of a command line program and all possible command line
/// arguments. Application settings are set using the "builder pattern" with the
Expand Down Expand Up @@ -939,6 +940,13 @@ impl<'a, 'b> App<'a, 'b> {
self.p.write_version(w).map_err(From::from)
}


/// Generate a completions file for a specified shell
pub fn gen_completions<T: Into<OsString>, S: Into<String>>(&mut self, bin_name: S, for_shell: Shell, out_dir: T) {
self.p.meta.bin_name = Some(bin_name.into());
self.p.gen_completions(for_shell, out_dir.into());
}

/// Starts the parsing process, upon a failed parse an error will be displayed to the user and
/// the process will exit with the appropriate error code. By default this method gets all user
/// provided arguments from [`env::args_os`] in order to allow for invalid UTF-8 code points,
Expand Down
56 changes: 50 additions & 6 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,24 @@ use fmt::{Format, ColorWhen};
use osstringext::OsStrExt2;
use app::meta::AppMeta;
use args::MatchedArg;
use shell::Shell;
use completions::ComplGen;

#[allow(missing_debug_implementations)]
#[doc(hidden)]
pub struct Parser<'a, 'b>
where 'a: 'b
{
required: Vec<&'b str>,
short_list: Vec<char>,
long_list: Vec<&'b str>,
pub short_list: Vec<char>,
pub long_list: Vec<&'b str>,
blacklist: Vec<&'b str>,
// A list of possible flags
flags: Vec<FlagBuilder<'a, 'b>>,
// A list of possible options
opts: Vec<OptBuilder<'a, 'b>>,
pub opts: Vec<OptBuilder<'a, 'b>>,
// A list of positional arguments
positionals: VecMap<PosBuilder<'a, 'b>>,
pub positionals: VecMap<PosBuilder<'a, 'b>>,
// A list of subcommands
#[doc(hidden)]
pub subcommands: Vec<App<'a, 'b>>,
Expand Down Expand Up @@ -97,6 +99,12 @@ impl<'a, 'b> Parser<'a, 'b>
.nth(0);
}

pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) {
self.propogate_help_version();
self.build_bin_names();
ComplGen::new(self, od).generate(for_shell)
}

// actually adds the arguments
pub fn add_arg(&mut self, a: &Arg<'a, 'b>) {
debug_assert!(!(self.flags.iter().any(|f| &f.name == &a.name) ||
Expand Down Expand Up @@ -236,6 +244,7 @@ impl<'a, 'b> Parser<'a, 'b>
self.required.iter()
}


#[cfg_attr(feature = "lints", allow(for_kv_map))]
pub fn get_required_from(&self,
reqs: &[&'a str],
Expand Down Expand Up @@ -652,7 +661,7 @@ impl<'a, 'b> Parser<'a, 'b>
self.meta
.bin_name
.as_ref()
.unwrap_or(&String::new()),
.unwrap_or(&self.meta.name.clone()),
if self.meta.bin_name.is_some() {
" "
} else {
Expand Down Expand Up @@ -788,6 +797,41 @@ impl<'a, 'b> Parser<'a, 'b>
Ok(())
}

fn propogate_help_version(&mut self) {
debugln!("exec=propogate_help_version;");
self.create_help_and_version();
for sc in self.subcommands.iter_mut() {
sc.p.propogate_help_version();
}
}

fn build_bin_names(&mut self) {
debugln!("exec=build_bin_names;");
for sc in self.subcommands.iter_mut() {
debug!("bin_name set...");
if sc.p.meta.bin_name.is_none() {
sdebugln!("No");
let bin_name = format!("{}{}{}",
self.meta
.bin_name
.as_ref()
.unwrap_or(&self.meta.name.clone()),
if self.meta.bin_name.is_some() {
" "
} else {
""
},
&*sc.p.meta.name);
debugln!("Setting bin_name of {} to {}", self.meta.name, bin_name);
sc.p.meta.bin_name = Some(bin_name);
} else {
sdebugln!("yes ({:?})", sc.p.meta.bin_name);
}
debugln!("Calling build_bin_names from...{}", sc.p.meta.name);
sc.p.build_bin_names();
}
}

fn parse_subcommand<I, T>(&mut self,
sc_name: String,
matcher: &mut ArgMatcher<'a>,
Expand Down Expand Up @@ -1258,7 +1302,7 @@ impl<'a, 'b> Parser<'a, 'b>
// If there was a delimiter used, we're not looking for more values
if val.contains_byte(delim as u32 as u8) || arg.is_set(ArgSettings::RequireDelimiter) {
ret = None;
}
}
}
} else {
ret = try!(self.add_single_val_to_arg(arg, val, matcher));
Expand Down
Loading

0 comments on commit e75b6c7

Please sign in to comment.