Repository: clap-rs/clap
Pull request #1678 (2020-02-05 21:55:06)

Refactor clap_generate
I have copied the code from [clap_generate]( https://github.com/clap-rs/clap_generate) and refactored the structure a bit.

This new structure will allow people to write their own generators using our `Generator` trait which will contain some helpers (Still working on polishing them).

pksunkara: The tests are failing because something changed in clap v3 since the `subcommand -h` and `subcommand -V` are not getting printed in completions
pksunkara: I thought it would be just moving it to this repo, but then I got bogged down with refactoring. 😞 
CreepySkeleton: That's what I'm talking about :) If you want to refactor something - refactor stuff in `src/`, we do need it and it will help to get a grasp of the codebase (we're going to need to comprehend the codebase anyway). Generators can wait. I'm working on this "multiple trait" derive of ours and I expect it to be done by the evening (we all know what my deadlines really worth, but I'll try really hard).  
pksunkara: I haven't pulled [this](https://github.com/clap-rs/clap_generate/pull/1) PR because `man` is going to be changed according to recent discussions. Other than that, this PR is good to go and this crate should be good to release when we are doing 3.0
pksunkara: I assume you gave a close look at this PR because there probably will be no changes for 3.0 in this crate.
CreepySkeleton: Not so close, just reviewed the public API and it looks pretty good. I haven't looked at the impl.
pksunkara: Just validate the `test/completions.rs` from this and clap_generate repo. The fixtures should be the same.
CreepySkeleton: There's no `test/completions.rs` in https://github.com/clap-rs/clap/tree/generate/tests 😕 
pksunkara: https://github.com/clap-rs/clap_generate/tree/master/tests
CreepySkeleton: Looks fine.

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index b4b40c35..610a5b50 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,7 +84,10 @@ features = ["doc"]
 [workspace]
 members = [
 	"clap_derive",
+	"clap_generate",
 ]
 default-members = [
-	".", "clap_derive",
+	".",
+	"clap_derive",
+	"clap_generate",
 ]
diff --git a/clap_derive/Cargo.toml b/clap_derive/Cargo.toml
index 489c031d..a4734eef 100644
--- a/clap_derive/Cargo.toml
+++ b/clap_derive/Cargo.toml
@@ -46,6 +46,7 @@ proc-macro-error = "0.4.3"
 clap = { path = "../", version = "3.0.0-beta.1" }
 trybuild = "1.0.5"
 rustversion = "1"
+version-sync = "0.8"
 
 [features]
 default = []
diff --git a/clap_derive/README.md b/clap_derive/README.md
index ff89f477..68851041 100644
--- a/clap_derive/README.md
+++ b/clap_derive/README.md
@@ -1,9 +1,4 @@
-# Work in Progress
-
-This crate is currently a work in progress and not meant to be used. Please use [`structopt`](https://github.com/TeXitoi/structopt)
-while this crate is being built.
-
-# clap_derive[![Build status](https://travis-ci.org/clap-rs/clap_derive.svg?branch=master)](https://travis-ci.org/clap-rs/clap_derive) [![](https://img.shields.io/crates/v/clap_derive.svg)](https://crates.io/crates/clap_derive) [![](https://docs.rs/clap_derive/badge.svg)](https://docs.rs/clap_derive)
+# clap_derive
 
 Parse command line argument by defining a struct.  It combines [structopt](https://github.com/TeXitoi/structopt) and [clap](https://crates.io/crates/clap) into a single experience. This crate is used by clap, and not meant to be used directly by
 consumers.
diff --git a/clap_generate/Cargo.toml b/clap_generate/Cargo.toml
new file mode 100644
index 00000000..e7caa6c1
--- /dev/null
+++ b/clap_generate/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "clap_generate"
+version = "3.0.0-beta.1"
+edition = "2018"
+authors = [
+	"Kevin K. <kbknapp@gmail.com>",
+	"Pavan Kumar Sunkara <pavan.sss1991@gmail.com>",
+]
+include = [
+	"src/**/*",
+	"Cargo.toml",
+	"README.md"
+]
+description = "A generator library used with clap for shell completion scripts, manpage, etc."
+repository = "https://github.com/clap-rs/clap/tree/master/clap_generate"
+documentation = "https://docs.rs/clap_generate"
+homepage = "https://clap.rs/"
+keywords = [
+	"clap",
+	"cli",
+	"generate",
+	"completion",
+	"manpage",
+	"parse"
+]
+categories = ["command-line-interface"]
+license = "MIT OR Apache-2.0"
+readme = "README.md"
+
+[badges]
+is-it-maintained-issue-resolution = { repository = "clap-rs/clap" }
+is-it-maintained-open-issues = { repository = "clap-rs/clap" }
+maintenance = {status = "actively-developed"}
+
+[dependencies]
+clap = { path = "../", version = "3.0.0-beta.1" }
+
+[dev-dependencies]
+pretty_assertions = "0.6"
+version-sync = "0.8"
+
+[features]
+default = []
+unstable = ["clap/unstable"]
+nightly = ["clap/nightly"]
+debug = ["clap/debug"]
+doc = []
+
+[package.metadata.docs.rs]
+features = ["doc"]
diff --git a/clap_generate/README.md b/clap_generate/README.md
new file mode 100644
index 00000000..17ce782f
--- /dev/null
+++ b/clap_generate/README.md
@@ -0,0 +1,1 @@
+# clap_generate
diff --git a/clap_generate/examples/bash_completion.rs b/clap_generate/examples/bash_completion.rs
new file mode 100644
index 00000000..d80d5848
--- /dev/null
+++ b/clap_generate/examples/bash_completion.rs
@@ -0,0 +1,11 @@
+use clap::App;
+use clap_generate::{generate, generators::Bash};
+use std::io;
+
+fn main() {
+    let mut app = App::new("myapp")
+        .subcommand(App::new("test").subcommand(App::new("config")))
+        .subcommand(App::new("hello"));
+
+    generate::<Bash, _>(&mut app, "myapp", &mut io::stdout());
+}
diff --git a/clap_generate/src/generators/mod.rs b/clap_generate/src/generators/mod.rs
new file mode 100644
index 00000000..3dd8fe18
--- /dev/null
+++ b/clap_generate/src/generators/mod.rs
@@ -0,0 +1,323 @@
+mod shells;
+
+// Std
+use std::io::Write;
+
+// Internal
+use clap::*;
+pub use shells::*;
+
+/// Generator trait which can be used to write generators
+pub trait Generator {
+    /// Returns the file name that is created when this generator is called during compile time.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use std::io::Write;
+    /// # use clap::App;
+    /// use clap_generate::Generator;
+    ///
+    /// pub struct Fish;
+    ///
+    /// impl Generator for Fish {
+    /// #   fn generate(app: &App, buf: &mut dyn Write) {}
+    ///     fn file_name(name: &str) -> String {
+    ///         format!("{}.fish", name)
+    ///     }
+    /// }
+    /// ```
+    fn file_name(name: &str) -> String;
+
+    /// Generates output out of [`clap::App`](../clap/struct.App.html).
+    ///
+    /// # Examples
+    ///
+    /// The following example generator displays the [`clap::App`](../clap/struct.App.html)
+    /// as if it is printed using [`std::println`](https://doc.rust-lang.org/std/macro.println.html).
+    ///
+    /// ```
+    /// use std::{io::Write, fmt::write};
+    /// use clap::App;
+    /// use clap_generate::Generator;
+    ///
+    /// pub struct ClapDebug;
+    ///
+    /// impl Generator for ClapDebug {
+    ///     fn generate(app: &App, buf: &mut dyn Write) {
+    ///         write!(buf, "{}", app).unwrap();
+    ///     }
+    /// #   fn file_name(name: &str) -> String {
+    /// #    name.into()
+    /// #   }
+    /// }
+    /// ```
+    fn generate(app: &App, buf: &mut dyn Write);
+
+    /// Gets all subcommands including child subcommands in the form of `("name", "bin_name")`.
+    ///
+    /// Subcommand `rustup toolchain install` would be converted to
+    /// `("install", "rustup toolchain install")`.
+    fn all_subcommands(app: &App) -> Vec<(String, String)> {
+        let mut subcmds: Vec<_> = Self::subcommands(app);
+
+        for sc_v in subcommands!(app).map(|s| Self::all_subcommands(&s)) {
+            subcmds.extend(sc_v);
+        }
+
+        subcmds
+    }
+
+    /// Finds the subcommand [`clap::App`][clap] from the given [`clap::App`][clap] with the given path.
+    ///
+    /// **NOTE:** `path` should not contain the root `bin_name`.
+    ///
+    /// [clap]: ../clap/struct.App.html
+    fn find_subcommand_with_path<'b>(p: &'b App<'b>, path: Vec<&str>) -> &'b App<'b> {
+        let mut app = p;
+
+        for sc in path {
+            app = find_subcmd!(app, sc).unwrap();
+        }
+
+        app
+    }
+
+    /// Gets subcommands of [`clap::App`](../clap/struct.App.html) in the form of `("name", "bin_name")`.
+    ///
+    /// Subcommand `rustup toolchain install` would be converted to
+    /// `("install", "rustup toolchain install")`.
+    fn subcommands(p: &App) -> Vec<(String, String)> {
+        debugln!("subcommands: name={}", p.name);
+        debugln!("subcommands: Has subcommands...{:?}", p.has_subcommands());
+
+        let mut subcmds = vec![];
+
+        if !p.has_subcommands() {
+            return subcmds;
+        }
+
+        for sc in &p.subcommands {
+            let sc_bin_name = sc.get_bin_name().unwrap();
+
+            debugln!(
+                "subcommands:iter: name={}, bin_name={}",
+                sc.name,
+                sc_bin_name
+            );
+
+            subcmds.push((sc.name.clone(), sc_bin_name.to_string()));
+        }
+
+        subcmds
+    }
+
+    /// Gets all the short options and flags of a [`clap::App`](../clap/struct.App.html).
+    /// Includes `h` and `V` depending on the [`clap::AppSettings`](../clap/enum.AppSettings.html).
+    fn shorts<'b>(p: &'b App<'b>) -> Vec<char> {
+        debugln!("shorts: name={}", p.name);
+
+        let mut shorts: Vec<char> = p
+            .args
+            .args
+            .iter()
+            .filter_map(|a| {
+                if a.index.is_none() && a.short.is_some() {
+                    Some(a.short.unwrap())
+                } else {
+                    None
+                }
+            })
+            .collect();
+
+        if shorts.iter().find(|x| **x == 'h').is_none() {
+            shorts.push('h');
+        }
+
+        if !p.is_set(AppSettings::DisableVersion) && shorts.iter().find(|x| **x == 'V').is_none() {
+            shorts.push('V');
+        }
+
+        shorts
+    }
+
+    /// Gets all the long options and flags of a [`clap::App`](../clap/struct.App.html).
+    /// Includes `help` and `version` depending on the [`clap::AppSettings`](../clap/enum.AppSettings.html).
+    fn longs<'b>(p: &'b App<'b>) -> Vec<String> {
+        debugln!("longs: name={}", p.name);
+
+        let mut longs: Vec<String> = p
+            .args
+            .args
+            .iter()
+            .filter_map(|a| {
+                if a.index.is_none() && a.long.is_some() {
+                    Some(a.long.unwrap().to_string())
+                } else {
+                    None
+                }
+            })
+            .collect();
+
+        if longs.iter().find(|x| **x == "help").is_none() {
+            longs.push(String::from("help"));
+        }
+
+        if !p.is_set(AppSettings::DisableVersion)
+            && longs.iter().find(|x| **x == "version").is_none()
+        {
+            longs.push(String::from("version"));
+        }
+
+        longs
+    }
+
+    /// Gets all the flags of a [`clap::App`](../clap/struct.App.html).
+    /// Includes `help` and `version` depending on the [`clap::AppSettings`](../clap/enum.AppSettings.html).
+    fn flags<'b>(p: &'b App<'b>) -> Vec<Arg> {
+        debugln!("flags: name={}", p.name);
+
+        let mut flags: Vec<_> = flags!(p).cloned().collect();
+
+        if flags.iter().find(|x| x.name == "help").is_none() {
+            flags.push(
+                Arg::with_name("help")
+                    .short('h')
+                    .long("help")
+                    .help("Prints help information"),
+            );
+        }
+
+        if !p.is_set(AppSettings::DisableVersion)
+            && flags.iter().find(|x| x.name == "version").is_none()
+        {
+            flags.push(
+                Arg::with_name("version")
+                    .short('V')
+                    .long("version")
+                    .help("Prints version information"),
+            );
+        }
+
+        flags
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use pretty_assertions::assert_eq;
+
+    struct Foo;
+
+    impl Generator for Foo {
+        fn generate(_: &App, _: &mut dyn Write) {}
+
+        fn file_name(name: &str) -> String {
+            name.to_string()
+        }
+    }
+
+    fn common() -> App<'static> {
+        let mut app = App::new("myapp")
+            .subcommand(
+                App::new("test")
+                    .subcommand(App::new("config"))
+                    .arg(Arg::with_name("file").short('f').long("file")),
+            )
+            .subcommand(App::new("hello"))
+            .bin_name("my-app");
+
+        app._build();
+        app._build_bin_names();
+        app
+    }
+
+    #[test]
+    fn test_subcommands() {
+        let app = common();
+
+        assert_eq!(
+            Foo::subcommands(&app),
+            vec![
+                ("test".to_string(), "my-app test".to_string()),
+                ("hello".to_string(), "my-app hello".to_string()),
+                ("help".to_string(), "my-app help".to_string()),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_all_subcommands() {
+        let app = common();
+
+        assert_eq!(
+            Foo::all_subcommands(&app),
+            vec![
+                ("test".to_string(), "my-app test".to_string()),
+                ("hello".to_string(), "my-app hello".to_string()),
+                ("help".to_string(), "my-app help".to_string()),
+                ("config".to_string(), "my-app test config".to_string()),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_find_subcommand_with_path() {
+        let app = common();
+        let sc_app = Foo::find_subcommand_with_path(&app, "test config".split(" ").collect());
+
+        assert_eq!(sc_app.name, "config");
+    }
+
+    #[test]
+    fn test_flags() {
+        let app = common();
+        let flags = Foo::flags(&app);
+
+        assert_eq!(flags.len(), 2);
+        assert_eq!(flags[0].long, Some("help"));
+        assert_eq!(flags[1].long, Some("version"));
+
+        let sc_flags = Foo::flags(Foo::find_subcommand_with_path(&app, vec!["test"]));
+
+        assert_eq!(sc_flags.len(), 3);
+        assert_eq!(sc_flags[0].long, Some("file"));
+        assert_eq!(sc_flags[1].long, Some("help"));
+        assert_eq!(sc_flags[2].long, Some("version"));
+    }
+
+    #[test]
+    fn test_shorts() {
+        let app = common();
+        let shorts = Foo::shorts(&app);
+
+        assert_eq!(shorts.len(), 2);
+        assert_eq!(shorts[0], 'h');
+        assert_eq!(shorts[1], 'V');
+
+        let sc_shorts = Foo::shorts(Foo::find_subcommand_with_path(&app, vec!["test"]));
+
+        assert_eq!(sc_shorts.len(), 3);
+        assert_eq!(sc_shorts[0], 'f');
+        assert_eq!(sc_shorts[1], 'h');
+        assert_eq!(sc_shorts[2], 'V');
+    }
+
+    #[test]
+    fn test_longs() {
+        let app = common();
+        let longs = Foo::longs(&app);
+
+        assert_eq!(longs.len(), 2);
+        assert_eq!(longs[0], "help");
+        assert_eq!(longs[1], "version");
+
+        let sc_longs = Foo::longs(Foo::find_subcommand_with_path(&app, vec!["test"]));
+
+        assert_eq!(sc_longs.len(), 3);
+        assert_eq!(sc_longs[0], "file");
+        assert_eq!(sc_longs[1], "help");
+        assert_eq!(sc_longs[2], "version");
+    }
+}
diff --git a/clap_generate/src/generators/shells/bash.rs b/clap_generate/src/generators/shells/bash.rs
new file mode 100644
index 00000000..f7ffef21
--- /dev/null
+++ b/clap_generate/src/generators/shells/bash.rs
@@ -0,0 +1,208 @@
+// Std
+use std::io::Write;
+
+// Internal
+use crate::Generator;
+use clap::*;
+
+/// Generate bash completion file
+pub struct Bash;
+
+impl Generator for Bash {
+    fn file_name(name: &str) -> String {
+        format!("{}.bash", name)
+    }
+
+    fn generate(app: &App, buf: &mut dyn Write) {
+        let bin_name = app.get_bin_name().unwrap();
+
+        w!(
+            buf,
+            format!(
+                "_{name}() {{
+    local i cur prev opts cmds
+    COMPREPLY=()
+    cur=\"${{COMP_WORDS[COMP_CWORD]}}\"
+    prev=\"${{COMP_WORDS[COMP_CWORD-1]}}\"
+    cmd=\"\"
+    opts=\"\"
+
+    for i in ${{COMP_WORDS[@]}}
+    do
+        case \"${{i}}\" in
+            {name})
+                cmd=\"{name}\"
+                ;;
+            {subcmds}
+            *)
+                ;;
+        esac
+    done
+
+    case \"${{cmd}}\" in
+        {name})
+            opts=\"{name_opts}\"
+            if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then
+                COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+                return 0
+            fi
+            case \"${{prev}}\" in
+                {name_opts_details}
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+            return 0
+            ;;
+        {subcmd_details}
+    esac
+}}
+
+complete -F _{name} -o bashdefault -o default {name}
+",
+                name = bin_name,
+                name_opts = all_options_for_path(app, bin_name),
+                name_opts_details = option_details_for_path(app, bin_name),
+                subcmds = all_subcommands(app),
+                subcmd_details = subcommand_details(app)
+            )
+            .as_bytes()
+        );
+    }
+}
+
+fn all_subcommands(app: &App) -> String {
+    debugln!("Bash::all_subcommands;");
+
+    let mut subcmds = String::new();
+    let mut scs = Bash::all_subcommands(app)
+        .iter()
+        .map(|x| x.0.clone())
+        .collect::<Vec<_>>();
+
+    scs.sort();
+    scs.dedup();
+
+    for sc in &scs {
+        subcmds = format!(
+            "{}
+            {name})
+                cmd+=\"__{fn_name}\"
+                ;;",
+            subcmds,
+            name = sc,
+            fn_name = sc.replace("-", "__")
+        );
+    }
+
+    subcmds
+}
+
+fn subcommand_details(app: &App) -> String {
+    debugln!("Bash::subcommand_details;");
+
+    let mut subcmd_dets = String::new();
+    let mut scs = Bash::all_subcommands(app)
+        .iter()
+        .map(|x| x.1.replace(" ", "__"))
+        .collect::<Vec<_>>();
+
+    scs.sort();
+
+    for sc in &scs {
+        subcmd_dets = format!(
+            "{}
+        {subcmd})
+            opts=\"{sc_opts}\"
+            if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then
+                COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+                return 0
+            fi
+            case \"${{prev}}\" in
+                {opts_details}
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) )
+            return 0
+            ;;",
+            subcmd_dets,
+            subcmd = sc.replace("-", "__"),
+            sc_opts = all_options_for_path(app, &*sc),
+            level = sc.split("__").map(|_| 1).sum::<u64>(),
+            opts_details = option_details_for_path(app, &*sc)
+        );
+    }
+
+    subcmd_dets
+}
+
+fn option_details_for_path(app: &App, path: &str) -> String {
+    debugln!("Bash::option_details_for_path: path={}", path);
+
+    let p = Bash::find_subcommand_with_path(app, path.split("__").skip(1).collect());
+    let mut opts = String::new();
+
+    for o in opts!(p) {
+        if let Some(l) = o.long {
+            opts = format!(
+                "{}
+                --{})
+                    COMPREPLY=({})
+                    return 0
+                    ;;",
+                opts,
+                l,
+                vals_for(o)
+            );
+        }
+
+        if let Some(s) = o.short {
+            opts = format!(
+                "{}
+                    -{})
+                    COMPREPLY=({})
+                    return 0
+                    ;;",
+                opts,
+                s,
+                vals_for(o)
+            );
+        }
+    }
+
+    opts
+}
+
+fn vals_for(o: &Arg) -> String {
+    debugln!("Bash::vals_for: o={}", o.name);
+
+    if let Some(ref vals) = o.possible_vals {
+        format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" "))
+    } else {
+        String::from("$(compgen -f ${cur})")
+    }
+}
+
+fn all_options_for_path(app: &App, path: &str) -> String {
+    debugln!("Bash::all_options_for_path: path={}", path);
+
+    let p = Bash::find_subcommand_with_path(app, path.split("__").skip(1).collect());
+    let scs: Vec<_> = Bash::subcommands(p).iter().map(|x| x.0.clone()).collect();
+
+    let opts = format!(
+        "{shorts} {longs} {pos} {subcmds}",
+        shorts = Bash::shorts(p)
+            .iter()
+            .fold(String::new(), |acc, s| format!("{} -{}", acc, s)),
+        longs = Bash::longs(p)
+            .iter()
+            .fold(String::new(), |acc, l| format!("{} --{}", acc, l)),
+        pos = positionals!(p).fold(String::new(), |acc, p| format!("{} {}", acc, p)),
+        subcmds = scs.join(" "),
+    );
+
+    opts
+}
diff --git a/clap_generate/src/generators/shells/elvish.rs b/clap_generate/src/generators/shells/elvish.rs
new file mode 100644
index 00000000..aa4bfa5c
--- /dev/null
+++ b/clap_generate/src/generators/shells/elvish.rs
@@ -0,0 +1,133 @@
+// Std
+use std::io::Write;
+
+// Internal
+use crate::Generator;
+use crate::INTERNAL_ERROR_MSG;
+use clap::*;
+
+/// Generate elvish completion file
+pub struct Elvish;
+
+impl Generator for Elvish {
+    fn file_name(name: &str) -> String {
+        format!("{}.elv", name)
+    }
+
+    fn generate(app: &App, buf: &mut dyn Write) {
+        let bin_name = app.get_bin_name().unwrap();
+
+        let mut names = vec![];
+        let subcommands_cases = generate_inner(app, "", &mut names);
+
+        let result = format!(
+            r#"
+edit:completion:arg-completer[{bin_name}] = [@words]{{
+    fn spaces [n]{{
+        repeat $n ' ' | joins ''
+    }}
+    fn cand [text desc]{{
+        edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
+    }}
+    command = '{bin_name}'
+    for word $words[1:-1] {{
+        if (has-prefix $word '-') {{
+            break
+        }}
+        command = $command';'$word
+    }}
+    completions = [{subcommands_cases}
+    ]
+    $completions[$command]
+}}
+"#,
+            bin_name = bin_name,
+            subcommands_cases = subcommands_cases
+        );
+
+        w!(buf, result.as_bytes());
+    }
+}
+
+// Escape string inside single quotes
+fn escape_string(string: &str) -> String {
+    string.replace("'", "''")
+}
+
+fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
+    match help {
+        Some(help) => escape_string(help),
+        _ => data.to_string(),
+    }
+}
+
+fn generate_inner<'b>(
+    p: &'b App<'b>,
+    previous_command_name: &str,
+    names: &mut Vec<&'b str>,
+) -> String {
+    debugln!("Elvish::generate_inner;");
+
+    let command_name = if previous_command_name.is_empty() {
+        p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()
+    } else {
+        format!("{};{}", previous_command_name, &p.name)
+    };
+
+    let mut completions = String::new();
+    let preamble = String::from("\n            cand ");
+
+    for option in opts!(p) {
+        if let Some(data) = option.short {
+            let tooltip = get_tooltip(option.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
+        }
+
+        if let Some(data) = option.long {
+            let tooltip = get_tooltip(option.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
+        }
+    }
+
+    for flag in Elvish::flags(p) {
+        if let Some(data) = flag.short {
+            let tooltip = get_tooltip(flag.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
+        }
+
+        if let Some(data) = flag.long {
+            let tooltip = get_tooltip(flag.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
+        }
+    }
+
+    for subcommand in &p.subcommands {
+        let data = &subcommand.name;
+        let tooltip = get_tooltip(subcommand.about, data);
+
+        completions.push_str(&preamble);
+        completions.push_str(format!("{} '{}'", data, tooltip).as_str());
+    }
+
+    let mut subcommands_cases = format!(
+        r"
+        &'{}'= {{{}
+        }}",
+        &command_name, completions
+    );
+
+    for subcommand in &p.subcommands {
+        let subcommand_subcommands_cases = generate_inner(&subcommand, &command_name, names);
+        subcommands_cases.push_str(&subcommand_subcommands_cases);
+    }
+
+    subcommands_cases
+}
diff --git a/clap_generate/src/generators/shells/fish.rs b/clap_generate/src/generators/shells/fish.rs
new file mode 100644
index 00000000..966f08d0
--- /dev/null
+++ b/clap_generate/src/generators/shells/fish.rs
@@ -0,0 +1,117 @@
+// Std
+use std::io::Write;
+
+// Internal
+use crate::Generator;
+use clap::*;
+
+/// Generate fish completion file
+pub struct Fish;
+
+impl Generator for Fish {
+    fn file_name(name: &str) -> String {
+        format!("{}.fish", name)
+    }
+
+    fn generate(app: &App, buf: &mut dyn Write) {
+        let command = app.get_bin_name().unwrap();
+        let mut buffer = String::new();
+
+        gen_fish_inner(command, app, &mut buffer);
+        w!(buf, buffer.as_bytes());
+    }
+}
+
+// Escape string inside single quotes
+fn escape_string(string: &str) -> String {
+    string.replace("\\", "\\\\").replace("'", "\\'")
+}
+
+fn gen_fish_inner(root_command: &str, app: &App, buffer: &mut String) {
+    debugln!("Fish::gen_fish_inner;");
+    // example :
+    //
+    // complete
+    //      -c {command}
+    //      -d "{description}"
+    //      -s {short}
+    //      -l {long}
+    //      -a "{possible_arguments}"
+    //      -r # if require parameter
+    //      -f # don't use file completion
+    //      -n "__fish_use_subcommand"               # complete for command "myprog"
+    //      -n "__fish_seen_subcommand_from subcmd1" # complete for command "myprog subcmd1"
+
+    let mut basic_template = format!("complete -c {} -n ", root_command);
+    let mut bin_name = app.get_bin_name().unwrap();
+
+    if root_command == bin_name {
+        basic_template.push_str("\"__fish_use_subcommand\"");
+    } else {
+        bin_name = &app.name;
+        basic_template.push_str(format!("\"__fish_seen_subcommand_from {}\"", bin_name).as_str());
+    }
+
+    debugln!("Fish::gen_fish_inner; bin_name={}", bin_name);
+
+    for option in opts!(app) {
+        let mut template = basic_template.clone();
+
+        if let Some(data) = option.short {
+            template.push_str(format!(" -s {}", data).as_str());
+        }
+
+        if let Some(data) = option.long {
+            template.push_str(format!(" -l {}", data).as_str());
+        }
+
+        if let Some(data) = option.help {
+            template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
+        }
+
+        if let Some(ref data) = option.possible_vals {
+            template.push_str(format!(" -r -f -a \"{}\"", data.join(" ")).as_str());
+        }
+
+        buffer.push_str(template.as_str());
+        buffer.push_str("\n");
+    }
+
+    for flag in Fish::flags(app) {
+        let mut template = basic_template.clone();
+
+        if let Some(data) = flag.short {
+            template.push_str(format!(" -s {}", data).as_str());
+        }
+
+        if let Some(data) = flag.long {
+            template.push_str(format!(" -l {}", data).as_str());
+        }
+
+        if let Some(data) = flag.help {
+            template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
+        }
+
+        buffer.push_str(template.as_str());
+        buffer.push_str("\n");
+    }
+
+    for subcommand in &app.subcommands {
+        let mut template = basic_template.clone();
+
+        template.push_str(" -f");
+        template.push_str(format!(" -a \"{}\"", &subcommand.name).as_str());
+
+        if let Some(data) = subcommand.about {
+            template.push_str(format!(" -d '{}'", escape_string(data)).as_str())
+        }
+
+        buffer.push_str(template.as_str());
+        buffer.push_str("\n");
+    }
+
+    // generate options of subcommands
+    for subcommand in &app.subcommands {
+        gen_fish_inner(root_command, subcommand, buffer);
+    }
+}
diff --git a/clap_generate/src/generators/shells/mod.rs b/clap_generate/src/generators/shells/mod.rs
new file mode 100644
index 00000000..ef3f4e52
--- /dev/null
+++ b/clap_generate/src/generators/shells/mod.rs
@@ -0,0 +1,11 @@
+mod bash;
+mod elvish;
+mod fish;
+mod powershell;
+mod zsh;
+
+pub use bash::Bash;
+pub use elvish::Elvish;
+pub use fish::Fish;
+pub use powershell::PowerShell;
+pub use zsh::Zsh;
diff --git a/clap_generate/src/generators/shells/powershell.rs b/clap_generate/src/generators/shells/powershell.rs
new file mode 100644
index 00000000..3d83c4a2
--- /dev/null
+++ b/clap_generate/src/generators/shells/powershell.rs
@@ -0,0 +1,171 @@
+// Std
+use std::io::Write;
+
+// Internal
+use crate::Generator;
+use crate::INTERNAL_ERROR_MSG;
+use clap::*;
+
+/// Generate powershell completion file
+pub struct PowerShell;
+
+impl Generator for PowerShell {
+    fn file_name(name: &str) -> String {
+        format!("_{}.ps1", name)
+    }
+
+    fn generate(app: &App, buf: &mut dyn Write) {
+        let bin_name = app.get_bin_name().unwrap();
+
+        let mut names = vec![];
+        let subcommands_cases = generate_inner(app, "", &mut names);
+
+        let result = format!(
+            r#"
+using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+
+Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
+    param($wordToComplete, $commandAst, $cursorPosition)
+
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        '{bin_name}'
+        for ($i = 1; $i -lt $commandElements.Count; $i++) {{
+            $element = $commandElements[$i]
+            if ($element -isnot [StringConstantExpressionAst] -or
+                $element.StringConstantType -ne [StringConstantType]::BareWord -or
+                $element.Value.StartsWith('-')) {{
+                break
+        }}
+        $element.Value
+    }}) -join ';'
+
+    $completions = @(switch ($command) {{{subcommands_cases}
+    }})
+
+    $completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} |
+        Sort-Object -Property ListItemText
+}}
+"#,
+            bin_name = bin_name,
+            subcommands_cases = subcommands_cases
+        );
+
+        w!(buf, result.as_bytes());
+    }
+}
+
+// Escape string inside single quotes
+fn escape_string(string: &str) -> String {
+    string.replace("'", "''")
+}
+
+fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
+    match help {
+        Some(help) => escape_string(&help),
+        _ => data.to_string(),
+    }
+}
+
+fn generate_inner<'b>(
+    p: &'b App<'b>,
+    previous_command_name: &str,
+    names: &mut Vec<&'b str>,
+) -> String {
+    debugln!("PowerShell::generate_inner;");
+
+    let command_name = if previous_command_name.is_empty() {
+        p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()
+    } else {
+        format!("{};{}", previous_command_name, &p.name)
+    };
+
+    let mut completions = String::new();
+    let preamble = String::from("\n            [CompletionResult]::new(");
+
+    for option in opts!(p) {
+        if let Some(data) = option.short {
+            let tooltip = get_tooltip(option.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(
+                format!(
+                    "'-{}', '{}', {}, '{}')",
+                    data, data, "[CompletionResultType]::ParameterName", tooltip
+                )
+                .as_str(),
+            );
+        }
+
+        if let Some(data) = option.long {
+            let tooltip = get_tooltip(option.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(
+                format!(
+                    "'--{}', '{}', {}, '{}')",
+                    data, data, "[CompletionResultType]::ParameterName", tooltip
+                )
+                .as_str(),
+            );
+        }
+    }
+
+    for flag in PowerShell::flags(p) {
+        if let Some(data) = flag.short {
+            let tooltip = get_tooltip(flag.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(
+                format!(
+                    "'-{}', '{}', {}, '{}')",
+                    data, data, "[CompletionResultType]::ParameterName", tooltip
+                )
+                .as_str(),
+            );
+        }
+
+        if let Some(data) = flag.long {
+            let tooltip = get_tooltip(flag.help, data);
+
+            completions.push_str(&preamble);
+            completions.push_str(
+                format!(
+                    "'--{}', '{}', {}, '{}')",
+                    data, data, "[CompletionResultType]::ParameterName", tooltip
+                )
+                .as_str(),
+            );
+        }
+    }
+
+    for subcommand in subcommands!(p) {
+        let data = &subcommand.name;
+        let tooltip = get_tooltip(subcommand.about, data);
+
+        completions.push_str(&preamble);
+        completions.push_str(
+            format!(
+                "'{}', '{}', {}, '{}')",
+                data, data, "[CompletionResultType]::ParameterValue", tooltip
+            )
+            .as_str(),
+        );
+    }
+
+    let mut subcommands_cases = format!(
+        r"
+        '{}' {{{}
+            break
+        }}",
+        &command_name, completions
+    );
+
+    for subcommand in &p.subcommands {
+        let subcommand_subcommands_cases = generate_inner(&subcommand, &command_name, names);
+        subcommands_cases.push_str(&subcommand_subcommands_cases);
+    }
+
+    subcommands_cases
+}
diff --git a/clap_generate/src/generators/shells/zsh.rs b/clap_generate/src/generators/shells/zsh.rs
new file mode 100644
index 00000000..3acdaab2
--- /dev/null
+++ b/clap_generate/src/generators/shells/zsh.rs
@@ -0,0 +1,527 @@
+// Std
+use std::io::Write;
+
+// Internal
+use crate::Generator;
+use crate::INTERNAL_ERROR_MSG;
+use clap::*;
+
+/// Generate zsh completion file
+pub struct Zsh;
+
+impl Generator for Zsh {
+    fn file_name(name: &str) -> String {
+        format!("_{}", name)
+    }
+
+    fn generate(app: &App, buf: &mut dyn Write) {
+        w!(
+            buf,
+            format!(
+                "\
+#compdef {name}
+
+autoload -U is-at-least
+
+_{name}() {{
+    typeset -A opt_args
+    typeset -a _arguments_options
+    local ret=1
+
+    if is-at-least 5.2; then
+        _arguments_options=(-s -S -C)
+    else
+        _arguments_options=(-s -C)
+    fi
+
+    local context curcontext=\"$curcontext\" state line
+    {initial_args}
+    {subcommands}
+}}
+
+{subcommand_details}
+
+_{name} \"$@\"",
+                name = app.get_bin_name().unwrap(),
+                initial_args = get_args_of(app),
+                subcommands = get_subcommands_of(app),
+                subcommand_details = subcommand_details(app)
+            )
+            .as_bytes()
+        );
+    }
+}
+
+// Displays the commands of a subcommand
+// (( $+functions[_[bin_name_underscore]_commands] )) ||
+// _[bin_name_underscore]_commands() {
+//     local commands; commands=(
+//         '[arg_name]:[arg_help]'
+//     )
+//     _describe -t commands '[bin_name] commands' commands "$@"
+//
+// Where the following variables are present:
+//    [bin_name_underscore]: The full space deliniated bin_name, where spaces have been replaced by
+//                           underscore characters
+//    [arg_name]: The name of the subcommand
+//    [arg_help]: The help message of the subcommand
+//    [bin_name]: The full space deliniated bin_name
+//
+// Here's a snippet from rustup:
+//
+// (( $+functions[_rustup_commands] )) ||
+// _rustup_commands() {
+//     local commands; commands=(
+//         'show:Show the active and installed toolchains'
+//      'update:Update Rust toolchains'
+//      # ... snip for brevity
+//      'help:Prints this message or the help of the given subcommand(s)'
+//     )
+//     _describe -t commands 'rustup commands' commands "$@"
+//
+fn subcommand_details(p: &App) -> String {
+    debugln!("ZshGen::subcommand_details;");
+
+    let name = p.get_bin_name().unwrap();
+
+    // First we do ourself
+    let mut ret = vec![format!(
+        "\
+(( $+functions[_{bin_name_underscore}_commands] )) ||
+_{bin_name_underscore}_commands() {{
+    local commands; commands=(
+        {subcommands_and_args}
+    )
+    _describe -t commands '{bin_name} commands' commands \"$@\"
+}}",
+        bin_name_underscore = name.replace(" ", "__"),
+        bin_name = name,
+        subcommands_and_args = subcommands_of(p)
+    )];
+
+    // Next we start looping through all the children, grandchildren, etc.
+    let mut all_subcommands = Zsh::all_subcommands(p);
+
+    all_subcommands.sort();
+    all_subcommands.dedup();
+
+    for &(_, ref bin_name) in &all_subcommands {
+        debugln!("Zsh::subcommand_details:iter: bin_name={}", bin_name);
+
+        ret.push(format!(
+            "\
+(( $+functions[_{bin_name_underscore}_commands] )) ||
+_{bin_name_underscore}_commands() {{
+    local commands; commands=(
+        {subcommands_and_args}
+    )
+    _describe -t commands '{bin_name} commands' commands \"$@\"
+}}",
+            bin_name_underscore = bin_name.replace(" ", "__"),
+            bin_name = bin_name,
+            subcommands_and_args = subcommands_of(parser_of(p, bin_name))
+        ));
+    }
+
+    ret.join("\n")
+}
+
+// Generates subcommand completions in form of
+//
+//         '[arg_name]:[arg_help]'
+//
+// Where:
+//    [arg_name]: the subcommand's name
+//    [arg_help]: the help message of the subcommand
+//
+// A snippet from rustup:
+//         'show:Show the active and installed toolchains'
+//      'update:Update Rust toolchains'
+fn subcommands_of(p: &App) -> String {
+    debugln!("Zsh::subcommands_of;");
+
+    let mut ret = vec![];
+
+    fn add_sc(sc: &App, n: &str, ret: &mut Vec<String>) {
+        debugln!("Zsh::add_sc;");
+
+        let s = format!(
+            "\"{name}:{help}\" \\",
+            name = n,
+            help = sc
+                .about
+                .unwrap_or("")
+                .replace("[", "\\[")
+                .replace("]", "\\]")
+        );
+
+        if !s.is_empty() {
+            ret.push(s);
+        }
+    }
+
+    // The subcommands
+    for sc in subcommands!(p) {
+        debugln!("Zsh::subcommands_of:iter: subcommand={}", sc.name);
+
+        add_sc(sc, &sc.name, &mut ret);
+
+        if let Some(ref v) = sc.aliases {
+            for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) {
+                add_sc(sc, alias, &mut ret);
+            }
+        }
+    }
+
+    ret.join("\n")
+}
+
+// Get's the subcommand section of a completion file
+// This looks roughly like:
+//
+// case $state in
+// ([bin_name]_args)
+//     curcontext=\"${curcontext%:*:*}:[name_hyphen]-command-$words[1]:\"
+//     case $line[1] in
+//
+//         ([name])
+//         _arguments -C -s -S \
+//             [subcommand_args]
+//         && ret=0
+//
+//         [RECURSIVE_CALLS]
+//
+//         ;;",
+//
+//         [repeat]
+//
+//     esac
+// ;;
+// esac",
+//
+// Where the following variables are present:
+//    [name] = The subcommand name in the form of "install" for "rustup toolchain install"
+//    [bin_name] = The full space deliniated bin_name such as "rustup toolchain install"
+//    [name_hyphen] = The full space deliniated bin_name, but replace spaces with hyphens
+//    [repeat] = From the same recursive calls, but for all subcommands
+//    [subcommand_args] = The same as zsh::get_args_of
+fn get_subcommands_of(p: &App) -> String {
+    debugln!("Zsh::get_subcommands_of;");
+    debugln!(
+        "Zsh::get_subcommands_of: Has subcommands...{:?}",
+        p.has_subcommands()
+    );
+
+    if !p.has_subcommands() {
+        return String::new();
+    }
+
+    let sc_names = Zsh::subcommands(p);
+    let mut subcmds = vec![];
+
+    for &(ref name, ref bin_name) in &sc_names {
+        let mut v = vec![format!("({})", name)];
+        let subcommand_args = get_args_of(parser_of(p, &*bin_name));
+
+        if !subcommand_args.is_empty() {
+            v.push(subcommand_args);
+        }
+
+        let subcommands = get_subcommands_of(parser_of(p, &*bin_name));
+
+        if !subcommands.is_empty() {
+            v.push(subcommands);
+        }
+
+        v.push(String::from(";;"));
+        subcmds.push(v.join("\n"));
+    }
+
+    format!(
+        "case $state in
+    ({name})
+        words=($line[{pos}] \"${{words[@]}}\")
+        (( CURRENT += 1 ))
+        curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\"
+        case $line[{pos}] in
+            {subcommands}
+        esac
+    ;;
+esac",
+        name = p.name,
+        name_hyphen = p.get_bin_name().unwrap().replace(" ", "-"),
+        subcommands = subcmds.join("\n"),
+        pos = positionals!(p).count() + 1
+    )
+}
+
+fn parser_of<'b>(p: &'b App<'b>, mut sc: &str) -> &'b App<'b> {
+    debugln!("Zsh::parser_of: sc={}", sc);
+
+    if sc == p.get_bin_name().unwrap_or(&String::new()) {
+        return p;
+    }
+
+    sc = sc.split(' ').last().unwrap();
+    find_subcmd!(p, sc).expect(INTERNAL_ERROR_MSG)
+}
+
+// Writes out the args section, which ends up being the flags, opts and postionals, and a jump to
+// another ZSH function if there are subcommands.
+// The structer works like this:
+//    ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)]
+//       ^-- list '-v -h'    ^--'*'          ^--'+'                   ^-- list 'one two three'
+//
+// An example from the rustup command:
+//
+// _arguments -C -s -S \
+//         '(-h --help --verbose)-v[Enable verbose output]' \
+//         '(-V -v --version --verbose --help)-h[Prints help information]' \
+//      # ... snip for brevity
+//         ':: :_rustup_commands' \    # <-- displays subcommands
+//         '*::: :->rustup' \          # <-- displays subcommand args and child subcommands
+//     && ret=0
+//
+// The args used for _arguments are as follows:
+//    -C: modify the $context internal variable
+//    -s: Allow stacking of short args (i.e. -a -b -c => -abc)
+//    -S: Do not complete anything after '--' and treat those as argument values
+fn get_args_of(p: &App) -> String {
+    debugln!("Zsh::get_args_of;");
+
+    let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")];
+    let opts = write_opts_of(p);
+    let flags = write_flags_of(p);
+    let positionals = write_positionals_of(p);
+
+    let sc_or_a = if p.has_subcommands() {
+        format!(
+            "\":: :_{name}_commands\" \\",
+            name = p.bin_name.as_ref().unwrap().replace(" ", "__")
+        )
+    } else {
+        String::new()
+    };
+
+    let sc = if p.has_subcommands() {
+        format!("\"*::: :->{name}\" \\", name = p.name)
+    } else {
+        String::new()
+    };
+
+    if !opts.is_empty() {
+        ret.push(opts);
+    }
+
+    if !flags.is_empty() {
+        ret.push(flags);
+    }
+
+    if !positionals.is_empty() {
+        ret.push(positionals);
+    }
+
+    if !sc_or_a.is_empty() {
+        ret.push(sc_or_a);
+    }
+
+    if !sc.is_empty() {
+        ret.push(sc);
+    }
+
+    ret.push(String::from("&& ret=0"));
+    ret.join("\n")
+}
+
+// Escape help string inside single quotes and brackets
+fn escape_help(string: &str) -> String {
+    string
+        .replace("\\", "\\\\")
+        .replace("'", "'\\''")
+        .replace("[", "\\[")
+        .replace("]", "\\]")
+}
+
+// Escape value string inside single quotes and parentheses
+fn escape_value(string: &str) -> String {
+    string
+        .replace("\\", "\\\\")
+        .replace("'", "'\\''")
+        .replace("(", "\\(")
+        .replace(")", "\\)")
+        .replace(" ", "\\ ")
+}
+
+fn write_opts_of(p: &App) -> String {
+    debugln!("Zsh::write_opts_of;");
+
+    let mut ret = vec![];
+
+    for o in opts!(p) {
+        debugln!("Zsh::write_opts_of:iter: o={}", o.name);
+
+        let help = o.help.map_or(String::new(), escape_help);
+        let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG);
+
+        conflicts = if conflicts.is_empty() {
+            String::new()
+        } else {
+            format!("({})", conflicts)
+        };
+
+        // @TODO @soundness should probably be either multiple occurrences or multiple values and
+        // not both
+        let multiple = if o.is_set(ArgSettings::MultipleOccurrences)
+            || o.is_set(ArgSettings::MultipleValues)
+        {
+            "*"
+        } else {
+            ""
+        };
+
+        let pv = if let Some(ref pv_vec) = o.possible_vals {
+            format!(
+                ": :({})",
+                pv_vec
+                    .iter()
+                    .map(|v| escape_value(*v))
+                    .collect::<Vec<String>>()
+                    .join(" ")
+            )
+        } else {
+            String::new()
+        };
+
+        if let Some(short) = o.short {
+            let s = format!(
+                "'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\",
+                conflicts = conflicts,
+                multiple = multiple,
+                arg = short,
+                possible_values = pv,
+                help = help
+            );
+
+            debugln!("write_opts_of:iter: Wrote...{}", &*s);
+            ret.push(s);
+        }
+
+        if let Some(long) = o.long {
+            let l = format!(
+                "'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\",
+                conflicts = conflicts,
+                multiple = multiple,
+                arg = long,
+                possible_values = pv,
+                help = help
+            );
+
+            debugln!("write_opts_of:iter: Wrote...{}", &*l);
+            ret.push(l);
+        }
+    }
+
+    ret.join("\n")
+}
+
+fn write_flags_of(p: &App) -> String {
+    debugln!("Zsh::write_flags_of;");
+
+    let mut ret = vec![];
+
+    for f in Zsh::flags(p) {
+        debugln!("Zsh::write_flags_of:iter: f={}", f.name);
+
+        let help = f.help.map_or(String::new(), escape_help);
+        let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG);
+
+        conflicts = if conflicts.is_empty() {
+            String::new()
+        } else {
+            format!("({})", conflicts)
+        };
+
+        let multiple = if f.is_set(ArgSettings::MultipleOccurrences) {
+            "*"
+        } else {
+            ""
+        };
+
+        if let Some(short) = f.short {
+            let s = format!(
+                "'{conflicts}{multiple}-{arg}[{help}]' \\",
+                multiple = multiple,
+                conflicts = conflicts,
+                arg = short,
+                help = help
+            );
+
+            debugln!("Zsh::write_flags_of:iter: Wrote...{}", &*s);
+
+            ret.push(s);
+        }
+
+        if let Some(long) = f.long {
+            let l = format!(
+                "'{conflicts}{multiple}--{arg}[{help}]' \\",
+                conflicts = conflicts,
+                multiple = multiple,
+                arg = long,
+                help = help
+            );
+
+            debugln!("Zsh::write_flags_of:iter: Wrote...{}", &*l);
+
+            ret.push(l);
+        }
+    }
+
+    ret.join("\n")
+}
+
+fn write_positionals_of(p: &App) -> String {
+    debugln!("Zsh::write_positionals_of;");
+
+    let mut ret = vec![];
+
+    for arg in positionals!(p) {
+        debugln!("Zsh::write_positionals_of:iter: arg={}", arg.name);
+
+        let optional = if !arg.is_set(ArgSettings::Required) {
+            ":"
+        } else {
+            ""
+        };
+
+        let a = format!(
+            "'{optional}:{name}{help}:{action}' \\",
+            optional = optional,
+            name = arg.name,
+            help = arg
+                .help
+                .map_or("".to_owned(), |v| " -- ".to_owned() + v)
+                .replace("[", "\\[")
+                .replace("]", "\\]")
+                .replace(":", "\\:"),
+            action = arg
+                .possible_vals
+                .as_ref()
+                .map_or("_files".to_owned(), |values| {
+                    format!(
+                        "({})",
+                        values
+                            .iter()
+                            .map(|v| escape_value(*v))
+                            .collect::<Vec<String>>()
+                            .join(" ")
+                    )
+                })
+        );
+
+        debugln!("Zsh::write_positionals_of:iter: Wrote...{}", a);
+
+        ret.push(a);
+    }
+
+    ret.join("\n")
+}
diff --git a/clap_generate/src/lib.rs b/clap_generate/src/lib.rs
new file mode 100644
index 00000000..bd6b4925
--- /dev/null
+++ b/clap_generate/src/lib.rs
@@ -0,0 +1,189 @@
+// Copyright ⓒ 2015-2018 Kevin B. Knapp
+//
+// `clap_generate` is distributed under the terms of both the MIT license and the Apache License
+// (Version 2.0).
+// See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files in this repository
+// for more information.
+
+//! Generates stuff for [`clap`](https://github.com/clap-rs/clap) based CLIs
+
+#![doc(html_root_url = "https://docs.rs/clap_generate/3.0.0-beta.1")]
+#![deny(
+    missing_docs,
+    trivial_casts,
+    unused_import_braces,
+    unused_allocation,
+    trivial_numeric_casts
+)]
+#![allow(clippy::needless_doctest_main)]
+
+const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \
+                                  report at https://github.com/clap-rs/clap/issues";
+
+#[macro_use]
+#[allow(missing_docs)]
+mod macros;
+
+/// Contains some popular generators
+pub mod generators;
+
+use std::ffi::OsString;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+#[doc(inline)]
+pub use generators::Generator;
+
+/// Generate a file for a specified generator at compile time.
+///
+/// **NOTE:** to generate the file at compile time you must use a `build.rs` "Build Script"
+///
+/// # Examples
+///
+/// The following example generates a bash completion script via a `build.rs` script. In this
+/// simple example, we'll demo a very small application with only a single subcommand and two
+/// args. Real applications could be many multiple levels deep in subcommands, and have tens or
+/// potentially hundreds of arguments.
+///
+/// First, it helps if we separate out our `App` definition into a separate file. Whether you
+/// do this as a function, or bare App definition is a matter of personal preference.
+///
+/// ```
+/// // src/cli.rs
+///
+/// use clap::{App, Arg};
+///
+/// pub fn build_cli() -> App<'static> {
+///     App::new("compl")
+///         .about("Tests completions")
+///         .arg(Arg::with_name("file")
+///             .help("some input file"))
+///         .subcommand(App::new("test")
+///             .about("tests things")
+///             .arg(Arg::with_name("case")
+///                 .long("case")
+///                 .takes_value(true)
+///                 .help("the case to test")))
+/// }
+/// ```
+///
+/// In our regular code, we can simply call this `build_cli()` function, then call
+/// `get_matches()`, or any of the other normal methods directly after. For example:
+///
+/// ```ignore
+/// // src/main.rs
+///
+/// mod cli;
+///
+/// fn main() {
+///     let m = cli::build_cli().get_matches();
+///
+///     // normal logic continues...
+/// }
+/// ```
+///
+/// Next, we set up our `Cargo.toml` to use a `build.rs` build script.
+///
+/// ```toml
+/// # Cargo.toml
+/// build = "build.rs"
+///
+/// [build-dependencies]
+/// clap = "*"
+/// ```
+///
+/// Next, we place a `build.rs` in our project root.
+///
+/// ```ignore
+/// use clap_generate::{generate_to, generators::Bash};
+///
+/// include!("src/cli.rs");
+///
+/// fn main() {
+///     let outdir = match env::var_os("OUT_DIR") {
+///         None => return,
+///         Some(outdir) => outdir,
+///     };
+///
+///     let mut app = build_cli();
+///     generate_to::<Bash, _>(&mut app,     // We need to specify what generator to use
+///                            "myapp",      // We need to specify the bin name manually
+///                            outdir);      // We need to specify where to write to
+/// }
+/// ```
+///
+/// Now, once we compile there will be a `{bin_name}.bash` file in the directory.
+/// Assuming we compiled with debug mode, it would be somewhere similar to
+/// `<project>/target/debug/build/myapp-<hash>/out/myapp.bash`.
+///
+/// **NOTE:** Please look at the individual [generators](./generators/index.html)
+/// to see the name of the files generated.
+pub fn generate_to<G, S, T>(app: &mut clap::App, bin_name: S, out_dir: T)
+where
+    G: Generator,
+    S: Into<String>,
+    T: Into<OsString>,
+{
+    use std::error::Error;
+
+    let out_dir = PathBuf::from(out_dir.into());
+    let file_name = G::file_name(app.get_bin_name().unwrap());
+
+    let mut file = match File::create(out_dir.join(file_name)) {
+        Err(why) => panic!("couldn't create completion file: {}", why.description()),
+        Ok(file) => file,
+    };
+
+    generate::<G, S>(app, bin_name, &mut file)
+}
+
+/// 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](./fn.generate_to.html),
+/// we can let users generate a completion script using a command:
+///
+/// ```ignore
+/// // src/main.rs
+///
+/// mod cli;
+/// use std::io;
+/// use clap_generate::{generate, generators::Bash};
+///
+/// fn main() {
+///     let matches = cli::build_cli().get_matches();
+///
+///     if matches.is_present("generate-bash-completions") {
+///         generate::<Bash, _>(&mut cli::build_cli(), "myapp", &mut io::stdout());
+///     }
+///
+///     // normal logic continues...
+/// }
+///
+/// ```
+///
+/// Usage:
+///
+/// ```shell
+/// $ myapp generate-bash-completions > /usr/share/bash-completion/completions/myapp.bash
+/// ```
+pub fn generate<G, S>(app: &mut clap::App, bin_name: S, buf: &mut dyn Write)
+where
+    G: Generator,
+    S: Into<String>,
+{
+    app.bin_name = Some(bin_name.into());
+
+    if !app.is_set(clap::AppSettings::Built) {
+        app._build();
+        app._build_bin_names();
+    }
+
+    G::generate(app, buf)
+}
diff --git a/clap_generate/src/macros.rs b/clap_generate/src/macros.rs
new file mode 100644
index 00000000..748b258e
--- /dev/null
+++ b/clap_generate/src/macros.rs
@@ -0,0 +1,87 @@
+macro_rules! w {
+    ($buf:expr, $to_w:expr) => {
+        match $buf.write_all($to_w) {
+            Ok(..) => (),
+            Err(..) => panic!("Failed to write to generated file"),
+        }
+    };
+}
+
+macro_rules! get_zsh_arg_conflicts {
+    ($app:expr, $arg:ident, $msg:ident) => {
+        if let Some(ref conf_vec) = $arg.blacklist {
+            let mut v = vec![];
+
+            for arg_name in conf_vec {
+                let arg = find!($app, arg_name).expect($msg);
+
+                if let Some(s) = arg.short {
+                    v.push(format!("-{}", s));
+                }
+
+                if let Some(l) = arg.long {
+                    v.push(format!("--{}", l));
+                }
+            }
+
+            v.join(" ")
+        } else {
+            String::new()
+        }
+    };
+}
+
+#[cfg(feature = "debug")]
+#[cfg_attr(feature = "debug", macro_use)]
+#[cfg_attr(feature = "debug", allow(unused_macros))]
+mod debug_macros {
+    macro_rules! debugln {
+        ($fmt:expr) => (println!(concat!("DEBUG:clap_generate:", $fmt)));
+        ($fmt:expr, $($arg:tt)*) => (println!(concat!("DEBUG:clap_generate:",$fmt), $($arg)*));
+    }
+    macro_rules! sdebugln {
+        ($fmt:expr) => (println!($fmt));
+        ($fmt:expr, $($arg:tt)*) => (println!($fmt, $($arg)*));
+    }
+    macro_rules! debug {
+        ($fmt:expr) => (print!(concat!("DEBUG:clap_generate:", $fmt)));
+        ($fmt:expr, $($arg:tt)*) => (print!(concat!("DEBUG:clap_generate:",$fmt), $($arg)*));
+    }
+    macro_rules! sdebug {
+        ($fmt:expr) => (print!($fmt));
+        ($fmt:expr, $($arg:tt)*) => (print!($fmt, $($arg)*));
+    }
+}
+
+#[cfg(not(feature = "debug"))]
+#[cfg_attr(not(feature = "debug"), macro_use)]
+#[cfg_attr(not(feature = "debug"), allow(unused_macros))]
+mod debug_macros {
+    macro_rules! debugln {
+        ($fmt:expr) => {};
+        ($fmt:expr, $($arg:tt)*) => {};
+    }
+    macro_rules! sdebugln {
+        ($fmt:expr) => {};
+        ($fmt:expr, $($arg:tt)*) => {};
+    }
+    macro_rules! debug {
+        ($fmt:expr) => {};
+        ($fmt:expr, $($arg:tt)*) => {};
+    }
+}
+
+macro_rules! find {
+    ($app:expr, $name:expr, $what:ident) => {
+        $what!($app).find(|a| &a.name == $name)
+    };
+    ($app:expr, $name:expr) => {
+        $app.args.args.iter().find(|a| {
+            if let Some(v) = a.index {
+                &v == $name
+            } else {
+                false
+            }
+        })
+    };
+}
diff --git a/src/lib.rs b/src/lib.rs
index 300c292e..2286a22d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -475,8 +475,11 @@ mod output;
 mod parse;
 mod util;
 
