Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add - (stdin) support in rustdoc #124611

Merged
merged 3 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ pub enum DumpSolverProofTree {
Never,
}

#[derive(Clone)]
pub enum Input {
/// Load source code from a file.
File(PathBuf),
Expand Down
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 `-` as the INPUT on the command line, then `rustdoc` will read the
source code from stdin (standard input stream) until the EOF, instead of the file
system 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
51 changes: 37 additions & 14 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::fmt;
use std::io;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;

Expand All @@ -9,14 +12,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 +63,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 +182,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 +325,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 +470,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 @@ -796,8 +817,10 @@ 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")
pub(crate) fn markdown_input(&self) -> Option<&Path> {
self.input
.opt_path()
.filter(|p| matches!(p.extension(), Some(e) if 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
10 changes: 5 additions & 5 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -730,10 +730,10 @@ fn main_args(
core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);

match (options.should_test, options.markdown_input()) {
(true, true) => return wrap_return(&diag, markdown::test(options)),
(true, false) => return doctest::run(&diag, options),
(false, true) => {
let input = options.input.clone();
(true, Some(_)) => return wrap_return(&diag, markdown::test(options)),
(true, None) => return doctest::run(&diag, options),
(false, Some(input)) => {
let input = input.to_owned();
let edition = options.edition;
let config = core::create_config(options, &render_options, using_internal_features);

Expand All @@ -747,7 +747,7 @@ fn main_args(
}),
);
}
(false, false) => {}
(false, None) => {}
}

// need to move these items separately because we lose them by the time the closure is called,
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();
}
Loading