Skip to content
This repository has been archived by the owner on Oct 25, 2021. It is now read-only.

Add plugin subcommand to generate shell completions #173

Merged
merged 5 commits into from
Sep 13, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions src/cmd/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ use super::ls;
use super::check;
use super::fmt as fmtcmd;
use super::update;
use super::server;
#[cfg(feature = "beta")]
use super::plugin;

pub fn get_matches<'a, I, T>(args: I) -> ClapResult<ArgMatches<'a>>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + clone::Clone,
{
pub fn art_app<'a, 'b>() -> App<'a, 'b> {
let app = App::new("artifact")
.version(env!("CARGO_PKG_VERSION"))
.about(
Expand Down Expand Up @@ -67,20 +66,28 @@ where
.subcommand(fmtcmd::get_subcommand())
.subcommand(export::get_subcommand())
.subcommand(update::get_subcommand())
.subcommand(::cmd::server::get_subcommand());
.subcommand(server::get_subcommand());

add_beta_cmds(app)
}

let app = add_beta_cmds(app);
pub fn get_matches<'a, I, T>(args: I) -> ClapResult<ArgMatches<'a>>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + clone::Clone,
{
let app = art_app();
app.get_matches_from_safe(args)
}

#[cfg(feature = "beta")]
/// add any beta cmdline features here
pub fn add_beta_cmds<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
app
fn add_beta_cmds<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
app.subcommand(plugin::get_subcommand())
}

#[cfg(not(feature = "beta"))]
/// add any beta cmdline features in the `[#cfg(feature = "beta")]` function
pub fn add_beta_cmds<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
fn add_beta_cmds<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
app
}
28 changes: 24 additions & 4 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ mod fmt;
mod init;
mod tutorial;
mod update;
#[cfg(feature = "beta")]
mod plugin;

mod server;

Expand All @@ -62,13 +64,25 @@ pub fn get_loglevel(matches: &ArgMatches) -> Option<(u8, bool)> {

#[cfg(feature = "beta")]
/// run beta commands here
fn run_beta(project: &Project, matches: &ArgMatches) -> Result<u8> {
Err(ErrorKind::NothingDone.into())
fn run_beta<W>(_project: Option<&Project>, matches: &ArgMatches, w: &mut W) -> Result<u8>
where
W: io::Write,
{
if let Some(mat) = matches.subcommand_matches("plugin") {
info!("Calling plugin command");
let c = plugin::get_cmd(mat)?;
plugin::run_cmd(&c, w)
} else {
Err(ErrorKind::NothingDone.into())
}
}

#[cfg(not(feature = "beta"))]
/// run beta commands in the `[#cfg(feature = "beta")]` function
fn run_beta(_: &Project, _: &ArgMatches) -> Result<u8> {
fn run_beta<W>(_: Option<&Project>, _: &ArgMatches, _: &mut W) -> Result<u8>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, Project does have to be an option... doesn't it!

where
W: io::Write,
{
Err(ErrorKind::NothingDone.into())
}

Expand Down Expand Up @@ -130,6 +144,12 @@ where
return Ok(0);
}

// If plugin is selected, run it.
// NB: plugin is a BETA command
if let Some(_) = matches.subcommand_matches("plugin") {
return run_beta(None, &matches, w);
}

// load the artifacts
let repo = match utils::find_repo(&work_tree) {
Ok(r) => r,
Expand Down Expand Up @@ -169,7 +189,7 @@ where
let addr = server::get_cmd(mat);
server::run_cmd(project.clone(), &addr);
Ok(0)
} else if match run_beta(&project, &matches) {
} else if match run_beta(Some(&project), &matches, w) {
Ok(r) => return Ok(r),
Err(err) => match *err.kind() {
ErrorKind::NothingDone => false,
Expand Down
138 changes: 138 additions & 0 deletions src/cmd/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* artifact: the requirements tracking tool made for developers
* Copyright (C) 2017 Garrett Berg <@vitiral, vitiral@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Lesser GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* */

use clap::{App, Shell};
use std::fs;
use dev_prefix::*;
use types::*;
use cmd::types::*;
use cmd::matches::art_app;

pub fn get_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("plugin")
.settings(&SUBCMD_SETTINGS)
.about(
"Command for integrating with external plugins, currently only \
supports generating shell completions",
)
.arg(
Arg::with_name("name")
.required(true)
.possible_values(&[
"bash-completions",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: make these constants. I can tackle if you are busy.

static BASH_COMPLETIONS: &str = "bash-completions";
... etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want const not static, with const the match expression can be

    BASH_COMPLETIONS => Plugin::Comp(Shell::Bash),
    FISH_COMPLETIONS => Plugin::Comp(Shell::Fish),
    ZSH_COMPLETIONS => Plugin::Comp(Shell::Zsh),
    PS_COMPLETIONS => Plugin::Comp(Shell::PowerShell),
    ...

as the const effectively inlines the constant. Otherwise we would need to do something like

    x if x == BASH_COMPLETIONS => Plugin::Comp(Shell::Bash),
    x if x == FISH_COMPLETIONS => Plugin::Comp(Shell::Fish),
    x if x == ZSH_COMPLETIONS => Plugin::Comp(Shell::Zsh),
    x if x == PS_COMPLETIONS => Plugin::Comp(Shell::PowerShell),
    ...

I'm not certain what this becomes, but it seems less elegant for serving the same purpose.

"fish-completions",
"zsh-completions",
"ps-completions",
])
.help("Plugin name"),
)
.arg(
Arg::with_name("output")
.long("output")
.short("o")
.value_name("PATH")
.help(
"Path of file to place the output of the plugin command \
(defaults to standard output)",
),
)
}

#[derive(Debug)]
enum Plugin {
Comp(Shell),
}

#[derive(Debug)]
pub struct Cmd<'a> {
plugin: Plugin,
output: Option<&'a Path>,
}

pub fn get_cmd<'a>(matches: &'a ArgMatches) -> Result<Cmd<'a>> {
let plugin = match matches
.value_of("name")
.expect("clap error in argument parsing!")
{
"bash-completions" => Plugin::Comp(Shell::Bash),
"fish-completions" => Plugin::Comp(Shell::Fish),
"zsh-completions" => Plugin::Comp(Shell::Zsh),
"ps-completions" => Plugin::Comp(Shell::PowerShell),
_ => panic!("clap error in argument parsing!"),
};
let out = matches.value_of("output").map(|p| Path::new(p).as_ref());
Ok(Cmd {
plugin: plugin,
output: out,
})
}

pub fn run_cmd<W: io::Write>(cmd: &Cmd, w: W) -> Result<u8> {
match cmd.plugin {
Plugin::Comp(_) => if let Some(path) = cmd.output {
let md = fs::metadata(path);
if md.ok().map_or(false, |md| md.is_dir()) {
run_cmd_completions(cmd, w, true)
} else {
// we can't see it as a directory, try to create it as
// a file.
let file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
run_cmd_completions(cmd, file, false)
}
} else {
run_cmd_completions(cmd, w, false)
},
}
}

fn run_cmd_completions<W: io::Write>(cmd: &Cmd, mut w: W, is_dir: bool) -> Result<u8> {
// If use_gen_to is true then we use gen_completions_to as our output
// function, otherwise we use gen_completions. This is only relevant
// for completion generating options.
match cmd.plugin {
Plugin::Comp(shell) => {
let (sh, name) = match shell {
Shell::Bash => ("bash", "art.bash-completion"),
Shell::Fish => ("fish", "art.fish"),
Shell::Zsh => ("zsh", "_art"),
Shell::PowerShell => ("PowerShell", "_art.ps1"),
};
if !is_dir {
info!(
"Generating {} completions to {}",
sh,
cmd.output.unwrap_or("standard output".as_ref()).display(),
);
art_app().gen_completions_to("art", shell, &mut w)
} else {
// unwrap is safe as cmd.output had to be Some for this branch to
// be taken
info!(
"Generating {} completions to {}",
sh,
cmd.output.unwrap().join(name).display(),
);
art_app().gen_completions("art", shell, cmd.output.unwrap())
}
Ok(0)
}
}
}