Merge remote-tracking branch 'origin/parse-toml' into ebooks
This commit is contained in:
commit
2cae064040
13
Cargo.toml
13
Cargo.toml
|
@ -15,13 +15,14 @@ exclude = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.2.1"
|
clap = "2.19.2"
|
||||||
handlebars = { version = "0.20.0", features = ["serde_type"] }
|
handlebars = { version = "0.23.0", features = ["serde_type"] }
|
||||||
serde = "0.8.17"
|
serde = "0.8"
|
||||||
serde_json = "0.8.3"
|
serde_json = "0.8"
|
||||||
pulldown-cmark = "0.0.8"
|
pulldown-cmark = "0.0.8"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
env_logger = "0.3.4"
|
env_logger = "0.3"
|
||||||
|
toml = { version = "0.2", features = ["serde"] }
|
||||||
|
|
||||||
# Watch feature
|
# Watch feature
|
||||||
notify = { version = "2.5.5", optional = true }
|
notify = { version = "2.5.5", optional = true }
|
||||||
|
@ -33,12 +34,10 @@ iron = { version = "0.4", optional = true }
|
||||||
staticfile = { version = "0.3", optional = true }
|
staticfile = { version = "0.3", optional = true }
|
||||||
ws = { version = "0.5.1", optional = true}
|
ws = { version = "0.5.1", optional = true}
|
||||||
|
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3.4"
|
tempdir = "0.3.4"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["output", "watch", "serve"]
|
default = ["output", "watch", "serve"]
|
||||||
debug = []
|
debug = []
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"title": "mdBook Documentation",
|
|
||||||
"description": "Create book from markdown files. Like Gitbook but implemented in Rust",
|
|
||||||
"author": "Mathieu David"
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
title = "mdBook Documentation"
|
||||||
|
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||||
|
author = "Mathieu David"
|
|
@ -10,7 +10,7 @@ mdBook supports a `test` command that will run all available tests in mdBook. At
|
||||||
- checking for unused files
|
- checking for unused files
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
In the future I would like the user to be able to enable / disable test from the `book.json` configuration file and support custom tests.
|
In the future I would like the user to be able to enable / disable test from the `book.toml` configuration file and support custom tests.
|
||||||
|
|
||||||
**How to use it:**
|
**How to use it:**
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
You can configure the parameters for your book in the ***book.json*** file.
|
You can configure the parameters for your book in the ***book.toml*** file.
|
||||||
|
|
||||||
Here is an example of what a ***book.json*** file might look like:
|
We encourage using the TOML format, but JSON is also recognized and parsed.
|
||||||
|
|
||||||
```json
|
Here is an example of what a ***book.toml*** file might look like:
|
||||||
{
|
|
||||||
"title": "Example book",
|
```toml
|
||||||
"author": "Name",
|
title = "Example book"
|
||||||
"description": "The example book covers examples.",
|
author = "Name"
|
||||||
"dest": "output/my-book"
|
description = "The example book covers examples."
|
||||||
}
|
dest = "output/my-book"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Supported variables
|
#### Supported variables
|
||||||
|
|
|
@ -4,5 +4,5 @@ In this section you will learn how to:
|
||||||
|
|
||||||
- Structure your book correctly
|
- Structure your book correctly
|
||||||
- Format your `SUMMARY.md` file
|
- Format your `SUMMARY.md` file
|
||||||
- Configure your book using `book.json`
|
- Configure your book using `book.toml`
|
||||||
- Customize your theme
|
- Customize your theme
|
||||||
|
|
|
@ -19,7 +19,7 @@ Here is a list of the properties that are exposed:
|
||||||
|
|
||||||
- ***language*** Language of the book in the form `en`. To use in <code class="language-html">\<html lang="{{ language }}"></code> for example.
|
- ***language*** Language of the book in the form `en`. To use in <code class="language-html">\<html lang="{{ language }}"></code> for example.
|
||||||
At the moment it is hardcoded.
|
At the moment it is hardcoded.
|
||||||
- ***title*** Title of the book, as specified in `book.json`
|
- ***title*** Title of the book, as specified in `book.toml`
|
||||||
|
|
||||||
- ***path*** Relative path to the original markdown file from the source directory
|
- ***path*** Relative path to the original markdown file from the source directory
|
||||||
- ***content*** This is the rendered markdown.
|
- ***content*** This is the rendered markdown.
|
||||||
|
|
|
@ -47,7 +47,7 @@ Will render as
|
||||||
# }
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
**At the moment, this only works for code examples that are annotated with `rust`. Because it would collide with semantics of some programming languages. In the future, we want to make this configurable through the `book.json` so that everyone can benefit from it.**
|
**At the moment, this only works for code examples that are annotated with `rust`. Because it would collide with semantics of some programming languages. In the future, we want to make this configurable through the `book.toml` so that everyone can benefit from it.**
|
||||||
|
|
||||||
|
|
||||||
## Improve default theme
|
## Improve default theme
|
||||||
|
|
|
@ -13,7 +13,7 @@ fn main() {
|
||||||
let mut book = MDBook::new(Path::new("my-book")) // Path to root
|
let mut book = MDBook::new(Path::new("my-book")) // Path to root
|
||||||
.set_src(Path::new("src")) // Path from root to source directory
|
.set_src(Path::new("src")) // Path from root to source directory
|
||||||
.set_dest(Path::new("book")) // Path from root to output directory
|
.set_dest(Path::new("book")) // Path from root to output directory
|
||||||
.read_config(); // Parse book.json file for configuration
|
.read_config(); // Parse book.toml or book.json file for configuration
|
||||||
|
|
||||||
book.build().unwrap(); // Render the book
|
book.build().unwrap(); // Render the book
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
use serde_json;
|
extern crate toml;
|
||||||
|
|
||||||
|
use std::process::exit;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BookConfig {
|
pub struct BookConfig {
|
||||||
|
@ -18,7 +23,6 @@ pub struct BookConfig {
|
||||||
multilingual: bool,
|
multilingual: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl BookConfig {
|
impl BookConfig {
|
||||||
pub fn new(root: &Path) -> Self {
|
pub fn new(root: &Path) -> Self {
|
||||||
BookConfig {
|
BookConfig {
|
||||||
|
@ -40,71 +44,117 @@ impl BookConfig {
|
||||||
|
|
||||||
debug!("[fn]: read_config");
|
debug!("[fn]: read_config");
|
||||||
|
|
||||||
// If the file does not exist, return early
|
let read_file = |path: PathBuf| -> String {
|
||||||
let mut config_file = match File::open(root.join("book.json")) {
|
let mut data = String::new();
|
||||||
Ok(f) => f,
|
let mut f: File = match File::open(&path) {
|
||||||
Err(_) => {
|
Ok(x) => x,
|
||||||
debug!("[*]: Failed to open {:?}", root.join("book.json"));
|
Err(_) => {
|
||||||
return self;
|
error!("[*]: Failed to open {:?}", &path);
|
||||||
},
|
exit(2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(_) = f.read_to_string(&mut data) {
|
||||||
|
error!("[*]: Failed to read {:?}", &path);
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("[*]: Reading config");
|
// Read book.toml or book.json if exists
|
||||||
let mut data = String::new();
|
|
||||||
|
|
||||||
// Just return if an error occured.
|
if Path::new(root.join("book.toml").as_os_str()).exists() {
|
||||||
// I would like to propagate the error, but I have to return `&self`
|
|
||||||
if let Err(_) = config_file.read_to_string(&mut data) {
|
debug!("[*]: Reading config");
|
||||||
return self;
|
let data = read_file(root.join("book.toml"));
|
||||||
|
self.parse_from_toml_string(&data);
|
||||||
|
|
||||||
|
} else if Path::new(root.join("book.json").as_os_str()).exists() {
|
||||||
|
|
||||||
|
debug!("[*]: Reading config");
|
||||||
|
let data = read_file(root.join("book.json"));
|
||||||
|
self.parse_from_json_string(&data);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
debug!("[*]: No book.toml or book.json was found, using defaults.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to JSON
|
self
|
||||||
if let Ok(config) = serde_json::from_str::<serde_json::Value>(&data) {
|
}
|
||||||
// Extract data
|
|
||||||
|
|
||||||
let config = config.as_object().unwrap();
|
pub fn parse_from_toml_string(&mut self, data: &String) -> &mut Self {
|
||||||
|
|
||||||
debug!("[*]: Extracting data from config");
|
let mut parser = toml::Parser::new(&data);
|
||||||
// Title, author, description
|
|
||||||
if let Some(a) = config.get("title") {
|
let config = match parser.parse() {
|
||||||
self.title = a.to_string().replace("\"", "")
|
Some(x) => {x},
|
||||||
}
|
None => {
|
||||||
if let Some(a) = config.get("author") {
|
error!("[*]: Toml parse errors in book.toml: {:?}", parser.errors);
|
||||||
self.author = a.to_string().replace("\"", "")
|
exit(2);
|
||||||
}
|
|
||||||
if let Some(a) = config.get("description") {
|
|
||||||
self.description = a.to_string().replace("\"", "")
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Destination folder
|
self.parse_from_btreemap(&config);
|
||||||
if let Some(a) = config.get("dest") {
|
|
||||||
let mut dest = PathBuf::from(&a.to_string().replace("\"", ""));
|
|
||||||
|
|
||||||
// If path is relative make it absolute from the parent directory of src
|
self
|
||||||
if dest.is_relative() {
|
}
|
||||||
dest = self.get_root().join(&dest);
|
|
||||||
}
|
/// Parses the string to JSON and converts it to BTreeMap<String, toml::Value>.
|
||||||
self.set_dest(&dest);
|
pub fn parse_from_json_string(&mut self, data: &String) -> &mut Self {
|
||||||
|
|
||||||
|
let c: serde_json::Value = match serde_json::from_str(&data) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
error!("[*]: JSON parse errors in book.json: {:?}", e);
|
||||||
|
exit(2);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Source folder
|
let config = json_object_to_btreemap(&c.as_object().unwrap());
|
||||||
if let Some(a) = config.get("src") {
|
self.parse_from_btreemap(&config);
|
||||||
let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
|
|
||||||
if src.is_relative() {
|
self
|
||||||
src = self.get_root().join(&src);
|
}
|
||||||
}
|
|
||||||
self.set_src(&src);
|
pub fn parse_from_btreemap(&mut self, config: &BTreeMap<String, toml::Value>) -> &mut Self {
|
||||||
|
|
||||||
|
// Title, author, description
|
||||||
|
if let Some(a) = config.get("title") {
|
||||||
|
self.title = a.to_string().replace("\"", "");
|
||||||
|
}
|
||||||
|
if let Some(a) = config.get("author") {
|
||||||
|
self.author = a.to_string().replace("\"", "");
|
||||||
|
}
|
||||||
|
if let Some(a) = config.get("description") {
|
||||||
|
self.description = a.to_string().replace("\"", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination folder
|
||||||
|
if let Some(a) = config.get("dest") {
|
||||||
|
let mut dest = PathBuf::from(&a.to_string().replace("\"", ""));
|
||||||
|
|
||||||
|
// If path is relative make it absolute from the parent directory of src
|
||||||
|
if dest.is_relative() {
|
||||||
|
dest = self.get_root().join(&dest);
|
||||||
}
|
}
|
||||||
|
self.set_dest(&dest);
|
||||||
|
}
|
||||||
|
|
||||||
// Theme path folder
|
// Source folder
|
||||||
if let Some(a) = config.get("theme_path") {
|
if let Some(a) = config.get("src") {
|
||||||
let mut theme_path = PathBuf::from(&a.to_string().replace("\"", ""));
|
let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
|
||||||
if theme_path.is_relative() {
|
if src.is_relative() {
|
||||||
theme_path = self.get_root().join(&theme_path);
|
src = self.get_root().join(&src);
|
||||||
}
|
|
||||||
self.set_theme_path(&theme_path);
|
|
||||||
}
|
}
|
||||||
|
self.set_src(&src);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme path folder
|
||||||
|
if let Some(a) = config.get("theme_path") {
|
||||||
|
let mut theme_path = PathBuf::from(&a.to_string().replace("\"", ""));
|
||||||
|
if theme_path.is_relative() {
|
||||||
|
theme_path = self.get_root().join(&theme_path);
|
||||||
|
}
|
||||||
|
self.set_theme_path(&theme_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -146,3 +196,33 @@ impl BookConfig {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn json_object_to_btreemap(json: &serde_json::Map<String, serde_json::Value>) -> BTreeMap<String, toml::Value> {
|
||||||
|
let mut config: BTreeMap<String, toml::Value> = BTreeMap::new();
|
||||||
|
|
||||||
|
for (key, value) in json.iter() {
|
||||||
|
config.insert(
|
||||||
|
String::from_str(key).unwrap(),
|
||||||
|
json_value_to_toml_value(value.to_owned())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json_value_to_toml_value(json: serde_json::Value) -> toml::Value {
|
||||||
|
match json {
|
||||||
|
serde_json::Value::Null => toml::Value::String("".to_string()),
|
||||||
|
serde_json::Value::Bool(x) => toml::Value::Boolean(x),
|
||||||
|
serde_json::Value::I64(x) => toml::Value::Integer(x),
|
||||||
|
serde_json::Value::U64(x) => toml::Value::Integer(x as i64),
|
||||||
|
serde_json::Value::F64(x) => toml::Value::Float(x),
|
||||||
|
serde_json::Value::String(x) => toml::Value::String(x),
|
||||||
|
serde_json::Value::Array(x) => {
|
||||||
|
toml::Value::Array(x.iter().map(|v| json_value_to_toml_value(v.to_owned())).collect())
|
||||||
|
},
|
||||||
|
serde_json::Value::Object(x) => {
|
||||||
|
toml::Value::Table(json_object_to_btreemap(&x))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,349 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use serde_json;
|
||||||
|
use book::bookconfig::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_parses_json_config() {
|
||||||
|
let text = r#"
|
||||||
|
{
|
||||||
|
"title": "mdBook Documentation",
|
||||||
|
"description": "Create book from markdown files. Like Gitbook but implemented in Rust",
|
||||||
|
"author": "Mathieu David"
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
// TODO don't require path argument, take pwd
|
||||||
|
let mut config = BookConfig::new(Path::new("."));
|
||||||
|
|
||||||
|
config.parse_from_json_string(&text.to_string());
|
||||||
|
|
||||||
|
let mut expected = BookConfig::new(Path::new("."));
|
||||||
|
expected.title = "mdBook Documentation".to_string();
|
||||||
|
expected.author = "Mathieu David".to_string();
|
||||||
|
expected.description = "Create book from markdown files. Like Gitbook but implemented in Rust".to_string();
|
||||||
|
|
||||||
|
assert_eq!(format!("{:#?}", config), format!("{:#?}", expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_parses_toml_config() {
|
||||||
|
let text = r#"
|
||||||
|
title = "mdBook Documentation"
|
||||||
|
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||||
|
author = "Mathieu David"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
// TODO don't require path argument, take pwd
|
||||||
|
let mut config = BookConfig::new(Path::new("."));
|
||||||
|
|
||||||
|
config.parse_from_toml_string(&text.to_string());
|
||||||
|
|
||||||
|
let mut expected = BookConfig::new(Path::new("."));
|
||||||
|
expected.title = "mdBook Documentation".to_string();
|
||||||
|
expected.author = "Mathieu David".to_string();
|
||||||
|
expected.description = "Create book from markdown files. Like Gitbook but implemented in Rust".to_string();
|
||||||
|
|
||||||
|
assert_eq!(format!("{:#?}", config), format!("{:#?}", expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_parses_json_nested_array_to_toml() {
|
||||||
|
|
||||||
|
// Example from:
|
||||||
|
// toml-0.2.1/tests/valid/arrays-nested.json
|
||||||
|
|
||||||
|
let text = r#"
|
||||||
|
{
|
||||||
|
"nest": {
|
||||||
|
"type": "array",
|
||||||
|
"value": [
|
||||||
|
{"type": "array", "value": [
|
||||||
|
{"type": "string", "value": "a"}
|
||||||
|
]},
|
||||||
|
{"type": "array", "value": [
|
||||||
|
{"type": "string", "value": "b"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let c: serde_json::Value = serde_json::from_str(&text).unwrap();
|
||||||
|
|
||||||
|
let result = json_object_to_btreemap(&c.as_object().unwrap());
|
||||||
|
|
||||||
|
let expected = r#"{
|
||||||
|
"nest": Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"array"
|
||||||
|
),
|
||||||
|
"value": Array(
|
||||||
|
[
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"array"
|
||||||
|
),
|
||||||
|
"value": Array(
|
||||||
|
[
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"string"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"a"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"array"
|
||||||
|
),
|
||||||
|
"value": Array(
|
||||||
|
[
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"string"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"b"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
assert_eq!(format!("{:#?}", result), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_parses_json_arrays_to_toml() {
|
||||||
|
|
||||||
|
// Example from:
|
||||||
|
// toml-0.2.1/tests/valid/arrays.json
|
||||||
|
|
||||||
|
let text = r#"
|
||||||
|
{
|
||||||
|
"ints": {
|
||||||
|
"type": "array",
|
||||||
|
"value": [
|
||||||
|
{"type": "integer", "value": "1"},
|
||||||
|
{"type": "integer", "value": "2"},
|
||||||
|
{"type": "integer", "value": "3"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"floats": {
|
||||||
|
"type": "array",
|
||||||
|
"value": [
|
||||||
|
{"type": "float", "value": "1.1"},
|
||||||
|
{"type": "float", "value": "2.1"},
|
||||||
|
{"type": "float", "value": "3.1"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"strings": {
|
||||||
|
"type": "array",
|
||||||
|
"value": [
|
||||||
|
{"type": "string", "value": "a"},
|
||||||
|
{"type": "string", "value": "b"},
|
||||||
|
{"type": "string", "value": "c"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dates": {
|
||||||
|
"type": "array",
|
||||||
|
"value": [
|
||||||
|
{"type": "datetime", "value": "1987-07-05T17:45:00Z"},
|
||||||
|
{"type": "datetime", "value": "1979-05-27T07:32:00Z"},
|
||||||
|
{"type": "datetime", "value": "2006-06-01T11:00:00Z"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let c: serde_json::Value = serde_json::from_str(&text).unwrap();
|
||||||
|
|
||||||
|
let result = json_object_to_btreemap(&c.as_object().unwrap());
|
||||||
|
|
||||||
|
let expected = r#"{
|
||||||
|
"dates": Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"array"
|
||||||
|
),
|
||||||
|
"value": Array(
|
||||||
|
[
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"datetime"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"1987-07-05T17:45:00Z"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"datetime"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"1979-05-27T07:32:00Z"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"datetime"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"2006-06-01T11:00:00Z"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"floats": Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"array"
|
||||||
|
),
|
||||||
|
"value": Array(
|
||||||
|
[
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"float"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"1.1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"float"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"2.1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"float"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"3.1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"ints": Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"array"
|
||||||
|
),
|
||||||
|
"value": Array(
|
||||||
|
[
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"integer"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"integer"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"2"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"integer"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"3"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"strings": Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"array"
|
||||||
|
),
|
||||||
|
"value": Array(
|
||||||
|
[
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"string"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"a"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"string"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"b"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
{
|
||||||
|
"type": String(
|
||||||
|
"string"
|
||||||
|
),
|
||||||
|
"value": String(
|
||||||
|
"c"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
assert_eq!(format!("{:#?}", result), expected);
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ pub use self::metadata::{Author, Language, BookMetadata};
|
||||||
pub use self::chapter::Chapter;
|
pub use self::chapter::Chapter;
|
||||||
pub use self::book::Book;
|
pub use self::book::Book;
|
||||||
|
|
||||||
|
pub mod bookconfig_test;
|
||||||
|
|
||||||
pub use self::bookitem::{BookItem, BookItems};
|
pub use self::bookitem::{BookItem, BookItems};
|
||||||
pub use self::bookconfig::BookConfig;
|
pub use self::bookconfig::BookConfig;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{VecDeque, BTreeMap};
|
||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use serde_json::value::ToJson;
|
use serde_json::value::ToJson;
|
||||||
use handlebars::{Handlebars, RenderError, RenderContext, Helper, Context, Renderable};
|
use handlebars::{Handlebars, RenderError, RenderContext, Helper, Context, Renderable};
|
||||||
|
|
||||||
|
|
||||||
// Handlebars helper for navigation
|
// Handlebars helper for navigation
|
||||||
|
|
||||||
pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
||||||
|
@ -14,9 +15,9 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
|
||||||
// get value from context data
|
// get value from context data
|
||||||
// rc.get_path() is current json parent path, you should always use it like this
|
// rc.get_path() is current json parent path, you should always use it like this
|
||||||
// param is the key of value you want to display
|
// param is the key of value you want to display
|
||||||
let chapters = c.navigate(rc.get_path(), "chapters");
|
let chapters = c.navigate(rc.get_path(), &VecDeque::new(), "chapters");
|
||||||
|
|
||||||
let current = c.navigate(rc.get_path(), "path")
|
let current = c.navigate(rc.get_path(), &VecDeque::new(), "path")
|
||||||
.to_string()
|
.to_string()
|
||||||
.replace("\"", "");
|
.replace("\"", "");
|
||||||
|
|
||||||
|
@ -114,9 +115,9 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
|
||||||
// get value from context data
|
// get value from context data
|
||||||
// rc.get_path() is current json parent path, you should always use it like this
|
// rc.get_path() is current json parent path, you should always use it like this
|
||||||
// param is the key of value you want to display
|
// param is the key of value you want to display
|
||||||
let chapters = c.navigate(rc.get_path(), "chapters");
|
let chapters = c.navigate(rc.get_path(), &VecDeque::new(), "chapters");
|
||||||
|
|
||||||
let current = c.navigate(rc.get_path(), "path")
|
let current = c.navigate(rc.get_path(), &VecDeque::new(), "path")
|
||||||
.to_string()
|
.to_string()
|
||||||
.replace("\"", "");
|
.replace("\"", "");
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{VecDeque, BTreeMap};
|
||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper, Context};
|
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper, Context};
|
||||||
|
@ -15,8 +15,8 @@ impl HelperDef for RenderToc {
|
||||||
// get value from context data
|
// get value from context data
|
||||||
// rc.get_path() is current json parent path, you should always use it like this
|
// rc.get_path() is current json parent path, you should always use it like this
|
||||||
// param is the key of value you want to display
|
// param is the key of value you want to display
|
||||||
let chapters = c.navigate(rc.get_path(), "chapters");
|
let chapters = c.navigate(rc.get_path(), &VecDeque::new(), "chapters");
|
||||||
let current = c.navigate(rc.get_path(), "path").to_string().replace("\"", "");
|
let current = c.navigate(rc.get_path(), &VecDeque::new(), "path").to_string().replace("\"", "");
|
||||||
try!(rc.writer.write("<ul class=\"chapter\">".as_bytes()));
|
try!(rc.writer.write("<ul class=\"chapter\">".as_bytes()));
|
||||||
|
|
||||||
// Decode json format
|
// Decode json format
|
||||||
|
|
Loading…
Reference in New Issue