Skip to content

Commit

Permalink
rustdoc: add - (stdin) support
Browse files Browse the repository at this point in the history
  • Loading branch information
Urgau committed May 3, 2024
1 parent 1211b45 commit ee98bf0
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 24 deletions.
6 changes: 6 additions & 0 deletions src/doc/rustdoc/src/command-line-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,12 @@ When `rustdoc` receives this flag, it will print an extra "Version (version)" in
the crate root's docs. You can use this flag to differentiate between different versions of your
library's documentation.

## `-`: load source code from the standard input

If you specify `-` on the command line, then `rustdoc` will read until EOF from the
stdin (standard input) for the source code, instead of reading from the filesystem
with an otherwise specified PATH.

## `@path`: load command-line flags from a path

If you specify `@path` on the command-line, then it will open `path` and read
Expand Down
50 changes: 37 additions & 13 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::fmt;
use std::io;
use std::io::Read;
use std::path::PathBuf;
use std::str::FromStr;

Expand All @@ -9,14 +11,14 @@ use rustc_session::config::{
self, parse_crate_types_from_list, parse_externs, parse_target_triple, CrateType,
};
use rustc_session::config::{get_cmd_lint_options, nightly_options};
use rustc_session::config::{
CodegenOptions, ErrorOutputType, Externs, JsonUnusedExterns, UnstableOptions,
};
use rustc_session::config::{CodegenOptions, ErrorOutputType, Externs, Input};
use rustc_session::config::{JsonUnusedExterns, UnstableOptions};
use rustc_session::getopts;
use rustc_session::lint::Level;
use rustc_session::search_paths::SearchPath;
use rustc_session::EarlyDiagCtxt;
use rustc_span::edition::Edition;
use rustc_span::FileName;
use rustc_target::spec::TargetTriple;

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

f.debug_struct("Options")
.field("input", &self.input)
.field("input", &self.input.source_name())
.field("crate_name", &self.crate_name)
.field("bin_crate", &self.bin_crate)
.field("proc_macro_crate", &self.proc_macro_crate)
Expand Down Expand Up @@ -322,6 +324,23 @@ impl RenderOptions {
}
}

/// Create the input (string or file path)
///
/// Warning: Return an unrecoverable error in case of error!
fn make_input(early_dcx: &EarlyDiagCtxt, input: &str) -> Input {
if input == "-" {
let mut src = String::new();
if io::stdin().read_to_string(&mut src).is_err() {
// Immediately stop compilation if there was an issue reading
// the input (for example if the input stream is not UTF-8).
early_dcx.early_fatal("couldn't read from stdin, as it did not contain valid UTF-8");
}
Input::Str { name: FileName::anon_source_code(&src), input: src }
} else {
Input::File(PathBuf::from(input))
}
}

impl Options {
/// Parses the given command-line for options. If an error message or other early-return has
/// been printed, returns `Err` with the exit code.
Expand Down Expand Up @@ -450,15 +469,16 @@ impl Options {

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

let input = PathBuf::from(if describe_lints {
let input = if describe_lints {
"" // dummy, this won't be used
} else if matches.free.is_empty() {
dcx.fatal("missing file operand");
} else if matches.free.len() > 1 {
dcx.fatal("too many file operands");
} else {
&matches.free[0]
});
match matches.free.as_slice() {
[] => dcx.fatal("missing file operand"),
[input] => input,
_ => dcx.fatal("too many file operands"),
}
};
let input = make_input(early_dcx, &input);

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

/// Returns `true` if the file given as `self.input` is a Markdown file.
pub(crate) fn markdown_input(&self) -> bool {
self.input.extension().is_some_and(|e| e == "md" || e == "markdown")
self.input
.opt_path()
.map(std::path::Path::extension)
.flatten()
.is_some_and(|e| e == "md" || e == "markdown")
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
use crate::formats::cache::Cache;
use crate::passes::{self, Condition::*};

pub(crate) use rustc_session::config::{Input, Options, UnstableOptions};
pub(crate) use rustc_session::config::{Options, UnstableOptions};

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

let input = Input::File(input);

// By default, rustdoc ignores all lints.
// Specifically unblock lints relevant to documentation or the lint machinery itself.
let mut lints_to_show = vec![
Expand Down
4 changes: 1 addition & 3 deletions src/librustdoc/doctest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ pub(crate) fn run(
dcx: &rustc_errors::DiagCtxt,
options: RustdocOptions,
) -> Result<(), ErrorGuaranteed> {
let input = config::Input::File(options.input.clone());

let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;

// See core::create_config for what's going on here.
Expand Down Expand Up @@ -140,7 +138,7 @@ pub(crate) fn run(
opts: sessopts,
crate_cfg: cfgs,
crate_check_cfg: options.check_cfgs.clone(),
input,
input: options.input.clone(),
output_file: None,
output_dir: None,
file_loader: None,
Expand Down
7 changes: 6 additions & 1 deletion src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,10 +733,15 @@ fn main_args(
(true, true) => return wrap_return(&diag, markdown::test(options)),
(true, false) => return doctest::run(&diag, options),
(false, true) => {
let input = options.input.clone();
let edition = options.edition;
let config = core::create_config(options, &render_options, using_internal_features);

use rustc_session::config::Input;
let input = match &config.input {
Input::File(path) => path.clone(),
Input::Str { .. } => unreachable!("only path to markdown are supported"),
};

// `markdown::render` can invoke `doctest::make_test`, which
// requires session globals and a thread pool, so we use
// `run_compiler`.
Expand Down
14 changes: 10 additions & 4 deletions src/librustdoc/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,14 @@ pub(crate) fn render<P: AsRef<Path>>(

/// Runs any tests/code examples in the markdown file `input`.
pub(crate) fn test(options: Options) -> Result<(), String> {
let input_str = read_to_string(&options.input)
.map_err(|err| format!("{input}: {err}", input = options.input.display()))?;
use rustc_session::config::Input;
let input_str = match &options.input {
Input::File(path) => {
read_to_string(&path).map_err(|err| format!("{}: {err}", path.display()))?
}
Input::Str { name: _, input } => input.clone(),
};

let mut opts = GlobalTestOptions::default();
opts.no_crate_inject = true;

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

let mut collector = Collector::new(
options.input.display().to_string(),
options.input.filestem().to_string(),
options.clone(),
true,
opts,
None,
Some(options.input),
options.input.opt_path().map(ToOwned::to_owned),
options.enable_per_target_ignores,
file_path,
);
Expand Down
25 changes: 25 additions & 0 deletions tests/run-make/stdin-rustdoc/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! This test checks rustdoc `-` (stdin) handling

use run_make_support::{rustdoc, tmp_dir};

static INPUT: &str = r#"
//! ```
//! dbg!(());
//! ```
pub struct F;
"#;

fn main() {
let tmp_dir = tmp_dir();
let out_dir = tmp_dir.join("doc");

// rustdoc -
rustdoc().arg("-").out_dir(&out_dir).stdin(INPUT).run();
assert!(out_dir.join("rust_out/struct.F.html").try_exists().unwrap());

// rustdoc --test -
rustdoc().arg("--test").arg("-").stdin(INPUT).run();

// rustdoc file.rs -
rustdoc().arg("file.rs").arg("-").run_fail();
}

0 comments on commit ee98bf0

Please sign in to comment.