Skip to content

Commit 99cf501

Browse files
authored
Merge pull request rust-lang#1163 from kngwyu/kpp-edition2018
Make new [rust] section to config and place edition under it
2 parents 1fb8b5f + 30c0928 commit 99cf501

File tree

5 files changed

+203
-15
lines changed

5 files changed

+203
-15
lines changed

book-example/book.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ description = "Create book from markdown files. Like Gitbook but implemented in
44
authors = ["Mathieu David", "Michael-F-Bryan"]
55
language = "en"
66

7+
[rust]
8+
edition = "2018"
9+
710
[output.html]
811
mathjax-support = true
912

book-example/src/format/config.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ title = "Example book"
1010
author = "John Doe"
1111
description = "The example book covers examples."
1212

13+
[rust]
14+
edition = "2018"
15+
1316
[build]
1417
build-dir = "my-example-book"
1518
create-missing = false
@@ -54,6 +57,22 @@ src = "my-src" # the source files will be found in `root/my-src` instead of `ro
5457
language = "en"
5558
```
5659

60+
### Rust options
61+
62+
Options for the Rust language, relevant to running tests and playground
63+
integration.
64+
65+
- **edition**: Rust edition to use by default for the code snippets. Default
66+
is "2015". Individual code blocks can be controlled with the `edition2015`
67+
or `edition2018` annotations, such as:
68+
69+
~~~text
70+
```rust,edition2015
71+
// This only works in 2015.
72+
let try = true;
73+
```
74+
~~~
75+
5776
### Build options
5877

5978
This controls the build process of your book.
@@ -178,7 +197,7 @@ The following configuration options are available:
178197
an icon link will be output in the menu bar of the book.
179198
- **git-repository-icon:** The FontAwesome icon class to use for the git
180199
repository link. Defaults to `fa-github`.
181-
200+
182201
Available configuration options for the `[output.html.fold]` table:
183202

184203
- **enable:** Enable section-folding. When off, all folds are open.

src/book/mod.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::preprocess::{
2727
use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
2828
use crate::utils;
2929

30-
use crate::config::Config;
30+
use crate::config::{Config, RustEdition};
3131

3232
/// The object used to manage and build a book.
3333
pub struct MDBook {
@@ -262,11 +262,21 @@ impl MDBook {
262262
let mut tmpf = utils::fs::create_file(&path)?;
263263
tmpf.write_all(ch.content.as_bytes())?;
264264

265-
let output = Command::new("rustdoc")
266-
.arg(&path)
267-
.arg("--test")
268-
.args(&library_args)
269-
.output()?;
265+
let mut cmd = Command::new("rustdoc");
266+
cmd.arg(&path).arg("--test").args(&library_args);
267+
268+
if let Some(edition) = self.config.rust.edition {
269+
match edition {
270+
RustEdition::E2015 => {
271+
cmd.args(&["--edition", "2015"]);
272+
}
273+
RustEdition::E2018 => {
274+
cmd.args(&["--edition", "2018"]);
275+
}
276+
}
277+
}
278+
279+
let output = cmd.output()?;
270280

271281
if !output.status.success() {
272282
bail!(ErrorKind::Subprocess(

src/config.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ pub struct Config {
7272
pub book: BookConfig,
7373
/// Information about the build environment.
7474
pub build: BuildConfig,
75+
/// Information about Rust language support.
76+
pub rust: RustConfig,
7577
rest: Value,
7678
}
7779

@@ -280,6 +282,7 @@ impl Default for Config {
280282
Config {
281283
book: BookConfig::default(),
282284
build: BuildConfig::default(),
285+
rust: RustConfig::default(),
283286
rest: Value::Table(Table::default()),
284287
}
285288
}
@@ -320,9 +323,15 @@ impl<'de> Deserialize<'de> for Config {
320323
.and_then(|value| value.try_into().ok())
321324
.unwrap_or_default();
322325

326+
let rust: RustConfig = table
327+
.remove("rust")
328+
.and_then(|value| value.try_into().ok())
329+
.unwrap_or_default();
330+
323331
Ok(Config {
324332
book,
325333
build,
334+
rust,
326335
rest: Value::Table(table),
327336
})
328337
}
@@ -331,6 +340,7 @@ impl<'de> Deserialize<'de> for Config {
331340
impl Serialize for Config {
332341
fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
333342
use serde::ser::Error;
343+
// TODO: This should probably be removed and use a derive instead.
334344

335345
let mut table = self.rest.clone();
336346

@@ -340,8 +350,10 @@ impl Serialize for Config {
340350
return Err(S::Error::custom("Unable to serialize the BookConfig"));
341351
}
342352
};
353+
let rust_config = Value::try_from(&self.rust).expect("should always be serializable");
343354

344355
table.insert("book", book_config).expect("unreachable");
356+
table.insert("rust", rust_config).expect("unreachable");
345357
table.serialize(s)
346358
}
347359
}
@@ -432,6 +444,25 @@ impl Default for BuildConfig {
432444
}
433445
}
434446

447+
/// Configuration for the Rust compiler(e.g., for playpen)
448+
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
449+
#[serde(default, rename_all = "kebab-case")]
450+
pub struct RustConfig {
451+
/// Rust edition used in playpen
452+
pub edition: Option<RustEdition>,
453+
}
454+
455+
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
456+
/// Rust edition to use for the code.
457+
pub enum RustEdition {
458+
/// The 2018 edition of Rust
459+
#[serde(rename = "2018")]
460+
E2018,
461+
/// The 2015 edition of Rust
462+
#[serde(rename = "2015")]
463+
E2015,
464+
}
465+
435466
/// Configuration for the HTML renderer.
436467
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
437468
#[serde(default, rename_all = "kebab-case")]
@@ -653,6 +684,7 @@ mod tests {
653684
create_missing: false,
654685
use_default_preprocessors: true,
655686
};
687+
let rust_should_be = RustConfig { edition: None };
656688
let playpen_should_be = Playpen {
657689
editable: true,
658690
copyable: true,
@@ -675,9 +707,62 @@ mod tests {
675707

676708
assert_eq!(got.book, book_should_be);
677709
assert_eq!(got.build, build_should_be);
710+
assert_eq!(got.rust, rust_should_be);
678711
assert_eq!(got.html_config().unwrap(), html_should_be);
679712
}
680713

714+
#[test]
715+
fn edition_2015() {
716+
let src = r#"
717+
[book]
718+
title = "mdBook Documentation"
719+
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
720+
authors = ["Mathieu David"]
721+
src = "./source"
722+
[rust]
723+
edition = "2015"
724+
"#;
725+
726+
let book_should_be = BookConfig {
727+
title: Some(String::from("mdBook Documentation")),
728+
description: Some(String::from(
729+
"Create book from markdown files. Like Gitbook but implemented in Rust",
730+
)),
731+
authors: vec![String::from("Mathieu David")],
732+
src: PathBuf::from("./source"),
733+
..Default::default()
734+
};
735+
736+
let got = Config::from_str(src).unwrap();
737+
assert_eq!(got.book, book_should_be);
738+
739+
let rust_should_be = RustConfig {
740+
edition: Some(RustEdition::E2015),
741+
};
742+
let got = Config::from_str(src).unwrap();
743+
assert_eq!(got.rust, rust_should_be);
744+
}
745+
746+
#[test]
747+
fn edition_2018() {
748+
let src = r#"
749+
[book]
750+
title = "mdBook Documentation"
751+
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
752+
authors = ["Mathieu David"]
753+
src = "./source"
754+
[rust]
755+
edition = "2018"
756+
"#;
757+
758+
let rust_should_be = RustConfig {
759+
edition: Some(RustEdition::E2018),
760+
};
761+
762+
let got = Config::from_str(src).unwrap();
763+
assert_eq!(got.rust, rust_should_be);
764+
}
765+
681766
#[test]
682767
fn load_arbitrary_output_type() {
683768
#[derive(Debug, Deserialize, PartialEq)]

src/renderer/html_handlebars/hbs_renderer.rs

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::book::{Book, BookItem};
2-
use crate::config::{Config, HtmlConfig, Playpen};
2+
use crate::config::{Config, HtmlConfig, Playpen, RustEdition};
33
use crate::errors::*;
44
use crate::renderer::html_handlebars::helpers;
55
use crate::renderer::{RenderContext, Renderer};
@@ -85,7 +85,7 @@ impl HtmlHandlebars {
8585
debug!("Render template");
8686
let rendered = ctx.handlebars.render("index", &ctx.data)?;
8787

88-
let rendered = self.post_process(rendered, &ctx.html_config.playpen);
88+
let rendered = self.post_process(rendered, &ctx.html_config.playpen, ctx.edition);
8989

9090
// Write to file
9191
debug!("Creating {}", filepath.display());
@@ -96,7 +96,8 @@ impl HtmlHandlebars {
9696
ctx.data.insert("path_to_root".to_owned(), json!(""));
9797
ctx.data.insert("is_index".to_owned(), json!("true"));
9898
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
99-
let rendered_index = self.post_process(rendered_index, &ctx.html_config.playpen);
99+
let rendered_index =
100+
self.post_process(rendered_index, &ctx.html_config.playpen, ctx.edition);
100101
debug!("Creating index.html from {}", path);
101102
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
102103
}
@@ -106,10 +107,15 @@ impl HtmlHandlebars {
106107
}
107108

108109
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
109-
fn post_process(&self, rendered: String, playpen_config: &Playpen) -> String {
110+
fn post_process(
111+
&self,
112+
rendered: String,
113+
playpen_config: &Playpen,
114+
edition: Option<RustEdition>,
115+
) -> String {
110116
let rendered = build_header_links(&rendered);
111117
let rendered = fix_code_blocks(&rendered);
112-
let rendered = add_playpen_pre(&rendered, playpen_config);
118+
let rendered = add_playpen_pre(&rendered, playpen_config, edition);
113119

114120
rendered
115121
}
@@ -343,6 +349,7 @@ impl Renderer for HtmlHandlebars {
343349
data: data.clone(),
344350
is_index,
345351
html_config: html_config.clone(),
352+
edition: ctx.config.rust.edition,
346353
};
347354
self.render_item(item, ctx, &mut print_content)?;
348355
is_index = false;
@@ -358,7 +365,7 @@ impl Renderer for HtmlHandlebars {
358365
debug!("Render template");
359366
let rendered = handlebars.render("index", &data)?;
360367

361-
let rendered = self.post_process(rendered, &html_config.playpen);
368+
let rendered = self.post_process(rendered, &html_config.playpen, ctx.config.rust.edition);
362369

363370
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
364371
debug!("Creating print.html ✓");
@@ -605,7 +612,7 @@ fn fix_code_blocks(html: &str) -> String {
605612
.into_owned()
606613
}
607614

608-
fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
615+
fn add_playpen_pre(html: &str, playpen_config: &Playpen, edition: Option<RustEdition>) -> String {
609616
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
610617
regex
611618
.replace_all(html, |caps: &Captures<'_>| {
@@ -617,10 +624,24 @@ fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
617624
if (!classes.contains("ignore") && !classes.contains("noplaypen"))
618625
|| classes.contains("mdbook-runnable")
619626
{
627+
let contains_e2015 = classes.contains("edition2015");
628+
let contains_e2018 = classes.contains("edition2018");
629+
let edition_class = if contains_e2015 || contains_e2018 {
630+
// the user forced edition, we should not overwrite it
631+
""
632+
} else {
633+
match edition {
634+
Some(RustEdition::E2015) => " edition2015",
635+
Some(RustEdition::E2018) => " edition2018",
636+
None => "",
637+
}
638+
};
639+
620640
// wrap the contents in an external pre block
621641
format!(
622-
"<pre class=\"playpen\"><code class=\"{}\">{}</code></pre>",
642+
"<pre class=\"playpen\"><code class=\"{}{}\">{}</code></pre>",
623643
classes,
644+
edition_class,
624645
{
625646
let content: Cow<'_, str> = if playpen_config.editable
626647
&& classes.contains("editable")
@@ -711,6 +732,7 @@ struct RenderItemContext<'a> {
711732
data: serde_json::Map<String, serde_json::Value>,
712733
is_index: bool,
713734
html_config: HtmlConfig,
735+
edition: Option<RustEdition>,
714736
}
715737

716738
#[cfg(test)]
@@ -777,6 +799,55 @@ mod tests {
777799
editable: true,
778800
..Playpen::default()
779801
},
802+
None,
803+
);
804+
assert_eq!(&*got, *should_be);
805+
}
806+
}
807+
#[test]
808+
fn add_playpen_edition2015() {
809+
let inputs = [
810+
("<code class=\"language-rust\">x()</code>",
811+
"<pre class=\"playpen\"><code class=\"language-rust edition2015\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
812+
("<code class=\"language-rust\">fn main() {}</code>",
813+
"<pre class=\"playpen\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
814+
("<code class=\"language-rust edition2015\">fn main() {}</code>",
815+
"<pre class=\"playpen\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
816+
("<code class=\"language-rust edition2018\">fn main() {}</code>",
817+
"<pre class=\"playpen\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
818+
];
819+
for (src, should_be) in &inputs {
820+
let got = add_playpen_pre(
821+
src,
822+
&Playpen {
823+
editable: true,
824+
..Playpen::default()
825+
},
826+
Some(RustEdition::E2015),
827+
);
828+
assert_eq!(&*got, *should_be);
829+
}
830+
}
831+
#[test]
832+
fn add_playpen_edition2018() {
833+
let inputs = [
834+
("<code class=\"language-rust\">x()</code>",
835+
"<pre class=\"playpen\"><code class=\"language-rust edition2018\">\n<span class=\"boring\">#![allow(unused)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
836+
("<code class=\"language-rust\">fn main() {}</code>",
837+
"<pre class=\"playpen\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
838+
("<code class=\"language-rust edition2015\">fn main() {}</code>",
839+
"<pre class=\"playpen\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
840+
("<code class=\"language-rust edition2018\">fn main() {}</code>",
841+
"<pre class=\"playpen\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
842+
];
843+
for (src, should_be) in &inputs {
844+
let got = add_playpen_pre(
845+
src,
846+
&Playpen {
847+
editable: true,
848+
..Playpen::default()
849+
},
850+
Some(RustEdition::E2018),
780851
);
781852
assert_eq!(&*got, *should_be);
782853
}

0 commit comments

Comments
 (0)