diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 56179ef1..78387ba1 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -129,7 +129,7 @@ fn init(args: &ArgMatches) -> Result<(), Box> { // Skip this if `--force` is present if !args.is_present("force") { // Print warning - print!("\nCopying the default theme to {:?}", book.get_src()); + print!("\nCopying the default theme to {:?}", book.get_source()); println!("could potentially overwrite files already present in that directory."); print!("\nAre you sure you want to continue? (y/n) "); @@ -148,7 +148,9 @@ fn init(args: &ArgMatches) -> Result<(), Box> { } // Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root` - let is_dest_inside_root = book.get_dest().starts_with(book.get_root()); + let is_dest_inside_root = book.get_destination() + .map(|p| p.starts_with(book.get_root())) + .unwrap_or(false); if !args.is_present("force") && is_dest_inside_root { println!("\nDo you want a .gitignore to be created? (y/n)"); @@ -168,10 +170,10 @@ fn init(args: &ArgMatches) -> Result<(), Box> { // Build command implementation fn build(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); - let book = MDBook::new(&book_dir).read_config(); + let book = MDBook::new(&book_dir).read_config()?; let mut book = match args.value_of("dest-dir") { - Some(dest_dir) => book.set_dest(Path::new(dest_dir)), + Some(dest_dir) => book.with_destination(Path::new(dest_dir)), None => book, }; @@ -181,8 +183,10 @@ fn build(args: &ArgMatches) -> Result<(), Box> { book.build()?; - if args.is_present("open") { - open(book.get_dest().join("index.html")); + if let Some(d) = book.get_destination() { + if args.is_present("open") { + open(d.join("index.html")); + } } Ok(()) @@ -193,16 +197,18 @@ fn build(args: &ArgMatches) -> Result<(), Box> { #[cfg(feature = "watch")] fn watch(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); - let book = MDBook::new(&book_dir).read_config(); + let book = MDBook::new(&book_dir).read_config()?; let mut book = match args.value_of("dest-dir") { - Some(dest_dir) => book.set_dest(Path::new(dest_dir)), + Some(dest_dir) => book.with_destination(Path::new(dest_dir)), None => book, }; if args.is_present("open") { book.build()?; - open(book.get_dest().join("index.html")); + if let Some(d) = book.get_destination() { + open(d.join("index.html")); + } } trigger_on_change(&mut book, |path, book| { @@ -223,13 +229,18 @@ fn serve(args: &ArgMatches) -> Result<(), Box> { const RELOAD_COMMAND: &'static str = "reload"; let book_dir = get_book_dir(args); - let book = MDBook::new(&book_dir).read_config(); + let book = MDBook::new(&book_dir).read_config()?; let mut book = match args.value_of("dest-dir") { - Some(dest_dir) => book.set_dest(Path::new(dest_dir)), + Some(dest_dir) => book.with_destination(Path::new(dest_dir)), None => book, }; + if let None = book.get_destination() { + println!("The HTML renderer is not set up, impossible to serve the files."); + std::process::exit(2); + } + let port = args.value_of("port").unwrap_or("3000"); let ws_port = args.value_of("websocket-port").unwrap_or("3001"); let interface = args.value_of("interface").unwrap_or("localhost"); @@ -260,7 +271,7 @@ fn serve(args: &ArgMatches) -> Result<(), Box> { book.build()?; - let staticfile = staticfile::Static::new(book.get_dest()); + let staticfile = staticfile::Static::new(book.get_destination().expect("destination is present, checked before")); let iron = iron::Iron::new(staticfile); let _iron = iron.http(&*address).unwrap(); @@ -292,7 +303,7 @@ fn serve(args: &ArgMatches) -> Result<(), Box> { fn test(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); - let mut book = MDBook::new(&book_dir).read_config(); + let mut book = MDBook::new(&book_dir).read_config()?; book.test()?; @@ -341,8 +352,8 @@ fn trigger_on_change(book: &mut MDBook, closure: F) -> () }; // Add the source directory to the watcher - if let Err(e) = watcher.watch(book.get_src(), Recursive) { - println!("Error while watching {:?}:\n {:?}", book.get_src(), e); + if let Err(e) = watcher.watch(book.get_source(), Recursive) { + println!("Error while watching {:?}:\n {:?}", book.get_source(), e); ::std::process::exit(0); }; diff --git a/src/book/mod.rs b/src/book/mod.rs index 0a0e1099..c23687d4 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -4,29 +4,24 @@ pub mod bookconfig; pub mod bookconfig_test; pub use self::bookitem::{BookItem, BookItems}; -pub use self::bookconfig::BookConfig; use std::path::{Path, PathBuf}; use std::fs::{self, File}; use std::error::Error; use std::io; -use std::io::Write; +use std::io::{Read, Write}; use std::io::ErrorKind; use std::process::Command; use {theme, parse, utils}; use renderer::{Renderer, HtmlHandlebars}; +use config::{BookConfig, HtmlConfig}; +use config::tomlconfig::TomlConfig; + pub struct MDBook { - root: PathBuf, - dest: PathBuf, - src: PathBuf, - theme_path: PathBuf, - - pub title: String, - pub author: String, - pub description: String, + config: BookConfig, pub content: Vec, renderer: Box, @@ -50,7 +45,7 @@ impl MDBook { /// # use mdbook::MDBook; /// # use std::path::Path; /// # fn main() { - /// let book = MDBook::new(Path::new("root_dir")); + /// let book = MDBook::new("root_dir"); /// # } /// ``` /// @@ -75,14 +70,7 @@ impl MDBook { } MDBook { - root: root.to_owned(), - dest: root.join("book"), - src: root.join("src"), - theme_path: root.join("theme"), - - title: String::new(), - author: String::new(), - description: String::new(), + config: BookConfig::new(root), content: vec![], renderer: Box::new(HtmlHandlebars::new()), @@ -149,31 +137,33 @@ impl MDBook { debug!("[fn]: init"); - if !self.root.exists() { - fs::create_dir_all(&self.root).unwrap(); - info!("{:?} created", &self.root); + if !self.config.get_root().exists() { + fs::create_dir_all(&self.config.get_root()).unwrap(); + info!("{:?} created", &self.config.get_root()); } { - if !self.dest.exists() { - debug!("[*]: {:?} does not exist, trying to create directory", self.dest); - fs::create_dir_all(&self.dest)?; + if let Some(htmlconfig) = self.config.get_html_config() { + if !htmlconfig.get_destination().exists() { + debug!("[*]: {:?} does not exist, trying to create directory", htmlconfig.get_destination()); + fs::create_dir_all(htmlconfig.get_destination())?; + } + } + + + if !self.config.get_source().exists() { + debug!("[*]: {:?} does not exist, trying to create directory", self.config.get_source()); + fs::create_dir_all(self.config.get_source())?; } - if !self.src.exists() { - debug!("[*]: {:?} does not exist, trying to create directory", self.src); - fs::create_dir_all(&self.src)?; - } - - let summary = self.src.join("SUMMARY.md"); + let summary = self.config.get_source().join("SUMMARY.md"); if !summary.exists() { // Summary does not exist, create it - - debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", self.src.join("SUMMARY.md")); - let mut f = File::create(&self.src.join("SUMMARY.md"))?; + debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", &summary); + let mut f = File::create(&summary)?; debug!("[*]: Writing to SUMMARY.md"); @@ -195,7 +185,7 @@ impl MDBook { BookItem::Affix(ref ch) => ch, }; if !ch.path.as_os_str().is_empty() { - let path = self.src.join(&ch.path); + let path = self.config.get_source().join(&ch.path); if !path.exists() { if !self.create_missing { @@ -219,22 +209,23 @@ impl MDBook { pub fn create_gitignore(&self) { let gitignore = self.get_gitignore(); - if !gitignore.exists() { - // Gitignore does not exist, create it + // If the HTML renderer is not set, return + if self.config.get_html_config().is_none() { return; } + + let destination = self.config.get_html_config() + .expect("The HtmlConfig does exist, checked just before") + .get_destination(); + + // Check that the gitignore does not extist and that the destination path begins with the root path + // We assume tha if it does begin with the root path it is contained within. This assumption + // will not hold true for paths containing double dots to go back up e.g. `root/../destination` + if !gitignore.exists() && destination.starts_with(self.config.get_root()) { - // Because of `src/book/mdbook.rs#L37-L39`, - // `dest` will always start with `root`. - // If it is not, `strip_prefix` will return an Error. - if !self.get_dest().starts_with(&self.root) { - return; - } - - let relative = self.get_dest() - .strip_prefix(&self.root) - .expect("Destination is not relative to root."); - let relative = relative + let relative = destination + .strip_prefix(self.config.get_root()) + .expect("Could not strip the root prefix, path is not relative to root") .to_str() - .expect("Path could not be yielded into a string slice."); + .expect("Could not convert to &str"); debug!("[*]: {:?} does not exist, trying to create .gitignore", gitignore); @@ -258,8 +249,10 @@ impl MDBook { self.init()?; // Clean output directory - utils::fs::remove_dir_content(&self.dest)?; - + if let Some(htmlconfig) = self.config.get_html_config() { + utils::fs::remove_dir_content(htmlconfig.get_destination())?; + } + self.renderer.render(&self)?; Ok(()) @@ -267,51 +260,56 @@ impl MDBook { pub fn get_gitignore(&self) -> PathBuf { - self.root.join(".gitignore") + self.config.get_root().join(".gitignore") } pub fn copy_theme(&self) -> Result<(), Box> { debug!("[fn]: copy_theme"); - let theme_dir = self.src.join("theme"); + if let Some(themedir) = self.config.get_html_config().and_then(HtmlConfig::get_theme) { - if !theme_dir.exists() { - debug!("[*]: {:?} does not exist, trying to create directory", theme_dir); - fs::create_dir(&theme_dir)?; + if !themedir.exists() { + debug!("[*]: {:?} does not exist, trying to create directory", themedir); + fs::create_dir(&themedir)?; + } + + // index.hbs + let mut index = File::create(&themedir.join("index.hbs"))?; + index.write_all(theme::INDEX)?; + + // book.css + let mut css = File::create(&themedir.join("book.css"))?; + css.write_all(theme::CSS)?; + + // favicon.png + let mut favicon = File::create(&themedir.join("favicon.png"))?; + favicon.write_all(theme::FAVICON)?; + + // book.js + let mut js = File::create(&themedir.join("book.js"))?; + js.write_all(theme::JS)?; + + // highlight.css + let mut highlight_css = File::create(&themedir.join("highlight.css"))?; + highlight_css.write_all(theme::HIGHLIGHT_CSS)?; + + // highlight.js + let mut highlight_js = File::create(&themedir.join("highlight.js"))?; + highlight_js.write_all(theme::HIGHLIGHT_JS)?; } - - // index.hbs - let mut index = File::create(&theme_dir.join("index.hbs"))?; - index.write_all(theme::INDEX)?; - - // book.css - let mut css = File::create(&theme_dir.join("book.css"))?; - css.write_all(theme::CSS)?; - - // favicon.png - let mut favicon = File::create(&theme_dir.join("favicon.png"))?; - favicon.write_all(theme::FAVICON)?; - - // book.js - let mut js = File::create(&theme_dir.join("book.js"))?; - js.write_all(theme::JS)?; - - // highlight.css - let mut highlight_css = File::create(&theme_dir.join("highlight.css"))?; - highlight_css.write_all(theme::HIGHLIGHT_CSS)?; - - // highlight.js - let mut highlight_js = File::create(&theme_dir.join("highlight.js"))?; - highlight_js.write_all(theme::HIGHLIGHT_JS)?; - + Ok(()) } pub fn write_file>(&self, filename: P, content: &[u8]) -> Result<(), Box> { - let path = self.get_dest().join(filename); + let path = self.get_destination() + .ok_or(String::from("HtmlConfig not set, could not find a destination"))? + .join(filename); + utils::fs::create_file(&path) .and_then(|mut file| file.write_all(content)) .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Could not create {}: {}", path.display(), e)))?; + Ok(()) } @@ -320,21 +318,23 @@ impl MDBook { /// The `book.json` file should be in the root directory of the book. /// The root directory is the one specified when creating a new `MDBook` - pub fn read_config(mut self) -> Self { + pub fn read_config(mut self) -> Result> { - let config = BookConfig::new(&self.root) - .read_config(&self.root) - .to_owned(); + let toml = self.get_root().join("book.toml"); + let json = self.get_root().join("book.json"); - self.title = config.title; - self.description = config.description; - self.author = config.author; + if toml.exists() { + let mut file = File::open(toml)?; + let mut content = String::new(); + file.read_to_string(&mut content)?; - self.dest = config.dest; - self.src = config.src; - self.theme_path = config.theme_path; + let parsed_config = TomlConfig::from_toml(&content)?; + self.config.fill_from_tomlconfig(parsed_config); + } else if json.exists() { + unimplemented!(); + } - self + Ok(self) } /// You can change the default renderer to another one @@ -374,7 +374,7 @@ impl MDBook { if let BookItem::Chapter(_, ref ch) = *item { if ch.path != PathBuf::new() { - let path = self.get_src().join(&ch.path); + let path = self.get_source().join(&ch.path); println!("[*]: Testing file: {:?}", path); @@ -395,52 +395,49 @@ impl MDBook { } pub fn get_root(&self) -> &Path { - &self.root + self.config.get_root() } - pub fn set_dest(mut self, dest: &Path) -> Self { - - // Handle absolute and relative paths - if dest.is_absolute() { - self.dest = dest.to_owned(); + + pub fn with_destination>(mut self, destination: T) -> Self { + let root = self.config.get_root().to_owned(); + if let Some(htmlconfig) = self.config.get_mut_html_config() { + htmlconfig.set_destination(&root, &destination.into()); } else { - let dest = self.root.join(dest).to_owned(); - self.dest = dest; + error!("There is no HTML renderer set..."); } self } + - pub fn get_dest(&self) -> &Path { - &self.dest - } - - pub fn set_src(mut self, src: &Path) -> Self { - - // Handle absolute and relative paths - if src.is_absolute() { - self.src = src.to_owned(); - } else { - let src = self.root.join(src).to_owned(); - self.src = src; + pub fn get_destination(&self) -> Option<&Path> { + if let Some(htmlconfig) = self.config.get_html_config() { + return Some(htmlconfig.get_destination()); } + None + } + + pub fn with_source>(mut self, source: T) -> Self { + self.config.set_source(source); self } - pub fn get_src(&self) -> &Path { - &self.src + pub fn get_source(&self) -> &Path { + self.config.get_source() } - pub fn set_title(mut self, title: &str) -> Self { - self.title = title.to_owned(); + pub fn with_title>(mut self, title: T) -> Self { + self.config.set_title(title); self } pub fn get_title(&self) -> &str { - &self.title + self.config.get_title() } +/* pub fn set_author(mut self, author: &str) -> Self { self.author = author.to_owned(); self @@ -449,14 +446,14 @@ impl MDBook { pub fn get_author(&self) -> &str { &self.author } - - pub fn set_description(mut self, description: &str) -> Self { - self.description = description.to_owned(); +*/ + pub fn with_description>(mut self, description: T) -> Self { + self.config.set_description(description); self } pub fn get_description(&self) -> &str { - &self.description + self.config.get_description() } pub fn set_livereload(&mut self, livereload: String) -> &mut Self { @@ -473,23 +470,28 @@ impl MDBook { self.livereload.as_ref() } - pub fn set_theme_path(mut self, theme_path: &Path) -> Self { - self.theme_path = if theme_path.is_absolute() { - theme_path.to_owned() + pub fn with_theme_path>(mut self, theme_path: T) -> Self { + let root = self.config.get_root().to_owned(); + if let Some(htmlconfig) = self.config.get_mut_html_config() { + htmlconfig.set_theme(&root, &theme_path.into()); } else { - self.root.join(theme_path).to_owned() - }; + error!("There is no HTML renderer set..."); + } self } - pub fn get_theme_path(&self) -> &Path { - &self.theme_path + pub fn get_theme_path(&self) -> Option<&PathBuf> { + if let Some(htmlconfig) = self.config.get_html_config() { + return htmlconfig.get_theme(); + } + + None } // Construct book fn parse_summary(&mut self) -> Result<(), Box> { // When append becomes stable, use self.content.append() ... - self.content = parse::construct_bookitems(&self.src.join("SUMMARY.md"))?; + self.content = parse::construct_bookitems(&self.get_source().join("SUMMARY.md"))?; Ok(()) } } diff --git a/src/config/bookconfig.rs b/src/config/bookconfig.rs index 2c2673a0..9f445b2b 100644 --- a/src/config/bookconfig.rs +++ b/src/config/bookconfig.rs @@ -102,14 +102,45 @@ impl BookConfig { } if let Some(tomlhtmlconfig) = tomlconfig.output.and_then(|o| o.html) { - let source = config.get_source().to_owned(); let mut htmlconfig = config.get_mut_html_config().expect("We just created a new config and it creates a default HtmlConfig"); - htmlconfig.fill_from_tomlconfig(&root, &source, tomlhtmlconfig); + htmlconfig.fill_from_tomlconfig(&root, tomlhtmlconfig); } config } + pub fn fill_from_tomlconfig(&mut self, tomlconfig: TomlConfig) -> &mut Self { + + if let Some(s) = tomlconfig.source { + self.set_source(s); + } + + if let Some(t) = tomlconfig.title { + self.set_title(t); + } + + if let Some(d) = tomlconfig.description { + self.set_description(d); + } + + if let Some(a) = tomlconfig.authors { + self.set_authors(a); + } + + if let Some(a) = tomlconfig.author { + self.set_authors(vec![a]); + } + + if let Some(tomlhtmlconfig) = tomlconfig.output.and_then(|o| o.html) { + let root = self.root.clone(); + if let Some(htmlconfig) = self.get_mut_html_config() { + htmlconfig.fill_from_tomlconfig(root, tomlhtmlconfig); + } + } + + self + } + pub fn set_root>(&mut self, root: T) -> &mut Self { self.root = root.into(); self diff --git a/src/config/htmlconfig.rs b/src/config/htmlconfig.rs index 5f8d40ae..93bbc522 100644 --- a/src/config/htmlconfig.rs +++ b/src/config/htmlconfig.rs @@ -29,10 +29,12 @@ impl HtmlConfig { } } - pub fn fill_from_tomlconfig>(&mut self, root: T, source: T, tomlconfig: TomlHtmlConfig) -> &mut Self { + pub fn fill_from_tomlconfig>(&mut self, root: T, tomlconfig: TomlHtmlConfig) -> &mut Self { + let root = root.into(); + if let Some(d) = tomlconfig.destination { if d.is_relative() { - self.destination = root.into().join(d); + self.destination = root.join(d); } else { self.destination = d; } @@ -40,7 +42,7 @@ impl HtmlConfig { if let Some(t) = tomlconfig.theme { if t.is_relative() { - self.theme = Some(source.into().join(t)); + self.theme = Some(root.join(t)); } else { self.theme = Some(t); } @@ -53,6 +55,17 @@ impl HtmlConfig { self } + pub fn set_destination>(&mut self, root: T, destination: T) -> &mut Self { + let d = destination.into(); + if d.is_relative() { + self.destination = root.into().join(d); + } else { + self.destination = d; + } + + self + } + pub fn get_destination(&self) -> &Path { &self.destination } @@ -61,4 +74,15 @@ impl HtmlConfig { pub fn get_theme(&self) -> Option<&PathBuf> { self.theme.as_ref() } + + pub fn set_theme>(&mut self, root: T, theme: T) -> &mut Self { + let d = theme.into(); + if d.is_relative() { + self.theme = Some(root.into().join(d)); + } else { + self.theme = Some(d); + } + + self + } } diff --git a/src/lib.rs b/src/lib.rs index 882e22c4..ce6d4103 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,5 +89,4 @@ pub mod utils; pub use book::MDBook; pub use book::BookItem; -pub use book::BookConfig; pub use renderer::Renderer; diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index a97b8e66..07dd8c4a 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -33,7 +33,7 @@ impl Renderer for HtmlHandlebars { let mut handlebars = Handlebars::new(); // Load theme - let theme = theme::Theme::new(book.get_theme_path()); + let theme = theme::Theme::new(book.get_theme_path().expect("If the HTML renderer is called, one would assume the HtmlConfig is set...")); // Register template debug!("[*]: Register handlebars template"); @@ -53,7 +53,7 @@ impl Renderer for HtmlHandlebars { // Check if dest directory exists debug!("[*]: Check if destination directory exists"); - if fs::create_dir_all(book.get_dest()).is_err() { + if fs::create_dir_all(book.get_destination().expect("If the HTML renderer is called, one would assume the HtmlConfig is set...")).is_err() { return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Unexpected error when constructing destination path"))); } @@ -67,7 +67,7 @@ impl Renderer for HtmlHandlebars { BookItem::Affix(ref ch) => { if ch.path != PathBuf::new() { - let path = book.get_src().join(&ch.path); + let path = book.get_source().join(&ch.path); debug!("[*]: Opening file: {:?}", path); let mut f = File::open(&path)?; @@ -116,8 +116,12 @@ impl Renderer for HtmlHandlebars { debug!("[*]: index.html"); let mut content = String::new(); - let _source = File::open(book.get_dest().join(&ch.path.with_extension("html")))? - .read_to_string(&mut content); + + let _source = File::open( + book.get_destination() + .expect("If the HTML renderer is called, one would assume the HtmlConfig is set...") + .join(&ch.path.with_extension("html")) + )?.read_to_string(&mut content); // This could cause a problem when someone displays // code containing @@ -131,7 +135,10 @@ impl Renderer for HtmlHandlebars { book.write_file("index.html", content.as_bytes())?; info!("[*] Creating index.html from {:?} ✓", - book.get_dest().join(&ch.path.with_extension("html"))); + book.get_destination() + .expect("If the HTML renderer is called, one would assume the HtmlConfig is set...") + .join(&ch.path.with_extension("html")) + ); index = false; } } @@ -181,7 +188,12 @@ impl Renderer for HtmlHandlebars { book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)?; // Copy all remaining files - utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"])?; + utils::fs::copy_files_except_ext( + book.get_source(), + book.get_destination() + .expect("If the HTML renderer is called, one would assume the HtmlConfig is set..."), true, &["md"] + )?; + Ok(()) }