parse either book.toml or book.json

This commit is contained in:
Gambhiro 2016-12-23 08:15:32 +00:00
parent f67ae7c71a
commit 791487bc84
3 changed files with 203 additions and 51 deletions

View File

@ -1,8 +1,13 @@
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 serde::{Serialize, Deserialize};
use serde_json;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BookConfig { pub struct BookConfig {
root: PathBuf, root: PathBuf,
@ -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,154 @@ 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 // TODO this is very similar to how the JSON is parsed. Combine somehow?
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 // Title, author, description
if dest.is_relative() { if let Some(a) = config.get("title") {
dest = self.get_root().join(&dest); self.title = a.to_string().replace("\"", "");
} }
self.set_dest(&dest); 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);
}
// Source folder // Source folder
if let Some(a) = config.get("src") { if let Some(a) = config.get("src") {
let mut src = PathBuf::from(&a.to_string().replace("\"", "")); let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
if src.is_relative() { if src.is_relative() {
src = self.get_root().join(&src); src = self.get_root().join(&src);
}
self.set_src(&src);
} }
self.set_src(&src);
}
// Theme path folder // Theme path folder
if let Some(a) = config.get("theme_path") { if let Some(a) = config.get("theme_path") {
let mut theme_path = PathBuf::from(&a.to_string().replace("\"", "")); let mut theme_path = PathBuf::from(&a.to_string().replace("\"", ""));
if theme_path.is_relative() { if theme_path.is_relative() {
theme_path = self.get_root().join(&theme_path); theme_path = self.get_root().join(&theme_path);
}
self.set_theme_path(&theme_path);
} }
self.set_theme_path(&theme_path);
}
self
}
pub fn parse_from_json_string(&mut self, data: &String) -> &mut Self {
let config: serde_json::Value = match serde_json::from_str(&data) {
Ok(x) => {x},
Err(e) => {
error!("[*]: JSON parse errors in book.json: {:?}", e);
exit(2);
}
};
// Extract data
let config = config.as_object().unwrap();
debug!("[*]: Extracting data from config");
// 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);
}
// Source folder
if let Some(a) = config.get("src") {
let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
if src.is_relative() {
src = self.get_root().join(&src);
}
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

View File

@ -0,0 +1,63 @@
#[cfg(test)]
use std::path::Path;
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 expected = r#"BookConfig {
root: ".",
dest: "./book",
src: "./src",
theme_path: "./theme",
title: "mdBook Documentation",
author: "Mathieu David",
description: "Create book from markdown files. Like Gitbook but implemented in Rust",
indent_spaces: 4,
multilingual: false
}"#;
assert_eq!(format!("{:#?}", config), 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());
println!("{:#?}", config);
let expected = r#"BookConfig {
root: ".",
dest: "./book",
src: "./src",
theme_path: "./theme",
title: "mdBook Documentation",
author: "Mathieu David",
description: "Create book from markdown files. Like Gitbook but implemented in Rust",
indent_spaces: 4,
multilingual: false
}"#;
assert_eq!(format!("{:#?}", config), expected);
}

View File

@ -1,6 +1,8 @@
pub mod bookitem; pub mod bookitem;
pub mod bookconfig; pub mod bookconfig;
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;