From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/mdbook/src/cmd/build.rs | 50 ++++++++++++ vendor/mdbook/src/cmd/clean.rs | 44 ++++++++++ vendor/mdbook/src/cmd/init.rs | 126 +++++++++++++++++++++++++++++ vendor/mdbook/src/cmd/mod.rs | 10 +++ vendor/mdbook/src/cmd/serve.rs | 177 +++++++++++++++++++++++++++++++++++++++++ vendor/mdbook/src/cmd/test.rs | 54 +++++++++++++ vendor/mdbook/src/cmd/watch.rs | 175 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 636 insertions(+) create mode 100644 vendor/mdbook/src/cmd/build.rs create mode 100644 vendor/mdbook/src/cmd/clean.rs create mode 100644 vendor/mdbook/src/cmd/init.rs create mode 100644 vendor/mdbook/src/cmd/mod.rs create mode 100644 vendor/mdbook/src/cmd/serve.rs create mode 100644 vendor/mdbook/src/cmd/test.rs create mode 100644 vendor/mdbook/src/cmd/watch.rs (limited to 'vendor/mdbook/src/cmd') diff --git a/vendor/mdbook/src/cmd/build.rs b/vendor/mdbook/src/cmd/build.rs new file mode 100644 index 000000000..5fe73236c --- /dev/null +++ b/vendor/mdbook/src/cmd/build.rs @@ -0,0 +1,50 @@ +use crate::{get_book_dir, open}; +use clap::{arg, App, Arg, ArgMatches}; +use mdbook::errors::Result; +use mdbook::MDBook; + +// Create clap subcommand arguments +pub fn make_subcommand<'help>() -> App<'help> { + App::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")) +} + +// Build command implementation +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.build()?; + + if args.is_present("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() { + error!("No chapter available to open"); + std::process::exit(1) + } + open(path); + } + + Ok(()) +} diff --git a/vendor/mdbook/src/cmd/clean.rs b/vendor/mdbook/src/cmd/clean.rs new file mode 100644 index 000000000..0569726e1 --- /dev/null +++ b/vendor/mdbook/src/cmd/clean.rs @@ -0,0 +1,44 @@ +use crate::get_book_dir; +use anyhow::Context; +use clap::{arg, App, Arg, ArgMatches}; +use mdbook::MDBook; +use std::fs; + +// Create clap subcommand arguments +pub fn make_subcommand<'help>() -> App<'help> { + App::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)" + )) +} + +// Clean command implementation +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") { + Some(dest_dir) => dest_dir.into(), + None => book.root.join(&book.config.build.build_dir), + }; + + if dir_to_remove.exists() { + fs::remove_dir_all(&dir_to_remove) + .with_context(|| "Unable to remove the build directory")?; + } + + Ok(()) +} diff --git a/vendor/mdbook/src/cmd/init.rs b/vendor/mdbook/src/cmd/init.rs new file mode 100644 index 000000000..c964dcc13 --- /dev/null +++ b/vendor/mdbook/src/cmd/init.rs @@ -0,0 +1,126 @@ +use crate::get_book_dir; +use clap::{arg, App, Arg, ArgMatches}; +use mdbook::config; +use mdbook::errors::Result; +use mdbook::MDBook; +use std::io; +use std::io::Write; +use std::process::Command; + +// Create clap subcommand arguments +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(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::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), + ) +} + +// Init command implementation +pub fn execute(args: &ArgMatches) -> Result<()> { + let book_dir = get_book_dir(args); + 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") { + 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() { + println!("This could potentially overwrite files already present in that directory."); + print!("\nAre you sure you want to continue? (y/n) "); + + // Read answer from user and exit if it's not 'yes' + if confirm() { + builder.copy_theme(true); + } + } else { + builder.copy_theme(true); + } + } + + if let Some(ignore) = args.value_of("ignore") { + match ignore { + "git" => builder.create_gitignore(true), + _ => builder.create_gitignore(false), + }; + } else { + println!("\nDo you want a .gitignore to be created? (y/n)"); + if confirm() { + builder.create_gitignore(true); + } + } + + config.book.title = if args.is_present("title") { + args.value_of("title").map(String::from) + } else { + request_book_title() + }; + + if let Some(author) = get_author_name() { + debug!("Obtained user name from gitconfig: {:?}", author); + config.book.authors.push(author); + builder.with_config(config); + } + + builder.build()?; + println!("\nAll done, no errors..."); + + Ok(()) +} + +/// Obtains author name from git config file by running the `git config` command. +fn get_author_name() -> Option { + let output = Command::new("git") + .args(&["config", "--get", "user.name"]) + .output() + .ok()?; + + if output.status.success() { + Some(String::from_utf8_lossy(&output.stdout).trim().to_owned()) + } else { + None + } +} + +/// Request book title from user and return if provided. +fn request_book_title() -> Option { + println!("What title would you like to give the book? "); + io::stdout().flush().unwrap(); + let mut resp = String::new(); + io::stdin().read_line(&mut resp).unwrap(); + let resp = resp.trim(); + if resp.is_empty() { + None + } else { + Some(resp.into()) + } +} + +// Simple function for user confirmation +fn confirm() -> bool { + io::stdout().flush().unwrap(); + let mut s = String::new(); + io::stdin().read_line(&mut s).ok(); + matches!(&*s.trim(), "Y" | "y" | "yes" | "Yes") +} diff --git a/vendor/mdbook/src/cmd/mod.rs b/vendor/mdbook/src/cmd/mod.rs new file mode 100644 index 000000000..c5b6730f1 --- /dev/null +++ b/vendor/mdbook/src/cmd/mod.rs @@ -0,0 +1,10 @@ +//! Subcommand modules for the `mdbook` binary. + +pub mod build; +pub mod clean; +pub mod init; +#[cfg(feature = "serve")] +pub mod serve; +pub mod test; +#[cfg(feature = "watch")] +pub mod watch; diff --git a/vendor/mdbook/src/cmd/serve.rs b/vendor/mdbook/src/cmd/serve.rs new file mode 100644 index 000000000..bafbfd52e --- /dev/null +++ b/vendor/mdbook/src/cmd/serve.rs @@ -0,0 +1,177 @@ +#[cfg(feature = "watch")] +use super::watch; +use crate::{get_book_dir, open}; +use clap::{arg, App, Arg, ArgMatches}; +use futures_util::sink::SinkExt; +use futures_util::StreamExt; +use mdbook::errors::*; +use mdbook::utils; +use mdbook::utils::fs::get_404_output_file; +use mdbook::MDBook; +use std::net::{SocketAddr, ToSocketAddrs}; +use std::path::PathBuf; +use tokio::sync::broadcast; +use warp::ws::Message; +use warp::Filter; + +/// The HTTP endpoint for the websocket used to trigger reloads when a file changes. +const LIVE_RELOAD_ENDPOINT: &str = "__livereload"; + +// Create clap subcommand arguments +pub fn make_subcommand<'help>() -> App<'help> { + App::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( + Arg::new("hostname") + .short('n') + .long("hostname") + .takes_value(true) + .default_value("localhost") + .forbid_empty_values(true) + .help("Hostname to listen on for HTTP connections"), + ) + .arg( + Arg::new("port") + .short('p') + .long("port") + .takes_value(true) + .default_value("3000") + .forbid_empty_values(true) + .help("Port to use for HTTP connections"), + ) + .arg(arg!(-o --open "Opens the compiled book in a web browser")) +} + +// Serve command implementation +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 address = format!("{}:{}", hostname, port); + + let update_config = |book: &mut MDBook| { + book.config + .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") { + book.config.build.build_dir = dest_dir.into(); + } + // Override site-url for local serving of the 404 file + book.config.set("output.html.site-url", "/").unwrap(); + }; + update_config(&mut book); + book.build()?; + + let sockaddr: SocketAddr = address + .to_socket_addrs()? + .next() + .ok_or_else(|| anyhow::anyhow!("no address found for {}", address))?; + let build_dir = book.build_dir_for("html"); + let input_404 = book + .config + .get("output.html.input-404") + .map(toml::Value::as_str) + .and_then(std::convert::identity) // flatten + .map(ToString::to_string); + let file_404 = get_404_output_file(&input_404); + + // A channel used to broadcast to any websockets to reload when a file changes. + let (tx, _rx) = tokio::sync::broadcast::channel::(100); + + let reload_tx = tx.clone(); + let thread_handle = std::thread::spawn(move || { + serve(build_dir, sockaddr, reload_tx, &file_404); + }); + + let serving_url = format!("http://{}", address); + info!("Serving on: {}", serving_url); + + if open_browser { + open(serving_url); + } + + #[cfg(feature = "watch")] + watch::trigger_on_change(&book, move |paths, book_dir| { + info!("Files changed: {:?}", paths); + info!("Building book..."); + + // FIXME: This area is really ugly because we need to re-set livereload :( + let result = MDBook::load(&book_dir).and_then(|mut b| { + update_config(&mut b); + b.build() + }); + + if let Err(e) = result { + error!("Unable to load the book"); + utils::log_backtrace(&e); + } else { + let _ = tx.send(Message::text("reload")); + } + }); + + let _ = thread_handle.join(); + + Ok(()) +} + +#[tokio::main] +async fn serve( + build_dir: PathBuf, + address: SocketAddr, + reload_tx: broadcast::Sender, + file_404: &str, +) { + // A warp Filter which captures `reload_tx` and provides an `rx` copy to + // receive reload messages. + let sender = warp::any().map(move || reload_tx.subscribe()); + + // A warp Filter to handle the livereload endpoint. This upgrades to a + // websocket, and then waits for any filesystem change notifications, and + // relays them over the websocket. + let livereload = warp::path(LIVE_RELOAD_ENDPOINT) + .and(warp::ws()) + .and(sender) + .map(|ws: warp::ws::Ws, mut rx: broadcast::Receiver| { + ws.on_upgrade(move |ws| async move { + let (mut user_ws_tx, _user_ws_rx) = ws.split(); + trace!("websocket got connection"); + if let Ok(m) = rx.recv().await { + trace!("notify of reload"); + let _ = user_ws_tx.send(m).await; + } + }) + }); + // A warp Filter that serves from the filesystem. + let book_route = warp::fs::dir(build_dir.clone()); + // The fallback route for 404 errors + let fallback_route = warp::fs::file(build_dir.join(file_404)) + .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND)); + let routes = livereload.or(book_route).or(fallback_route); + + std::panic::set_hook(Box::new(move |panic_info| { + // exit if serve panics + error!("Unable to serve: {}", panic_info); + std::process::exit(1); + })); + + warp::serve(routes).run(address).await; +} diff --git a/vendor/mdbook/src/cmd/test.rs b/vendor/mdbook/src/cmd/test.rs new file mode 100644 index 000000000..02f982a49 --- /dev/null +++ b/vendor/mdbook/src/cmd/test.rs @@ -0,0 +1,54 @@ +use crate::get_book_dir; +use clap::{arg, App, Arg, ArgMatches}; +use mdbook::errors::Result; +use mdbook::MDBook; + +// Create clap subcommand arguments +pub fn make_subcommand<'help>() -> App<'help> { + App::new("test") + .about("Tests that a book's Rust code samples compile") + .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::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) + .unwrap_or_default(); + 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/vendor/mdbook/src/cmd/watch.rs b/vendor/mdbook/src/cmd/watch.rs new file mode 100644 index 000000000..9336af779 --- /dev/null +++ b/vendor/mdbook/src/cmd/watch.rs @@ -0,0 +1,175 @@ +use crate::{get_book_dir, open}; +use clap::{arg, App, Arg, ArgMatches}; +use mdbook::errors::Result; +use mdbook::utils; +use mdbook::MDBook; +use notify::Watcher; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::channel; +use std::thread::sleep; +use std::time::Duration; + +// Create clap subcommand arguments +pub fn make_subcommand<'help>() -> App<'help> { + App::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")) +} + +// Watch command implementation +pub fn execute(args: &ArgMatches) -> Result<()> { + let book_dir = get_book_dir(args); + let mut book = MDBook::load(&book_dir)?; + + let update_config = |book: &mut MDBook| { + if let Some(dest_dir) = args.value_of("dest-dir") { + book.config.build.build_dir = dest_dir.into(); + } + }; + update_config(&mut book); + + if args.is_present("open") { + book.build()?; + let path = book.build_dir_for("html").join("index.html"); + if !path.exists() { + error!("No chapter available to open"); + std::process::exit(1) + } + open(path); + } + + trigger_on_change(&book, |paths, book_dir| { + info!("Files changed: {:?}\nBuilding book...\n", paths); + let result = MDBook::load(&book_dir).and_then(|mut b| { + update_config(&mut b); + b.build() + }); + + if let Err(e) = result { + error!("Unable to build the book"); + utils::log_backtrace(&e); + } + }); + + Ok(()) +} + +fn remove_ignored_files(book_root: &Path, paths: &[PathBuf]) -> Vec { + if paths.is_empty() { + return vec![]; + } + + match find_gitignore(book_root) { + Some(gitignore_path) => { + match gitignore::File::new(gitignore_path.as_path()) { + Ok(exclusion_checker) => filter_ignored_files(exclusion_checker, paths), + Err(_) => { + // We're unable to read the .gitignore file, so we'll silently allow everything. + // Please see discussion: https://github.com/rust-lang/mdBook/pull/1051 + paths.iter().map(|path| path.to_path_buf()).collect() + } + } + } + None => { + // There is no .gitignore file. + paths.iter().map(|path| path.to_path_buf()).collect() + } + } +} + +fn find_gitignore(book_root: &Path) -> Option { + book_root + .ancestors() + .map(|p| p.join(".gitignore")) + .find(|p| p.exists()) +} + +fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec { + paths + .iter() + .filter(|path| match exclusion_checker.is_excluded(path) { + Ok(exclude) => !exclude, + Err(error) => { + warn!( + "Unable to determine if {:?} is excluded: {:?}. Including it.", + &path, error + ); + true + } + }) + .map(|path| path.to_path_buf()) + .collect() +} + +/// Calls the closure when a book source file is changed, blocking indefinitely. +pub fn trigger_on_change(book: &MDBook, closure: F) +where + F: Fn(Vec, &Path), +{ + use notify::DebouncedEvent::*; + use notify::RecursiveMode::*; + + // Create a channel to receive the events. + let (tx, rx) = channel(); + + let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) { + Ok(w) => w, + Err(e) => { + error!("Error while trying to watch the files:\n\n\t{:?}", e); + std::process::exit(1) + } + }; + + // Add the source directory to the watcher + if let Err(e) = watcher.watch(book.source_dir(), Recursive) { + error!("Error while watching {:?}:\n {:?}", book.source_dir(), e); + std::process::exit(1); + }; + + let _ = watcher.watch(book.theme_dir(), Recursive); + + // Add the book.toml file to the watcher if it exists + let _ = watcher.watch(book.root.join("book.toml"), NonRecursive); + + info!("Listening for changes..."); + + loop { + let first_event = rx.recv().unwrap(); + sleep(Duration::from_millis(50)); + let other_events = rx.try_iter(); + + let all_events = std::iter::once(first_event).chain(other_events); + + let paths = all_events + .filter_map(|event| { + debug!("Received filesystem event: {:?}", event); + + match event { + Create(path) | Write(path) | Remove(path) | Rename(_, path) => Some(path), + _ => None, + } + }) + .collect::>(); + + let paths = remove_ignored_files(&book.root, &paths[..]); + + if !paths.is_empty() { + closure(paths, &book.root); + } + } +} -- cgit v1.2.3