diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 39624230..bfd114ca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,8 @@ jobs: rust: stable - build: msrv os: ubuntu-latest - rust: 1.46.0 + # sync MSRV with docs: guide/src/guide/installation.md + rust: 1.54.0 steps: - uses: actions/checkout@master - name: Install Rust diff --git a/Cargo.lock b/Cargo.lock index bf6c881d..f7833466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,17 +185,27 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.3" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375" dependencies = [ - "ansi_term", "atty", "bitflags", + "indexmap", + "lazy_static", + "os_str_bytes", "strsim", + "termcolor", "textwrap", - "unicode-width", - "vec_map", +] + +[[package]] +name = "clap_complete" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d044e9db8cd0f68191becdeb5246b7462e4cf0c069b19ae00d1bf3fa9889498d" +dependencies = [ + "clap", ] [[package]] @@ -837,6 +847,7 @@ dependencies = [ "assert_cmd", "chrono", "clap", + "clap_complete", "elasticlunr-rs", "env_logger", "futures-util", @@ -1053,6 +1064,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + [[package]] name = "output_vt100" version = "0.1.2" @@ -1578,9 +1598,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" @@ -1647,12 +1667,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.11.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" [[package]] name = "time" @@ -1860,12 +1877,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - [[package]] name = "unicode-xid" version = "0.2.2" @@ -1890,12 +1901,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index 6e225ea6..bf0cb286 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ description = "Creates a book from markdown files" [dependencies] anyhow = "1.0.28" chrono = "0.4" -clap = "2.24" +clap = { version = "3.0", features = ["cargo"] } +clap_complete = "3.0" env_logger = "0.7.1" handlebars = "4.0" lazy_static = "1.0" diff --git a/examples/nop-preprocessor.rs b/examples/nop-preprocessor.rs index 486fd86d..ace40093 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, SubCommand}; +use clap::{App, Arg, ArgMatches}; use mdbook::book::Book; use mdbook::errors::Error; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; @@ -7,12 +7,12 @@ use semver::{Version, VersionReq}; use std::io; use std::process; -pub fn make_app() -> App<'static, 'static> { +pub fn make_app() -> App<'static> { App::new("nop-preprocessor") .about("A mdbook preprocessor which does precisely nothing") .subcommand( - SubCommand::with_name("supports") - .arg(Arg::with_name("renderer").required(true)) + App::new("supports") + .arg(Arg::new("renderer").required(true)) .about("Check whether a renderer is supported by this preprocessor"), ) } diff --git a/guide/src/guide/installation.md b/guide/src/guide/installation.md index b68f807a..d7946587 100644 --- a/guide/src/guide/installation.md +++ b/guide/src/guide/installation.md @@ -20,7 +20,7 @@ To make it easier to run, put the path to the binary into your `PATH`. To build the `mdbook` executable from source, you will first need to install Rust and Cargo. Follow the instructions on the [Rust installation page]. -mdBook currently requires at least Rust version 1.46. +mdBook currently requires at least Rust version 1.54. Once you have installed Rust, the following command can be used to build and install mdBook: diff --git a/src/cmd/build.rs b/src/cmd/build.rs index d1c66302..0091d482 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,22 +1,28 @@ use crate::{get_book_dir, open}; -use clap::{App, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::MDBook; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("build") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("build") .about("Builds a book from its markdown files") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] '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("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_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg_from_usage("-o, --open 'Opens the compiled book in a web browser'") + .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")) } // Build command implementation diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index b58f937e..0569726e 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -1,23 +1,28 @@ use crate::get_book_dir; use anyhow::Context; -use clap::{App, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::MDBook; use std::fs; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("clean") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("clean") .about("Deletes a built book") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ - Relative paths are interpreted relative to the book's root directory.{n}\ - Running this command deletes this directory.{n}\ - If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.'", - ) - .arg_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", + .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)" + )) } // Clean command implementation diff --git a/src/cmd/init.rs b/src/cmd/init.rs index ed0aa17d..1ee5ff21 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,5 +1,5 @@ use crate::get_book_dir; -use clap::{App, Arg, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::config; use mdbook::errors::Result; use mdbook::MDBook; @@ -8,25 +8,25 @@ use std::io::Write; use std::process::Command; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("init") +pub fn make_subcommand<'help>() -> App<'help> { + App::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_from_usage( - "[dir] 'Directory to create the book in{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg_from_usage("--theme 'Copies the default theme into your source folder'") - .arg_from_usage("--force 'Skips confirmation prompts'") + .arg(arg!([dir] + "Directory to create the book in{n}\ + (Defaults to the Current Directory when omitted)" + )) + .arg(arg!(--theme "Copies the default theme into your source folder")) + .arg(arg!(--force "Skips confirmation prompts")) .arg( - Arg::with_name("title") + Arg::new("title") .long("title") .takes_value(true) .help("Sets the book title") .required(false), ) .arg( - Arg::with_name("ignore") + Arg::new("ignore") .long("ignore") .takes_value(true) .possible_values(&["none", "git"]) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index b4782575..bafbfd52 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -1,7 +1,7 @@ #[cfg(feature = "watch")] use super::watch; use crate::{get_book_dir, open}; -use clap::{App, Arg, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use futures_util::sink::SinkExt; use futures_util::StreamExt; use mdbook::errors::*; @@ -18,37 +18,43 @@ use warp::Filter; const LIVE_RELOAD_ENDPOINT: &str = "__livereload"; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("serve") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("serve") .about("Serves a book at http://localhost:3000, and rebuilds it on changes") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] '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_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", - ) .arg( - Arg::with_name("hostname") - .short("n") + 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::new("hostname") + .short('n') .long("hostname") .takes_value(true) .default_value("localhost") - .empty_values(false) + .forbid_empty_values(true) .help("Hostname to listen on for HTTP connections"), ) .arg( - Arg::with_name("port") - .short("p") + Arg::new("port") + .short('p') .long("port") .takes_value(true) .default_value("3000") - .empty_values(false) + .forbid_empty_values(true) .help("Port to use for HTTP connections"), ) - .arg_from_usage("-o, --open 'Opens the book server in a web browser'") + .arg(arg!(-o --open "Opens the compiled book in a web browser")) } // Serve command implementation diff --git a/src/cmd/test.rs b/src/cmd/test.rs index f6d97aa6..02f982a4 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -1,29 +1,37 @@ use crate::get_book_dir; -use clap::{App, Arg, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::MDBook; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("test") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("test") .about("Tests that a book's Rust code samples compile") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] '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("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_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg(Arg::with_name("library-path") - .short("L") + .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(true) - .empty_values(false) + .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")) } diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index b27516b0..78ae1968 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,5 +1,5 @@ use crate::{get_book_dir, open}; -use clap::{App, ArgMatches, SubCommand}; +use clap::{arg, App, Arg, ArgMatches}; use mdbook::errors::Result; use mdbook::utils; use mdbook::MDBook; @@ -10,19 +10,25 @@ use std::thread::sleep; use std::time::Duration; // Create clap subcommand arguments -pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { - SubCommand::with_name("watch") +pub fn make_subcommand<'help>() -> App<'help> { + App::new("watch") .about("Watches a book's files and rebuilds it on changes") - .arg_from_usage( - "-d, --dest-dir=[dest-dir] '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("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_from_usage( - "[dir] 'Root directory for the book{n}\ - (Defaults to the Current Directory when omitted)'", - ) - .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .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")) } // Watch command implementation diff --git a/src/main.rs b/src/main.rs index 1f286d2d..35562e64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,8 @@ extern crate log; use anyhow::anyhow; use chrono::Local; -use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand}; +use clap::{App, AppSettings, Arg, ArgMatches}; +use clap_complete::Shell; use env_logger::Builder; use log::LevelFilter; use mdbook::utils; @@ -25,25 +26,31 @@ fn main() { // Check which subcomamnd the user ran... let res = match app.get_matches().subcommand() { - ("init", Some(sub_matches)) => cmd::init::execute(sub_matches), - ("build", Some(sub_matches)) => cmd::build::execute(sub_matches), - ("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches), + 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), #[cfg(feature = "watch")] - ("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches), + Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches), #[cfg(feature = "serve")] - ("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches), - ("test", Some(sub_matches)) => cmd::test::execute(sub_matches), - ("completions", Some(sub_matches)) => (|| { + 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))?; - create_clap_app().gen_completions_to("mdbook", shell, &mut std::io::stdout().lock()); + let mut complete_app = create_clap_app(); + clap_complete::generate( + shell, + &mut complete_app, + "mdbook", + &mut std::io::stdout().lock(), + ); Ok(()) })(), - (_, _) => unreachable!(), + _ => unreachable!(), }; if let Err(e) = res { @@ -54,14 +61,13 @@ fn main() { } /// Create a list of valid arguments and sub-commands -fn create_clap_app<'a, 'b>() -> App<'a, 'b> { +fn create_clap_app() -> App<'static> { let app = App::new(crate_name!()) .about(crate_description!()) .author("Mathieu David ") .version(VERSION) - .setting(AppSettings::GlobalVersion) + .setting(AppSettings::PropagateVersion) .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::ColoredHelp) .after_help( "For more information about a specific command, try `mdbook --help`\n\ The source code for mdBook is available at: https://github.com/rust-lang/mdBook", @@ -71,12 +77,12 @@ fn create_clap_app<'a, 'b>() -> App<'a, 'b> { .subcommand(cmd::test::make_subcommand()) .subcommand(cmd::clean::make_subcommand()) .subcommand( - SubCommand::with_name("completions") + App::new("completions") .about("Generate shell completions for your shell to stdout") .arg( - Arg::with_name("shell") + Arg::new("shell") .takes_value(true) - .possible_values(&Shell::variants()) + .possible_values(Shell::possible_values()) .help("the shell to generate completions for") .value_name("SHELL") .required(true), @@ -137,3 +143,8 @@ fn open>(path: P) { error!("Error opening web browser: {}", e); } } + +#[test] +fn verify_app() { + create_clap_app().debug_assert(); +}