From 76e967fa59a44417581914a62f72039fd20bd157 Mon Sep 17 00:00:00 2001 From: sigoden Date: Wed, 6 Jul 2022 12:11:00 +0800 Subject: [PATCH] feat: add completions (#97) --- Cargo.lock | 26 ++++++++++++++++++-------- Cargo.toml | 1 + src/args.rs | 16 ++++++++++++---- src/main.rs | 12 ++++++++++-- tests/args.rs | 2 ++ tests/cli.rs | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 tests/cli.rs diff --git a/Cargo.lock b/Cargo.lock index 462486463..1f7a8a4f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,6 +234,15 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap_complete" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1" +dependencies = [ + "clap", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -289,9 +298,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" dependencies = [ "generic-array", "typenum", @@ -379,6 +388,7 @@ dependencies = [ "base64", "chrono", "clap", + "clap_complete", "diqwest", "futures", "headers", @@ -1087,9 +1097,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -1431,9 +1441,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -1448,9 +1458,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" diff --git a/Cargo.toml b/Cargo.toml index f0a735a3a..9f6dfb30f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ keywords = ["static", "file", "server", "webdav", "cli"] [dependencies] clap = { version = "3", default-features = false, features = ["std", "wrap_help"] } +clap_complete = "3" chrono = "0.4" tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util", "signal"]} tokio-util = { version = "0.7", features = ["io-util"] } diff --git a/src/args.rs b/src/args.rs index 11fbfe10b..e9efc6dcd 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,4 +1,5 @@ -use clap::{AppSettings, Arg, ArgMatches, Command}; +use clap::{value_parser, AppSettings, Arg, ArgMatches, Command}; +use clap_complete::{generate, Generator, Shell}; #[cfg(feature = "tls")] use rustls::{Certificate, PrivateKey}; use std::env; @@ -11,7 +12,7 @@ use crate::auth::AuthMethod; use crate::tls::{load_certs, load_private_key}; use crate::BoxResult; -fn app() -> Command<'static> { +pub fn build_cli() -> Command<'static> { let app = Command::new(env!("CARGO_CRATE_NAME")) .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS")) @@ -118,6 +119,13 @@ fn app() -> Command<'static> { Arg::new("render-spa") .long("render-spa") .help("Serve SPA(Single Page Application)"), + ) + .arg( + Arg::new("completions") + .long("completions") + .value_name("shell") + .value_parser(value_parser!(Shell)) + .help("Print shell completion script for "), ); #[cfg(feature = "tls")] @@ -138,8 +146,8 @@ fn app() -> Command<'static> { app } -pub fn matches() -> ArgMatches { - app().get_matches() +pub fn print_completions(gen: G, cmd: &mut Command) { + generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout()); } #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index 9a2ed2496..c3a120b5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ mod utils; #[macro_use] extern crate log; -use crate::args::{matches, Args}; +use crate::args::{build_cli, print_completions, Args}; use crate::server::{Request, Server}; #[cfg(feature = "tls")] use crate::tls::{TlsAcceptor, TlsStream}; @@ -19,6 +19,7 @@ use std::net::{IpAddr, SocketAddr, TcpListener as StdTcpListener}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use clap_complete::Shell; use futures::future::join_all; use tokio::net::TcpListener; use tokio::task::JoinHandle; @@ -37,7 +38,14 @@ async fn main() { async fn run() -> BoxResult<()> { logger::init().map_err(|e| format!("Failed to init logger, {}", e))?; - let args = Args::parse(matches())?; + let cmd = build_cli(); + let matches = cmd.get_matches(); + if let Some(generator) = matches.get_one::("completions") { + let mut cmd = build_cli(); + print_completions(*generator, &mut cmd); + return Ok(()); + } + let args = Args::parse(matches)?; let args = Arc::new(args); let running = Arc::new(AtomicBool::new(true)); let handles = serve(args.clone(), running.clone())?; diff --git a/tests/args.rs b/tests/args.rs index 83086e965..db6180325 100644 --- a/tests/args.rs +++ b/tests/args.rs @@ -1,3 +1,5 @@ +//! Run file server with different args + mod fixtures; mod utils; diff --git a/tests/cli.rs b/tests/cli.rs new file mode 100644 index 000000000..01b02b600 --- /dev/null +++ b/tests/cli.rs @@ -0,0 +1,32 @@ +//! Run cli with different args, not starting a server + +mod fixtures; + +use assert_cmd::prelude::*; +use clap::ValueEnum; +use clap_complete::Shell; +use fixtures::Error; +use std::process::Command; + +#[test] +/// Show help and exit. +fn help_shows() -> Result<(), Error> { + Command::cargo_bin("dufs")?.arg("-h").assert().success(); + + Ok(()) +} + +#[test] +/// Print completions and exit. +fn print_completions() -> Result<(), Error> { + // let shell_enums = EnumValueParser::::new(); + for shell in Shell::value_variants() { + Command::cargo_bin("dufs")? + .arg("--completions") + .arg(shell.to_string()) + .assert() + .success(); + } + + Ok(()) +}