Skip to content

Commit

Permalink
feat(Completions): adds the ability to generate completions to io::Wr…
Browse files Browse the repository at this point in the history
…ite object
davidszotten committed Jul 14, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 691ef58 commit 9f62cf7
Showing 4 changed files with 90 additions and 23 deletions.
40 changes: 39 additions & 1 deletion src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -978,7 +978,7 @@ impl<'a, 'b> App<'a, 'b> {
/// `get_matches()`, or any of the other normal methods directly after. For example:
///
/// ```ignore
/// src/main.rs
/// // src/main.rs
///
/// mod cli;
///
@@ -1023,6 +1023,44 @@ impl<'a, 'b> App<'a, 'b> {
self.p.gen_completions(for_shell, out_dir.into());
}


/// Generate a completions file for a specified shell at runtime. Until `cargo install` can
/// install extra files like a completion script, this may be used e.g. in a command that
/// outputs the contents of the completion script, to be redirected into a file by the user.
///
/// # Examples
///
/// Assuming a separate `cli.rs` like the [example above](./struct.App.html#method.gen_completions),
/// we can let users generate a completion script using a command:
///
/// ```ignore
/// // src/main.rs
///
/// mod cli;
/// use std::io;
///
/// fn main() {
/// let matches = cli::build_cli().get_matches();
///
/// if matches.is_present("generate-bash-completions") {
/// cli::build_cli().gen_completions_to("myapp", Shell::Bash, &mut io::stdout());
/// }
///
/// // normal logic continues...
/// }
///
/// ```
///
/// Usage:
///
/// ```shell
/// $ myapp generate-bash-completions > /etc/bash_completion.d/myapp
/// ```
pub fn gen_completions_to<W: Write, S: Into<String>>(&mut self, bin_name: S, for_shell: Shell, buf: &mut W) {
self.p.meta.bin_name = Some(bin_name.into());
self.p.gen_completions_to(for_shell, buf);
}

/// 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,
21 changes: 19 additions & 2 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ use app::meta::AppMeta;
use args::MatchedArg;
use shell::Shell;
use completions::ComplGen;
use std::fs::File;
use std::path::PathBuf;

#[allow(missing_debug_implementations)]
#[doc(hidden)]
@@ -99,10 +101,25 @@ impl<'a, 'b> Parser<'a, 'b>
.nth(0);
}

pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) {
pub fn gen_completions_to<W: Write>(&mut self, for_shell: Shell, buf: &mut W) {

self.propogate_help_version();
self.build_bin_names();
ComplGen::new(self, od).generate(for_shell)

ComplGen::new(self).generate(for_shell, buf)
}

pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) {
use std::error::Error;

let out_dir = PathBuf::from(od);

let mut file = match File::create(out_dir.join(format!("{}_bash.sh", &*self.meta.bin_name.as_ref().unwrap()))) {
Err(why) => panic!("couldn't create bash completion file: {}",
why.description()),
Ok(file) => file,
};
self.gen_completions_to(for_shell, &mut file)
}

// actually adds the arguments
27 changes: 7 additions & 20 deletions src/completions.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use std::path::PathBuf;
use std::fs::File;
use std::ffi::OsString;
use std::io::Write;

use app::parser::Parser;
use shell::Shell;
use args::{ArgSettings, OptBuilder};

macro_rules! w {
($_self:ident, $f:ident, $to_w:expr) => {
match $f.write_all($to_w) {
($buf:expr, $to_w:expr) => {
match $buf.write_all($to_w) {
Ok(..) => (),
Err(..) => panic!(format!("Failed to write to file completions file")),
}
@@ -18,33 +15,23 @@ macro_rules! w {

pub struct ComplGen<'a, 'b> where 'a: 'b {
p: &'b Parser<'a, 'b>,
out_dir: OsString,
}

impl<'a, 'b> ComplGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>, od: OsString) -> Self {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
ComplGen {
p: p,
out_dir: od,
}
}

pub fn generate(&self, for_shell: Shell) {
pub fn generate<W: Write>(&self, for_shell: Shell, buf: &mut W) {
match for_shell {
Shell::Bash => self.gen_bash(),
Shell::Bash => self.gen_bash(buf),
}
}

fn gen_bash(&self) {
use std::error::Error;
let out_dir = PathBuf::from(&self.out_dir);

let mut file = match File::create(out_dir.join(format!("{}_bash.sh", &*self.p.meta.bin_name.as_ref().unwrap()))) {
Err(why) => panic!("couldn't create bash completion file: {}",
why.description()),
Ok(file) => file,
};
w!(self, file, format!(
fn gen_bash<W: Write>(&self, buf: &mut W) {
w!(buf, format!(
"_{name}() {{
local i cur prev opts cmds
COMPREPLY=()
25 changes: 25 additions & 0 deletions tests/completions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
extern crate clap;

use clap::{App, Arg, SubCommand, Shell};

#[test]
fn test_generation() {
let mut app = App::new("myapp")
.about("Tests completions")
.arg(Arg::with_name("file")
.help("some input file"))
.subcommand(SubCommand::with_name("test")
.about("tests things")
.arg(Arg::with_name("case")
.long("case")
.takes_value(true)
.help("the case to test")));
let mut buf = vec![];
app.gen_completions_to("myapp", Shell::Bash, &mut buf);
let string = String::from_utf8(buf).unwrap();
let first_line = string.lines().nth(0).unwrap();
let last_line = string.lines().rev().nth(0).unwrap();

assert_eq!(first_line, "_myapp() {");
assert_eq!(last_line, "complete -F _myapp myapp");
}

0 comments on commit 9f62cf7

Please sign in to comment.