chore: upgrade clap to v4 (#146)

This commit is contained in:
sigoden 2022-11-11 21:46:07 +08:00 committed by GitHub
parent 6ebf619430
commit 1329e42b9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 482 additions and 249 deletions

571
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,8 +11,8 @@ categories = ["command-line-utilities", "web-programming::http-server"]
keywords = ["static", "file", "server", "webdav", "cli"]
[dependencies]
clap = { version = "3", default-features = false, features = ["std", "wrap_help"] }
clap_complete = "3"
clap = { version = "4", features = ["wrap_help"] }
clap_complete = "4"
chrono = "0.4"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util", "signal"]}
tokio-util = { version = "0.7", features = ["io-util"] }

View file

@ -42,35 +42,34 @@ Download from [Github Releases](https://github.com/sigoden/dufs/releases), unzip
```
Dufs is a distinctive utility file server - https://github.com/sigoden/dufs
USAGE:
dufs [OPTIONS] [--] [root]
Usage: dufs [OPTIONS] [root]
ARGS:
<root> Specific path to serve [default: .]
Arguments:
[root] Specific path to serve [default: .]
OPTIONS:
-b, --bind <addr>... Specify bind address or unix socket
-p, --port <port> Specify port to listen on [default: 5000]
--path-prefix <path> Specify a path prefix
--hidden <value> Hide paths from directory listings, separated by `,`
-a, --auth <rule>... Add auth for path
--auth-method <value> Select auth method [default: digest] [possible values: basic, digest]
-A, --allow-all Allow all operations
--allow-upload Allow upload files/folders
--allow-delete Allow delete files/folders
--allow-search Allow search files/folders
--allow-symlink Allow symlink to files/folders outside root directory
--enable-cors Enable CORS, sets `Access-Control-Allow-Origin: *`
--render-index Serve index.html when requesting a directory, returns 404 if not found index.html
--render-try-index Serve index.html when requesting a directory, returns directory listing if not found index.html
--render-spa Serve SPA(Single Page Application)
--assets <path> Use custom assets to override builtin assets
--tls-cert <path> Path to an SSL/TLS certificate to serve with HTTPS
--tls-key <path> Path to the SSL/TLS certificate's private key
--log-format <format> Customize http log format
--completions <shell> Print shell completion script for <shell> [possible values: bash, elvish, fish, powershell, zsh]
-h, --help Print help information
-V, --version Print version information
Options:
-b, --bind <addrs> Specify bind address or unix socket
-p, --port <port> Specify port to listen on [default: 5000]
--path-prefix <path> Specify a path prefix
--hidden <value> Hide paths from directory listings, separated by `,`
-a, --auth <rules> Add auth for path
--auth-method <value> Select auth method [default: digest] [possible values: basic, digest]
-A, --allow-all Allow all operations
--allow-upload Allow upload files/folders
--allow-delete Allow delete files/folders
--allow-search Allow search files/folders
--allow-symlink Allow symlink to files/folders outside root directory
--enable-cors Enable CORS, sets `Access-Control-Allow-Origin: *`
--render-index Serve index.html when requesting a directory, returns 404 if not found index.html
--render-try-index Serve index.html when requesting a directory, returns directory listing if not found index.html
--render-spa Serve SPA(Single Page Application)
--assets <path> Use custom assets to override builtin assets
--tls-cert <path> Path to an SSL/TLS certificate to serve with HTTPS
--tls-key <path> Path to the SSL/TLS certificate's private key
--log-format <format> Customize http log format
--completions <shell> Print shell completion script for <shell> [possible values: bash, elvish, fish, powershell, zsh]
-h, --help Print help information
-V, --version Print version information
```
## Examples

View file

@ -1,4 +1,5 @@
use clap::{value_parser, AppSettings, Arg, ArgAction, ArgMatches, Command};
use clap::builder::PossibleValuesParser;
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use clap_complete::{generate, Generator, Shell};
#[cfg(feature = "tls")]
use rustls::{Certificate, PrivateKey};
@ -14,7 +15,7 @@ use crate::tls::{load_certs, load_private_key};
use crate::utils::encode_uri;
use crate::BoxResult;
pub fn build_cli() -> Command<'static> {
pub fn build_cli() -> Command {
let app = Command::new(env!("CARGO_CRATE_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
@ -23,31 +24,30 @@ pub fn build_cli() -> Command<'static> {
" - ",
env!("CARGO_PKG_REPOSITORY")
))
.global_setting(AppSettings::DeriveDisplayOrder)
.arg(
Arg::new("root")
.default_value(".")
.value_parser(value_parser!(PathBuf))
.help("Specific path to serve"),
)
.arg(
Arg::new("bind")
.short('b')
.long("bind")
.help("Specify bind address or unix socket")
.multiple_values(true)
.value_delimiter(',')
.action(ArgAction::Append)
.value_name("addr"),
.value_delimiter(',')
.value_name("addrs"),
)
.arg(
Arg::new("port")
.short('p')
.long("port")
.default_value("5000")
.value_parser(value_parser!(u16))
.help("Specify port to listen on")
.value_name("port"),
)
.arg(
Arg::new("root")
.default_value(".")
.allow_invalid_utf8(true)
.help("Specific path to serve"),
)
.arg(
Arg::new("path-prefix")
.long("path-prefix")
@ -66,15 +66,14 @@ pub fn build_cli() -> Command<'static> {
.long("auth")
.help("Add auth for path")
.action(ArgAction::Append)
.multiple_values(true)
.value_delimiter(',')
.value_name("rule"),
.value_name("rules"),
)
.arg(
Arg::new("auth-method")
.long("auth-method")
.help("Select auth method")
.possible_values(["basic", "digest"])
.value_parser(PossibleValuesParser::new(["basic", "digest"]))
.default_value("digest")
.value_name("value"),
)
@ -82,53 +81,62 @@ pub fn build_cli() -> Command<'static> {
Arg::new("allow-all")
.short('A')
.long("allow-all")
.action(ArgAction::SetTrue)
.help("Allow all operations"),
)
.arg(
Arg::new("allow-upload")
.long("allow-upload")
.action(ArgAction::SetTrue)
.help("Allow upload files/folders"),
)
.arg(
Arg::new("allow-delete")
.long("allow-delete")
.action(ArgAction::SetTrue)
.help("Allow delete files/folders"),
)
.arg(
Arg::new("allow-search")
.long("allow-search")
.action(ArgAction::SetTrue)
.help("Allow search files/folders"),
)
.arg(
Arg::new("allow-symlink")
.long("allow-symlink")
.action(ArgAction::SetTrue)
.help("Allow symlink to files/folders outside root directory"),
)
.arg(
Arg::new("enable-cors")
.long("enable-cors")
.action(ArgAction::SetTrue)
.help("Enable CORS, sets `Access-Control-Allow-Origin: *`"),
)
.arg(
Arg::new("render-index")
.long("render-index")
.action(ArgAction::SetTrue)
.help("Serve index.html when requesting a directory, returns 404 if not found index.html"),
)
.arg(
Arg::new("render-try-index")
.long("render-try-index")
.action(ArgAction::SetTrue)
.help("Serve index.html when requesting a directory, returns directory listing if not found index.html"),
)
.arg(
Arg::new("render-spa")
.long("render-spa")
.action(ArgAction::SetTrue)
.help("Serve SPA(Single Page Application)"),
)
.arg(
Arg::new("assets")
.long("assets")
.help("Use custom assets to override builtin assets")
.allow_invalid_utf8(true)
.value_parser(value_parser!(PathBuf))
.value_name("path")
);
@ -138,12 +146,14 @@ pub fn build_cli() -> Command<'static> {
Arg::new("tls-cert")
.long("tls-cert")
.value_name("path")
.value_parser(value_parser!(PathBuf))
.help("Path to an SSL/TLS certificate to serve with HTTPS"),
)
.arg(
Arg::new("tls-key")
.long("tls-key")
.value_name("path")
.value_parser(value_parser!(PathBuf))
.help("Path to the SSL/TLS certificate's private key"),
);
@ -199,16 +209,16 @@ impl Args {
/// If a parsing error ocurred, exit the process and print out informative
/// error message to user.
pub fn parse(matches: ArgMatches) -> BoxResult<Args> {
let port = matches.value_of_t::<u16>("port")?;
let port = *matches.get_one::<u16>("port").unwrap();
let addrs = matches
.values_of("bind")
.map(|v| v.collect())
.get_many::<String>("bind")
.map(|bind| bind.map(|v| v.as_str()).collect())
.unwrap_or_else(|| vec!["0.0.0.0", "::"]);
let addrs: Vec<BindAddr> = Args::parse_addrs(&addrs)?;
let path = Args::parse_path(matches.value_of_os("root").unwrap_or_default())?;
let path = Args::parse_path(matches.get_one::<PathBuf>("root").unwrap())?;
let path_is_file = path.metadata()?.is_file();
let path_prefix = matches
.value_of("path-prefix")
.get_one::<String>("path-prefix")
.map(|v| v.trim_matches('/').to_owned())
.unwrap_or_default();
let uri_prefix = if path_prefix.is_empty() {
@ -217,28 +227,31 @@ impl Args {
format!("/{}/", &encode_uri(&path_prefix))
};
let hidden: Vec<String> = matches
.value_of("hidden")
.get_one::<String>("hidden")
.map(|v| v.split(',').map(|x| x.to_string()).collect())
.unwrap_or_default();
let enable_cors = matches.is_present("enable-cors");
let enable_cors = matches.get_flag("enable-cors");
let auth: Vec<&str> = matches
.values_of("auth")
.map(|v| v.collect())
.get_many::<String>("auth")
.map(|auth| auth.map(|v| v.as_str()).collect())
.unwrap_or_default();
let auth_method = match matches.value_of("auth-method").unwrap() {
let auth_method = match matches.get_one::<String>("auth-method").unwrap().as_str() {
"basic" => AuthMethod::Basic,
_ => AuthMethod::Digest,
};
let auth = AccessControl::new(&auth, &uri_prefix)?;
let allow_upload = matches.is_present("allow-all") || matches.is_present("allow-upload");
let allow_delete = matches.is_present("allow-all") || matches.is_present("allow-delete");
let allow_search = matches.is_present("allow-all") || matches.is_present("allow-search");
let allow_symlink = matches.is_present("allow-all") || matches.is_present("allow-symlink");
let render_index = matches.is_present("render-index");
let render_try_index = matches.is_present("render-try-index");
let render_spa = matches.is_present("render-spa");
let allow_upload = matches.get_flag("allow-all") || matches.get_flag("allow-upload");
let allow_delete = matches.get_flag("allow-all") || matches.get_flag("allow-delete");
let allow_search = matches.get_flag("allow-all") || matches.get_flag("allow-search");
let allow_symlink = matches.get_flag("allow-all") || matches.get_flag("allow-symlink");
let render_index = matches.get_flag("render-index");
let render_try_index = matches.get_flag("render-try-index");
let render_spa = matches.get_flag("render-spa");
#[cfg(feature = "tls")]
let tls = match (matches.value_of("tls-cert"), matches.value_of("tls-key")) {
let tls = match (
matches.get_one::<PathBuf>("tls-cert"),
matches.get_one::<PathBuf>("tls-key"),
) {
(Some(certs_file), Some(key_file)) => {
let certs = load_certs(certs_file)?;
let key = load_private_key(key_file)?;
@ -249,10 +262,11 @@ impl Args {
#[cfg(not(feature = "tls"))]
let tls = None;
let log_http: LogHttp = matches
.value_of("log-format")
.get_one::<String>("log-format")
.map(|v| v.as_str())
.unwrap_or(DEFAULT_LOG_FORMAT)
.parse()?;
let assets_path = match matches.value_of_os("assets") {
let assets_path = match matches.get_one::<PathBuf>("assets") {
Some(v) => Some(Args::parse_assets_path(v)?),
None => None,
};

View file

@ -5,6 +5,7 @@ use hyper::server::conn::{AddrIncoming, AddrStream};
use rustls::{Certificate, PrivateKey};
use std::future::Future;
use std::net::SocketAddr;
use std::path::Path;
use std::pin::Pin;
use std::sync::Arc;
use std::{fs, io};
@ -123,10 +124,12 @@ impl Accept for TlsAcceptor {
}
// Load public certificate from file.
pub fn load_certs(filename: &str) -> Result<Vec<Certificate>, Box<dyn std::error::Error>> {
pub fn load_certs<T: AsRef<Path>>(
filename: T,
) -> Result<Vec<Certificate>, Box<dyn std::error::Error>> {
// Open certificate file.
let cert_file =
fs::File::open(filename).map_err(|e| format!("Failed to access `{}`, {}", &filename, e))?;
let cert_file = fs::File::open(filename.as_ref())
.map_err(|e| format!("Failed to access `{}`, {}", filename.as_ref().display(), e))?;
let mut reader = io::BufReader::new(cert_file);
// Load and return certificate.
@ -138,9 +141,11 @@ pub fn load_certs(filename: &str) -> Result<Vec<Certificate>, Box<dyn std::error
}
// Load private key from file.
pub fn load_private_key(filename: &str) -> Result<PrivateKey, Box<dyn std::error::Error>> {
let key_file =
fs::File::open(filename).map_err(|e| format!("Failed to access `{}`, {}", &filename, e))?;
pub fn load_private_key<T: AsRef<Path>>(
filename: T,
) -> Result<PrivateKey, Box<dyn std::error::Error>> {
let key_file = fs::File::open(filename.as_ref())
.map_err(|e| format!("Failed to access `{}`, {}", filename.as_ref().display(), e))?;
let mut reader = io::BufReader::new(key_file);
// Load and return a single private key.