Skip to content

Commit ee98bf0

Browse files
committed
rustdoc: add - (stdin) support
1 parent 1211b45 commit ee98bf0

File tree

7 files changed

+86
-24
lines changed

7 files changed

+86
-24
lines changed

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 `-` on the command line, then `rustdoc` will read until EOF from the
423+
stdin (standard input) for the source code, instead of reading from the filesystem
424+
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-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::collections::BTreeMap;
22
use std::ffi::OsStr;
33
use std::fmt;
4+
use std::io;
5+
use std::io::Read;
46
use std::path::PathBuf;
57
use std::str::FromStr;
68

@@ -9,14 +11,14 @@ use rustc_session::config::{
911
self, parse_crate_types_from_list, parse_externs, parse_target_triple, CrateType,
1012
};
1113
use rustc_session::config::{get_cmd_lint_options, nightly_options};
12-
use rustc_session::config::{
13-
CodegenOptions, ErrorOutputType, Externs, JsonUnusedExterns, UnstableOptions,
14-
};
14+
use rustc_session::config::{CodegenOptions, ErrorOutputType, Externs, Input};
15+
use rustc_session::config::{JsonUnusedExterns, UnstableOptions};
1516
use rustc_session::getopts;
1617
use rustc_session::lint::Level;
1718
use rustc_session::search_paths::SearchPath;
1819
use rustc_session::EarlyDiagCtxt;
1920
use rustc_span::edition::Edition;
21+
use rustc_span::FileName;
2022
use rustc_target::spec::TargetTriple;
2123

2224
use crate::core::new_dcx;
@@ -60,7 +62,7 @@ impl TryFrom<&str> for OutputFormat {
6062
pub(crate) struct Options {
6163
// Basic options / Options passed directly to rustc
6264
/// The crate root or Markdown file to load.
63-
pub(crate) input: PathBuf,
65+
pub(crate) input: Input,
6466
/// The name of the crate being documented.
6567
pub(crate) crate_name: Option<String>,
6668
/// Whether or not this is a bin crate
@@ -179,7 +181,7 @@ impl fmt::Debug for Options {
179181
}
180182

181183
f.debug_struct("Options")
182-
.field("input", &self.input)
184+
.field("input", &self.input.source_name())
183185
.field("crate_name", &self.crate_name)
184186
.field("bin_crate", &self.bin_crate)
185187
.field("proc_macro_crate", &self.proc_macro_crate)
@@ -322,6 +324,23 @@ impl RenderOptions {
322324
}
323325
}
324326

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

451470
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
452471

453-
let input = PathBuf::from(if describe_lints {
472+
let input = if describe_lints {
454473
"" // dummy, this won't be used
455-
} else if matches.free.is_empty() {
456-
dcx.fatal("missing file operand");
457-
} else if matches.free.len() > 1 {
458-
dcx.fatal("too many file operands");
459474
} else {
460-
&matches.free[0]
461-
});
475+
match matches.free.as_slice() {
476+
[] => dcx.fatal("missing file operand"),
477+
[input] => input,
478+
_ => dcx.fatal("too many file operands"),
479+
}
480+
};
481+
let input = make_input(early_dcx, &input);
462482

463483
let externs = parse_externs(early_dcx, matches, &unstable_opts);
464484
let extern_html_root_urls = match parse_extern_html_roots(matches) {
@@ -797,7 +817,11 @@ impl Options {
797817

798818
/// Returns `true` if the file given as `self.input` is a Markdown file.
799819
pub(crate) fn markdown_input(&self) -> bool {
800-
self.input.extension().is_some_and(|e| e == "md" || e == "markdown")
820+
self.input
821+
.opt_path()
822+
.map(std::path::Path::extension)
823+
.flatten()
824+
.is_some_and(|e| e == "md" || e == "markdown")
801825
}
802826
}
803827

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -733,10 +733,15 @@ fn main_args(
733733
(true, true) => return wrap_return(&diag, markdown::test(options)),
734734
(true, false) => return doctest::run(&diag, options),
735735
(false, true) => {
736-
let input = options.input.clone();
737736
let edition = options.edition;
738737
let config = core::create_config(options, &render_options, using_internal_features);
739738

739+
use rustc_session::config::Input;
740+
let input = match &config.input {
741+
Input::File(path) => path.clone(),
742+
Input::Str { .. } => unreachable!("only path to markdown are supported"),
743+
};
744+
740745
// `markdown::render` can invoke `doctest::make_test`, which
741746
// requires session globals and a thread pool, so we use
742747
// `run_compiler`.

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)