Skip to content

Commit f1df53a

Browse files
authored
Merge pull request #190 from gambhiro/parse-toml
Parse toml
2 parents e53dcdc + 8a178e3 commit f1df53a

File tree

16 files changed

+519
-85
lines changed

16 files changed

+519
-85
lines changed

Cargo.toml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ exclude = [
1515
]
1616

1717
[dependencies]
18-
clap = "2.2.1"
19-
handlebars = { version = "0.20.0", features = ["serde_type"] }
20-
serde = "0.8.17"
21-
serde_json = "0.8.3"
18+
clap = "2.19.2"
19+
handlebars = { version = "0.23.0", features = ["serde_type"] }
20+
serde = "0.8"
21+
serde_json = "0.8"
2222
pulldown-cmark = "0.0.8"
2323
log = "0.3"
24-
env_logger = "0.3.4"
24+
env_logger = "0.3"
25+
toml = { version = "0.2", features = ["serde"] }
2526

2627
# Watch feature
2728
notify = { version = "2.5.5", optional = true }
@@ -33,12 +34,10 @@ iron = { version = "0.4", optional = true }
3334
staticfile = { version = "0.3", optional = true }
3435
ws = { version = "0.5.1", optional = true}
3536

36-
3737
# Tests
3838
[dev-dependencies]
3939
tempdir = "0.3.4"
4040

41-
4241
[features]
4342
default = ["output", "watch", "serve"]
4443
debug = []

book-example/book.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

book-example/book.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
title = "mdBook Documentation"
2+
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
3+
author = "Mathieu David"

book-example/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Summary
22

3+
[Introduction](misc/introduction.md)
4+
35
- [mdBook](README.md)
46
- [Command Line Tool](cli/cli-tool.md)
57
- [init](cli/init.md)

book-example/src/cli/test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mdBook supports a `test` command that will run all available tests in mdBook. At
1010
- checking for unused files
1111
- ...
1212

13-
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.
13+
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.
1414

1515
**How to use it:**
1616
```bash

book-example/src/format/config.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Configuration
22

3-
You can configure the parameters for your book in the ***book.json*** file.
3+
You can configure the parameters for your book in the ***book.toml*** file.
44

5-
Here is an example of what a ***book.json*** file might look like:
5+
We encourage using the TOML format, but JSON is also recognized and parsed.
66

7-
```json
8-
{
9-
"title": "Example book",
10-
"author": "Name",
11-
"description": "The example book covers examples.",
12-
"dest": "output/my-book"
13-
}
7+
Here is an example of what a ***book.toml*** file might look like:
8+
9+
```toml
10+
title = "Example book"
11+
author = "Name"
12+
description = "The example book covers examples."
13+
dest = "output/my-book"
1414
```
1515

1616
#### Supported variables

book-example/src/format/format.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ In this section you will learn how to:
44

55
- Structure your book correctly
66
- Format your `SUMMARY.md` file
7-
- Configure your book using `book.json`
7+
- Configure your book using `book.toml`
88
- Customize your theme

book-example/src/format/theme/index-hbs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Here is a list of the properties that are exposed:
1919

2020
- ***language*** Language of the book in the form `en`. To use in <code class="language-html">\<html lang="{{ language }}"></code> for example.
2121
At the moment it is hardcoded.
22-
- ***title*** Title of the book, as specified in `book.json`
22+
- ***title*** Title of the book, as specified in `book.toml`
2323

2424
- ***path*** Relative path to the original markdown file from the source directory
2525
- ***content*** This is the rendered markdown.

