Skip to content

Commit 98959f6

Browse files
Add --book-location option to provide guide alongside documentation
1 parent f836ae4 commit 98959f6

File tree

11 files changed

+684
-32
lines changed

11 files changed

+684
-32
lines changed

Cargo.lock

+574-31
Large diffs are not rendered by default.

src/librustdoc/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ askama = { version = "0.13", default-features = false, features = ["alloc", "con
1313
base64 = "0.21.7"
1414
itertools = "0.12"
1515
indexmap = "2"
16+
mdbook = "0.4.48"
1617
minifier = { version = "0.3.5", default-features = false }
1718
pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false }
1819
pulldown-cmark-escape = { version = "0.11.0", features = ["simd"] }

src/librustdoc/config.rs

+20
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,22 @@ impl fmt::Debug for Options {
229229
}
230230
}
231231

232+
#[derive(Clone, Debug)]
233+
pub(crate) enum PathOrURL {
234+
Path(PathBuf),
235+
URL(String),
236+
}
237+
238+
impl PathOrURL {
239+
fn new(s: String) -> Self {
240+
if s.starts_with("https://") || s.starts_with("http://") {
241+
Self::URL(s)
242+
} else {
243+
Self::Path(s.into())
244+
}
245+
}
246+
}
247+
232248
/// Configuration options for the HTML page-creation process.
233249
#[derive(Clone, Debug)]
234250
pub(crate) struct RenderOptions {
@@ -310,6 +326,8 @@ pub(crate) struct RenderOptions {
310326
pub(crate) parts_out_dir: Option<PathToParts>,
311327
/// disable minification of CSS/JS
312328
pub(crate) disable_minification: bool,
329+
/// Location where the associated book is located.
330+
pub(crate) book_location: Option<PathOrURL>,
313331
}
314332

315333
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -814,6 +832,7 @@ impl Options {
814832
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
815833

816834
let disable_minification = matches.opt_present("disable-minification");
835+
let book_location = matches.opt_str("book-location").map(PathOrURL::new);
817836

818837
let options = Options {
819838
bin_crate,
@@ -893,6 +912,7 @@ impl Options {
893912
include_parts_dir,
894913
parts_out_dir,
895914
disable_minification,
915+
book_location,
896916
};
897917
Some((input, options, render_options))
898918
}

src/librustdoc/html/markdown.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,7 @@ fn is_default_id(id: &str) -> bool {
20612061
| "copy-path"
20622062
| "rustdoc-toc"
20632063
| "rustdoc-modnav"
2064+
| "book-loc"
20642065
// This is the list of IDs used by rustdoc sections (but still generated by
20652066
// rustdoc).
20662067
| "fields"

src/librustdoc/html/render/context.rs

+4
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ pub(crate) struct SharedContext<'tcx> {
146146
/// Controls whether we read / write to cci files in the doc root. Defaults read=true,
147147
/// write=true
148148
should_merge: ShouldMerge,
149+
pub(crate) book_location: Option<crate::config::PathOrURL>,
149150
}
150151

151152
impl SharedContext<'_> {
@@ -489,6 +490,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
489490
call_locations,
490491
no_emit_shared,
491492
html_no_source,
493+
book_location,
492494
..
493495
} = options;
494496

@@ -575,6 +577,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
575577
cache,
576578
call_locations,
577579
should_merge: options.should_merge,
580+
book_location,
578581
};
579582

580583
let dst = output;
@@ -646,6 +649,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
646649
parent_is_crate: false,
647650
blocks: vec![blocks],
648651
path: String::new(),
652+
book_location: shared.book_location.as_ref(),
649653
};
650654

651655
bar.render_into(&mut sidebar).unwrap();

src/librustdoc/html/render/sidebar.rs

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub(super) struct Sidebar<'a> {
4242
pub(super) is_mod: bool,
4343
pub(super) blocks: Vec<LinkBlock<'a>>,
4444
pub(super) path: String,
45+
pub(super) book_location: Option<&'a crate::config::PathOrURL>,
4546
}
4647

4748
impl Sidebar<'_> {
@@ -194,6 +195,7 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Str
194195
parent_is_crate: sidebar_path.len() == 1,
195196
blocks,
196197
path,
198+
book_location: cx.shared.book_location.as_ref(),
197199
};
198200
sidebar.render_into(buffer).unwrap();
199201
}

src/librustdoc/html/static/css/noscript.css

+2
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ nav.sub {
135135
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
136136
--sidebar-resizer-hover: hsl(207, 90%, 66%);
137137
--sidebar-resizer-active: hsl(207, 90%, 54%);
138+
--book-img-filter: invert(0%);
138139
}
139140
/* End theme: light */
140141

@@ -244,6 +245,7 @@ nav.sub {
244245
--scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0);
245246
--sidebar-resizer-hover: hsl(207, 30%, 54%);
246247
--sidebar-resizer-active: hsl(207, 90%, 54%);
248+
--book-img-filter: invert(80%);
247249
}
248250
/* End theme: dark */
249251
}

src/librustdoc/html/static/css/rustdoc.css

+24
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,27 @@ ul.block, .block li, .block ul {
833833
margin-bottom: 1rem;
834834
}
835835

836+
.sidebar .book {
837+
display: flex;
838+
}
839+
840+
.book::before {
841+
width: 1em;
842+
height: 1em;
843+
/* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License -
844+
https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.*/
845+
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">\
846+
<path d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 \
847+
32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 \
848+
384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 \
849+
0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 \
850+
16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>');
851+
display: block;
852+
margin-right: 6px;
853+
padding-top: 3px;
854+
filter: var(--book-img-filter);
855+
}
856+
836857
.mobile-topbar {
837858
display: none;
838859
}
@@ -2985,6 +3006,7 @@ by default.
29853006
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
29863007
--sidebar-resizer-hover: hsl(207, 90%, 66%);
29873008
--sidebar-resizer-active: hsl(207, 90%, 54%);
3009+
--book-img-filter: invert(0%);
29883010
}
29893011
/* End theme: light */
29903012

@@ -3093,6 +3115,7 @@ by default.
30933115
--scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0);
30943116
--sidebar-resizer-hover: hsl(207, 30%, 54%);
30953117
--sidebar-resizer-active: hsl(207, 90%, 54%);
3118+
--book-img-filter: invert(80%);
30963119
}
30973120
/* End theme: dark */
30983121

@@ -3205,6 +3228,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
32053228
--scrape-example-code-wrapper-background-end: rgba(15, 20, 25, 0);
32063229
--sidebar-resizer-hover: hsl(34, 50%, 33%);
32073230
--sidebar-resizer-active: hsl(34, 100%, 66%);
3231+
--book-img-filter: invert(100%);
32083232
}
32093233

32103234
:root[data-theme="ayu"] h1,

src/librustdoc/html/templates/sidebar.html

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
<div class="sidebar-elems">
22
{% if is_crate %}
3+
{% if let Some(book_location) = book_location %}
4+
<ul class="block"> {# #}
5+
<li> {# #}
6+
<h3> {# #}
7+
<a id="book-loc" class="book" href="
8+
{% match book_location %}
9+
{% when crate::config::PathOrURL::Path(s) %}{{s.display()}}
10+
{% when crate::config::PathOrURL::URL(s) %}{{s}}
11+
{% endmatch %}
12+
">Book</a> {# #}
13+
</h3> {# #}
14+
</li> {# #}
15+
</ul>
16+
{% endif %}
317
<ul class="block"> {# #}
418
<li><a id="all-types" href="all.html">All Items</a></li> {# #}
519
</ul>

src/librustdoc/lib.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,14 @@ fn opts() -> Vec<RustcOptGroup> {
658658
"disable the minification of CSS/JS files (perma-unstable, do not use with cached files)",
659659
"",
660660
),
661+
opt(
662+
Unstable,
663+
Opt,
664+
"",
665+
"book-location",
666+
"URL where the book is hosted or the folder where the mdBook source is located",
667+
"PATH or URL",
668+
),
661669
// deprecated / removed options
662670
opt(
663671
Stable,
@@ -741,6 +749,32 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
741749
}
742750
}
743751

752+
fn generate_book(render_options: &mut config::RenderOptions) -> Result<(), String> {
753+
let Some(config::PathOrURL::Path(ref mut book_dir)) = render_options.book_location else {
754+
return Ok(());
755+
};
756+
if !book_dir.is_dir() {
757+
return Err(format!(
758+
"`{}` is not a folder, expected a folder or a URL for `--book-location` argument",
759+
book_dir.display(),
760+
));
761+
}
762+
let mut book = match mdbook::MDBook::load(&book_dir) {
763+
Ok(book) => book,
764+
Err(error) => return Err(format!("failed to load book: {error:?}")),
765+
};
766+
let output_dir = render_options.output.join("doc-book");
767+
*book_dir = output_dir.join("index.html");
768+
book.config.build.build_dir = output_dir;
769+
if let Err(error) = book.build() {
770+
return Err(format!(
771+
"failed to generate book into `{}`: {error:?}",
772+
book.config.build.build_dir.display()
773+
));
774+
}
775+
Ok(())
776+
}
777+
744778
/// Renders and writes cross-crate info files, like the search index. This function exists so that
745779
/// we can run rustdoc without a crate root in the `--merge=finalize` mode. Cross-crate info files
746780
/// discovered via `--include-parts-dir` are combined and written to the doc root.
@@ -793,7 +827,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
793827

794828
// Note that we discard any distinction between different non-zero exit
795829
// codes from `from_matches` here.
796-
let (input, options, render_options) =
830+
let (input, options, mut render_options) =
797831
match config::Options::from_matches(early_dcx, &matches, args) {
798832
Some(opts) => opts,
799833
None => return,
@@ -857,6 +891,10 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
857891
let scrape_examples_options = options.scrape_examples_options.clone();
858892
let bin_crate = options.bin_crate;
859893

894+
if let Err(error) = generate_book(&mut render_options) {
895+
early_dcx.early_fatal(error);
896+
}
897+
860898
let config = core::create_config(input, options, &render_options);
861899

862900
let registered_lints = config.register_lints.is_some();

tests/run-make/rustdoc-default-output/output-default.stdout

+3
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ Options:
196196
--disable-minification
197197
disable the minification of CSS/JS files
198198
(perma-unstable, do not use with cached files)
199+
--book-location PATH or URL
200+
URL where the book is hosted or the folder where the
201+
mdBook source is located
199202
--plugin-path DIR
200203
removed, see issue #44136
201204
<https://github.com/rust-lang/rust/issues/44136> for

0 commit comments

Comments
 (0)