feat(Completions): adds the ability to generate completions to io::Write object

This commit is contained in:
David Szotten 2016-07-05 09:53:28 +01:00
parent 691ef58dfb
commit 9f62cf7378
4 changed files with 90 additions and 23 deletions

View file

@ -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,

View file

@ -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

View file

@ -1,6 +1,3 @@
use std::path::PathBuf;
use std::fs::File;
use std::ffi::OsString;
use std::io::Write;
use app::parser::Parser;
@ -8,8 +5,8 @@ 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
tests/completions.rs Normal file
View file

@ -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");
}