From b8f8e768998dbca1fb29ee09f8a1f737297556d3 Mon Sep 17 00:00:00 2001 From: Matt Ickstadt Date: Thu, 2 Aug 2018 15:48:22 -0500 Subject: [PATCH] Improve command-line argument parsing --- src/cmd/build.rs | 14 ++++----- src/cmd/clean.rs | 13 +++++---- src/cmd/init.rs | 8 +++--- src/cmd/serve.rs | 75 ++++++++++++++++++++++++++++++++++-------------- src/cmd/test.rs | 25 +++++++++++++--- src/cmd/watch.rs | 11 +++++-- src/main.rs | 29 ++++++++++--------- 7 files changed, 116 insertions(+), 59 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 7887c06c..350a3ece 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -1,21 +1,21 @@ use clap::{App, ArgMatches, SubCommand}; use mdbook::errors::Result; use mdbook::MDBook; -use std::path::PathBuf; use {get_book_dir, open}; // Create clap subcommand arguments pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("build") - .about("Build the book from the markdown files") - .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .about("Builds a book from its markdown files") .arg_from_usage( - "-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book \ - when omitted)'", + "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ + (If omitted, uses build.build-dir from book.toml or defaults to ./book)'", ) .arg_from_usage( - "[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'", + "[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'") } // Build command implementation @@ -24,7 +24,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let mut book = MDBook::load(&book_dir)?; if let Some(dest_dir) = args.value_of("dest-dir") { - book.config.build.build_dir = PathBuf::from(dest_dir); + book.config.build.build_dir = dest_dir.into(); } book.build()?; diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index 92b69566..9bcf3f20 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -3,15 +3,18 @@ use get_book_dir; use mdbook::errors::*; use mdbook::MDBook; use std::fs; -use std::path::PathBuf; // Create clap subcommand arguments pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("clean") - .about("Delete built book") + .about("Deletes a built book") .arg_from_usage( - "-d, --dest-dir=[dest-dir] 'The directory of built book{n}(Defaults to ./book when \ - omitted)'", + "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ + (If omitted, 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)'", ) } @@ -21,7 +24,7 @@ pub fn execute(args: &ArgMatches) -> ::mdbook::errors::Result<()> { let book = MDBook::load(&book_dir)?; let dir_to_remove = match args.value_of("dest-dir") { - Some(dest_dir) => PathBuf::from(dest_dir), + Some(dest_dir) => dest_dir.into(), None => book.root.join(&book.config.build.build_dir), }; fs::remove_dir_all(&dir_to_remove).chain_err(|| "Unable to remove the build directory")?; diff --git a/src/cmd/init.rs b/src/cmd/init.rs index 134eda80..629563b2 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -10,12 +10,12 @@ use std::process::Command; // Create clap subcommand arguments pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("init") - .about("Create boilerplate structure and files in the directory") + .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] 'A directory for your book{n}(Defaults to Current Directory \ - when omitted)'") + .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 'skip confirmation prompts'") + .arg_from_usage("--force 'Skips confirmation prompts'") } // Init command implementation diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 22aa3ab9..421c37e1 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -7,7 +7,7 @@ use self::iron::{ }; #[cfg(feature = "watch")] use super::watch; -use clap::{App, ArgMatches, SubCommand}; +use clap::{App, Arg, ArgMatches, SubCommand}; use mdbook::errors::*; use mdbook::utils; use mdbook::MDBook; @@ -19,23 +19,52 @@ struct ErrorRecover; // Create clap subcommand arguments pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("serve") - .about("Serve the book at http://localhost:3000. Rebuild and reload on change.") + .about("Serves a book at http://localhost:3000, and rebuilds it on changes") .arg_from_usage( - "[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'", - ) - .arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'") - .arg_from_usage( - "-w, --websocket-port=[ws-port] 'Use another port for the websocket connection \ - (livereload){n}(Defaults to 3001)'", + "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ + (If omitted, uses build.build-dir from book.toml or defaults to ./book)'", ) .arg_from_usage( - "-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'", + "[dir] 'Root directory for the book{n}\ + (Defaults to the Current Directory when omitted)'", ) - .arg_from_usage( - "-a, --address=[address] 'Address that the browser can reach the websocket server \ - from{n}(Defaults to the interface address)'", + .arg( + Arg::with_name("hostname") + .short("n") + .long("hostname") + .takes_value(true) + .default_value("localhost") + .empty_values(false) + .help("Hostname to listen on for HTTP connections"), ) - .arg_from_usage("-o, --open 'Open the book server in a web browser'") + .arg( + Arg::with_name("port") + .short("p") + .long("port") + .takes_value(true) + .default_value("3000") + .empty_values(false) + .help("Port to use for HTTP connections"), + ) + .arg( + Arg::with_name("websocket-hostname") + .long("websocket-hostname") + .takes_value(true) + .empty_values(false) + .help( + "Hostname to connect to for WebSockets connections (Defaults to the HTTP hostname)", + ), + ) + .arg( + Arg::with_name("websocket-port") + .short("w") + .long("websocket-port") + .takes_value(true) + .default_value("3001") + .empty_values(false) + .help("Port to use for WebSockets livereload connections"), + ) + .arg_from_usage("-o, --open 'Opens the book server in a web browser'") } // Watch command implementation @@ -43,19 +72,23 @@ 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_or("3000"); - let ws_port = args.value_of("websocket-port").unwrap_or("3001"); - let interface = args.value_of("interface").unwrap_or("localhost"); - let public_address = args.value_of("address").unwrap_or(interface); + let port = args.value_of("port").unwrap(); + let ws_port = args.value_of("websocket-port").unwrap(); + let hostname = args.value_of("hostname").unwrap(); + let public_address = args.value_of("websocket-address").unwrap_or(hostname); let open_browser = args.is_present("open"); - let address = format!("{}:{}", interface, port); - let ws_address = format!("{}:{}", interface, ws_port); + let address = format!("{}:{}", hostname, port); + let ws_address = format!("{}:{}", hostname, ws_port); let livereload_url = format!("ws://{}:{}", public_address, ws_port); book.config .set("output.html.livereload-url", &livereload_url)?; + if let Some(dest_dir) = args.value_of("dest-dir") { + book.config.build.build_dir = dest_dir.into(); + } + book.build()?; let mut chain = Chain::new(staticfile::Static::new(book.build_dir_for("html"))); @@ -87,10 +120,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> { // FIXME: This area is really ugly because we need to re-set livereload :( - let livereload_url = livereload_url.clone(); - let result = MDBook::load(&book_dir) - .and_then(move |mut b| { + .and_then(|mut b| { b.config.set("output.html.livereload-url", &livereload_url)?; Ok(b) }) diff --git a/src/cmd/test.rs b/src/cmd/test.rs index 8409590a..dac841c6 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -1,4 +1,4 @@ -use clap::{App, ArgMatches, SubCommand}; +use clap::{App, Arg, ArgMatches, SubCommand}; use get_book_dir; use mdbook::errors::Result; use mdbook::MDBook; @@ -6,11 +6,24 @@ use mdbook::MDBook; // Create clap subcommand arguments pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("test") - .about("Test that code samples compile") - .arg_from_usage("-L, --library-path [DIR]... 'directories to add to crate search path'") + .about("Tests that a book's Rust code samples compile") .arg_from_usage( - "[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'", + "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ + (If omitted, 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") + .long("library-path") + .value_name("dir") + .takes_value(true) + .require_delimiter(true) + .multiple(true) + .empty_values(false) + .help("A comma-separated list of directories to add to {n}the crate search path when building tests")) } // test command implementation @@ -21,6 +34,10 @@ 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") { + book.config.build.build_dir = dest_dir.into(); + } + book.test(library_paths)?; Ok(()) diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index e5848970..0f809251 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -13,11 +13,16 @@ use {get_book_dir, open}; // Create clap subcommand arguments pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("watch") - .about("Watch the files for changes") - .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .about("Watches a book's files and rebuilds it on changes") .arg_from_usage( - "[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'", + "-d, --dest-dir=[dest-dir] 'Output directory for the book{n}\ + (If omitted, 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'") } // Watch command implementation diff --git a/src/main.rs b/src/main.rs index 8a1a2e9b..86147532 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,26 +20,27 @@ use std::path::{Path, PathBuf}; mod cmd; -const NAME: &'static str = "mdbook"; +const NAME: &'static str = "mdBook"; +const VERSION: &'static str = concat!("v", crate_version!()); fn main() { init_logger(); // Create a list of valid arguments and sub-commands let app = App::new(NAME) - .about("Create a book in form of a static website from markdown files") - .author("Mathieu David ") - // Get the version from our Cargo.toml using clap's crate_version!() macro - .version(concat!("v",crate_version!())) - .setting(AppSettings::ArgRequiredElseHelp) - .after_help("For more information about a specific command, \ - try `mdbook --help`\n\ - Source code for mdbook available \ - at: https://github.com/rust-lang-nursery/mdBook") - .subcommand(cmd::init::make_subcommand()) - .subcommand(cmd::build::make_subcommand()) - .subcommand(cmd::test::make_subcommand()) - .subcommand(cmd::clean::make_subcommand()); + .about("Creates a book from markdown files") + .author("Mathieu David ") + .version(VERSION) + .setting(AppSettings::GlobalVersion) + .setting(AppSettings::ArgRequiredElseHelp) + .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-nursery/mdBook", + ) + .subcommand(cmd::init::make_subcommand()) + .subcommand(cmd::build::make_subcommand()) + .subcommand(cmd::test::make_subcommand()) + .subcommand(cmd::clean::make_subcommand()); #[cfg(feature = "watch")] let app = app.subcommand(cmd::watch::make_subcommand());