summaryrefslogtreecommitdiffstats
path: root/vendor/mdbook/src/cmd
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /vendor/mdbook/src/cmd
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/mdbook/src/cmd')
-rw-r--r--vendor/mdbook/src/cmd/build.rs50
-rw-r--r--vendor/mdbook/src/cmd/clean.rs44
-rw-r--r--vendor/mdbook/src/cmd/init.rs126
-rw-r--r--vendor/mdbook/src/cmd/mod.rs10
-rw-r--r--vendor/mdbook/src/cmd/serve.rs177
-rw-r--r--vendor/mdbook/src/cmd/test.rs54
-rw-r--r--vendor/mdbook/src/cmd/watch.rs175
7 files changed, 636 insertions, 0 deletions
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<String> {
+ 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<String> {
+ 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::<Message>(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<Message>,
+ 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<Message>| {
+ 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<PathBuf> {
+ 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<PathBuf> {
+ book_root
+ .ancestors()
+ .map(|p| p.join(".gitignore"))
+ .find(|p| p.exists())
+}
+
+fn filter_ignored_files(exclusion_checker: gitignore::File, paths: &[PathBuf]) -> Vec<PathBuf> {
+ 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<F>(book: &MDBook, closure: F)
+where
+ F: Fn(Vec<PathBuf>, &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::<Vec<_>>();
+
+ let paths = remove_ignored_files(&book.root, &paths[..]);
+
+ if !paths.is_empty() {
+ closure(paths, &book.root);
+ }
+ }
+}