Skip to content

Commit bb97203

Browse files
committed
Auto merge of #124611 - Urgau:rustdoc-stdin, r=GuillaumeGomez
Add `-` (stdin) support in rustdoc This PR adds support for the special `-` input which threats the input as coming from *stdin* instead of being a filepath. Doing this also makes `rustdoc` consistent with `rustc` and ~~every~~ other tools. Full [motivation](#124611 (comment)). Fixes #123671 r? `@fmease`
2 parents 1c90b9f + 7e1dc74 commit bb97203

File tree

8 files changed

+86
-29
lines changed

8 files changed

+86
-29
lines changed

compiler/rustc_session/src/config.rs

+1
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ pub enum DumpSolverProofTree {
798798
Never,
799799
}
800800

801+
#[derive(Clone)]
801802
pub enum Input {
802803
/// Load source code from a file.
803804
File(PathBuf),

src/doc/rustdoc/src/command-line-arguments.md

+6
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,12 @@ When `rustdoc` receives this flag, it will print an extra "Version (version)" in
417417
the crate root's docs. You can use this flag to differentiate between different versions of your
418418
library's documentation.
419419

420+
## `-`: load source code from the standard input
421+
422+
If you specify `-` as the INPUT on the command line, then `rustdoc` will read the
423+
source code from stdin (standard input stream) until the EOF, instead of the file
424+
system with an otherwise specified path.
425+
420426
## `@path`: load command-line flags from a path
421427

422428
If you specify `@path` on the command-line, then it will open `path` and read

src/librustdoc/config.rs

+37-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::collections::BTreeMap;
22
use std::ffi::OsStr;
33
use std::fmt;
4+
use std::io;
5+
use std::io::Read;
6+
use std::path::Path;
47
use std::path::PathBuf;
58
use std::str::FromStr;
69

@@ -9,14 +12,14 @@ use rustc_session::config::{
912
self, parse_crate_types_from_list, parse_externs, parse_target_triple, CrateType,
1013
};
1114
use rustc_session::config::{get_cmd_lint_options, nightly_options};
12-
use rustc_session::config::{
13-
CodegenOptions, ErrorOutputType, Externs, JsonUnusedExterns, UnstableOptions,
14-
};
15+
use rustc_session::config::{CodegenOptions, ErrorOutputType, Externs, Input};
16+
use rustc_session::config::{JsonUnusedExterns, UnstableOptions};
1517
use rustc_session::getopts;
1618
use rustc_session::lint::Level;
1719
use rustc_session::search_paths::SearchPath;
1820
use rustc_session::EarlyDiagCtxt;
1921
use rustc_span::edition::Edition;
22+
use rustc_span::FileName;
2023
use rustc_target::spec::TargetTriple;
2124

2225
use crate::core::new_dcx;
@@ -60,7 +63,7 @@ impl TryFrom<&str> for OutputFormat {
6063
pub(crate) struct Options {
6164
// Basic options / Options passed directly to rustc
6265
/// The crate root or Markdown file to load.
63-
pub(crate) input: PathBuf,
66+
pub(crate) input: Input,
6467
/// The name of the crate being documented.
6568
pub(crate) crate_name: Option<String>,
6669
/// Whether or not this is a bin crate
@@ -179,7 +182,7 @@ impl fmt::Debug for Options {
179182
}
180183

181184
f.debug_struct("Options")
182-
.field("input", &self.input)
185+
.field("input", &self.input.source_name())
183186
.field("crate_name", &self.crate_name)
184187
.field("bin_crate", &self.bin_crate)
185188
.field("proc_macro_crate", &self.proc_macro_crate)
@@ -320,6 +323,23 @@ impl RenderOptions {
320323
}
321324
}
322325

326+
/// Create the input (string or file path)
327+
///
328+
/// Warning: Return an unrecoverable error in case of error!
329+
fn make_input(early_dcx: &EarlyDiagCtxt, input: &str) -> Input {
330+
if input == "-" {
331+
let mut src = String::new();
332+
if io::stdin().read_to_string(&mut src).is_err() {
333+
// Immediately stop compilation if there was an issue reading
334+
// the input (for example if the input stream is not UTF-8).
335+
early_dcx.early_fatal("couldn't read from stdin, as it did not contain valid UTF-8");
336+
}
337+
Input::Str { name: FileName::anon_source_code(&src), input: src }
338+
} else {
339+
Input::File(PathBuf::from(input))
340+
}
341+
}
342+
323343
impl Options {
324344
/// Parses the given command-line for options. If an error message or other early-return has
325345
/// been printed, returns `Err` with the exit code.
@@ -447,15 +467,16 @@ impl Options {
447467

448468
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
449469

450-
let input = PathBuf::from(if describe_lints {
470+
let input = if describe_lints {
451471
"" // dummy, this won't be used
452-
} else if matches.free.is_empty() {
453-
dcx.fatal("missing file operand");
454-
} else if matches.free.len() > 1 {
455-
dcx.fatal("too many file operands");
456472
} else {
457-
&matches.free[0]
458-
});
473+
match matches.free.as_slice() {
474+
[] => dcx.fatal("missing file operand"),
475+
[input] => input,
476+
_ => dcx.fatal("too many file operands"),
477+
}
478+
};
479+
let input = make_input(early_dcx, &input);
459480

460481
let externs = parse_externs(early_dcx, matches, &unstable_opts);
461482
let extern_html_root_urls = match parse_extern_html_roots(matches) {
@@ -792,8 +813,10 @@ impl Options {
792813
}
793814

794815
/// Returns `true` if the file given as `self.input` is a Markdown file.
795-
pub(crate) fn markdown_input(&self) -> bool {
796-
self.input.extension().is_some_and(|e| e == "md" || e == "markdown")
816+
pub(crate) fn markdown_input(&self) -> Option<&Path> {
817+
self.input
818+
.opt_path()
819+
.filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown"))
797820
}
798821
}
799822

src/librustdoc/core.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
3232
use crate::formats::cache::Cache;
3333
use crate::passes::{self, Condition::*};
3434

35-
pub(crate) use rustc_session::config::{Input, Options, UnstableOptions};
35+
pub(crate) use rustc_session::config::{Options, UnstableOptions};
3636

3737
pub(crate) struct DocContext<'tcx> {
3838
pub(crate) tcx: TyCtxt<'tcx>,
@@ -204,8 +204,6 @@ pub(crate) fn create_config(
204204
// Add the doc cfg into the doc build.
205205
cfgs.push("doc".to_string());
206206

207-
let input = Input::File(input);
208-
209207
// By default, rustdoc ignores all lints.
210208
// Specifically unblock lints relevant to documentation or the lint machinery itself.
211209
let mut lints_to_show = vec![

src/librustdoc/doctest.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ pub(crate) fn run(
9393
dcx: &rustc_errors::DiagCtxt,
9494
options: RustdocOptions,
9595
) -> Result<(), ErrorGuaranteed> {
96-
let input = config::Input::File(options.input.clone());
97-
9896
let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
9997

10098
// See core::create_config for what's going on here.
@@ -140,7 +138,7 @@ pub(crate) fn run(
140138
opts: sessopts,
141139
crate_cfg: cfgs,
142140
crate_check_cfg: options.check_cfgs.clone(),
143-
input,
141+
input: options.input.clone(),
144142
output_file: None,
145143
output_dir: None,
146144
file_loader: None,

src/librustdoc/lib.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -730,10 +730,10 @@ fn main_args(
730730
core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);
731731

732732
match (options.should_test, options.markdown_input()) {
733-
(true, true) => return wrap_return(&diag, markdown::test(options)),
734-
(true, false) => return doctest::run(&diag, options),
735-
(false, true) => {
736-
let input = options.input.clone();
733+
(true, Some(_)) => return wrap_return(&diag, markdown::test(options)),
734+
(true, None) => return doctest::run(&diag, options),
735+
(false, Some(input)) => {
736+
let input = input.to_owned();
737737
let edition = options.edition;
738738
let config = core::create_config(options, &render_options, using_internal_features);
739739

@@ -747,7 +747,7 @@ fn main_args(
747747
}),
748748
);
749749
}
750-
(false, false) => {}
750+
(false, None) => {}
751751
}
752752

753753
// need to move these items separately because we lose them by the time the closure is called,

src/librustdoc/markdown.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,14 @@ pub(crate) fn render<P: AsRef<Path>>(
144144

145145
/// Runs any tests/code examples in the markdown file `input`.
146146
pub(crate) fn test(options: Options) -> Result<(), String> {
147-
let input_str = read_to_string(&options.input)
148-
.map_err(|err| format!("{input}: {err}", input = options.input.display()))?;
147+
use rustc_session::config::Input;
148+
let input_str = match &options.input {
149+
Input::File(path) => {
150+
read_to_string(&path).map_err(|err| format!("{}: {err}", path.display()))?
151+
}
152+
Input::Str { name: _, input } => input.clone(),
153+
};
154+
149155
let mut opts = GlobalTestOptions::default();
150156
opts.no_crate_inject = true;
151157

@@ -155,12 +161,12 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
155161
generate_args_file(&file_path, &options)?;
156162

157163
let mut collector = Collector::new(
158-
options.input.display().to_string(),
164+
options.input.filestem().to_string(),
159165
options.clone(),
160166
true,
161167
opts,
162168
None,
163-
Some(options.input),
169+
options.input.opt_path().map(ToOwned::to_owned),
164170
options.enable_per_target_ignores,
165171
file_path,
166172
);

tests/run-make/stdin-rustdoc/rmake.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! This test checks rustdoc `-` (stdin) handling
2+
3+
use run_make_support::{rustdoc, tmp_dir};
4+
5+
static INPUT: &str = r#"
6+
//! ```
7+
//! dbg!(());
8+
//! ```
9+
pub struct F;
10+
"#;
11+
12+
fn main() {
13+
let tmp_dir = tmp_dir();
14+
let out_dir = tmp_dir.join("doc");
15+
16+
// rustdoc -
17+
rustdoc().arg("-").out_dir(&out_dir).stdin(INPUT).run();
18+
assert!(out_dir.join("rust_out/struct.F.html").try_exists().unwrap());
19+
20+
// rustdoc --test -
21+
rustdoc().arg("--test").arg("-").stdin(INPUT).run();
22+
23+
// rustdoc file.rs -
24+
rustdoc().arg("file.rs").arg("-").run_fail();
25+
}

0 commit comments

Comments
 (0)