Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make new [rust] section to config and place edition under it #1163

Merged
merged 4 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions book-example/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ description = "Create book from markdown files. Like Gitbook but implemented in
authors = ["Mathieu David", "Michael-F-Bryan"]
language = "en"

[rust]
edition = "2018"

[output.html]
mathjax-support = true

Expand Down
21 changes: 20 additions & 1 deletion book-example/src/format/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ title = "Example book"
author = "John Doe"
description = "The example book covers examples."

[rust]
edition = "2018"

[build]
build-dir = "my-example-book"
create-missing = false
Expand Down Expand Up @@ -54,6 +57,22 @@ src = "my-src" # the source files will be found in `root/my-src` instead of `ro
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

This controls the build process of your book.
Expand Down Expand Up @@ -178,7 +197,7 @@ The following configuration options are available:
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
repository link. Defaults to `fa-github`.

Available configuration options for the `[output.html.fold]` table:

- **enable:** Enable section-folding. When off, all folds are open.
Expand Down
22 changes: 16 additions & 6 deletions src/book/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::preprocess::{
use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer};
use crate::utils;

use crate::config::Config;
use crate::config::{Config, RustEdition};

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

let output = Command::new("rustdoc")
.arg(&path)
.arg("--test")
.args(&library_args)
.output()?;
let mut cmd = Command::new("rustdoc");
cmd.arg(&path).arg("--test").args(&library_args);

if let Some(edition) = self.config.rust.edition {
match edition {
RustEdition::E2015 => {
cmd.args(&["--edition", "2015"]);
}
RustEdition::E2018 => {
cmd.args(&["--edition", "2018"]);
}
}
}

let output = cmd.output()?;

if !output.status.success() {
bail!(ErrorKind::Subprocess(
Expand Down
85 changes: 85 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ pub struct Config {
pub book: BookConfig,
/// Information about the build environment.
pub build: BuildConfig,
/// Information about Rust language support.
pub rust: RustConfig,
rest: Value,
}

Expand Down Expand Up @@ -280,6 +282,7 @@ impl Default for Config {
Config {
book: BookConfig::default(),
build: BuildConfig::default(),
rust: RustConfig::default(),
rest: Value::Table(Table::default()),
}
}
Expand Down Expand Up @@ -320,9 +323,15 @@ impl<'de> Deserialize<'de> for Config {
.and_then(|value| value.try_into().ok())
.unwrap_or_default();

let rust: RustConfig = table
.remove("rust")
.and_then(|value| value.try_into().ok())
.unwrap_or_default();

Ok(Config {
book,
build,
rust,
rest: Value::Table(table),
})
}
Expand All @@ -331,6 +340,7 @@ impl<'de> Deserialize<'de> for Config {
impl Serialize for Config {
fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
use serde::ser::Error;
// TODO: This should probably be removed and use a derive instead.

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

Expand All @@ -340,8 +350,10 @@ impl Serialize for Config {
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("rust", rust_config).expect("unreachable");
table.serialize(s)
}
}
Expand Down Expand Up @@ -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.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
Expand Down Expand Up @@ -653,6 +684,7 @@ mod tests {
create_missing: false,
use_default_preprocessors: true,
};
let rust_should_be = RustConfig { edition: None };
let playpen_should_be = Playpen {
editable: true,
copyable: true,
Expand All @@ -675,9 +707,62 @@ mod tests {

assert_eq!(got.book, book_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);
}

#[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]
fn load_arbitrary_output_type() {
#[derive(Debug, Deserialize, PartialEq)]
Expand Down
87 changes: 79 additions & 8 deletions src/renderer/html_handlebars/hbs_renderer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::book::{Book, BookItem};
use crate::config::{Config, HtmlConfig, Playpen};
use crate::config::{Config, HtmlConfig, Playpen, RustEdition};
use crate::errors::*;
use crate::renderer::html_handlebars::helpers;
use crate::renderer::{RenderContext, Renderer};
Expand Down Expand Up @@ -85,7 +85,7 @@ impl HtmlHandlebars {
debug!("Render template");
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
debug!("Creating {}", filepath.display());
Expand All @@ -96,7 +96,8 @@ impl HtmlHandlebars {
ctx.data.insert("path_to_root".to_owned(), json!(""));
ctx.data.insert("is_index".to_owned(), json!("true"));
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);
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
}
Expand All @@ -106,10 +107,15 @@ impl HtmlHandlebars {
}

#[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 = fix_code_blocks(&rendered);
let rendered = add_playpen_pre(&rendered, playpen_config);
let rendered = add_playpen_pre(&rendered, playpen_config, edition);

rendered
}
Expand Down Expand Up @@ -343,6 +349,7 @@ impl Renderer for HtmlHandlebars {
data: data.clone(),
is_index,
html_config: html_config.clone(),
edition: ctx.config.rust.edition,
};
self.render_item(item, ctx, &mut print_content)?;
is_index = false;
Expand All @@ -358,7 +365,7 @@ impl Renderer for HtmlHandlebars {
debug!("Render template");
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())?;
debug!("Creating print.html ✓");
Expand Down Expand Up @@ -605,7 +612,7 @@ fn fix_code_blocks(html: &str) -> String {
.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();
regex
.replace_all(html, |caps: &Captures<'_>| {
Expand All @@ -617,10 +624,24 @@ fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
if (!classes.contains("ignore") && !classes.contains("noplaypen"))
|| 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
format!(
"<pre class=\"playpen\"><code class=\"{}\">{}</code></pre>",
"<pre class=\"playpen\"><code class=\"{}{}\">{}</code></pre>",
classes,
edition_class,
{
let content: Cow<'_, str> = if playpen_config.editable
&& classes.contains("editable")
Expand Down Expand Up @@ -711,6 +732,7 @@ struct RenderItemContext<'a> {
data: serde_json::Map<String, serde_json::Value>,
is_index: bool,
html_config: HtmlConfig,
edition: Option<RustEdition>,
}

#[cfg(test)]
Expand Down Expand Up @@ -777,6 +799,55 @@ mod tests {
editable: true,
..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);
}
Expand Down