Merge pull request #1731 from epage/clap3

Port mdBook to clap3
This commit is contained in:
Eric Huss 2022-03-28 12:34:17 -07:00 committed by GitHub
commit 7c37dd5e85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 177 additions and 128 deletions

View File

@ -31,7 +31,8 @@ jobs:
rust: stable rust: stable
- build: msrv - build: msrv
os: ubuntu-latest os: ubuntu-latest
rust: 1.46.0 # sync MSRV with docs: guide/src/guide/installation.md
rust: 1.54.0
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- name: Install Rust - name: Install Rust

53
Cargo.lock generated
View File

@ -185,17 +185,27 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.33.3" version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" checksum = "7a30c3bf9ff12dfe5dae53f0a96e0febcd18420d1c0e7fad77796d9d5c4b5375"
dependencies = [ dependencies = [
"ansi_term",
"atty", "atty",
"bitflags", "bitflags",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim", "strsim",
"termcolor",
"textwrap", "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]] [[package]]
@ -837,6 +847,7 @@ dependencies = [
"assert_cmd", "assert_cmd",
"chrono", "chrono",
"clap", "clap",
"clap_complete",
"elasticlunr-rs", "elasticlunr-rs",
"env_logger", "env_logger",
"futures-util", "futures-util",
@ -1053,6 +1064,15 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "output_vt100" name = "output_vt100"
version = "0.1.2" version = "0.1.2"
@ -1578,9 +1598,9 @@ dependencies = [
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "strum" name = "strum"
@ -1647,12 +1667,9 @@ dependencies = [
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "time" name = "time"
@ -1860,12 +1877,6 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -1890,12 +1901,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.3" version = "0.9.3"

View File

@ -18,7 +18,8 @@ description = "Creates a book from markdown files"
[dependencies] [dependencies]
anyhow = "1.0.28" anyhow = "1.0.28"
chrono = "0.4" chrono = "0.4"
clap = "2.24" clap = { version = "3.0", features = ["cargo"] }
clap_complete = "3.0"
env_logger = "0.7.1" env_logger = "0.7.1"
handlebars = "4.0" handlebars = "4.0"
lazy_static = "1.0" lazy_static = "1.0"

View File

@ -1,5 +1,5 @@
use crate::nop_lib::Nop; use crate::nop_lib::Nop;
use clap::{App, Arg, ArgMatches, SubCommand}; use clap::{App, Arg, ArgMatches};
use mdbook::book::Book; use mdbook::book::Book;
use mdbook::errors::Error; use mdbook::errors::Error;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}; use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
@ -7,12 +7,12 @@ use semver::{Version, VersionReq};
use std::io; use std::io;
use std::process; use std::process;
pub fn make_app() -> App<'static, 'static> { pub fn make_app() -> App<'static> {
App::new("nop-preprocessor") App::new("nop-preprocessor")
.about("A mdbook preprocessor which does precisely nothing") .about("A mdbook preprocessor which does precisely nothing")
.subcommand( .subcommand(
SubCommand::with_name("supports") App::new("supports")
.arg(Arg::with_name("renderer").required(true)) .arg(Arg::new("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"), .about("Check whether a renderer is supported by this preprocessor"),
) )
} }

View File

@ -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. To build the `mdbook` executable from source, you will first need to install Rust and Cargo.
Follow the instructions on the [Rust installation page]. 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: Once you have installed Rust, the following command can be used to build and install mdBook:

View File

@ -1,22 +1,28 @@
use crate::{get_book_dir, open}; use crate::{get_book_dir, open};
use clap::{App, ArgMatches, SubCommand}; use clap::{arg, App, Arg, ArgMatches};
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::MDBook; use mdbook::MDBook;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand<'help>() -> App<'help> {
SubCommand::with_name("build") App::new("build")
.about("Builds a book from its markdown files") .about("Builds a book from its markdown files")
.arg_from_usage( .arg(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{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}\ 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`.'", If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
) )
.arg_from_usage( .arg(arg!([dir]
"[dir] 'Root directory for the book{n}\ "Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'", (Defaults to the Current Directory when omitted)"
) ))
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'") .arg(arg!(-o --open "Opens the compiled book in a web browser"))
} }
// Build command implementation // Build command implementation

View File

@ -1,23 +1,28 @@
use crate::get_book_dir; use crate::get_book_dir;
use anyhow::Context; use anyhow::Context;
use clap::{App, ArgMatches, SubCommand}; use clap::{arg, App, Arg, ArgMatches};
use mdbook::MDBook; use mdbook::MDBook;
use std::fs; use std::fs;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand<'help>() -> App<'help> {
SubCommand::with_name("clean") App::new("clean")
.about("Deletes a built book") .about("Deletes a built book")
.arg_from_usage( .arg(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{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}\ 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`.",
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!([dir]
"Root directory for the book{n}\
(Defaults to the Current Directory when omitted)"
))
} }
// Clean command implementation // Clean command implementation

View File

@ -1,5 +1,5 @@
use crate::get_book_dir; use crate::get_book_dir;
use clap::{App, Arg, ArgMatches, SubCommand}; use clap::{arg, App, Arg, ArgMatches};
use mdbook::config; use mdbook::config;
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::MDBook; use mdbook::MDBook;
@ -8,25 +8,25 @@ use std::io::Write;
use std::process::Command; use std::process::Command;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand<'help>() -> App<'help> {
SubCommand::with_name("init") App::new("init")
.about("Creates the boilerplate structure and files for a new book") .about("Creates the boilerplate structure and files for a new book")
// the {n} denotes a newline which will properly aligned in all help messages // the {n} denotes a newline which will properly aligned in all help messages
.arg_from_usage( .arg(arg!([dir]
"[dir] 'Directory to create the book in{n}\ "Directory to create the book in{n}\
(Defaults to the Current Directory when omitted)'", (Defaults to the Current Directory when omitted)"
) ))
.arg_from_usage("--theme 'Copies the default theme into your source folder'") .arg(arg!(--theme "Copies the default theme into your source folder"))
.arg_from_usage("--force 'Skips confirmation prompts'") .arg(arg!(--force "Skips confirmation prompts"))
.arg( .arg(
Arg::with_name("title") Arg::new("title")
.long("title") .long("title")
.takes_value(true) .takes_value(true)
.help("Sets the book title") .help("Sets the book title")
.required(false), .required(false),
) )
.arg( .arg(
Arg::with_name("ignore") Arg::new("ignore")
.long("ignore") .long("ignore")
.takes_value(true) .takes_value(true)
.possible_values(&["none", "git"]) .possible_values(&["none", "git"])

View File

@ -1,7 +1,7 @@
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
use super::watch; use super::watch;
use crate::{get_book_dir, open}; 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::sink::SinkExt;
use futures_util::StreamExt; use futures_util::StreamExt;
use mdbook::errors::*; use mdbook::errors::*;
@ -18,37 +18,43 @@ use warp::Filter;
const LIVE_RELOAD_ENDPOINT: &str = "__livereload"; const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand<'help>() -> App<'help> {
SubCommand::with_name("serve") App::new("serve")
.about("Serves a book at http://localhost:3000, and rebuilds it on changes") .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(
Arg::with_name("hostname") Arg::new("dest-dir")
.short("n") .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") .long("hostname")
.takes_value(true) .takes_value(true)
.default_value("localhost") .default_value("localhost")
.empty_values(false) .forbid_empty_values(true)
.help("Hostname to listen on for HTTP connections"), .help("Hostname to listen on for HTTP connections"),
) )
.arg( .arg(
Arg::with_name("port") Arg::new("port")
.short("p") .short('p')
.long("port") .long("port")
.takes_value(true) .takes_value(true)
.default_value("3000") .default_value("3000")
.empty_values(false) .forbid_empty_values(true)
.help("Port to use for HTTP connections"), .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 // Serve command implementation

View File

@ -1,29 +1,37 @@
use crate::get_book_dir; use crate::get_book_dir;
use clap::{App, Arg, ArgMatches, SubCommand}; use clap::{arg, App, Arg, ArgMatches};
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::MDBook; use mdbook::MDBook;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand<'help>() -> App<'help> {
SubCommand::with_name("test") App::new("test")
.about("Tests that a book's Rust code samples compile") .about("Tests that a book's Rust code samples compile")
.arg_from_usage( .arg(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{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}\ 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`.'", If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
) )
.arg_from_usage( .arg(arg!([dir]
"[dir] 'Root directory for the book{n}\ "Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'", (Defaults to the Current Directory when omitted)"
) ))
.arg(Arg::with_name("library-path") .arg(Arg::new("library-path")
.short("L") .short('L')
.long("library-path") .long("library-path")
.value_name("dir") .value_name("dir")
.takes_value(true) .takes_value(true)
.use_delimiter(true)
.require_delimiter(true) .require_delimiter(true)
.multiple(true) .multiple_values(true)
.empty_values(false) .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")) .help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
} }

View File

@ -1,5 +1,5 @@
use crate::{get_book_dir, open}; use crate::{get_book_dir, open};
use clap::{App, ArgMatches, SubCommand}; use clap::{arg, App, Arg, ArgMatches};
use mdbook::errors::Result; use mdbook::errors::Result;
use mdbook::utils; use mdbook::utils;
use mdbook::MDBook; use mdbook::MDBook;
@ -10,19 +10,25 @@ use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand<'help>() -> App<'help> {
SubCommand::with_name("watch") App::new("watch")
.about("Watches a book's files and rebuilds it on changes") .about("Watches a book's files and rebuilds it on changes")
.arg_from_usage( .arg(
"-d, --dest-dir=[dest-dir] 'Output directory for the book{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}\ 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`.'", If omitted, mdBook uses build.build-dir from book.toml or defaults to `./book`.",
),
) )
.arg_from_usage( .arg(arg!([dir]
"[dir] 'Root directory for the book{n}\ "Root directory for the book{n}\
(Defaults to the Current Directory when omitted)'", (Defaults to the Current Directory when omitted)"
) ))
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'") .arg(arg!(-o --open "Opens the compiled book in a web browser"))
} }
// Watch command implementation // Watch command implementation

View File

@ -5,7 +5,8 @@ extern crate log;
use anyhow::anyhow; use anyhow::anyhow;
use chrono::Local; 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 env_logger::Builder;
use log::LevelFilter; use log::LevelFilter;
use mdbook::utils; use mdbook::utils;
@ -25,25 +26,31 @@ fn main() {
// Check which subcomamnd the user ran... // Check which subcomamnd the user ran...
let res = match app.get_matches().subcommand() { let res = match app.get_matches().subcommand() {
("init", Some(sub_matches)) => cmd::init::execute(sub_matches), Some(("init", sub_matches)) => cmd::init::execute(sub_matches),
("build", Some(sub_matches)) => cmd::build::execute(sub_matches), Some(("build", sub_matches)) => cmd::build::execute(sub_matches),
("clean", Some(sub_matches)) => cmd::clean::execute(sub_matches), Some(("clean", sub_matches)) => cmd::clean::execute(sub_matches),
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
("watch", Some(sub_matches)) => cmd::watch::execute(sub_matches), Some(("watch", sub_matches)) => cmd::watch::execute(sub_matches),
#[cfg(feature = "serve")] #[cfg(feature = "serve")]
("serve", Some(sub_matches)) => cmd::serve::execute(sub_matches), Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
("test", Some(sub_matches)) => cmd::test::execute(sub_matches), Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
("completions", Some(sub_matches)) => (|| { Some(("completions", sub_matches)) => (|| {
let shell: Shell = sub_matches let shell: Shell = sub_matches
.value_of("shell") .value_of("shell")
.ok_or_else(|| anyhow!("Shell name missing."))? .ok_or_else(|| anyhow!("Shell name missing."))?
.parse() .parse()
.map_err(|s| anyhow!("Invalid shell: {}", s))?; .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(()) Ok(())
})(), })(),
(_, _) => unreachable!(), _ => unreachable!(),
}; };
if let Err(e) = res { if let Err(e) = res {
@ -54,14 +61,13 @@ fn main() {
} }
/// Create a list of valid arguments and sub-commands /// 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!()) let app = App::new(crate_name!())
.about(crate_description!()) .about(crate_description!())
.author("Mathieu David <mathieudavid@mathieudavid.org>") .author("Mathieu David <mathieudavid@mathieudavid.org>")
.version(VERSION) .version(VERSION)
.setting(AppSettings::GlobalVersion) .setting(AppSettings::PropagateVersion)
.setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::ArgRequiredElseHelp)
.setting(AppSettings::ColoredHelp)
.after_help( .after_help(
"For more information about a specific command, try `mdbook <command> --help`\n\ "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", 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::test::make_subcommand())
.subcommand(cmd::clean::make_subcommand()) .subcommand(cmd::clean::make_subcommand())
.subcommand( .subcommand(
SubCommand::with_name("completions") App::new("completions")
.about("Generate shell completions for your shell to stdout") .about("Generate shell completions for your shell to stdout")
.arg( .arg(
Arg::with_name("shell") Arg::new("shell")
.takes_value(true) .takes_value(true)
.possible_values(&Shell::variants()) .possible_values(Shell::possible_values())
.help("the shell to generate completions for") .help("the shell to generate completions for")
.value_name("SHELL") .value_name("SHELL")
.required(true), .required(true),
@ -137,3 +143,8 @@ fn open<P: AsRef<OsStr>>(path: P) {
error!("Error opening web browser: {}", e); error!("Error opening web browser: {}", e);
} }
} }
#[test]
fn verify_app() {
create_clap_app().debug_assert();
}