book-example/src/format/theme/syntax-highlighting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Will render as
4747
# }
4848
```
4949

50-
**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.**
50+
**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.**
5151

5252

5353
## Improve default theme

book-example/src/lib/lib.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn main() {
1313
let mut book = MDBook::new(Path::new("my-book")) // Path to root
1414
.set_src(Path::new("src")) // Path from root to source directory
1515
.set_dest(Path::new("book")) // Path from root to output directory
16-
.read_config(); // Parse book.json file for configuration
16+
.read_config(); // Parse book.toml or book.json file for configuration
1717
1818
book.build().unwrap(); // Render the book
1919
}

book-example/src/misc/introduction.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Introduction
2+
3+
A frontmatter chapter.

src/book/bookconfig.rs

Lines changed: 131 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
use serde_json;
1+
extern crate toml;
2+
3+
use std::process::exit;
24
use std::fs::File;
35
use std::io::Read;
46
use std::path::{Path, PathBuf};
7+
use std::collections::BTreeMap;
8+
use std::str::FromStr;
9+
use serde_json;
510

611
#[derive(Debug, Clone)]
712
pub struct BookConfig {
@@ -18,7 +23,6 @@ pub struct BookConfig {
1823
multilingual: bool,
1924
}
2025

21-
2226
impl BookConfig {
2327
pub fn new(root: &Path) -> Self {
2428
BookConfig {
@@ -40,71 +44,117 @@ impl BookConfig {
4044

4145
debug!("[fn]: read_config");
4246

43-
// If the file does not exist, return early
44-
let mut config_file = match File::open(root.join("book.json")) {
45-
Ok(f) => f,
46-
Err(_) => {
47-
debug!("[*]: Failed to open {:?}", root.join("book.json"));
48-
return self;
49-
},
47+
let read_file = |path: PathBuf| -> String {
48+
let mut data = String::new();
49+
let mut f: File = match File::open(&path) {
50+
Ok(x) => x,
51+
Err(_) => {
52+
error!("[*]: Failed to open {:?}", &path);
53+
exit(2);
54+
}
55+
};
56+
if let Err(_) = f.read_to_string(&mut data) {
57+
error!("[*]: Failed to read {:?}", &path);
58+
exit(2);
59+
}
60+
data
5061
};
5162

52-
debug!("[*]: Reading config");
53-
let mut data = String::new();
63+
// Read book.toml or book.json if exists
5464

55-
// Just return if an error occured.
56-
// I would like to propagate the error, but I have to return `&self`
57-
if let Err(_) = config_file.read_to_string(&mut data) {
58-
return self;
65+
if Path::new(root.join("book.toml").as_os_str()).exists() {
66+
67+
debug!("[*]: Reading config");
68+
let data = read_file(root.join("book.toml"));
69+
self.parse_from_toml_string(&data);
70+
71+
} else if Path::new(root.join("book.json").as_os_str()).exists() {
72+
73+
debug!("[*]: Reading config");
74+
let data = read_file(root.join("book.json"));
75+
self.parse_from_json_string(&data);
76+
77+
} else {
78+
debug!("[*]: No book.toml or book.json was found, using defaults.");
5979
}
6080

61-
// Convert to JSON
62-
if let Ok(config) = serde_json::from_str::<serde_json::Value>(&data) {
63-
// Extract data
81+
self
82+
}
6483

65-
let config = config.as_object().unwrap();
84+
pub fn parse_from_toml_string(&mut self, data: &String) -> &mut Self {
6685

67-
debug!("[*]: Extracting data from config");
68-
// Title, author, description
69-
if let Some(a) = config.get("title") {
70-
self.title = a.to_string().replace("\"", "")
71-
}
72-
if let Some(a) = config.get("author") {
73-
self.author = a.to_string().replace("\"", "")
74-
}
75-
if let Some(a) = config.get("description") {
76-
self.description = a.to_string().replace("\"", "")
86+
let mut parser = toml::Parser::new(&data);
87+
88+
let config = match parser.parse() {
89+
Some(x) => {x},
90+
None => {
91+
error!("[*]: Toml parse errors in book.toml: {:?}", parser.errors);
92+
exit(2);
7793
}
94+
};
7895

79-
// Destination folder
80-
if let Some(a) = config.get("dest") {
81-
let mut dest = PathBuf::from(&a.to_string().replace("\"", ""));
96+
self.parse_from_btreemap(&config);
8297

83-
// If path is relative make it absolute from the parent directory of src
84-
if dest.is_relative() {
85-
dest = self.get_root().join(&dest);
86-
}
87-
self.set_dest(&dest);
98+
self
99+
}
100+
101+
/// Parses the string to JSON and converts it to BTreeMap<String, toml::Value>.
102+
pub fn parse_from_json_string(&mut self, data: &String) -> &mut Self {
103+
104+
let c: serde_json::Value = match serde_json::from_str(&data) {
105+
Ok(x) => x,
106+
Err(e) => {
107+
error!("[*]: JSON parse errors in book.json: {:?}", e);
108+
exit(2);
88109
}
110+
};
89111

90-
// Source folder
91-
if let Some(a) = config.get("src") {
92-
let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
93-
if src.is_relative() {
94-
src = self.get_root().join(&src);
95-
}
96-
self.set_src(&src);
112+
let config = json_object_to_btreemap(&c.as_object().unwrap());
113+
self.parse_from_btreemap(&config);
114+
115+
self
116+
}
117+
118+
pub fn parse_from_btreemap(&mut self, config: &BTreeMap<String, toml::Value>) -> &mut Self {
119+
120+
// Title, author, description
121+
if let Some(a) = config.get("title") {
122+
self.title = a.to_string().replace("\"", "");
123+
}
124+
if let Some(a) = config.get("author") {
125+
self.author = a.to_string().replace("\"", "");
126+
}
127+
if let Some(a) = config.get("description") {
128+
self.description = a.to_string().replace("\"", "");
129+
}
130+
131+
// Destination folder
132+
if let Some(a) = config.get("dest") {
133+
let mut dest = PathBuf::from(&a.to_string().replace("\"", ""));
134+
135+
// If path is relative make it absolute from the parent directory of src
136+
if dest.is_relative() {
137+
dest = self.get_root().join(&dest);
97138
}
139+
self.set_dest(&dest);
140+
}
98141

99-
// Theme path folder
100-
if let Some(a) = config.get("theme_path") {
101-
let mut theme_path = PathBuf::from(&a.to_string().replace("\"", ""));
102-
if theme_path.is_relative() {
103-
theme_path = self.get_root().join(&theme_path);
104-
}
105-
self.set_theme_path(&theme_path);
142+
// Source folder
143+
if let Some(a) = config.get("src") {
144+
let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
145+
if src.is_relative() {
146+
src = self.get_root().join(&src);
106147
}
148+
self.set_src(&src);
149+
}
107150

151+
// Theme path folder
152+
if let Some(a) = config.get("theme_path") {
153+
let mut theme_path = PathBuf::from(&a.to_string().replace("\"", ""));
154+
if theme_path.is_relative() {
155+
theme_path = self.get_root().join(&theme_path);
156+
}
157+
self.set_theme_path(&theme_path);
108158
}
109159

110160
self
@@ -146,3 +196,33 @@ impl BookConfig {
146196
self
147197
}
148198
}
199+
200+
pub fn json_object_to_btreemap(json: &serde_json::Map<String, serde_json::Value>) -> BTreeMap<String, toml::Value> {
201+
let mut config: BTreeMap<String, toml::Value> = BTreeMap::new();
202+
203+
for (key, value) in json.iter() {
204+
config.insert(
205+
String::from_str(key).unwrap(),
206+
json_value_to_toml_value(value.to_owned())
207+
);
208+
}
209+
210+
config
211+
}
212+
213+
pub fn json_value_to_toml_value(json: serde_json::Value) -> toml::Value {
214+
match json {
215+
serde_json::Value::Null => toml::Value::String("".to_string()),
216+
serde_json::Value::Bool(x) => toml::Value::Boolean(x),
217+
serde_json::Value::I64(x) => toml::Value::Integer(x),
218+
serde_json::Value::U64(x) => toml::Value::Integer(x as i64),
219+
serde_json::Value::F64(x) => toml::Value::Float(x),
220+
serde_json::Value::String(x) => toml::Value::String(x),
221+
serde_json::Value::Array(x) => {
222+
toml::Value::Array(x.iter().map(|v| json_value_to_toml_value(v.to_owned())).collect())
223+
},
224+
serde_json::Value::Object(x) => {
225+
toml::Value::Table(json_object_to_btreemap(&x))
226+
},
227+
}
228+
}

0 commit comments

Comments
 (0)