Merge pull request #1163 from kngwyu/kpp-edition2018

Make new [rust] section to config and  place edition under it
This commit is contained in:
Eric Huss 2020-04-21 13:00:15 -07:00 committed by GitHub
commit 712362f9e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 203 additions and 15 deletions

View File

@ -4,6 +4,9 @@ description = "Create book from markdown files. Like Gitbook but implemented in
authors = ["Mathieu David", "Michael-F-Bryan"] authors = ["Mathieu David", "Michael-F-Bryan"]
language = "en" language = "en"
[rust]
edition = "2018"
[output.html] [output.html]
mathjax-support = true mathjax-support = true

View File

@ -10,6 +10,9 @@ title = "Example book"
author = "John Doe" author = "John Doe"
description = "The example book covers examples." description = "The example book covers examples."
[rust]
edition = "2018"
[build] [build]
build-dir = "my-example-book" build-dir = "my-example-book"
create-missing = false create-missing = false
@ -54,6 +57,22 @@ src = "my-src" # the source files will be found in `root/my-src` instead of `ro
language = "en" language = "en"
``` ```
### Rust options
Options for the Rust language, relevant to running tests and playground
integration.
- **edition**: Rust edition to use by default for the code snippets. Default
is "2015". Individual code blocks can be controlled with the `edition2015`
or `edition2018` annotations, such as:
~~~text
```rust,edition2015
// This only works in 2015.
let try = true;
```
~~~
### Build options ### Build options
This controls the build process of your book. This controls the build process of your book.

View File

