From 87a381e0a7f056c22f0536b6377f38ee4fd10f09 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 4 Jul 2022 23:16:31 +0800 Subject: [PATCH] upgrade clap to 4.0 --- Cargo.lock | 44 ++++++++++------------- Cargo.toml | 4 +-- examples/nop-preprocessor.rs | 12 ++++--- src/book/mod.rs | 1 + src/cmd/build.rs | 30 +++++----------- src/cmd/clean.rs | 26 ++++---------- src/cmd/command_prelude.rs | 45 +++++++++++++++++++++++ src/cmd/init.rs | 44 ++++++++++------------- src/cmd/mod.rs | 1 + src/cmd/serve.rs | 44 +++++++++-------------- src/cmd/test.rs | 69 ++++++++++++++++-------------------- src/cmd/watch.rs | 29 +++++---------- src/main.rs | 42 ++++++++++------------ 13 files changed, 181 insertions(+), 210 deletions(-) create mode 100644 src/cmd/command_prelude.rs diff --git a/Cargo.lock b/Cargo.lock index ae503d86..a2b7dd23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,17 +53,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -176,34 +165,33 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "4.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" dependencies = [ - "atty", "bitflags", "clap_lex", - "indexmap", + "is-terminal", "once_cell", "strsim", "termcolor", - "textwrap", + "terminal_size", ] [[package]] name = "clap_complete" -version = "3.2.5" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da" dependencies = [ "clap", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" dependencies = [ "os_str_bytes", ] @@ -1654,18 +1642,22 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +dependencies = [ + "rustix", + "windows-sys", +] + [[package]] name = "termtree" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" version = "1.0.37" diff --git a/Cargo.toml b/Cargo.toml index 052c5494..c3209656 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ rust-version = "1.60" [dependencies] anyhow = "1.0.28" chrono = "0.4" -clap = { version = "3.0", features = ["cargo"] } -clap_complete = "3.0" +clap = { version = "4.0.29", features = ["cargo", "wrap_help"] } +clap_complete = "4.0.6" once_cell = "1" env_logger = "0.10.0" handlebars = "4.0" diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index a5e47daa..398d7fc7 100644 --- a/examples/nop-preprocessor.rs +++ b/examples/nop-preprocessor.rs @@ -1,5 +1,5 @@ use crate::nop_lib::Nop; -use clap::{App, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command}; use mdbook::book::Book; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; @@ -7,11 +7,11 @@ use semver::{Version, VersionReq}; use std::io; use std::process; -pub fn make_app() -> App<'static> { - App::new("nop-preprocessor") +pub fn make_app() -> Command { + Command::new("nop-preprocessor") .about("A mdbook preprocessor which does precisely nothing") .subcommand( - App::new("supports") + Command::new("supports") .arg(Arg::new("renderer").required(true)) .about("Check whether a renderer is supported by this preprocessor"), ) @@ -54,7 +54,9 @@ fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> { } fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! { - let renderer = sub_args.value_of("renderer").expect("Required argument"); + let renderer = sub_args + .get_one::("renderer") + .expect("Required argument"); let supported = pre.supports_renderer(renderer); // Signal whether the renderer is supported by exiting with 1 or 0. diff --git a/src/book/mod.rs b/src/book/mod.rs index 5ec64d7c..75bbcc71 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -313,6 +313,7 @@ impl MDBook { } } + debug!("running {:?}", cmd); let output = cmd.output()?; if !output.status.success() { diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 5fe73236..14a9fec6 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,28 +1,16 @@ +use super::command_prelude::*; use crate::{get_book_dir, open}; -use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::MDBook; +use std::path::PathBuf; // Create clap subcommand arguments -pub fn make_subcommand<'help>() -> App<'help> { - App::new("build") +pub fn make_subcommand() -> Command { + Command::new("build") .about("Builds a book from its markdown files") - .arg( - Arg::new("dest-dir") - .short('d') - .long("dest-dir") - .value_name("dest-dir") - .help( - "Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", - ), - ) - .arg(arg!([dir] - "Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)" - )) - .arg(arg!(-o --open "Opens the compiled book in a web browser")) + .arg_dest_dir() + .arg_root_dir() + .arg_open() } // Build command implementation @@ -30,13 +18,13 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let mut book = MDBook::load(&book_dir)?; - if let Some(dest_dir) = args.value_of("dest-dir") { + if let Some(dest_dir) = args.get_one::("dest-dir") { book.config.build.build_dir = dest_dir.into(); } book.build()?; - if args.is_present("open") { + if args.get_flag("open") { // FIXME: What's the right behaviour if we don't use the HTML renderer? let path = book.build_dir_for("html").join("index.html"); if !path.exists() { diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index 0569726e..3ec605fe 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -1,28 +1,16 @@ +use super::command_prelude::*; use crate::get_book_dir; use anyhow::Context; -use clap::{arg, App, Arg, ArgMatches}; use mdbook::MDBook; use std::fs; +use std::path::PathBuf; // Create clap subcommand arguments -pub fn make_subcommand<'help>() -> App<'help> { - App::new("clean") +pub fn make_subcommand() -> Command { + Command::new("clean") .about("Deletes a built book") - .arg( - Arg::new("dest-dir") - .short('d') - .long("dest-dir") - .value_name("dest-dir") - .help( - "Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", - ), - ) - .arg(arg!([dir] - "Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)" - )) + .arg_dest_dir() + .arg_root_dir() } // Clean command implementation @@ -30,7 +18,7 @@ pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> { let book_dir = get_book_dir(args); let book = MDBook::load(&book_dir)?; - let dir_to_remove = match args.value_of("dest-dir") { + let dir_to_remove = match args.get_one::("dest-dir") { Some(dest_dir) => dest_dir.into(), None => book.root.join(&book.config.build.build_dir), }; diff --git a/src/cmd/command_prelude.rs b/src/cmd/command_prelude.rs new file mode 100644 index 00000000..b6362e60 --- /dev/null +++ b/src/cmd/command_prelude.rs @@ -0,0 +1,45 @@ +//! Helpers for building the command-line arguments for commands. + +pub use clap::{arg, Arg, ArgMatches, Command}; +use std::path::PathBuf; + +pub trait CommandExt: Sized { + fn _arg(self, arg: Arg) -> Self; + + fn arg_dest_dir(self) -> Self { + self._arg( + Arg::new("dest-dir") + .short('d') + .long("dest-dir") + .value_name("dest-dir") + .value_parser(clap::value_parser!(PathBuf)) + .help( + "Output directory for the book\n\ + Relative paths are interpreted relative to the book's root directory.\n\ + If omitted, mdBook uses build.build-dir from book.toml \ + or defaults to `./book`.", + ), + ) + } + + fn arg_root_dir(self) -> Self { + self._arg( + Arg::new("dir") + .help( + "Root directory for the book\n\ + (Defaults to the current directory when omitted)", + ) + .value_parser(clap::value_parser!(PathBuf)), + ) + } + + fn arg_open(self) -> Self { + self._arg(arg!(-o --open "Opens the compiled book in a web browser")) + } +} + +impl CommandExt for Command { + fn _arg(self, arg: Arg) -> Self { + self.arg(arg) + } +} diff --git a/src/cmd/init.rs b/src/cmd/init.rs index c964dcc1..d8ce93d1 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,5 +1,5 @@ use crate::get_book_dir; -use clap::{arg, App, Arg, ArgMatches}; +use clap::{arg, ArgMatches, Command as ClapCommand}; use mdbook::config; use mdbook::errors::Result; use mdbook::MDBook; @@ -8,30 +8,22 @@ use std::io::Write; use std::process::Command; // Create clap subcommand arguments -pub fn make_subcommand<'help>() -> App<'help> { - App::new("init") +pub fn make_subcommand() -> ClapCommand { + ClapCommand::new("init") .about("Creates the boilerplate structure and files for a new book") - // the {n} denotes a newline which will properly aligned in all help messages - .arg(arg!([dir] - "Directory to create the book in{n}\ - (Defaults to the Current Directory when omitted)" - )) + .arg( + arg!([dir] + "Directory to create the book in\n\ + (Defaults to the current directory when omitted)" + ) + .value_parser(clap::value_parser!(std::path::PathBuf)), + ) .arg(arg!(--theme "Copies the default theme into your source folder")) .arg(arg!(--force "Skips confirmation prompts")) + .arg(arg!(--title "Sets the book title")) .arg( - Arg::new("title") - .long("title") - .takes_value(true) - .help("Sets the book title") - .required(false), - ) - .arg( - Arg::new("ignore") - .long("ignore") - .takes_value(true) - .possible_values(&["none", "git"]) - .help("Creates a VCS ignore file (i.e. .gitignore)") - .required(false), + arg!(--ignore <ignore> "Creates a VCS ignore file (i.e. .gitignore)") + .value_parser(["none", "git"]), ) } @@ -41,12 +33,12 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let mut builder = MDBook::init(&book_dir); let mut config = config::Config::default(); // If flag `--theme` is present, copy theme to src - if args.is_present("theme") { + if args.get_flag("theme") { let theme_dir = book_dir.join("theme"); println!(); println!("Copying the default theme to {}", theme_dir.display()); // Skip this if `--force` is present - if !args.is_present("force") && theme_dir.exists() { + if !args.get_flag("force") && theme_dir.exists() { println!("This could potentially overwrite files already present in that directory."); print!("\nAre you sure you want to continue? (y/n) "); @@ -59,7 +51,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { } } - if let Some(ignore) = args.value_of("ignore") { + if let Some(ignore) = args.get_one::<String>("ignore").map(|s| s.as_str()) { match ignore { "git" => builder.create_gitignore(true), _ => builder.create_gitignore(false), @@ -71,8 +63,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> { } } - config.book.title = if args.is_present("title") { - args.value_of("title").map(String::from) + config.book.title = if args.contains_id("title") { + args.get_one::<String>("title").map(String::from) } else { request_book_title() }; diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index c5b6730f..b21979b2 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -2,6 +2,7 @@ pub mod build; pub mod clean; +pub mod command_prelude; pub mod init; #[cfg(feature = "serve")] pub mod serve; diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 00eaa46b..88898567 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -1,7 +1,8 @@ +use super::command_prelude::*; #[cfg(feature = "watch")] use super::watch; use crate::{get_book_dir, open}; -use clap::{arg, App, Arg, ArgMatches}; +use clap::builder::NonEmptyStringValueParser; use futures_util::sink::SinkExt; use futures_util::StreamExt; use mdbook::errors::*; @@ -18,43 +19,30 @@ use warp::Filter; const LIVE_RELOAD_ENDPOINT: &str = "__livereload"; // Create clap subcommand arguments -pub fn make_subcommand<'help>() -> App<'help> { - App::new("serve") +pub fn make_subcommand() -> Command { + Command::new("serve") .about("Serves a book at http://localhost:3000, and rebuilds it on changes") - .arg( - Arg::new("dest-dir") - .short('d') - .long("dest-dir") - .value_name("dest-dir") - .help( - "Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", - ), - ) - .arg(arg!([dir] - "Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)" - )) + .arg_dest_dir() + .arg_root_dir() .arg( Arg::new("hostname") .short('n') .long("hostname") - .takes_value(true) + .num_args(1) .default_value("localhost") - .forbid_empty_values(true) + .value_parser(NonEmptyStringValueParser::new()) .help("Hostname to listen on for HTTP connections"), ) .arg( Arg::new("port") .short('p') .long("port") - .takes_value(true) + .num_args(1) .default_value("3000") - .forbid_empty_values(true) + .value_parser(NonEmptyStringValueParser::new()) .help("Port to use for HTTP connections"), ) - .arg(arg!(-o --open "Opens the compiled book in a web browser")) + .arg_open() } // Serve command implementation @@ -62,17 +50,17 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let mut book = MDBook::load(&book_dir)?; - let port = args.value_of("port").unwrap(); - let hostname = args.value_of("hostname").unwrap(); - let open_browser = args.is_present("open"); + let port = args.get_one::<String>("port").unwrap(); + let hostname = args.get_one::<String>("hostname").unwrap(); + let open_browser = args.get_flag("open"); let address = format!("{}:{}", hostname, port); let update_config = |book: &mut MDBook| { book.config - .set("output.html.live-reload-endpoint", &LIVE_RELOAD_ENDPOINT) + .set("output.html.live-reload-endpoint", LIVE_RELOAD_ENDPOINT) .expect("live-reload-endpoint update failed"); - if let Some(dest_dir) = args.value_of("dest-dir") { + if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") { book.config.build.build_dir = dest_dir.into(); } // Override site-url for local serving of the 404 file diff --git a/src/cmd/test.rs b/src/cmd/test.rs index f5ca3ee4..f3b3fdea 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -1,63 +1,54 @@ +use super::command_prelude::*; use crate::get_book_dir; -use clap::{arg, App, Arg, ArgMatches}; +use clap::builder::NonEmptyStringValueParser; +use clap::{Arg, ArgAction, ArgMatches, Command}; use mdbook::errors::Result; use mdbook::MDBook; +use std::path::PathBuf; // Create clap subcommand arguments -pub fn make_subcommand<'help>() -> App<'help> { - App::new("test") +pub fn make_subcommand() -> Command { + Command::new("test") .about("Tests that a book's Rust code samples compile") + // FIXME: --dest-dir is unused by the test command, it should be removed + .arg_dest_dir() + .arg_root_dir() .arg( - Arg::new("dest-dir") - .short('d') - .long("dest-dir") - .value_name("dest-dir") - .help( - "Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", - ), - ).arg( Arg::new("chapter") .short('c') .long("chapter") - .value_name("chapter") - .help( - "Only test the specified chapter{n}\ - Where the name of the chapter is defined in the SUMMARY.md file.{n}\ - Use the special name \"?\" to the list of chapter names." - ) + .value_name("chapter"), + ) + .arg( + Arg::new("library-path") + .short('L') + .long("library-path") + .value_name("dir") + .value_delimiter(',') + .num_args(1..) + .value_parser(NonEmptyStringValueParser::new()) + .action(ArgAction::Append) + .help( + "A comma-separated list of directories to add to the crate \ + search path when building tests", + ), ) - .arg(arg!([dir] - "Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)" - )) - .arg(Arg::new("library-path") - .short('L') - .long("library-path") - .value_name("dir") - .takes_value(true) - .use_delimiter(true) - .require_delimiter(true) - .multiple_values(true) - .multiple_occurrences(true) - .forbid_empty_values(true) - .help("A comma-separated list of directories to add to {n}the crate search path when building tests")) } // test command implementation pub fn execute(args: &ArgMatches) -> Result<()> { let library_paths: Vec<&str> = args - .values_of("library-path") - .map(std::iter::Iterator::collect) + .get_many("library-path") + .map(|it| it.map(String::as_str).collect()) .unwrap_or_default(); - let chapter: Option<&str> = args.value_of("chapter"); + + let chapter: Option<&str> = args.get_one::<String>("chapter").map(|s| s.as_str()); let book_dir = get_book_dir(args); let mut book = MDBook::load(&book_dir)?; - if let Some(dest_dir) = args.value_of("dest-dir") { - book.config.build.build_dir = dest_dir.into(); + if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") { + book.config.build.build_dir = dest_dir.to_path_buf(); } match chapter { Some(_) => book.test_chapter(library_paths, chapter), diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index 44fbc012..bbc6bde7 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,5 +1,5 @@ +use super::command_prelude::*; use crate::{get_book_dir, open}; -use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::utils; use mdbook::MDBook; @@ -9,25 +9,12 @@ use std::thread::sleep; use std::time::Duration; // Create clap subcommand arguments -pub fn make_subcommand<'help>() -> App<'help> { - App::new("watch") +pub fn make_subcommand() -> Command { + Command::new("watch") .about("Watches a book's files and rebuilds it on changes") - .arg( - Arg::new("dest-dir") - .short('d') - .long("dest-dir") - .value_name("dest-dir") - .help( - "Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.", - ), - ) - .arg(arg!([dir] - "Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)" - )) - .arg(arg!(-o --open "Opens the compiled book in a web browser")) + .arg_dest_dir() + .arg_root_dir() + .arg_open() } // Watch command implementation @@ -36,13 +23,13 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let mut book = MDBook::load(&book_dir)?; let update_config = |book: &mut MDBook| { - if let Some(dest_dir) = args.value_of("dest-dir") { + if let Some(dest_dir) = args.get_one::<PathBuf>("dest-dir") { book.config.build.build_dir = dest_dir.into(); } }; update_config(&mut book); - if args.is_present("open") { + if args.get_flag("open") { book.build()?; let path = book.build_dir_for("html").join("index.html"); if !path.exists() { diff --git a/src/main.rs b/src/main.rs index 35562e64..3e576c5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ extern crate log; use anyhow::anyhow; use chrono::Local; -use clap::{App, AppSettings, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command}; use clap_complete::Shell; use env_logger::Builder; use log::LevelFilter; @@ -13,7 +13,7 @@ use mdbook::utils; use std::env; use std::ffi::OsStr; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; mod cmd; @@ -22,10 +22,10 @@ const VERSION: &str = concat!("v", crate_version!()); fn main() { init_logger(); - let app = create_clap_app(); + let command = create_clap_command(); - // Check which subcomamnd the user ran... - let res = match app.get_matches().subcommand() { + // Check which subcommand the user ran... + let res = match command.get_matches().subcommand() { Some(("init", sub_matches)) => cmd::init::execute(sub_matches), Some(("build", sub_matches)) => cmd::build::execute(sub_matches), Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches), @@ -35,15 +35,13 @@ fn main() { Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches), Some(("test", sub_matches)) => cmd::test::execute(sub_matches), Some(("completions", sub_matches)) => (|| { - let shell: Shell = sub_matches - .value_of("shell") - .ok_or_else(|| anyhow!("Shell name missing."))? - .parse() - .map_err(|s| anyhow!("Invalid shell: {}", s))?; + let shell = sub_matches + .get_one::<Shell>("shell") + .ok_or_else(|| anyhow!("Shell name missing."))?; - let mut complete_app = create_clap_app(); + let mut complete_app = create_clap_command(); clap_complete::generate( - shell, + *shell, &mut complete_app, "mdbook", &mut std::io::stdout().lock(), @@ -61,13 +59,13 @@ fn main() { } /// Create a list of valid arguments and sub-commands -fn create_clap_app() -> App<'static> { - let app = App::new(crate_name!()) +fn create_clap_command() -> Command { + let app = Command::new(crate_name!()) .about(crate_description!()) .author("Mathieu David <mathieudavid@mathieudavid.org>") .version(VERSION) - .setting(AppSettings::PropagateVersion) - .setting(AppSettings::ArgRequiredElseHelp) + .propagate_version(true) + .arg_required_else_help(true) .after_help( "For more information about a specific command, try `mdbook <command> --help`\n\ The source code for mdBook is available at: https://github.com/rust-lang/mdBook", @@ -77,12 +75,11 @@ fn create_clap_app() -> App<'static> { .subcommand(cmd::test::make_subcommand()) .subcommand(cmd::clean::make_subcommand()) .subcommand( - App::new("completions") + Command::new("completions") .about("Generate shell completions for your shell to stdout") .arg( Arg::new("shell") - .takes_value(true) - .possible_values(Shell::possible_values()) + .value_parser(clap::value_parser!(Shell)) .help("the shell to generate completions for") .value_name("SHELL") .required(true), @@ -124,11 +121,10 @@ fn init_logger() { } fn get_book_dir(args: &ArgMatches) -> PathBuf { - if let Some(dir) = args.value_of("dir") { + if let Some(p) = args.get_one::<PathBuf>("dir") { // Check if path is relative from current dir, or absolute... - let p = Path::new(dir); if p.is_relative() { - env::current_dir().unwrap().join(dir) + env::current_dir().unwrap().join(p) } else { p.to_path_buf() } @@ -146,5 +142,5 @@ fn open<P: AsRef<OsStr>>(path: P) { #[test] fn verify_app() { - create_clap_app().debug_assert(); + create_clap_command().debug_assert(); }