Skip to content

rustdoc: Add flag to use external source code pages #69167

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

Closed
Closed
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
19 changes: 18 additions & 1 deletion src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ override `ignore`.

### `--runtool`, `--runtool-arg`: program to run tests with; args to pass to it

Using thses options looks like this:
Using these options looks like this:

```bash
$ rustdoc src/lib.rs -Z unstable-options --runtool runner --runtool-arg --do-thing --runtool-arg --do-other-thing
Expand All @@ -467,3 +467,20 @@ $ rustdoc src/lib.rs -Z unstable-options --runtool valgrind
```

Another use case would be to run a test inside an emulator, or through a Virtual Machine.

### `--source-code-external-url`: using external source code

In case you don't want to generate the source code files and instead use existing ones (available
through an HTTP URL!), you can use this option:

```bash
$ rustdoc src/lib.rs -Z unstable-options --source-code-external-url 'https://somewhere.com'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's a good idea to use an example domain for the purpose of being used in examples, like https://example.com as per https://www.iana.org/domains/reserved.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the difference with what I wrote?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somewhere.com could be owned by anyone, and they could put any content there. example.com on the other hand is registered by the Internet Assigned Numbers Authority, and it's safe to use in examples.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't make sense to use it directly like this... Originally, I wanted to put http://a.a...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure where the misunderstanding is, but put simply, example.com is safe to use in examples, while any other random domain name is not (because anyone can grab those and host any content; example.com will always be safe). Sorry about the noise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't really understand your point... Like I said, if you use it as is, it'll just not do anything except creating links to "somewhere.com". Which isn't what any user want. If you're still not convinced, I propose you the following: open a PR changing the domain I'm pointing to in the example. I'll merge it without problem. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that sounds good to me if we can settle there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect! Then once this is merged, please open a PR and ping me on it so it can get merged quickly.

```

Note that generated source URLs will look like this:

```text
https://somewhere.com/[crate name]
```

It is equivalent to the local `src/[crate name]` folder.
9 changes: 9 additions & 0 deletions src/librustdoc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ pub struct RenderOptions {
pub generate_search_filter: bool,
/// Option (disabled by default) to generate files used by RLS and some other tools.
pub generate_redirect_pages: bool,
/// Base URL to use for source code linking.
pub source_code_external_url: Option<String>,
}

impl Options {
Expand Down Expand Up @@ -496,6 +498,12 @@ impl Options {
let enable_per_target_ignores = matches.opt_present("enable-per-target-ignores");
let document_private = matches.opt_present("document-private-items");
let document_hidden = matches.opt_present("document-hidden-items");
let source_code_external_url = matches.opt_str("source-code-external-url").map(|mut h| {
if !h.ends_with('/') {
h.push('/');
}
h.replace("\\", "/")
});

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

Expand Down Expand Up @@ -552,6 +560,7 @@ impl Options {
markdown_playground_url,
generate_search_filter,
generate_redirect_pages,
source_code_external_url,
},
})
}
Expand Down
45 changes: 38 additions & 7 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ crate struct SharedContext {
/// The default edition used to parse doctests.
pub edition: Edition,
pub codes: ErrorCodes,
pub source_code_external_url: Option<String>,
playground: Option<markdown::Playground>,
}

Expand Down Expand Up @@ -409,6 +410,7 @@ pub fn run(
static_root_path,
generate_search_filter,
generate_redirect_pages,
source_code_external_url,
..
} = options;

Expand Down Expand Up @@ -467,6 +469,7 @@ pub fn run(
collapsed: krate.collapsed,
src_root,
include_sources,
source_code_external_url,
local_sources: Default::default(),
issue_tracker_base_url,
layout,
Expand Down Expand Up @@ -1550,6 +1553,21 @@ impl Context {
}
}

fn compute_path(path: String) -> String {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to use canonicalize() or a URL library instead of writing this from scratch? This does not handle symbolic links for example.

if path.split('/').find(|x| *x == "..").is_none() {
return path;
}
let mut new_path = Vec::new();
for part in path.split('/') {
if part != ".." {
new_path.push(part);
} else if !new_path.is_empty() {
new_path.pop();
}
}
new_path.join("/")
}