@ -27,7 +27,7 @@ use crate::preprocess::{
use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
use crate::utils; use crate::utils;
use crate::config::Config; use crate::config::{Config, RustEdition};
/// The object used to manage and build a book. /// The object used to manage and build a book.
pub struct MDBook { pub struct MDBook {
@ -262,11 +262,21 @@ impl MDBook {
let mut tmpf = utils::fs::create_file(&path)?; let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(ch.content.as_bytes())?; tmpf.write_all(ch.content.as_bytes())?;
let output = Command::new("rustdoc") let mut cmd = Command::new("rustdoc");
.arg(&path) cmd.arg(&path).arg("--test").args(&library_args);
.arg("--test")
.args(&library_args) if let Some(edition) = self.config.rust.edition {
.output()?; match edition {
RustEdition::E2015 => {
cmd.args(&["--edition", "2015"]);
}
RustEdition::E2018 => {
cmd.args(&["--edition", "2018"]);
}
}
}
let output = cmd.output()?;
if !output.status.success() { if !output.status.success() {
bail!(ErrorKind::Subprocess( bail!(ErrorKind::Subprocess(

View File

@ -72,6 +72,8 @@ pub struct Config {
pub book: BookConfig, pub book: BookConfig,
/// Information about the build environment. /// Information about the build environment.
pub build: BuildConfig, pub build: BuildConfig,
/// Information about Rust language support.
pub rust: RustConfig,
rest: Value, rest: Value,
} }
@ -280,6 +282,7 @@ impl Default for Config {
Config { Config {
book: BookConfig::default(), book: BookConfig::default(),
build: BuildConfig::default(), build: BuildConfig::default(),
rust: RustConfig::default(),
rest: Value::Table(Table::default()), rest: Value::Table(Table::default()),
} }
} }
@ -320,9 +323,15 @@ impl<'de> Deserialize<'de> for Config {
.and_then(|value| value.try_into().ok()) .and_then(|value| value.try_into().ok())
.unwrap_or_default(); .unwrap_or_default();
let rust: RustConfig = table
.remove("rust")
.and_then(|value| value.try_into().ok())
.unwrap_or_default();
Ok(Config { Ok(Config {
book, book,
build, build,
rust,
rest: Value::Table(table), rest: Value::Table(table),
}) })
} }
@ -331,6 +340,7 @@ impl<'de> Deserialize<'de> for Config {
impl Serialize for Config { impl Serialize for Config {
fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
use serde::ser::Error; use serde::ser::Error;
// TODO: This should probably be removed and use a derive instead.
let mut table = self.rest.clone(); let mut table = self.rest.clone();
@ -340,8 +350,10 @@ impl Serialize for Config {
return Err(S::Error::custom("Unable to serialize the BookConfig")); return Err(S::Error::custom("Unable to serialize the BookConfig"));
} }
}; };
let rust_config = Value::try_from(&self.rust).expect("should always be serializable");
table.insert("book", book_config).expect("unreachable"); table.insert("book", book_config).expect("unreachable");
table.insert("rust", rust_config).expect("unreachable");
table.serialize(s) table.serialize(s)
} }
} }
@ -432,6 +444,25 @@ impl Default for BuildConfig {
} }
} }
/// Configuration for the Rust compiler(e.g., for playpen)
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct RustConfig {
/// Rust edition used in playpen
pub edition: Option<RustEdition>,
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
/// Rust edition to use for the code.
pub enum RustEdition {
/// The 2018 edition of Rust
#[serde(rename = "2018")]
E2018,
/// The 2015 edition of Rust
#[serde(rename = "2015")]
E2015,
}
/// Configuration for the HTML renderer. /// Configuration for the HTML renderer.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
@ -653,6 +684,7 @@ mod tests {
create_missing: false, create_missing: false,
use_default_preprocessors: true, use_default_preprocessors: true,
}; };
let rust_should_be = RustConfig { edition: None };
let playpen_should_be = Playpen { let playpen_should_be = Playpen {
editable: true, editable: true,
copyable: true, copyable: true,
@ -675,9 +707,62 @@ mod tests {
assert_eq!(got.book, book_should_be); assert_eq!(got.book, book_should_be);
assert_eq!(got.build, build_should_be); assert_eq!(got.build, build_should_be);
assert_eq!(got.rust, rust_should_be);
assert_eq!(got.html_config().unwrap(), html_should_be); assert_eq!(got.html_config().unwrap(), html_should_be);
} }
#[test]
fn edition_2015() {
let src = r#"
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David"]
src = "./source"
[rust]
edition = "2015"
"#;
let book_should_be = BookConfig {
title: Some(String::from("mdBook Documentation")),
description: Some(String::from(
"Create book from markdown files. Like Gitbook but implemented in Rust",
)),
authors: vec![String::from("Mathieu David")],
src: PathBuf::from("./source"),
..Default::default()
};
let got = Config::from_str(src).unwrap();
assert_eq!(got.book, book_should_be);
let rust_should_be = RustConfig {
edition: Some(RustEdition::E2015),
};
let got = Config::from_str(src).unwrap();
assert_eq!(got.rust, rust_should_be);
}
#[test]
fn edition_2018() {
let src = r#"
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David"]
src = "./source"
[rust]
edition = "2018"
"#;
let rust_should_be = RustConfig {
edition: Some(RustEdition::E2018),
};
let got = Config::from_str(src).unwrap();
assert_eq!(got.rust, rust_should_be);
}
#[test] #[test]
fn load_arbitrary_output_type() { fn load_arbitrary_output_type() {
#[derive(Debug, Deserialize, PartialEq)] #[derive(Debug, Deserialize, PartialEq)]

View File

@ -1,5 +1,5 @@
use crate::book::{Book, BookItem}; use crate::book::{Book, BookItem};
use crate::config::{Config, HtmlConfig, Playpen}; use crate::config::{Config, HtmlConfig, Playpen, RustEdition};
use crate::errors::*; use crate::errors::*;
use crate::renderer::html_handlebars::helpers; use crate::renderer::html_handlebars::helpers;
use crate::renderer::{RenderContext, Renderer}; use crate::renderer::{RenderContext, Renderer};
@ -85,7 +85,7 @@ impl HtmlHandlebars {
debug!("Render template"); debug!("Render template");
let rendered = ctx.handlebars.render("index", &ctx.data)?; let rendered = ctx.handlebars.render("index", &ctx.data)?;
let rendered = self.post_process(rendered, &ctx.html_config.playpen); let rendered = self.post_process(rendered, &ctx.html_config.playpen, ctx.edition);
// Write to file // Write to file
debug!("Creating {}", filepath.display()); debug!("Creating {}", filepath.display());
@ -96,7 +96,8 @@ impl HtmlHandlebars {
ctx.data.insert("path_to_root".to_owned(), json!("")); ctx.data.insert("path_to_root".to_owned(), json!(""));
ctx.data.insert("is_index".to_owned(), json!("true")); ctx.data.insert("is_index".to_owned(), json!("true"));
let rendered_index = ctx.handlebars.render("index", &ctx.data)?; let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
let rendered_index = self.post_process(rendered_index, &ctx.html_config.playpen); let rendered_index =
self.post_process(rendered_index, &ctx.html_config.playpen, ctx.edition);
debug!("Creating index.html from {}", path); debug!("Creating index.html from {}", path);
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?; utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
} }
@ -106,10 +107,15 @@ impl HtmlHandlebars {
} }
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))] #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
fn post_process(&self, rendered: String, playpen_config: &Playpen) -> String { fn post_process(
&self,
rendered: String,
playpen_config: &Playpen,
edition: Option<RustEdition>,
) -> String {
let rendered = build_header_links(&rendered); let rendered = build_header_links(&rendered);
let rendered = fix_code_blocks(&rendered); let rendered = fix_code_blocks(&rendered);
let rendered = add_playpen_pre(&rendered, playpen_config); let rendered = add_playpen_pre(&rendered, playpen_config, edition);
rendered rendered
} }
@ -343,6 +349,7 @@ impl Renderer for HtmlHandlebars {
data: data.clone(), data: data.clone(),
is_index, is_index,
html_config: html_config.clone(), html_config: html_config.clone(),
edition: ctx.config.rust.edition,
}; };
self.render_item(item, ctx, &mut print_content)?; self.render_item(item, ctx, &mut print_content)?;
is_index = false; is_index = false;
@ -358,7 +365,7 @@ impl Renderer for HtmlHandlebars {
debug!("Render template"); debug!("Render template");
let rendered = handlebars.render("index", &data)?; let rendered = handlebars.render("index", &data)?;
let rendered = self.post_process(rendered, &html_config.playpen); let rendered = self.post_process(rendered, &html_config.playpen, ctx.config.rust.edition);
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?; utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
debug!("Creating print.html ✓"); debug!("Creating print.html ✓");
@ -605,7 +612,7 @@ fn fix_code_blocks(html: &str) -> String {
.into_owned() .into_owned()
} }
fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String { fn add_playpen_pre(html: &str, playpen_config: &Playpen, edition: Option<RustEdition>) -> String {
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap(); let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
regex regex
.replace_all(html, |caps: &Captures<'_>| { .replace_all(html, |caps: &Captures<'_>| {
@ -617,10 +624,24 @@ fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
if (!classes.contains("ignore") && !classes.contains("noplaypen")) if (!classes.contains("ignore") && !classes.contains("noplaypen"))
|| classes.contains("mdbook-runnable") || classes.contains("mdbook-runnable")
{ {
let contains_e2015 = classes.contains("edition2015");
let contains_e2018 = classes.contains("edition2018");
let edition_class = if contains_e2015 || contains_e2018 {
// the user forced edition, we should not overwrite it
""
} else {
match edition {
Some(RustEdition::E2015) => " edition2015",
Some(RustEdition::E2018) => " edition2018",
None => "",
}
};
// wrap the contents in an external pre block // wrap the contents in an external pre block
format!( format!(
"<pre class=\"playpen\"><code class=\"{}\">{}</code></pre>", "<pre class=\"playpen\"><code class=\"{}{}\">{}</code></pre>",
classes, classes,
edition_class,
{ {
let content: Cow<'_, str> = if playpen_config.editable let content: Cow<'_, str> = if playpen_config.editable
&& classes.contains("editable") && classes.contains("editable")
@ -711,6 +732,7 @@ struct RenderItemContext<'a> {
data: serde_json::Map<String, serde_json::Value>, data: serde_json::Map<String, serde_json::Value>,
is_index: bool, is_index: bool,
html_config: HtmlConfig, html_config: HtmlConfig,
edition: Option<RustEdition>,
} }
#[cfg(test)] #[cfg(test)]
@ -777,6 +799,55 @@ mod tests {
editable: true, editable: true,
..Playpen::default() ..Playpen::default()
}, },
None,
);
assert_eq!(&*got, *should_be);
}
}
#[test]
fn add_playpen_edition2015() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<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>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playpen\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playpen\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playpen\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playpen_pre(
src,
&Playpen {
editable: true,
..Playpen::default()
},
Some(RustEdition::E2015),
);
assert_eq!(&*got, *should_be);
}
}
#[test]
fn add_playpen_edition2018() {
let inputs = [
("<code class=\"language-rust\">x()</code>",
"<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>"),
("<code class=\"language-rust\">fn main() {}</code>",
"<pre class=\"playpen\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
("<code class=\"language-rust edition2015\">fn main() {}</code>",
"<pre class=\"playpen\"><code class=\"language-rust edition2015\">fn main() {}\n</code></pre>"),
("<code class=\"language-rust edition2018\">fn main() {}</code>",
"<pre class=\"playpen\"><code class=\"language-rust edition2018\">fn main() {}\n</code></pre>"),
];
for (src, should_be) in &inputs {
let got = add_playpen_pre(
src,
&Playpen {
editable: true,
..Playpen::default()
},
Some(RustEdition::E2018),
); );
assert_eq!(&*got, *should_be); assert_eq!(&*got, *should_be);
} }