Add command to generate completions
This commit is contained in:
parent
d55b3e8ef4
commit
2fe8e24fb1
15 changed files with 278 additions and 7 deletions
|
@ -79,7 +79,7 @@ no-color = ["colored/no-color"]
|
|||
[dependencies]
|
||||
chbs = "0.0.8"
|
||||
chrono = "0.4"
|
||||
clap = "2.31"
|
||||
clap = "2.32"
|
||||
colored = "1.7"
|
||||
derive_builder = "0.7"
|
||||
directories = "1.0"
|
||||
|
|
|
@ -539,7 +539,7 @@ documentation [here][send-encryption].
|
|||
$ ffsend help
|
||||
|
||||
ffsend 0.2.36
|
||||
Tim Visee <timvisee.com>
|
||||
Tim Visee <3a4fb3964f@sinenomine.email>
|
||||
Easily and securely share files from the command line.
|
||||
A fully featured Firefox Send client.
|
||||
|
||||
|
@ -557,6 +557,7 @@ FLAGS:
|
|||
-y, --yes Assume yes for prompts
|
||||
|
||||
OPTIONS:
|
||||
-A, --api <VERSION> Server API version to use, '-' to lookup [env: FFSEND_API]
|
||||
-H, --history <FILE> Use the specified history file [env: FFSEND_HISTORY]
|
||||
-t, --timeout <SECONDS> Request timeout (0 to disable) [env: FFSEND_TIMEOUT]
|
||||
-T, --transfer-timeout <SECONDS> Transfer timeout (0 to disable) [env: FFSEND_TRANSFER_TIMEOUT]
|
||||
|
@ -567,6 +568,7 @@ SUBCOMMANDS:
|
|||
debug View debug information [aliases: dbg]
|
||||
delete Delete a shared file [aliases: del]
|
||||
exists Check whether a remote file exists [aliases: e]
|
||||
generate Generate assets [aliases: gen]
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
history View file history [aliases: h]
|
||||
info Fetch info about a shared file [aliases: i]
|
||||
|
|
61
src/action/generate/completions.rs
Normal file
61
src/action/generate/completions.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use std::fs;
|
||||
use std::io;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::cmd::matcher::{generate::completions::CompletionsMatcher, main::MainMatcher, Matcher};
|
||||
use crate::error::ActionError;
|
||||
|
||||
/// A file completions action.
|
||||
pub struct Completions<'a> {
|
||||
cmd_matches: &'a ArgMatches<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Completions<'a> {
|
||||
/// Construct a new completions action.
|
||||
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
|
||||
Self { cmd_matches }
|
||||
}
|
||||
|
||||
/// Invoke the completions action.
|
||||
// TODO: create a trait for this method
|
||||
pub fn invoke(&self) -> Result<(), ActionError> {
|
||||
// Create the command matchers
|
||||
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
|
||||
let matcher_completions = CompletionsMatcher::with(self.cmd_matches).unwrap();
|
||||
|
||||
// Obtian shells to generate completions for, build application definition
|
||||
let shells = matcher_completions.shells();
|
||||
let dir = matcher_completions.output();
|
||||
let verbose = matcher_main.verbose();
|
||||
let mut app = crate::cmd::handler::Handler::build();
|
||||
|
||||
// If the directory does not exist yet, attempt to create it
|
||||
if !dir.is_dir() {
|
||||
fs::create_dir_all(&dir).map_err(Error::CreateOutputDir)?;
|
||||
}
|
||||
|
||||
// Generate completions
|
||||
for shell in shells {
|
||||
if verbose {
|
||||
print!(
|
||||
"Generating completions for {}...",
|
||||
format!("{}", shell).to_lowercase()
|
||||
);
|
||||
}
|
||||
app.gen_completions(crate_name!(), shell, &dir);
|
||||
if verbose {
|
||||
println!(" done.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
/// An error occurred while creating the output directory.
|
||||
#[fail(display = "failed to create output directory, it doesn't exist")]
|
||||
CreateOutputDir(#[cause] io::Error),
|
||||
}
|
34
src/action/generate/mod.rs
Normal file
34
src/action/generate/mod.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
pub mod completions;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::cmd::matcher::{generate::GenerateMatcher, Matcher};
|
||||
use crate::error::ActionError;
|
||||
use completions::Completions;
|
||||
|
||||
/// A file generate action.
|
||||
pub struct Generate<'a> {
|
||||
cmd_matches: &'a ArgMatches<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Generate<'a> {
|
||||
/// Construct a new generate action.
|
||||
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
|
||||
Self { cmd_matches }
|
||||
}
|
||||
|
||||
/// Invoke the generate action.
|
||||
// TODO: create a trait for this method
|
||||
pub fn invoke(&self) -> Result<(), ActionError> {
|
||||
// Create the command matcher
|
||||
let matcher_generate = GenerateMatcher::with(self.cmd_matches).unwrap();
|
||||
|
||||
// Match shell completions
|
||||
if matcher_generate.matcher_completions().is_some() {
|
||||
return Completions::new(self.cmd_matches).invoke();
|
||||
}
|
||||
|
||||
// Unreachable, clap will print help for missing sub command instead
|
||||
unreachable!()
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ pub mod debug;
|
|||
pub mod delete;
|
||||
pub mod download;
|
||||
pub mod exists;
|
||||
pub mod generate;
|
||||
#[cfg(feature = "history")]
|
||||
pub mod history;
|
||||
pub mod info;
|
||||
|
|
|
@ -9,14 +9,14 @@ use super::arg::{ArgApi, CmdArg};
|
|||
#[cfg(feature = "history")]
|
||||
use super::matcher::HistoryMatcher;
|
||||
use super::matcher::{
|
||||
DebugMatcher, DeleteMatcher, DownloadMatcher, ExistsMatcher, InfoMatcher, Matcher,
|
||||
ParamsMatcher, PasswordMatcher, UploadMatcher, VersionMatcher,
|
||||
DebugMatcher, DeleteMatcher, DownloadMatcher, ExistsMatcher, GenerateMatcher, InfoMatcher,
|
||||
Matcher, ParamsMatcher, PasswordMatcher, UploadMatcher, VersionMatcher,
|
||||
};
|
||||
#[cfg(feature = "history")]
|
||||
use super::subcmd::CmdHistory;
|
||||
use super::subcmd::{
|
||||
CmdDebug, CmdDelete, CmdDownload, CmdExists, CmdInfo, CmdParams, CmdPassword, CmdUpload,
|
||||
CmdVersion,
|
||||
CmdDebug, CmdDelete, CmdDownload, CmdExists, CmdGenerate, CmdInfo, CmdParams, CmdPassword,
|
||||
CmdUpload, CmdVersion,
|
||||
};
|
||||
#[cfg(feature = "infer-command")]
|
||||
use crate::config::INFER_COMMANDS;
|
||||
|
@ -153,6 +153,7 @@ impl<'a: 'b, 'b> Handler<'a> {
|
|||
.subcommand(CmdDelete::build())
|
||||
.subcommand(CmdDownload::build().display_order(2))
|
||||
.subcommand(CmdExists::build())
|
||||
.subcommand(CmdGenerate::build())
|
||||
.subcommand(CmdInfo::build())
|
||||
.subcommand(CmdParams::build())
|
||||
.subcommand(CmdPassword::build())
|
||||
|
@ -257,6 +258,11 @@ impl<'a: 'b, 'b> Handler<'a> {
|
|||
ExistsMatcher::with(&self.matches)
|
||||
}
|
||||
|
||||
/// Get the generate sub command, if matched.
|
||||
pub fn generate(&'a self) -> Option<GenerateMatcher> {
|
||||
GenerateMatcher::with(&self.matches)
|
||||
}
|
||||
|
||||
/// Get the history sub command, if matched.
|
||||
#[cfg(feature = "history")]
|
||||
pub fn history(&'a self) -> Option<HistoryMatcher> {
|
||||
|
|
64
src/cmd/matcher/generate/completions.rs
Normal file
64
src/cmd/matcher/generate/completions.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::{ArgMatches, Shell};
|
||||
|
||||
use super::Matcher;
|
||||
|
||||
/// The completions completions command matcher.
|
||||
pub struct CompletionsMatcher<'a> {
|
||||
matches: &'a ArgMatches<'a>,
|
||||
}
|
||||
|
||||
impl<'a: 'b, 'b> CompletionsMatcher<'a> {
|
||||
/// Get the shells to generate completions for.
|
||||
pub fn shells(&'a self) -> Vec<Shell> {
|
||||
// Get the raw list of shells
|
||||
let raw = self
|
||||
.matches
|
||||
.values_of("SHELL")
|
||||
.expect("no shells were given");
|
||||
|
||||
// Parse the list of shell names, deduplicate
|
||||
let mut shells: Vec<_> = raw
|
||||
.into_iter()
|
||||
.map(|name| name.trim().to_lowercase())
|
||||
.map(|name| {
|
||||
if name == "all" {
|
||||
Shell::variants()
|
||||
.iter()
|
||||
.map(|name| name.to_string())
|
||||
.collect()
|
||||
} else {
|
||||
vec![name]
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
shells.sort_unstable();
|
||||
shells.dedup();
|
||||
|
||||
// Parse the shell names
|
||||
shells
|
||||
.into_iter()
|
||||
.map(|name| Shell::from_str(&name).expect("failed to parse shell name"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The target directory to output the shell completion files to.
|
||||
pub fn output(&'a self) -> PathBuf {
|
||||
self.matches
|
||||
.value_of("output")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("./"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Matcher<'a> for CompletionsMatcher<'a> {
|
||||
fn with(matches: &'a ArgMatches) -> Option<Self> {
|
||||
matches
|
||||
.subcommand_matches("generate")?
|
||||
.subcommand_matches("completions")
|
||||
.map(|matches| CompletionsMatcher { matches })
|
||||
}
|
||||
}
|
29
src/cmd/matcher/generate/mod.rs
Normal file
29
src/cmd/matcher/generate/mod.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
pub mod completions;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use super::Matcher;
|
||||
use completions::CompletionsMatcher;
|
||||
|
||||
/// The generate command matcher.
|
||||
pub struct GenerateMatcher<'a> {
|
||||
root: &'a ArgMatches<'a>,
|
||||
_matches: &'a ArgMatches<'a>,
|
||||
}
|
||||
|
||||
impl<'a: 'b, 'b> GenerateMatcher<'a> {
|
||||
/// Get the generate completions sub command, if matched.
|
||||
pub fn matcher_completions(&'a self) -> Option<CompletionsMatcher> {
|
||||
CompletionsMatcher::with(&self.root)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Matcher<'a> for GenerateMatcher<'a> {
|
||||
fn with(root: &'a ArgMatches) -> Option<Self> {
|
||||
root.subcommand_matches("generate")
|
||||
.map(|matches| GenerateMatcher {
|
||||
root,
|
||||
_matches: matches,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ pub mod debug;
|
|||
pub mod delete;
|
||||
pub mod download;
|
||||
pub mod exists;
|
||||
pub mod generate;
|
||||
#[cfg(feature = "history")]
|
||||
pub mod history;
|
||||
pub mod info;
|
||||
|
@ -16,6 +17,7 @@ pub use self::debug::DebugMatcher;
|
|||
pub use self::delete::DeleteMatcher;
|
||||
pub use self::download::DownloadMatcher;
|
||||
pub use self::exists::ExistsMatcher;
|
||||
pub use self::generate::GenerateMatcher;
|
||||
#[cfg(feature = "history")]
|
||||
pub use self::history::HistoryMatcher;
|
||||
pub use self::info::InfoMatcher;
|
||||
|
|
|
@ -23,7 +23,7 @@ impl CmdDownload {
|
|||
.alias("out")
|
||||
.alias("file")
|
||||
.value_name("PATH")
|
||||
.help("The output file or directory"),
|
||||
.help("Output file or directory"),
|
||||
);
|
||||
|
||||
// Optional archive support
|
||||
|
|
33
src/cmd/subcmd/generate/completions.rs
Normal file
33
src/cmd/subcmd/generate/completions.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use clap::{App, Arg, Shell, SubCommand};
|
||||
|
||||
/// The generate completions command definition.
|
||||
pub struct CmdCompletions;
|
||||
|
||||
impl CmdCompletions {
|
||||
pub fn build<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("completions")
|
||||
.about("Shell completions")
|
||||
.alias("completion")
|
||||
.alias("complete")
|
||||
.arg(
|
||||
Arg::with_name("SHELL")
|
||||
.help("Shell type to generate completions for")
|
||||
.required(true)
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.possible_value("all")
|
||||
.possible_values(&Shell::variants())
|
||||
.case_insensitive(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output")
|
||||
.long("output")
|
||||
.short("o")
|
||||
.alias("output-dir")
|
||||
.alias("out")
|
||||
.alias("dir")
|
||||
.value_name("DIR")
|
||||
.help("Shell completion files output directory"),
|
||||
)
|
||||
}
|
||||
}
|
18
src/cmd/subcmd/generate/mod.rs
Normal file
18
src/cmd/subcmd/generate/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
pub mod completions;
|
||||
|
||||
use clap::{App, AppSettings, SubCommand};
|
||||
|
||||
use completions::CmdCompletions;
|
||||
|
||||
/// The generate command definition.
|
||||
pub struct CmdGenerate;
|
||||
|
||||
impl CmdGenerate {
|
||||
pub fn build<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("generate")
|
||||
.about("Generate assets")
|
||||
.visible_alias("gen")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(CmdCompletions::build())
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ pub mod debug;
|
|||
pub mod delete;
|
||||
pub mod download;
|
||||
pub mod exists;
|
||||
pub mod generate;
|
||||
#[cfg(feature = "history")]
|
||||
pub mod history;
|
||||
pub mod info;
|
||||
|
@ -15,6 +16,7 @@ pub use self::debug::CmdDebug;
|
|||
pub use self::delete::CmdDelete;
|
||||
pub use self::download::CmdDownload;
|
||||
pub use self::exists::CmdExists;
|
||||
pub use self::generate::CmdGenerate;
|
||||
#[cfg(feature = "history")]
|
||||
pub use self::history::CmdHistory;
|
||||
pub use self::info::CmdInfo;
|
||||
|
|
11
src/error.rs
11
src/error.rs
|
@ -6,6 +6,7 @@ use ffsend_api::action::version::Error as VersionError;
|
|||
use ffsend_api::file::remote_file::FileParseError;
|
||||
|
||||
use crate::action::download::Error as CliDownloadError;
|
||||
use crate::action::generate::completions::Error as CliGenerateCompletionsError;
|
||||
#[cfg(feature = "history")]
|
||||
use crate::action::history::Error as CliHistoryError;
|
||||
use crate::action::info::Error as CliInfoError;
|
||||
|
@ -56,6 +57,10 @@ pub enum ActionError {
|
|||
#[fail(display = "failed to check whether the file exists")]
|
||||
Exists(#[cause] ExistsError),
|
||||
|
||||
/// An error occurred while generating completions.
|
||||
#[fail(display = "failed to generate shell completions")]
|
||||
GenerateCompletions(#[cause] CliGenerateCompletionsError),
|
||||
|
||||
/// An error occurred while processing the file history.
|
||||
#[cfg(feature = "history")]
|
||||
#[fail(display = "failed to process the history")]
|
||||
|
@ -99,6 +104,12 @@ impl From<ExistsError> for ActionError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<CliGenerateCompletionsError> for ActionError {
|
||||
fn from(err: CliGenerateCompletionsError) -> ActionError {
|
||||
ActionError::GenerateCompletions(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "history")]
|
||||
impl From<CliHistoryError> for ActionError {
|
||||
fn from(err: CliHistoryError) -> ActionError {
|
||||
|
|
|
@ -31,6 +31,7 @@ use crate::action::debug::Debug;
|
|||
use crate::action::delete::Delete;
|
||||
use crate::action::download::Download;
|
||||
use crate::action::exists::Exists;
|
||||
use crate::action::generate::Generate;
|
||||
#[cfg(feature = "history")]
|
||||
use crate::action::history::History;
|
||||
use crate::action::info::Info;
|
||||
|
@ -92,6 +93,13 @@ fn invoke_action(handler: &Handler) -> Result<(), Error> {
|
|||
.map_err(|err| err.into());
|
||||
}
|
||||
|
||||
// Match the generate command
|
||||
if handler.generate().is_some() {
|
||||
return Generate::new(handler.matches())
|
||||
.invoke()
|
||||
.map_err(|err| err.into());
|
||||
}
|
||||
|
||||
// Match the history command
|
||||
#[cfg(feature = "history")]
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue