Skip to content

Commit 9689b41

Browse files
Add --book-location option to provide guide alongside documentation
1 parent db0e836 commit 9689b41

File tree

11 files changed

+701
-33
lines changed

11 files changed

+701
-33
lines changed

Cargo.lock

Lines changed: 591 additions & 32 deletions
Large diffs are not rendered by default.

src/librustdoc/Cargo.toml

Lines changed: 1 addition & 0 deletions
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-escape = { version = "0.11.0", features = ["simd"] }
1819
regex = "1"

src/librustdoc/config.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,22 @@ impl fmt::Debug for Options {
224224
}
225225
}
226226

227+
#[derive(Clone, Debug)]
228+
pub(crate) enum PathOrUrl {
229+
Path(PathBuf),
230+
Url(String),
231+
}
232+
233+
impl PathOrUrl {
234+
fn new(s: String) -> Self {
235+
if s.starts_with("https://") || s.starts_with("http://") {
236+
Self::Url(s)
237+
} else {
238+
Self::Path(s.into())
239+
}
240+
}
241+
}
242+
227243
/// Configuration options for the HTML page-creation process.
228244
#[derive(Clone, Debug)]
229245
pub(crate) struct RenderOptions {
@@ -305,6 +321,8 @@ pub(crate) struct RenderOptions {
305321
pub(crate) parts_out_dir: Option<PathToParts>,
306322
/// disable minification of CSS/JS
307323
pub(crate) disable_minification: bool,
324+
/// Location where the associated book is located.
325+
pub(crate) book_location: Option<PathOrUrl>,
308326
}
309327

310328
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -808,6 +826,7 @@ impl Options {
808826
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
809827

810828
let disable_minification = matches.opt_present("disable-minification");
829+
let book_location = matches.opt_str("book-location").map(PathOrUrl::new);
811830

812831
let options = Options {
813832
bin_crate,
@@ -886,6 +905,7 @@ impl Options {
886905
include_parts_dir,
887906
parts_out_dir,
888907
disable_minification,
908+
book_location,
889909
};
890910
Some((input, options, render_options))
891911
}

src/librustdoc/html/markdown.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,6 +2047,7 @@ fn is_default_id(id: &str) -> bool {
20472047
| "copy-path"
20482048
| "rustdoc-toc"
20492049
| "rustdoc-modnav"
2050+
| "book-loc"
20502051
// This is the list of IDs used by rustdoc sections (but still generated by
20512052
// rustdoc).
20522053
| "fields"

src/librustdoc/html/render/context.rs

Lines changed: 4 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,27 @@ ul.block, .block li, .block ul {
836836
margin-bottom: 1rem;
837837
}
838838

839+
.sidebar .book {
840+
display: flex;
841+
}
842+
843+
.book::before {
844+
width: 1em;
845+
height: 1em;
846+
/* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License -
847+
https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.*/
848+
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">\
849+
<path d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 \
850+
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 \
851+
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 \
852+
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 \
853+
16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/></svg>');
854+
display: block;
855+
margin-right: 6px;
856+
padding-top: 3px;
857+
filter: var(--book-img-filter);
858+
}
859+
839860
.mobile-topbar {
840861
display: none;
841862
}
@@ -2989,6 +3010,7 @@ by default.
29893010
--scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0);
29903011
--sidebar-resizer-hover: hsl(207, 90%, 66%);
29913012
--sidebar-resizer-active: hsl(207, 90%, 54%);
3013+
--book-img-filter: invert(0%);
29923014
}
29933015
/* End theme: light */
29943016

@@ -3097,6 +3119,7 @@ by default.
30973119
--scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0);
30983120
--sidebar-resizer-hover: hsl(207, 30%, 54%);
30993121
--sidebar-resizer-active: hsl(207, 90%, 54%);
3122+
--book-img-filter: invert(80%);
31003123
}
31013124
/* End theme: dark */
31023125

@@ -3209,6 +3232,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
32093232
--scrape-example-code-wrapper-background-end: rgba(15, 20, 25, 0);
32103233
--sidebar-resizer-hover: hsl(34, 50%, 33%);
32113234
--sidebar-resizer-active: hsl(34, 100%, 66%);
3235+
--book-img-filter: invert(100%);
32123236
}
32133237

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

src/librustdoc/html/templates/sidebar.html

Lines changed: 14 additions & 0 deletions
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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,14 @@ fn opts() -> Vec<RustcOptGroup> {
666666
"disable the minification of CSS/JS files (perma-unstable, do not use with cached files)",
667667
"",
668668
),
669+
opt(
670+
Unstable,
671+
Opt,
672+
"",
673+
"book-location",
674+
"URL where the book is hosted or the folder where the mdBook source is located",
675+
"PATH or URL",
676+
),
669677
// deprecated / removed options
670678
opt(
671679
Stable,
@@ -749,6 +757,32 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
749757
}
750758
}
751759

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

802836
// Note that we discard any distinction between different non-zero exit
803837
// codes from `from_matches` here.
804-
let (input, options, render_options) =
838+
let (input, options, mut render_options) =
805839
match config::Options::from_matches(early_dcx, &matches, args) {
806840
Some(opts) => opts,
807841
None => return,
@@ -865,6 +899,10 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
865899
let scrape_examples_options = options.scrape_examples_options.clone();
866900
let bin_crate = options.bin_crate;
867901

902+
if let Err(error) = generate_book(&mut render_options) {
903+
early_dcx.early_fatal(error);
904+
}
905+
868906
let config = core::create_config(input, options, &render_options);
869907

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ Options:
193193
--disable-minification
194194
disable the minification of CSS/JS files
195195
(perma-unstable, do not use with cached files)
196+
--book-location PATH or URL
197+
URL where the book is hosted or the folder where the
198+
mdBook source is located
196199
--plugin-path DIR
197200
removed, see issue #44136
198201
<https://github.com/rust-lang/rust/issues/44136> for

0 commit comments

Comments
 (0)