Merge pull request #1163 from kngwyu/kpp-edition2018
Make new [rust] section to config and place edition under it
This commit is contained in:
commit
712362f9e7
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -178,7 +197,7 @@ The following configuration options are available:
|
||||||
an icon link will be output in the menu bar of the book.
|
an icon link will be output in the menu bar of the book.
|
||||||
- **git-repository-icon:** The FontAwesome icon class to use for the git
|
- **git-repository-icon:** The FontAwesome icon class to use for the git
|
||||||
repository link. Defaults to `fa-github`.
|
repository link. Defaults to `fa-github`.
|
||||||
|
|
||||||
Available configuration options for the `[output.html.fold]` table:
|
Available configuration options for the `[output.html.fold]` table:
|
||||||
|
|
||||||
- **enable:** Enable section-folding. When off, all folds are open.
|
- **enable:** Enable section-folding. When off, all folds are open.
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue