From 49704cba3007988d9be08cc72e05d86631e4c991 Mon Sep 17 00:00:00 2001 From: Aneesh-382005 Date: Thu, 30 Jul 2026 14:34:14 +0530 Subject: [PATCH 1/2] Move doc() and man() into a new docs module --- src/cli.rs | 1 + src/cli/docs.rs | 249 +++++++++++++++++++++++++++++++++++++++++ src/cli/rustup_mode.rs | 242 +-------------------------------------- 3 files changed, 256 insertions(+), 236 deletions(-) create mode 100644 src/cli/docs.rs diff --git a/src/cli.rs b/src/cli.rs index 88a7586ce2..31807fa261 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,6 +2,7 @@ #[macro_use] pub mod log; pub mod common; +mod docs; pub mod errors; mod help; mod job; diff --git a/src/cli/docs.rs b/src/cli/docs.rs new file mode 100644 index 0000000000..8c0c82a288 --- /dev/null +++ b/src/cli/docs.rs @@ -0,0 +1,249 @@ +//! `rustup doc` and `rustup man`: opening toolchain documentation and man pages. + +use std::{ + borrow::Cow, + io::Write as _, + path::{Path, PathBuf}, +}; + +use anyhow::{Context, Result, anyhow}; +use clap::Args; +use tracing::info; + +use super::topical_doc; +use crate::{ + config::{ActiveSource, Cfg}, + dist::{PartialToolchainDesc, manifest::ComponentStatus}, + toolchain::DistributableToolchain, + utils::{self, ExitCode}, +}; + +macro_rules! docs_data { + ( + $( + $( #[$meta:meta] )* + ($ident:ident, $help:expr, $path:expr $(,)?) + ),+ $(,)? + ) => { + #[derive(Debug, Args)] + pub(crate) struct DocPage { + $( + #[doc = $help] + #[arg(long, group = "page")] + $( #[$meta] )* + $ident: bool, + )+ + } + + impl DocPage { + fn path_str(&self) -> Option<&'static str> { + $( if self.$ident { return Some($path); } )+ + None + } + } + }; +} + +docs_data![ + // flags can be used to open specific documents, e.g. `rustup doc --nomicon` + // tuple elements: document name used as flag, help message, document index path + ( + alloc, + "The Rust core allocation and collections library", + "alloc/index.html" + ), + ( + book, + "The Rust Programming Language book", + "book/index.html" + ), + (cargo, "The Cargo Book", "cargo/index.html"), + (clippy, "The Clippy Documentation", "clippy/index.html"), + (core, "The Rust Core Library", "core/index.html"), + ( + edition_guide, + "The Rust Edition Guide", + "edition-guide/index.html" + ), + ( + embedded_book, + "The Embedded Rust Book", + "embedded-book/index.html" + ), + ( + error_codes, + "The Rust Error Codes Index", + "error_codes/index.html" + ), + ( + nomicon, + "The Dark Arts of Advanced and Unsafe Rust Programming", + "nomicon/index.html" + ), + #[arg(long = "proc_macro")] + ( + proc_macro, + "A support library for macro authors when defining new macros", + "proc_macro/index.html" + ), + (reference, "The Rust Reference", "reference/index.html"), + (releases, "Rust Release Notes", "releases.html"), + ( + rust_by_example, + "A collection of runnable examples that illustrate various Rust concepts and standard libraries", + "rust-by-example/index.html" + ), + ( + rustc, + "The compiler for the Rust programming language", + "rustc/index.html" + ), + ( + rustc_docs, + "The API documentation for the Rust compiler and other toolchain components", + "rustc-docs/index.html" + ), + ( + rustdoc, + "Documentation generator for Rust projects", + "rustdoc/index.html" + ), + (std, "Standard library API documentation", "std/index.html"), + ( + style_guide, + "The Rust Style Guide", + "style-guide/index.html" + ), + ( + test, + "Support code for rustc's built in unit-test and micro-benchmarking framework", + "test/index.html" + ), + ( + unstable_book, + "The Unstable Book", + "unstable-book/index.html" + ), +]; + +impl DocPage { + fn path(&self) -> Option<&'static Path> { + self.path_str().map(Path::new) + } + + fn name(&self) -> Option<&'static str> { + Some(self.path_str()?.rsplit_once('/')?.0) + } + + fn resolve<'t>(&self, root: &Path, topic: &'t str) -> Option<(PathBuf, Option<&'t str>)> { + // Use `.parent()` to chop off the default top-level `index.html`. + let mut base = root.join(Path::new(self.path()?).parent()?); + base.extend(topic.split("::")); + let base_index_html = base.join("index.html"); + + if base_index_html.is_file() { + return Some((base_index_html, None)); + } + + let base_html = base.with_extension("html"); + if base_html.is_file() { + return Some((base_html, None)); + } + + let parent_html = base.parent()?.with_extension("html"); + if parent_html.is_file() { + return Some((parent_html, topic.rsplit_once("::").map(|(_, s)| s))); + } + + None + } +} + +pub(crate) async fn doc( + cfg: &Cfg<'_>, + path_only: bool, + toolchain: Option, + mut topic: Option<&str>, + doc_page: &DocPage, +) -> Result { + let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); + let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; + + if let Ok(distributable) = DistributableToolchain::try_from(&toolchain) + && let [_] = distributable + .components()? + .into_iter() + .filter(|cstatus| cstatus.component.short_name() == "rust-docs" && !cstatus.installed) + .take(1) + .collect::>() + .as_slice() + { + info!( + "`rust-docs` not installed in toolchain `{}`\nhelp: run `rustup component add --toolchain {} rust-docs` to install it", + distributable.desc(), + distributable.desc() + ); + return Err(anyhow!( + "unable to view documentation which is not installed" + )); + }; + + let (doc_path, fragment) = match (topic, doc_page.name()) { + (Some(topic), Some(name)) => { + let (doc_path, fragment) = doc_page + .resolve(&toolchain.doc_path("")?, topic) + .context(format!("no document for {name} on {topic}"))?; + (Cow::Owned(doc_path), fragment) + } + (Some(topic), None) => { + let doc_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?; + (Cow::Owned(doc_path), None) + } + (None, name) => { + topic = name; + let doc_path = doc_page.path().unwrap_or_else(|| Path::new("index.html")); + (Cow::Borrowed(doc_path), None) + } + }; + + if path_only { + let doc_path = toolchain.doc_path(&doc_path)?; + writeln!(cfg.process.stdout().lock(), "{}", doc_path.display())?; + return Ok(ExitCode::SUCCESS); + } + + if let Some(name) = topic { + writeln!( + cfg.process.stderr().lock(), + "Opening docs named `{name}` in your browser" + )?; + } else { + writeln!(cfg.process.stderr().lock(), "Opening docs in your browser")?; + } + toolchain.open_docs(&doc_path, fragment)?; + Ok(ExitCode::SUCCESS) +} + +#[cfg(not(windows))] +pub(crate) async fn man( + cfg: &Cfg<'_>, + command: &str, + toolchain: Option, +) -> Result { + let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); + let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; + let path = toolchain.man_path(); + utils::assert_is_directory(&path)?; + + let mut manpaths = std::ffi::OsString::from(path); + manpaths.push(":"); // prepend to the default MANPATH list + if let Some(path) = cfg.process.var_os("MANPATH") { + manpaths.push(path); + } + std::process::Command::new("man") + .env("MANPATH", manpaths) + .arg(command) + .status() + .expect("failed to open man page"); + Ok(ExitCode::SUCCESS) +} diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index d73e19a30a..4396492b8a 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -12,7 +12,7 @@ use std::{ use anstream::ColorChoice; use anstyle::Style; -use anyhow::{Context, Error, Result, anyhow}; +use anyhow::{Error, Result, anyhow}; use clap::{ Args, CommandFactory, Parser, Subcommand, ValueEnum, builder::{PossibleValue, ValueHint}, @@ -29,6 +29,7 @@ use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; use crate::{ cli::{ common::{self, PackageUpdate, update_console_filter}, + docs, errors::CliError, help::{ check_help, completions_help, default_help, doc_help, install_help, @@ -39,14 +40,13 @@ use crate::{ update_help, }, self_update::{self, SelfUpdateMode, check_rustup_update}, - topical_doc, }, command, component_for_bin, config::{ActiveSource, Cfg}, dist::{ DistOptions, PartialToolchainDesc, Profile, Switch, TargetTuple, download::DownloadCfg, - manifest::{Component, ComponentStatus, ManifestWithHash}, + manifest::{Component, ManifestWithHash}, }, errors::{DEFAULT_STABLE_HINT, RustupError}, install::{InstallMethod, UpdateStatus}, @@ -259,7 +259,7 @@ enum RustupSubcmd { topic: Option, #[command(flatten)] - page: DocPage, + page: docs::DocPage, }, /// View the man page for a given command @@ -820,9 +820,9 @@ pub async fn main( toolchain, topic, page, - } => doc(cfg, path, toolchain, topic.as_deref(), &page).await, + } => docs::doc(cfg, path, toolchain, topic.as_deref(), &page).await, #[cfg(not(windows))] - RustupSubcmd::Man { command, toolchain } => man(cfg, &command, toolchain).await, + RustupSubcmd::Man { command, toolchain } => docs::man(cfg, &command, toolchain).await, RustupSubcmd::Self_ { subcmd } => match subcmd { SelfSubcmd::Update => self_update::update(cfg).await, SelfSubcmd::Uninstall { @@ -1741,236 +1741,6 @@ fn override_remove(cfg: &Cfg<'_>, path: Option<&Path>, nonexistent: bool) -> Res Ok(ExitCode::SUCCESS) } -macro_rules! docs_data { - ( - $( - $( #[$meta:meta] )* - ($ident:ident, $help:expr, $path:expr $(,)?) - ),+ $(,)? - ) => { - #[derive(Debug, Args)] - struct DocPage { - $( - #[doc = $help] - #[arg(long, group = "page")] - $( #[$meta] )* - $ident: bool, - )+ - } - - impl DocPage { - fn path_str(&self) -> Option<&'static str> { - $( if self.$ident { return Some($path); } )+ - None - } - } - }; -} - -docs_data![ - // flags can be used to open specific documents, e.g. `rustup doc --nomicon` - // tuple elements: document name used as flag, help message, document index path - ( - alloc, - "The Rust core allocation and collections library", - "alloc/index.html" - ), - ( - book, - "The Rust Programming Language book", - "book/index.html" - ), - (cargo, "The Cargo Book", "cargo/index.html"), - (clippy, "The Clippy Documentation", "clippy/index.html"), - (core, "The Rust Core Library", "core/index.html"), - ( - edition_guide, - "The Rust Edition Guide", - "edition-guide/index.html" - ), - ( - embedded_book, - "The Embedded Rust Book", - "embedded-book/index.html" - ), - ( - error_codes, - "The Rust Error Codes Index", - "error_codes/index.html" - ), - ( - nomicon, - "The Dark Arts of Advanced and Unsafe Rust Programming", - "nomicon/index.html" - ), - #[arg(long = "proc_macro")] - ( - proc_macro, - "A support library for macro authors when defining new macros", - "proc_macro/index.html" - ), - (reference, "The Rust Reference", "reference/index.html"), - (releases, "Rust Release Notes", "releases.html"), - ( - rust_by_example, - "A collection of runnable examples that illustrate various Rust concepts and standard libraries", - "rust-by-example/index.html" - ), - ( - rustc, - "The compiler for the Rust programming language", - "rustc/index.html" - ), - ( - rustc_docs, - "The API documentation for the Rust compiler and other toolchain components", - "rustc-docs/index.html" - ), - ( - rustdoc, - "Documentation generator for Rust projects", - "rustdoc/index.html" - ), - (std, "Standard library API documentation", "std/index.html"), - ( - style_guide, - "The Rust Style Guide", - "style-guide/index.html" - ), - ( - test, - "Support code for rustc's built in unit-test and micro-benchmarking framework", - "test/index.html" - ), - ( - unstable_book, - "The Unstable Book", - "unstable-book/index.html" - ), -]; - -impl DocPage { - fn path(&self) -> Option<&'static Path> { - self.path_str().map(Path::new) - } - - fn name(&self) -> Option<&'static str> { - Some(self.path_str()?.rsplit_once('/')?.0) - } - - fn resolve<'t>(&self, root: &Path, topic: &'t str) -> Option<(PathBuf, Option<&'t str>)> { - // Use `.parent()` to chop off the default top-level `index.html`. - let mut base = root.join(Path::new(self.path()?).parent()?); - base.extend(topic.split("::")); - let base_index_html = base.join("index.html"); - - if base_index_html.is_file() { - return Some((base_index_html, None)); - } - - let base_html = base.with_extension("html"); - if base_html.is_file() { - return Some((base_html, None)); - } - - let parent_html = base.parent()?.with_extension("html"); - if parent_html.is_file() { - return Some((parent_html, topic.rsplit_once("::").map(|(_, s)| s))); - } - - None - } -} - -async fn doc( - cfg: &Cfg<'_>, - path_only: bool, - toolchain: Option, - mut topic: Option<&str>, - doc_page: &DocPage, -) -> Result { - let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); - let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; - - if let Ok(distributable) = DistributableToolchain::try_from(&toolchain) - && let [_] = distributable - .components()? - .into_iter() - .filter(|cstatus| cstatus.component.short_name() == "rust-docs" && !cstatus.installed) - .take(1) - .collect::>() - .as_slice() - { - info!( - "`rust-docs` not installed in toolchain `{}`\nhelp: run `rustup component add --toolchain {} rust-docs` to install it", - distributable.desc(), - distributable.desc() - ); - return Err(anyhow!( - "unable to view documentation which is not installed" - )); - }; - - let (doc_path, fragment) = match (topic, doc_page.name()) { - (Some(topic), Some(name)) => { - let (doc_path, fragment) = doc_page - .resolve(&toolchain.doc_path("")?, topic) - .context(format!("no document for {name} on {topic}"))?; - (Cow::Owned(doc_path), fragment) - } - (Some(topic), None) => { - let doc_path = topical_doc::local_path(&toolchain.doc_path("").unwrap(), topic)?; - (Cow::Owned(doc_path), None) - } - (None, name) => { - topic = name; - let doc_path = doc_page.path().unwrap_or_else(|| Path::new("index.html")); - (Cow::Borrowed(doc_path), None) - } - }; - - if path_only { - let doc_path = toolchain.doc_path(&doc_path)?; - writeln!(cfg.process.stdout().lock(), "{}", doc_path.display())?; - return Ok(ExitCode::SUCCESS); - } - - if let Some(name) = topic { - writeln!( - cfg.process.stderr().lock(), - "Opening docs named `{name}` in your browser" - )?; - } else { - writeln!(cfg.process.stderr().lock(), "Opening docs in your browser")?; - } - toolchain.open_docs(&doc_path, fragment)?; - Ok(ExitCode::SUCCESS) -} - -#[cfg(not(windows))] -async fn man( - cfg: &Cfg<'_>, - command: &str, - toolchain: Option, -) -> Result { - let toolchain = toolchain.map(|desc| (desc, ActiveSource::CommandLine)); - let toolchain = cfg.toolchain_from_partial(toolchain).await?.0; - let path = toolchain.man_path(); - utils::assert_is_directory(&path)?; - - let mut manpaths = std::ffi::OsString::from(path); - manpaths.push(":"); // prepend to the default MANPATH list - if let Some(path) = cfg.process.var_os("MANPATH") { - manpaths.push(path); - } - std::process::Command::new("man") - .env("MANPATH", manpaths) - .arg(command) - .status() - .expect("failed to open man page"); - Ok(ExitCode::SUCCESS) -} - fn set_auto_self_update(cfg: &Cfg<'_>, auto_self_update_mode: SelfUpdateMode) -> Result { if cfg!(feature = "no-self-update") { let mut args = cfg.process.args_os(); From e2349d30ef39eddb73a37c0ace21ddad62960a71 Mon Sep 17 00:00:00 2001 From: Aneesh-382005 Date: Sat, 1 Aug 2026 00:05:18 +0530 Subject: [PATCH 2/2] Add `rustup doc --serve` to serve docs over local HTTP --- Cargo.toml | 6 +- src/cli/docs.rs | 134 +++++++++++++++++- src/cli/help.rs | 6 +- src/cli/rustup_mode.rs | 8 +- .../rustup_doc_cmd_help_flag.stdout.term.svg | 82 ++++++----- 5 files changed, 195 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 495287536b..eeb5769af8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,9 @@ fs_at = "0.2.1" futures-util = "0.3.31" git-testament = "0.2" home = "0.5.4" +http-body-util = "0.1.0" +hyper = { version = "1.0", default-features = false, features = ["server", "http1"] } +hyper-util = { version = "0.1.1", features = ["tokio"] } indicatif = "0.18" itertools = "0.15" libc = "0.2" @@ -141,9 +144,6 @@ libz-sys = "=1.1.24" [dev-dependencies] enum-map = "3.0.0" -http-body-util = "0.1.0" -hyper = { version = "1.0", default-features = false, features = ["server", "http1"] } -hyper-util = { version = "0.1.1", features = ["tokio"] } proptest = "1.1.0" rustls-webpki = { version = "0.103.3" } tokio-rustls = "0.26.4" diff --git a/src/cli/docs.rs b/src/cli/docs.rs index 8c0c82a288..c343da84bd 100644 --- a/src/cli/docs.rs +++ b/src/cli/docs.rs @@ -1,19 +1,41 @@ //! `rustup doc` and `rustup man`: opening toolchain documentation and man pages. +//! +//! `rustup doc --serve` serves the documentation over a local HTTP server +//! instead of opening it directly as a `file://` URL. Some browsers (e.g. +//! Snap/Flatpak builds of Firefox or Brave) run in a sandbox that can't +//! access `file://` URLs under `~/.rustup`; serving the same static files +//! over `http://127.0.0.1` sidesteps that restriction entirely. +//! +//! The server binds to `127.0.0.1` only (never exposed on the network) and +//! rejects any request path that would resolve outside the served directory. use std::{ borrow::Cow, + convert::Infallible, io::Write as _, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, + sync::Arc, }; use anyhow::{Context, Result, anyhow}; use clap::Args; +use http_body_util::Full; +use hyper::{ + Request, Response, StatusCode, + body::{Bytes, Incoming}, + header::{CONTENT_LENGTH, CONTENT_TYPE}, + server::conn::http1, + service::service_fn, +}; +use hyper_util::rt::TokioIo; +use tokio::net::TcpListener; use tracing::info; use super::topical_doc; use crate::{ config::{ActiveSource, Cfg}, dist::{PartialToolchainDesc, manifest::ComponentStatus}, + process::Process, toolchain::DistributableToolchain, utils::{self, ExitCode}, }; @@ -162,6 +184,7 @@ impl DocPage { pub(crate) async fn doc( cfg: &Cfg<'_>, path_only: bool, + serve: bool, toolchain: Option, mut topic: Option<&str>, doc_page: &DocPage, @@ -212,6 +235,12 @@ pub(crate) async fn doc( return Ok(ExitCode::SUCCESS); } + if serve { + let root = toolchain.doc_path("")?; + serve_and_open(root, &doc_path, fragment, cfg.process).await?; + return Ok(ExitCode::SUCCESS); + } + if let Some(name) = topic { writeln!( cfg.process.stderr().lock(), @@ -247,3 +276,106 @@ pub(crate) async fn man( .expect("failed to open man page"); Ok(ExitCode::SUCCESS) } + +/// Blocks forever, accepting and serving connections until the process is +/// killed by Ctrl-C. +async fn serve_and_open( + root: PathBuf, + initial_path: &Path, + fragment: Option<&str>, + process: &Process, +) -> Result<()> { + let listener = TcpListener::bind(("127.0.0.1", 0)) + .await + .context("failed to bind local documentation server")?; + let addr = listener + .local_addr() + .context("failed to read local address")?; + let root = Arc::::from(root); + + let mut url = format!( + "http://{addr}/{}", + initial_path.to_string_lossy().replace('\\', "/") + ); + if let Some(fragment) = fragment { + url.push('#'); + url.push_str(fragment); + } + + writeln!( + process.stderr().lock(), + "Serving docs at {url} (press Ctrl-C to stop)" + )?; + utils::open_browser(&url)?; + + loop { + let (stream, _) = match listener.accept().await { + Ok(accepted) => accepted, + Err(err) => { + tracing::warn!("doc server: failed to accept connection: {err}"); + continue; + } + }; + let io = TokioIo::new(stream); + let root = root.clone(); + let svc = service_fn(move |req| serve(req, root.clone())); + + tokio::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, svc).await { + tracing::warn!("doc server: connection error: {err}"); + } + }); + } +} + +async fn serve( + req: Request, + root: Arc, +) -> Result>, Infallible> { + let request_path = req.uri().path().trim_start_matches('/'); + let mut path = root.to_path_buf(); + for segment in Path::new(request_path).components() { + match segment { + Component::Normal(part) => path.push(part), + _ => return Ok(not_found()), + } + } + if !path.starts_with(&root) { + return Ok(not_found()); + } + if path.is_dir() { + path.push("index.html"); + } + + let Ok(contents) = tokio::fs::read(&path).await else { + return Ok(not_found()); + }; + + let response = Response::builder() + .status(StatusCode::OK) + .header( + CONTENT_TYPE, + match path.extension().and_then(|ext| ext.to_str()) { + Some("html") => "text/html; charset=utf-8", + Some("css") => "text/css", + Some("js") => "text/javascript", + Some("svg") => "image/svg+xml", + Some("png") => "image/png", + Some("jpg" | "jpeg") => "image/jpeg", + Some("woff2") => "font/woff2", + Some("txt") => "text/plain; charset=utf-8", + _ => "application/octet-stream", + }, + ) + .header(CONTENT_LENGTH, contents.len()) + .body(Full::new(Bytes::from(contents))) + .expect("building a static response can't fail"); + Ok(response) +} + +fn not_found() -> Response> { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from_static(b"404 not found"))) + .expect("building a static response can't fail") +} diff --git a/src/cli/help.rs b/src/cli/help.rs index 015e847077..52d1f0ca42 100644 --- a/src/cli/help.rs +++ b/src/cli/help.rs @@ -232,7 +232,11 @@ pub(crate) fn doc_help() -> String { the default browser. By default, it opens the documentation index. Use the various - flags to open specific pieces of documentation." + flags to open specific pieces of documentation. + + If your browser is sandboxed (e.g. installed via Snap or Flatpak) it + may be unable to open `file://` URLs under `~/.rustup`. Pass `--serve` + to serve the documentation over a local HTTP server instead." ) } diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 4396492b8a..e9d64dcb3b 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -252,6 +252,11 @@ enum RustupSubcmd { #[arg(long)] path: bool, + /// Serve the documentation over a local HTTP server instead of + /// opening it directly as a `file://` URL + #[arg(long, conflicts_with = "path")] + serve: bool, + #[arg(long, help = official_toolchain_arg_help())] toolchain: Option, @@ -817,10 +822,11 @@ pub async fn main( RustupSubcmd::Which { command, toolchain } => which(cfg, &command, toolchain).await, RustupSubcmd::Doc { path, + serve, toolchain, topic, page, - } => docs::doc(cfg, path, toolchain, topic.as_deref(), &page).await, + } => docs::doc(cfg, path, serve, toolchain, topic.as_deref(), &page).await, #[cfg(not(windows))] RustupSubcmd::Man { command, toolchain } => docs::man(cfg, &command, toolchain).await, RustupSubcmd::Self_ { subcmd } => match subcmd { diff --git a/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg b/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg index 1a2571939b..479447b6cb 100644 --- a/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg +++ b/tests/suite/cli_rustup_ui/rustup_doc_cmd_help_flag.stdout.term.svg @@ -1,4 +1,4 @@ - +