Skip to content

Commit 9f75f54

Browse files
committed
rustdoc: add - (stdin) support
1 parent 5d2914a commit 9f75f54

File tree

6 files changed

+76
-18
lines changed

6 files changed

+76
-18
lines changed

Diff for: src/librustdoc/config.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ impl TryFrom<&str> for OutputFormat {
6060
pub(crate) struct Options {
6161
// Basic options / Options passed directly to rustc
6262
/// The crate root or Markdown file to load.
63-
pub(crate) input: PathBuf,
63+
pub(crate) input: String,
6464
/// The name of the crate being documented.
6565
pub(crate) crate_name: Option<String>,
6666
/// Whether or not this is a bin crate
@@ -450,15 +450,16 @@ impl Options {
450450

451451
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
452452

453-
let input = PathBuf::from(if describe_lints {
453+
let input = if describe_lints {
454454
"" // dummy, this won't be used
455455
} else if matches.free.is_empty() {
456456
dcx.fatal("missing file operand");
457457
} else if matches.free.len() > 1 {
458458
dcx.fatal("too many file operands");
459459
} else {
460460
&matches.free[0]
461-
});
461+
}
462+
.to_string();
462463

463464
let externs = parse_externs(early_dcx, matches, &unstable_opts);
464465
let extern_html_root_urls = match parse_extern_html_roots(matches) {
@@ -797,7 +798,7 @@ impl Options {
797798

798799
/// Returns `true` if the file given as `self.input` is a Markdown file.
799800
pub(crate) fn markdown_input(&self) -> bool {
800-
self.input.extension().is_some_and(|e| e == "md" || e == "markdown")
801+
self.input.rsplit_once('.').is_some_and(|(_, e)| e == "md" || e == "markdown")
801802
}
802803
}
803804

Diff for: src/librustdoc/core.rs

+26-4
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ use rustc_middle::hir::nested_filter;
1515
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
1616
use rustc_session::config::{self, CrateType, ErrorOutputType, ResolveDocLinks};
1717
use rustc_session::lint;
18+
use rustc_session::EarlyDiagCtxt;
1819
use rustc_session::Session;
1920
use rustc_span::symbol::sym;
20-
use rustc_span::{source_map, Span};
21+
use rustc_span::{source_map, FileName, Span};
2122

2223
use std::cell::RefCell;
2324
use std::io;
25+
use std::io::Read;
2426
use std::mem;
27+
use std::path::PathBuf;
2528
use std::rc::Rc;
2629
use std::sync::LazyLock;
2730
use std::sync::{atomic::AtomicBool, Arc};
@@ -174,10 +177,30 @@ pub(crate) fn new_dcx(
174177
rustc_errors::DiagCtxt::new(emitter).with_flags(unstable_opts.dcx_flags(true))
175178
}
176179

180+
/// Create the input (string or file path)
181+
pub(crate) fn make_input(
182+
early_dcx: &EarlyDiagCtxt,
183+
input: &String,
184+
) -> Result<Input, ErrorGuaranteed> {
185+
Ok(if input == "-" {
186+
let mut src = String::new();
187+
if io::stdin().read_to_string(&mut src).is_err() {
188+
// Immediately stop compilation if there was an issue reading
189+
// the input (for example if the input stream is not UTF-8).
190+
let reported =
191+
early_dcx.early_err("couldn't read from stdin, as it did not contain valid UTF-8");
192+
return Err(reported);
193+
}
194+
Input::Str { name: FileName::anon_source_code(&src), input: src }
195+
} else {
196+
Input::File(PathBuf::from(input))
197+
})
198+
}
199+
177200
/// Parse, resolve, and typecheck the given crate.
178201
pub(crate) fn create_config(
179202
RustdocOptions {
180-
input,
203+
input: _,
181204
crate_name,
182205
proc_macro_crate,
183206
error_format,
@@ -199,13 +222,12 @@ pub(crate) fn create_config(
199222
..
200223
}: RustdocOptions,
201224
RenderOptions { document_private, .. }: &RenderOptions,
225+
input: Input,
202226
using_internal_features: Arc<AtomicBool>,
203227
) -> rustc_interface::Config {
204228
// Add the doc cfg into the doc build.
205229
cfgs.push("doc".to_string());
206230

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

Diff for: src/librustdoc/doctest.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_middle::ty::TyCtxt;
1212
use rustc_parse::maybe_new_parser_from_source_str;
1313
use rustc_parse::parser::attr::InnerAttrPolicy;
1414
use rustc_resolve::rustdoc::span_of_fragments;
15+
use rustc_session::config::Input;
1516
use rustc_session::config::{self, CrateType, ErrorOutputType};
1617
use rustc_session::parse::ParseSess;
1718
use rustc_session::{lint, Session};
@@ -92,9 +93,8 @@ fn get_doctest_dir() -> io::Result<TempDir> {
9293
pub(crate) fn run(
9394
dcx: &rustc_errors::DiagCtxt,
9495
options: RustdocOptions,
96+
input: Input,
9597
) -> Result<(), ErrorGuaranteed> {
96-
let input = config::Input::File(options.input.clone());
97-
9898
let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
9999

100100
// See core::create_config for what's going on here.

Diff for: src/librustdoc/lib.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -729,13 +729,21 @@ fn main_args(
729729
let diag =
730730
core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);
731731

732+
let input = core::make_input(early_dcx, &options.input)?;
733+
732734
match (options.should_test, options.markdown_input()) {
733735
(true, true) => return wrap_return(&diag, markdown::test(options)),
734-
(true, false) => return doctest::run(&diag, options),
736+
(true, false) => return doctest::run(&diag, options, input),
735737
(false, true) => {
736-
let input = options.input.clone();
737738
let edition = options.edition;
738-
let config = core::create_config(options, &render_options, using_internal_features);
739+
let config =
740+
core::create_config(options, &render_options, input, using_internal_features);
741+
742+
use rustc_session::config::Input;
743+
let input = match &config.input {
744+
Input::File(path) => path.clone(),
745+
Input::Str { .. } => unreachable!("only path to markdown are supported"),
746+
};
739747

740748
// `markdown::render` can invoke `doctest::make_test`, which
741749
// requires session globals and a thread pool, so we use
@@ -768,7 +776,7 @@ fn main_args(
768776
let scrape_examples_options = options.scrape_examples_options.clone();
769777
let bin_crate = options.bin_crate;
770778

771-
let config = core::create_config(options, &render_options, using_internal_features);
779+
let config = core::create_config(options, &render_options, input, using_internal_features);
772780

773781
interface::run_compiler(config, |compiler| {
774782
let sess = &compiler.sess;

Diff for: src/librustdoc/markdown.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::Write as _;
22
use std::fs::{create_dir_all, read_to_string, File};
33
use std::io::prelude::*;
4-
use std::path::Path;
4+
use std::path::{Path, PathBuf};
55

66
use tempfile::tempdir;
77

@@ -145,7 +145,7 @@ pub(crate) fn render<P: AsRef<Path>>(
145145
/// Runs any tests/code examples in the markdown file `input`.
146146
pub(crate) fn test(options: Options) -> Result<(), String> {
147147
let input_str = read_to_string(&options.input)
148-
.map_err(|err| format!("{input}: {err}", input = options.input.display()))?;
148+
.map_err(|err| format!("{input}: {err}", input = options.input))?;
149149
let mut opts = GlobalTestOptions::default();
150150
opts.no_crate_inject = true;
151151

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

157157
let mut collector = Collector::new(
158-
options.input.display().to_string(),
158+
options.input.clone(),
159159
options.clone(),
160160
true,
161161
opts,
162162
None,
163-
Some(options.input),
163+
Some(PathBuf::from(options.input)),
164164
options.enable_per_target_ignores,
165165
file_path,
166166
);

Diff for: tests/run-make/stdin-rustdoc/rmake.rs

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

0 commit comments

Comments
 (0)