+#[doc(hidden)]
+pub use mkeymap::KeyType;
+
 const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \
-                                  report at https://github.com/kbknapp/clap-rs/issues";
+                                  report at https://github.com/clap-rs/clap/issues";
 const INVALID_UTF8: &str = "unexpected invalid UTF-8 code point";
 
 /// @TODO @release @docs
diff --git a/src/macros.rs b/src/macros.rs
index 7a1c735b..2fdff9a2 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -919,18 +919,18 @@ macro_rules! write_nspaces {
     }};
 }
 
+#[macro_export]
+#[doc(hidden)]
 macro_rules! flags {
     ($app:expr, $how:ident) => {{
         $app.args
             .args
             .$how()
-            .filter(|a| {
-                !a.settings.is_set(crate::build::ArgSettings::TakesValue) && a.index.is_none()
-            })
+            .filter(|a| !a.settings.is_set($crate::ArgSettings::TakesValue) && a.index.is_none())
             .filter(|a| !a.help_heading.is_some())
     }};
     ($app:expr) => {
-        flags!($app, iter)
+        $crate::flags!($app, iter)
     };
 }
 
@@ -941,14 +941,14 @@ macro_rules! flags_mut {
     };
 }
 
+#[macro_export]
+#[doc(hidden)]
 macro_rules! opts {
     ($app:expr, $how:ident) => {{
         $app.args
             .args
             .$how()
-            .filter(|a| {
-                a.settings.is_set(crate::build::ArgSettings::TakesValue) && a.index.is_none()
-            })
+            .filter(|a| a.settings.is_set($crate::ArgSettings::TakesValue) && a.index.is_none())
             .filter(|a| !a.help_heading.is_some())
     }};
     ($app:expr) => {
@@ -963,6 +963,8 @@ macro_rules! opts_mut {
     };
 }
 
+#[macro_export]
+#[doc(hidden)]
 macro_rules! positionals {
     ($app:expr) => {
         $app.args
@@ -997,6 +999,8 @@ macro_rules! subcommands_cloned {
     };
 }
 
+#[macro_export]
+#[doc(hidden)]
 macro_rules! subcommands {
     ($app:expr, $how:ident) => {
         $app.subcommands.$how()
@@ -1028,6 +1032,8 @@ macro_rules! find_subcmd_cloned {
     }};
 }
 
+#[macro_export]
+#[doc(hidden)]
 macro_rules! find_subcmd {
     ($app:expr, $sc:expr) => {{
         subcommands!($app).find(|a| match_alias!(a, $sc, &*a.name))
@@ -1047,7 +1053,9 @@ macro_rules! longs {
     }};
 }
 
-macro_rules! _names {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! names {
     (@args $app:expr) => {{
         $app.args.args.iter().map(|a| &*a.name)
     }};
@@ -1061,12 +1069,16 @@ macro_rules! _names {
     }};
 }
 
+#[macro_export]
+#[doc(hidden)]
 macro_rules! sc_names {
     ($app:expr) => {{
-        _names!(@sc $app)
+        names!(@sc $app)
     }};
 }
 
+#[macro_export]
+#[doc(hidden)]
 macro_rules! match_alias {
     ($a:expr, $to:expr, $what:expr) => {{
         $what == $to
diff --git a/src/mkeymap.rs b/src/mkeymap.rs
index f17cd225..d1e4097b 100644
--- a/src/mkeymap.rs
+++ b/src/mkeymap.rs
@@ -16,6 +16,7 @@ pub struct MKeyMap<'b> {
     built: bool, // mutation isn't possible after being built
 }
 
+#[doc(hidden)]
 #[derive(Debug, PartialEq, Eq, Hash, Clone)]
 pub enum KeyType {
     Short(char),


Test patch:
diff --git a/clap_generate/tests/completions.rs b/clap_generate/tests/completions.rs
new file mode 100644
index 00000000..05b1e66e
--- /dev/null
+++ b/clap_generate/tests/completions.rs
@@ -0,0 +1,858 @@
+use clap::{App, Arg};
+use clap_generate::{generate, generators::*};
+use std::fmt;
+
+#[derive(PartialEq, Eq)]
+pub struct PrettyString<'a>(pub &'a str);
+
+impl<'a> fmt::Debug for PrettyString<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(self.0)
+    }
+}
+
+macro_rules! assert_eq {
+    ($left:expr, $right:expr) => {
+        pretty_assertions::assert_eq!(PrettyString($left), PrettyString($right));
+    };
+}
+
+static BASH: &'static str = r#"_myapp() {
+    local i cur prev opts cmds
+    COMPREPLY=()
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+    cmd=""
+    opts=""
+
+    for i in ${COMP_WORDS[@]}
+    do
+        case "${i}" in
+            myapp)
+                cmd="myapp"
+                ;;
+            
+            help)
+                cmd+="__help"
+                ;;
+            test)
+                cmd+="__test"
+                ;;
+            *)
+                ;;
+        esac
+    done
+
+    case "${cmd}" in
+        myapp)
+            opts=" -h -V  --help --version  <file> test help"
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+        
+        myapp__help)
+            opts=" -h -V  --help --version  "
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+        myapp__test)
+            opts=" -h -V  --case --help --version  "
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                --case)
+                    COMPREPLY=($(compgen -f ${cur}))
+                    return 0
+                    ;;
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+    esac
+}
+
+complete -F _myapp -o bashdefault -o default myapp
+"#;
+
+static ZSH: &'static str = r#"#compdef myapp
+
+autoload -U is-at-least
+
+_myapp() {
+    typeset -A opt_args
+    typeset -a _arguments_options
+    local ret=1
+
+    if is-at-least 5.2; then
+        _arguments_options=(-s -S -C)
+    else
+        _arguments_options=(-s -C)
+    fi
+
+    local context curcontext="$curcontext" state line
+    _arguments "${_arguments_options[@]}" \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+'::file -- some input file:_files' \
+":: :_myapp_commands" \
+"*::: :->myapp" \
+&& ret=0
+    case $state in
+    (myapp)
+        words=($line[2] "${words[@]}")
+        (( CURRENT += 1 ))
+        curcontext="${curcontext%:*:*}:myapp-command-$line[2]:"
+        case $line[2] in
+            (test)
+_arguments "${_arguments_options[@]}" \
+'--case=[the case to test]' \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+&& ret=0
+;;
+(help)
+_arguments "${_arguments_options[@]}" \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+&& ret=0
+;;
+        esac
+    ;;
+esac
+}
+
+(( $+functions[_myapp_commands] )) ||
+_myapp_commands() {
+    local commands; commands=(
+        "test:tests things" \
+"help:Prints this message or the help of the given subcommand(s)" \
+    )
+    _describe -t commands 'myapp commands' commands "$@"
+}
+(( $+functions[_myapp__help_commands] )) ||
+_myapp__help_commands() {
+    local commands; commands=(
+        
+    )
+    _describe -t commands 'myapp help commands' commands "$@"
+}
+(( $+functions[_myapp__test_commands] )) ||
+_myapp__test_commands() {
+    local commands; commands=(
+        
+    )
+    _describe -t commands 'myapp test commands' commands "$@"
+}
+
+_myapp "$@""#;
+
+static FISH: &'static str = r#"complete -c myapp -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
+complete -c myapp -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
+complete -c myapp -n "__fish_use_subcommand" -f -a "test" -d 'tests things'
+complete -c myapp -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)'
+complete -c myapp -n "__fish_seen_subcommand_from test" -l case -d 'the case to test'
+complete -c myapp -n "__fish_seen_subcommand_from test" -s h -l help -d 'Prints help information'
+complete -c myapp -n "__fish_seen_subcommand_from test" -s V -l version -d 'Prints version information'
+complete -c myapp -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
+complete -c myapp -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information'
+"#;
+
+static POWERSHELL: &'static str = r#"
+using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+
+Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock {
+    param($wordToComplete, $commandAst, $cursorPosition)
+
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        'my_app'
+        for ($i = 1; $i -lt $commandElements.Count; $i++) {
+            $element = $commandElements[$i]
+            if ($element -isnot [StringConstantExpressionAst] -or
+                $element.StringConstantType -ne [StringConstantType]::BareWord -or
+                $element.Value.StartsWith('-')) {
+                break
+        }
+        $element.Value
+    }) -join ';'
+
+    $completions = @(switch ($command) {
+        'my_app' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things')
+            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)')
+            break
+        }
+        'my_app;test' {
+            [CompletionResult]::new('--case', 'case', [CompletionResultType]::ParameterName, 'the case to test')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;help' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+    })
+
+    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
+        Sort-Object -Property ListItemText
+}
+"#;
+
+static ELVISH: &'static str = r#"
+edit:completion:arg-completer[my_app] = [@words]{
+    fn spaces [n]{
+        repeat $n ' ' | joins ''
+    }
+    fn cand [text desc]{
+        edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
+    }
+    command = 'my_app'
+    for word $words[1:-1] {
+        if (has-prefix $word '-') {
+            break
+        }
+        command = $command';'$word
+    }
+    completions = [
+        &'my_app'= {
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+            cand test 'tests things'
+            cand help 'Prints this message or the help of the given subcommand(s)'
+        }
+        &'my_app;test'= {
+            cand --case 'the case to test'
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+        }
+        &'my_app;help'= {
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+        }
+    ]
+    $completions[$command]
+}
+"#;
+
+static ELVISH_SPECIAL_CMDS: &'static str = r#"
+edit:completion:arg-completer[my_app] = [@words]{
+    fn spaces [n]{
+        repeat $n ' ' | joins ''
+    }
+    fn cand [text desc]{
+        edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
+    }
+    command = 'my_app'
+    for word $words[1:-1] {
+        if (has-prefix $word '-') {
+            break
+        }
+        command = $command';'$word
+    }
+    completions = [
+        &'my_app'= {
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+            cand test 'tests things'
+            cand some_cmd 'tests other things'
+            cand some-cmd-with-hypens 'some-cmd-with-hypens'
+            cand help 'Prints this message or the help of the given subcommand(s)'
+        }
+        &'my_app;test'= {
+            cand --case 'the case to test'
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+        }
+        &'my_app;some_cmd'= {
+            cand --config 'the other case to test'
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+        }
+        &'my_app;some-cmd-with-hypens'= {
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+        }
+        &'my_app;help'= {
+            cand -h 'Prints help information'
+            cand --help 'Prints help information'
+            cand -V 'Prints version information'
+            cand --version 'Prints version information'
+        }
+    ]
+    $completions[$command]
+}
+"#;
+
+static POWERSHELL_SPECIAL_CMDS: &'static str = r#"
+using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+
+Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock {
+    param($wordToComplete, $commandAst, $cursorPosition)
+
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        'my_app'
+        for ($i = 1; $i -lt $commandElements.Count; $i++) {
+            $element = $commandElements[$i]
+            if ($element -isnot [StringConstantExpressionAst] -or
+                $element.StringConstantType -ne [StringConstantType]::BareWord -or
+                $element.Value.StartsWith('-')) {
+                break
+        }
+        $element.Value
+    }) -join ';'
+
+    $completions = @(switch ($command) {
+        'my_app' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things')
+            [CompletionResult]::new('some_cmd', 'some_cmd', [CompletionResultType]::ParameterValue, 'tests other things')
+            [CompletionResult]::new('some-cmd-with-hypens', 'some-cmd-with-hypens', [CompletionResultType]::ParameterValue, 'some-cmd-with-hypens')
+            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)')
+            break
+        }
+        'my_app;test' {
+            [CompletionResult]::new('--case', 'case', [CompletionResultType]::ParameterName, 'the case to test')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;some_cmd' {
+            [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'the other case to test')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;some-cmd-with-hypens' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;help' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+    })
+
+    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
+        Sort-Object -Property ListItemText
+}
+"#;
+
+static ZSH_SPECIAL_CMDS: &'static str = r#"#compdef my_app
+
+autoload -U is-at-least
+
+_my_app() {
+    typeset -A opt_args
+    typeset -a _arguments_options
+    local ret=1
+
+    if is-at-least 5.2; then
+        _arguments_options=(-s -S -C)
+    else
+        _arguments_options=(-s -C)
+    fi
+
+    local context curcontext="$curcontext" state line
+    _arguments "${_arguments_options[@]}" \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+'::file -- some input file:_files' \
+":: :_my_app_commands" \
+"*::: :->my_app" \
+&& ret=0
+    case $state in
+    (my_app)
+        words=($line[2] "${words[@]}")
+        (( CURRENT += 1 ))
+        curcontext="${curcontext%:*:*}:my_app-command-$line[2]:"
+        case $line[2] in
+            (test)
+_arguments "${_arguments_options[@]}" \
+'--case=[the case to test]' \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+&& ret=0
+;;
+(some_cmd)
+_arguments "${_arguments_options[@]}" \
+'--config=[the other case to test]' \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+&& ret=0
+;;
+(some-cmd-with-hypens)
+_arguments "${_arguments_options[@]}" \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+&& ret=0
+;;
+(help)
+_arguments "${_arguments_options[@]}" \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+&& ret=0
+;;
+        esac
+    ;;
+esac
+}
+
+(( $+functions[_my_app_commands] )) ||
+_my_app_commands() {
+    local commands; commands=(
+        "test:tests things" \
+"some_cmd:tests other things" \
+"some-cmd-with-hypens:" \
+"help:Prints this message or the help of the given subcommand(s)" \
+    )
+    _describe -t commands 'my_app commands' commands "$@"
+}
+(( $+functions[_my_app__help_commands] )) ||
+_my_app__help_commands() {
+    local commands; commands=(
+        
+    )
+    _describe -t commands 'my_app help commands' commands "$@"
+}
+(( $+functions[_my_app__some-cmd-with-hypens_commands] )) ||
+_my_app__some-cmd-with-hypens_commands() {
+    local commands; commands=(
+        
+    )
+    _describe -t commands 'my_app some-cmd-with-hypens commands' commands "$@"
+}
+(( $+functions[_my_app__some_cmd_commands] )) ||
+_my_app__some_cmd_commands() {
+    local commands; commands=(
+        
+    )
+    _describe -t commands 'my_app some_cmd commands' commands "$@"
+}
+(( $+functions[_my_app__test_commands] )) ||
+_my_app__test_commands() {
+    local commands; commands=(
+        
+    )
+    _describe -t commands 'my_app test commands' commands "$@"
+}
+
+_my_app "$@""#;
+
+static FISH_SPECIAL_CMDS: &'static str = r#"complete -c my_app -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
+complete -c my_app -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
+complete -c my_app -n "__fish_use_subcommand" -f -a "test" -d 'tests things'
+complete -c my_app -n "__fish_use_subcommand" -f -a "some_cmd" -d 'tests other things'
+complete -c my_app -n "__fish_use_subcommand" -f -a "some-cmd-with-hypens"
+complete -c my_app -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)'
+complete -c my_app -n "__fish_seen_subcommand_from test" -l case -d 'the case to test'
+complete -c my_app -n "__fish_seen_subcommand_from test" -s h -l help -d 'Prints help information'
+complete -c my_app -n "__fish_seen_subcommand_from test" -s V -l version -d 'Prints version information'
+complete -c my_app -n "__fish_seen_subcommand_from some_cmd" -l config -d 'the other case to test'
+complete -c my_app -n "__fish_seen_subcommand_from some_cmd" -s h -l help -d 'Prints help information'
+complete -c my_app -n "__fish_seen_subcommand_from some_cmd" -s V -l version -d 'Prints version information'
+complete -c my_app -n "__fish_seen_subcommand_from some-cmd-with-hypens" -s h -l help -d 'Prints help information'
+complete -c my_app -n "__fish_seen_subcommand_from some-cmd-with-hypens" -s V -l version -d 'Prints version information'
+complete -c my_app -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
+complete -c my_app -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information'
+"#;
+
+static BASH_SPECIAL_CMDS: &'static str = r#"_my_app() {
+    local i cur prev opts cmds
+    COMPREPLY=()
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+    cmd=""
+    opts=""
+
+    for i in ${COMP_WORDS[@]}
+    do
+        case "${i}" in
+            my_app)
+                cmd="my_app"
+                ;;
+            
+            help)
+                cmd+="__help"
+                ;;
+            some-cmd-with-hypens)
+                cmd+="__some__cmd__with__hypens"
+                ;;
+            some_cmd)
+                cmd+="__some_cmd"
+                ;;
+            test)
+                cmd+="__test"
+                ;;
+            *)
+                ;;
+        esac
+    done
+
+    case "${cmd}" in
+        my_app)
+            opts=" -h -V  --help --version  <file> test some_cmd some-cmd-with-hypens help"
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+        
+        my_app__help)
+            opts=" -h -V  --help --version  "
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+        my_app__some__cmd__with__hypens)
+            opts=" -h -V  --help --version  "
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+        my_app__some_cmd)
+            opts=" -h -V  --config --help --version  "
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                --config)
+                    COMPREPLY=($(compgen -f ${cur}))
+                    return 0
+                    ;;
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+        my_app__test)
+            opts=" -h -V  --case --help --version  "
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+                return 0
+            fi
+            case "${prev}" in
+                
+                --case)
+                    COMPREPLY=($(compgen -f ${cur}))
+                    return 0
+                    ;;
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
+    esac
+}
+
+complete -F _my_app -o bashdefault -o default my_app
+"#;
+
+static FISH_SPECIAL_HELP: &'static str = r#"complete -c my_app -n "__fish_use_subcommand" -l single-quotes -d 'Can be \'always\', \'auto\', or \'never\''
+complete -c my_app -n "__fish_use_subcommand" -l double-quotes -d 'Can be "always", "auto", or "never"'
+complete -c my_app -n "__fish_use_subcommand" -l backticks -d 'For more information see `echo test`'
+complete -c my_app -n "__fish_use_subcommand" -l backslash -d 'Avoid \'\\n\''
+complete -c my_app -n "__fish_use_subcommand" -l brackets -d 'List packages [filter]'
+complete -c my_app -n "__fish_use_subcommand" -l expansions -d 'Execute the shell command with $SHELL'
+complete -c my_app -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
+complete -c my_app -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
+"#;
+
+static ZSH_SPECIAL_HELP: &'static str = r#"#compdef my_app
+
+autoload -U is-at-least
+
+_my_app() {
+    typeset -A opt_args
+    typeset -a _arguments_options
+    local ret=1
+
+    if is-at-least 5.2; then
+        _arguments_options=(-s -S -C)
+    else
+        _arguments_options=(-s -C)
+    fi
+
+    local context curcontext="$curcontext" state line
+    _arguments "${_arguments_options[@]}" \
+'--single-quotes[Can be '\''always'\'', '\''auto'\'', or '\''never'\'']' \
+'--double-quotes[Can be "always", "auto", or "never"]' \
+'--backticks[For more information see `echo test`]' \
+'--backslash[Avoid '\''\\n'\'']' \
+'--brackets[List packages \[filter\]]' \
+'--expansions[Execute the shell command with $SHELL]' \
+'-h[Prints help information]' \
+'--help[Prints help information]' \
+'-V[Prints version information]' \
+'--version[Prints version information]' \
+&& ret=0
+    
+}
+
+(( $+functions[_my_app_commands] )) ||
+_my_app_commands() {
+    local commands; commands=(
+        
+    )
+    _describe -t commands 'my_app commands' commands "$@"
+}
+
+_my_app "$@""#;
+
+fn build_app() -> App<'static> {
+    build_app_with_name("myapp")
+}
+
+fn build_app_with_name(s: &'static str) -> App<'static> {
+    App::new(s)
+        .about("Tests completions")
+        .arg(Arg::with_name("file").help("some input file"))
+        .subcommand(
+            App::new("test").about("tests things").arg(
+                Arg::with_name("case")
+                    .long("case")
+                    .takes_value(true)
+                    .help("the case to test"),
+            ),
+        )
+}
+
+fn build_app_special_commands() -> App<'static> {
+    build_app_with_name("my_app")
+        .subcommand(
+            App::new("some_cmd").about("tests other things").arg(
+                Arg::with_name("config")
+                    .long("--config")
+                    .takes_value(true)
+                    .help("the other case to test"),
+            ),
+        )
+        .subcommand(App::new("some-cmd-with-hypens").alias("hyphen"))
+}
+
+fn build_app_special_help() -> App<'static> {
+    App::new("my_app")
+        .arg(
+            Arg::with_name("single-quotes")
+                .long("single-quotes")
+                .help("Can be 'always', 'auto', or 'never'"),
+        )
+        .arg(
+            Arg::with_name("double-quotes")
+                .long("double-quotes")
+                .help("Can be \"always\", \"auto\", or \"never\""),
+        )
+        .arg(
+            Arg::with_name("backticks")
+                .long("backticks")
+                .help("For more information see `echo test`"),
+        )
+        .arg(
+            Arg::with_name("backslash")
+                .long("backslash")
+                .help("Avoid '\\n'"),
+        )
+        .arg(
+            Arg::with_name("brackets")
+                .long("brackets")
+                .help("List packages [filter]"),
+        )
+        .arg(
+            Arg::with_name("expansions")
+                .long("expansions")
+                .help("Execute the shell command with $SHELL"),
+        )
+}
+
+fn common<G: Generator>(app: &mut App, name: &str, fixture: &str) {
+    let mut buf = vec![];
+    generate::<G, _>(app, name, &mut buf);
+    let string = String::from_utf8(buf).unwrap();
+
+    assert_eq!(&string, fixture);
+}
+
+#[test]
+fn bash() {
+    let mut app = build_app();
+    common::<Bash>(&mut app, "myapp", BASH);
+}
+
+#[test]
+fn zsh() {
+    let mut app = build_app();
+    common::<Zsh>(&mut app, "myapp", ZSH);
+}
+
+#[test]
+fn fish() {
+    let mut app = build_app();
+    common::<Fish>(&mut app, "myapp", FISH);
+}
+
+#[test]
+fn powershell() {
+    let mut app = build_app();
+    common::<PowerShell>(&mut app, "my_app", POWERSHELL);
+}
+
+#[test]
+fn elvish() {
+    let mut app = build_app();
+    common::<Elvish>(&mut app, "my_app", ELVISH);
+}
+
+#[test]
+fn elvish_with_special_commands() {
+    let mut app = build_app_special_commands();
+    common::<Elvish>(&mut app, "my_app", ELVISH_SPECIAL_CMDS);
+}
+
+#[test]
+fn powershell_with_special_commands() {
+    let mut app = build_app_special_commands();
+    common::<PowerShell>(&mut app, "my_app", POWERSHELL_SPECIAL_CMDS);
+}
+
+#[test]
+fn bash_with_special_commands() {
+    let mut app = build_app_special_commands();
+    common::<Bash>(&mut app, "my_app", BASH_SPECIAL_CMDS);
+}
+
+#[test]
+fn fish_with_special_commands() {
+    let mut app = build_app_special_commands();
+    common::<Fish>(&mut app, "my_app", FISH_SPECIAL_CMDS);
+}
+
+#[test]
+fn zsh_with_special_commands() {
+    let mut app = build_app_special_commands();
+    common::<Zsh>(&mut app, "my_app", ZSH_SPECIAL_CMDS);
+}
+
+#[test]
+fn fish_with_special_help() {
+    let mut app = build_app_special_help();
+    common::<Fish>(&mut app, "my_app", FISH_SPECIAL_HELP);
+}
+
+#[test]
+fn zsh_with_special_help() {
+    let mut app = build_app_special_help();
+    common::<Zsh>(&mut app, "my_app", ZSH_SPECIAL_HELP);
+}
diff --git a/clap_generate/tests/version-numbers.rs b/clap_generate/tests/version-numbers.rs
new file mode 100644
index 00000000..1b8189b7
--- /dev/null
+++ b/clap_generate/tests/version-numbers.rs
@@ -0,0 +1,6 @@
+use version_sync::assert_html_root_url_updated;
+
+#[test]
+fn test_html_root_url() {
+    assert_html_root_url_updated!("src/lib.rs");
+}