impl Context {
/// Generates a url appropriate for an `href` attribute back to the source of
/// this item.
Expand Down Expand Up @@ -1602,13 +1620,26 @@ impl Context {
} else {
format!("{}-{}", item.source.loline, item.source.hiline)
};
Some(format!(
"{root}src/{krate}/{path}#{lines}",
root = Escape(&root),
krate = krate,
path = path,
lines = lines
))
if let Some(ref source_code_external_url) = self.shared.source_code_external_url {
Some(format!(
"{path}#{lines}",
path = compute_path(format!(
"{root}/{path}",
root = source_code_external_url,
krate = krate,
path = path,
),),
lines = lines
))
} else {
Some(format!(
"{root}src/{krate}/{path}#{lines}",
root = Escape(&root),
krate = krate,
path = path,
lines = lines
))
}
}
}

Expand Down
73 changes: 38 additions & 35 deletions src/librustdoc/html/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,6 @@ impl<'a> SourceCollector<'a> {
return Ok(());
}

let contents = match fs::read_to_string(&p) {
Ok(contents) => contents,
Err(e) => {
return Err(Error::new(e, &p));
}
};

// Remove the utf-8 BOM if any
let contents =
if contents.starts_with("\u{feff}") { &contents[3..] } else { &contents[..] };

// Create the intermediate directories
let mut cur = self.dst.clone();
let mut root_path = String::from("../../");
Expand All @@ -101,30 +90,44 @@ impl<'a> SourceCollector<'a> {
cur.push(&fname);
href.push_str(&fname.to_string_lossy());

let title = format!(
"{} -- source",
cur.file_name().expect("failed to get file name").to_string_lossy()
);
let desc = format!("Source to the Rust file `{}`.", filename);
let page = layout::Page {
title: &title,
css_class: "source",
root_path: &root_path,
static_root_path: self.scx.static_root_path.as_deref(),
description: &desc,
keywords: BASIC_KEYWORDS,
resource_suffix: &self.scx.resource_suffix,
extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
};
let v = layout::render(
&self.scx.layout,
&page,
"",
|buf: &mut _| print_src(buf, &contents),
&self.scx.themes,
);
self.scx.fs.write(&cur, v.as_bytes())?;
// we don't emit source if we have an external location for it
if self.scx.source_code_external_url.is_none() {
let contents = match fs::read_to_string(&p) {
Ok(contents) => contents,
Err(e) => {
return Err(Error::new(e, &p));
}
};

// Remove the utf-8 BOM if any
let contents =
if contents.starts_with("\u{feff}") { &contents[3..] } else { &contents[..] };

let title = format!(
"{} -- source",
cur.file_name().expect("failed to get file name").to_string_lossy()
);
let desc = format!("Source to the Rust file `{}`.", filename);
let page = layout::Page {
title: &title,
css_class: "source",
root_path: &root_path,
static_root_path: self.scx.static_root_path.as_deref(),
description: &desc,
keywords: BASIC_KEYWORDS,
resource_suffix: &self.scx.resource_suffix,
extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
};
let v = layout::render(
&self.scx.layout,
&page,
"",
|buf: &mut _| print_src(buf, &contents),
&self.scx.themes,
);
self.scx.fs.write(&cur, v.as_bytes())?;
}
self.scx.local_sources.insert(p.clone(), href);
Ok(())
}
Expand Down
8 changes: 8 additions & 0 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,14 @@ fn opts() -> Vec<RustcOptGroup> {
"specified the rustc-like binary to use as the test builder",
)
}),
unstable("source-code-external-url", |o| {
o.optopt(
"",
"source-code-external-url",
"Base URL for external source code linking",
"URL",
)
}),
]
}

Expand Down
8 changes: 8 additions & 0 deletions src/test/rustdoc/source_code_external_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ignore-tidy-linelength
// compile-flags: -Z unstable-options --source-code-external-url https://a.a

#![crate_name = "foo"]

// @has foo/struct.Foo.html
// @has - '//h1[@class="fqn"]//a[@href="https://a.a/foo/source_code_external_url.rs.html#8"]' '[src]'
pub struct Foo;
8 changes: 8 additions & 0 deletions src/test/rustdoc/source_code_external_url2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ignore-tidy-linelength
// compile-flags: -Z unstable-options --source-code-external-url https://a.a/

#![crate_name = "foo"]

// @has foo/struct.Foo.html
// @has - '//h1[@class="fqn"]//a[@href="https://a.a/foo/source_code_external_url2.rs.html#8"]' '[src]'
pub struct Foo;