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
  • Loading branch information
davidszotten committed Jul 14, 2016
1 parent 691ef58 commit 9f62cf7
Show file tree
Hide file tree
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
Expand Up @@ -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;
///
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 19 additions & 2 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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
Expand Down
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")),
}
Expand All @@ -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=()
Expand Down
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.