Merge pull request #288 from budziq/rustfmt_update

Made changes with rustfmt including `use_try_shorthand`
This commit is contained in:
Mathieu David 2017-05-19 13:43:49 +02:00 committed by GitHub
commit f038dcb404
14 changed files with 402 additions and 320 deletions

View file

@ -20,7 +20,8 @@ fn main() {
.arg(format!("{}", theme_dir.to_str().unwrap())) .arg(format!("{}", theme_dir.to_str().unwrap()))
.arg("--use") .arg("--use")
.arg("nib") .arg("nib")
.status().unwrap() .status()
.unwrap()
.success() { .success() {
panic!("Stylus encoutered an error"); panic!("Stylus encoutered an error");
} }

View file

@ -10,6 +10,7 @@ enum_trailing_comma = true
match_block_trailing_comma = true match_block_trailing_comma = true
struct_trailing_comma = "Always" struct_trailing_comma = "Always"
wrap_comments = true wrap_comments = true
use_try_shorthand = true
report_todo = "Always" report_todo = "Always"
report_fixme = "Always" report_fixme = "Always"

View file

@ -121,7 +121,7 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
let mut book = MDBook::new(&book_dir); let mut book = MDBook::new(&book_dir);
// Call the function that does the initialization // Call the function that does the initialization
try!(book.init()); book.init()?;
// If flag `--theme` is present, copy theme to src // If flag `--theme` is present, copy theme to src
if args.is_present("theme") { if args.is_present("theme") {
@ -142,7 +142,7 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
} }
// Call the function that copies the theme // Call the function that copies the theme
try!(book.copy_theme()); book.copy_theme()?;
println!("\nTheme copied."); println!("\nTheme copied.");
} }
@ -172,14 +172,14 @@ fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
let mut book = match args.value_of("dest-dir") { let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)), Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
None => book None => book,
}; };
if args.is_present("no-create") { if args.is_present("no-create") {
book.create_missing = false; book.create_missing = false;
} }
try!(book.build()); book.build()?;
if args.is_present("open") { if args.is_present("open") {
open(book.get_dest().join("index.html")); open(book.get_dest().join("index.html"));
@ -197,11 +197,11 @@ fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
let mut book = match args.value_of("dest-dir") { let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)), Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
None => book None => book,
}; };
if args.is_present("open") { if args.is_present("open") {
try!(book.build()); book.build()?;
open(book.get_dest().join("index.html")); open(book.get_dest().join("index.html"));
} }
@ -227,7 +227,7 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
let mut book = match args.value_of("dest-dir") { let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)), Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
None => book None => book,
}; };
let port = args.value_of("port").unwrap_or("3000"); let port = args.value_of("port").unwrap_or("3000");
@ -253,25 +253,22 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
socket.close(); socket.close();
}} }}
</script> </script>
"#, public_address, ws_port, RELOAD_COMMAND).to_owned()); "#,
public_address,
ws_port,
RELOAD_COMMAND));
try!(book.build()); book.build()?;
let staticfile = staticfile::Static::new(book.get_dest()); let staticfile = staticfile::Static::new(book.get_dest());
let iron = iron::Iron::new(staticfile); let iron = iron::Iron::new(staticfile);
let _iron = iron.http(&*address).unwrap(); let _iron = iron.http(&*address).unwrap();
let ws_server = ws::WebSocket::new(|_| { let ws_server = ws::WebSocket::new(|_| |_| Ok(())).unwrap();
|_| {
Ok(())
}
}).unwrap();
let broadcaster = ws_server.broadcaster(); let broadcaster = ws_server.broadcaster();
std::thread::spawn(move || { std::thread::spawn(move || { ws_server.listen(&*ws_address).unwrap(); });
ws_server.listen(&*ws_address).unwrap();
});
println!("\nServing on {}", address); println!("\nServing on {}", address);
@ -296,7 +293,7 @@ fn test(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args); 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();
try!(book.test()); book.test()?;
Ok(()) Ok(())
} }
@ -339,7 +336,7 @@ fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
Err(e) => { Err(e) => {
println!("Error while trying to watch the files:\n\n\t{:?}", e); println!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(0); ::std::process::exit(0);
} },
}; };
// Add the source directory to the watcher // Add the source directory to the watcher
@ -350,10 +347,14 @@ fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
// Add the book.{json,toml} file to the watcher if it exists, because it's not // Add the book.{json,toml} file to the watcher if it exists, because it's not
// located in the source directory // located in the source directory
if watcher.watch(book.get_root().join("book.json"), NonRecursive).is_err() { if watcher
.watch(book.get_root().join("book.json"), NonRecursive)
.is_err() {
// do nothing if book.json is not found // do nothing if book.json is not found
} }
if watcher.watch(book.get_root().join("book.toml"), NonRecursive).is_err() { if watcher
.watch(book.get_root().join("book.toml"), NonRecursive)
.is_err() {
// do nothing if book.toml is not found // do nothing if book.toml is not found
} }
@ -361,16 +362,18 @@ fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
loop { loop {
match rx.recv() { match rx.recv() {
Ok(event) => match event { Ok(event) => {
NoticeWrite(path) | match event {
NoticeRemove(path) | NoticeWrite(path) |
Create(path) | NoticeRemove(path) |
Write(path) | Create(path) |
Remove(path) | Write(path) |
Rename(_, path) => { Remove(path) |
closure(&path, book); Rename(_, path) => {
closure(&path, book);
},
_ => {},
} }
_ => {}
}, },
Err(e) => { Err(e) => {
println!("An error occured: {:?}", e); println!("An error occured: {:?}", e);

View file

@ -53,7 +53,7 @@ impl BookConfig {
Err(_) => { Err(_) => {
error!("[*]: Failed to open {:?}", &path); error!("[*]: Failed to open {:?}", &path);
exit(2); exit(2);
} },
}; };
if f.read_to_string(&mut data).is_err() { if f.read_to_string(&mut data).is_err() {
error!("[*]: Failed to read {:?}", &path); error!("[*]: Failed to read {:?}", &path);
@ -85,11 +85,11 @@ impl BookConfig {
pub fn parse_from_toml_string(&mut self, data: &str) -> &mut Self { pub fn parse_from_toml_string(&mut self, data: &str) -> &mut Self {
let config = match toml::from_str(data) { let config = match toml::from_str(data) {
Ok(x) => {x}, Ok(x) => x,
Err(e) => { Err(e) => {
error!("[*]: Toml parse errors in book.toml: {:?}", e); error!("[*]: Toml parse errors in book.toml: {:?}", e);
exit(2); exit(2);
} },
}; };
self.parse_from_btreemap(&config); self.parse_from_btreemap(&config);
@ -97,7 +97,8 @@ impl BookConfig {
self self
} }
/// Parses the string to JSON and converts it to BTreeMap<String, toml::Value>. /// Parses the string to JSON and converts it
/// to BTreeMap<String, toml::Value>.
pub fn parse_from_json_string(&mut self, data: &str) -> &mut Self { pub fn parse_from_json_string(&mut self, data: &str) -> &mut Self {
let c: serde_json::Value = match serde_json::from_str(data) { let c: serde_json::Value = match serde_json::from_str(data) {
@ -105,7 +106,7 @@ impl BookConfig {
Err(e) => { Err(e) => {
error!("[*]: JSON parse errors in book.json: {:?}", e); error!("[*]: JSON parse errors in book.json: {:?}", e);
exit(2); exit(2);
} },
}; };
let config = json_object_to_btreemap(c.as_object().unwrap()); let config = json_object_to_btreemap(c.as_object().unwrap());
@ -205,10 +206,7 @@ pub fn json_object_to_btreemap(json: &serde_json::Map<String, serde_json::Value>
let mut config: BTreeMap<String, toml::Value> = BTreeMap::new(); let mut config: BTreeMap<String, toml::Value> = BTreeMap::new();
for (key, value) in json.iter() { for (key, value) in json.iter() {
config.insert( config.insert(String::from_str(key).unwrap(), json_value_to_toml_value(value.to_owned()));
String::from_str(key).unwrap(),
json_value_to_toml_value(value.to_owned())
);
} }
config config
@ -223,10 +221,10 @@ pub fn json_value_to_toml_value(json: serde_json::Value) -> toml::Value {
serde_json::Value::Number(x) => toml::Value::Float(x.as_f64().unwrap()), serde_json::Value::Number(x) => toml::Value::Float(x.as_f64().unwrap()),
serde_json::Value::String(x) => toml::Value::String(x), serde_json::Value::String(x) => toml::Value::String(x),
serde_json::Value::Array(x) => { serde_json::Value::Array(x) => {
toml::Value::Array(x.iter().map(|v| json_value_to_toml_value(v.to_owned())).collect()) toml::Value::Array(x.iter()
}, .map(|v| json_value_to_toml_value(v.to_owned()))
serde_json::Value::Object(x) => { .collect())
toml::Value::Table(json_object_to_btreemap(&x))
}, },
serde_json::Value::Object(x) => toml::Value::Table(json_object_to_btreemap(&x)),
} }
} }

View file

@ -37,10 +37,12 @@ impl Chapter {
impl Serialize for Chapter { impl Serialize for Chapter {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
let mut struct_ = try!(serializer.serialize_struct("Chapter", 2)); where S: Serializer
try!(struct_.serialize_field("name", &self.name)); {
try!(struct_.serialize_field("path", &self.path)); let mut struct_ = serializer.serialize_struct("Chapter", 2)?;
struct_.serialize_field("name", &self.name)?;
struct_.serialize_field("path", &self.path)?;
struct_.end() struct_.end()
} }
} }
@ -66,7 +68,8 @@ impl<'a> Iterator for BookItems<'a> {
let cur = &self.items[self.current_index]; let cur = &self.items[self.current_index];
match *cur { match *cur {
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => { BookItem::Chapter(_, ref ch) |
BookItem::Affix(ref ch) => {
self.stack.push((self.items, self.current_index)); self.stack.push((self.items, self.current_index));
self.items = &ch.sub_items[..]; self.items = &ch.sub_items[..];
self.current_index = 0; self.current_index = 0;

View file

@ -54,8 +54,10 @@ impl MDBook {
/// # } /// # }
/// ``` /// ```
/// ///
/// In this example, `root_dir` will be the root directory of our book and is specified in function /// In this example, `root_dir` will be the root directory of our book
/// of the current working directory by using a relative path instead of an absolute path. /// and is specified in function of the current working directory
/// by using a relative path instead of an
/// absolute path.
/// ///
/// Default directory paths: /// Default directory paths:
/// ///
@ -63,7 +65,8 @@ impl MDBook {
/// - output: `root/book` /// - output: `root/book`
/// - theme: `root/theme` /// - theme: `root/theme`
/// ///
/// They can both be changed by using [`set_src()`](#method.set_src) and [`set_dest()`](#method.set_dest) /// They can both be changed by using [`set_src()`](#method.set_src) and
/// [`set_dest()`](#method.set_dest)
pub fn new(root: &Path) -> MDBook { pub fn new(root: &Path) -> MDBook {
@ -90,7 +93,8 @@ impl MDBook {
} }
} }
/// Returns a flat depth-first iterator over the elements of the book, it returns an [BookItem enum](bookitem.html): /// Returns a flat depth-first iterator over the elements of the book,
/// it returns an [BookItem enum](bookitem.html):
/// `(section: String, bookitem: &BookItem)` /// `(section: String, bookitem: &BookItem)`
/// ///
/// ```no_run /// ```no_run
@ -126,7 +130,8 @@ impl MDBook {
} }
} }
/// `init()` creates some boilerplate files and directories to get you started with your book. /// `init()` creates some boilerplate files and directories
/// to get you started with your book.
/// ///
/// ```text /// ```text
/// book-test/ /// book-test/
@ -136,7 +141,8 @@ impl MDBook {
/// └── SUMMARY.md /// └── SUMMARY.md
/// ``` /// ```
/// ///
/// It uses the paths given as source and output directories and adds a `SUMMARY.md` and a /// It uses the paths given as source and output directories
/// and adds a `SUMMARY.md` and a
/// `chapter_1.md` to the source directory. /// `chapter_1.md` to the source directory.
pub fn init(&mut self) -> Result<(), Box<Error>> { pub fn init(&mut self) -> Result<(), Box<Error>> {
@ -152,12 +158,12 @@ impl MDBook {
if !self.dest.exists() { if !self.dest.exists() {
debug!("[*]: {:?} does not exist, trying to create directory", self.dest); debug!("[*]: {:?} does not exist, trying to create directory", self.dest);
try!(fs::create_dir_all(&self.dest)); fs::create_dir_all(&self.dest)?;
} }
if !self.src.exists() { if !self.src.exists() {
debug!("[*]: {:?} does not exist, trying to create directory", self.src); debug!("[*]: {:?} does not exist, trying to create directory", self.src);
try!(fs::create_dir_all(&self.src)); fs::create_dir_all(&self.src)?;
} }
let summary = self.src.join("SUMMARY.md"); let summary = self.src.join("SUMMARY.md");
@ -167,18 +173,18 @@ impl MDBook {
// Summary does not exist, create it // Summary does not exist, create it
debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", self.src.join("SUMMARY.md")); debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", self.src.join("SUMMARY.md"));
let mut f = try!(File::create(&self.src.join("SUMMARY.md"))); let mut f = File::create(&self.src.join("SUMMARY.md"))?;
debug!("[*]: Writing to SUMMARY.md"); debug!("[*]: Writing to SUMMARY.md");
try!(writeln!(f, "# Summary")); writeln!(f, "# Summary")?;
try!(writeln!(f, "")); writeln!(f, "")?;
try!(writeln!(f, "- [Chapter 1](./chapter_1.md)")); writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
} }
} }
// parse SUMMARY.md, and create the missing item related file // parse SUMMARY.md, and create the missing item related file
try!(self.parse_summary()); self.parse_summary()?;
debug!("[*]: constructing paths for missing files"); debug!("[*]: constructing paths for missing files");
for item in self.iter() { for item in self.iter() {
@ -193,16 +199,15 @@ impl MDBook {
if !path.exists() { if !path.exists() {
if !self.create_missing { if !self.create_missing {
return Err(format!( return Err(format!("'{}' referenced from SUMMARY.md does not exist.", path.to_string_lossy())
"'{}' referenced from SUMMARY.md does not exist.", .into());
path.to_string_lossy()).into());
} }
debug!("[*]: {:?} does not exist, trying to create file", path); debug!("[*]: {:?} does not exist, trying to create file", path);
try!(::std::fs::create_dir_all(path.parent().unwrap())); ::std::fs::create_dir_all(path.parent().unwrap())?;
let mut f = try!(File::create(path)); let mut f = File::create(path)?;
// debug!("[*]: Writing to {:?}", path); // debug!("[*]: Writing to {:?}", path);
try!(writeln!(f, "# {}", ch.name)); writeln!(f, "# {}", ch.name)?;
} }
} }
} }
@ -217,17 +222,19 @@ impl MDBook {
if !gitignore.exists() { if !gitignore.exists() {
// Gitignore does not exist, create it // Gitignore does not exist, create it
// Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root`. If it // Because of `src/book/mdbook.rs#L37-L39`,
// is not, `strip_prefix` will return an Error. // `dest` will always start with `root`.
// If it is not, `strip_prefix` will return an Error.
if !self.get_dest().starts_with(&self.root) { if !self.get_dest().starts_with(&self.root) {
return; return;
} }
let relative = self.get_dest() let relative = self.get_dest()
.strip_prefix(&self.root) .strip_prefix(&self.root)
.expect("Destination is not relative to root."); .expect("Destination is not relative to root.");
let relative = relative.to_str() let relative = relative
.expect("Path could not be yielded into a string slice."); .to_str()
.expect("Path could not be yielded into a string slice.");
debug!("[*]: {:?} does not exist, trying to create .gitignore", gitignore); debug!("[*]: {:?} does not exist, trying to create .gitignore", gitignore);
@ -239,20 +246,21 @@ impl MDBook {
} }
} }
/// The `build()` method is the one where everything happens. First it parses `SUMMARY.md` to /// The `build()` method is the one where everything happens.
/// construct the book's structure in the form of a `Vec<BookItem>` and then calls `render()` /// First it parses `SUMMARY.md` to construct the book's structure
/// in the form of a `Vec<BookItem>` and then calls `render()`
/// method of the current renderer. /// method of the current renderer.
/// ///
/// It is the renderer who generates all the output files. /// It is the renderer who generates all the output files.
pub fn build(&mut self) -> Result<(), Box<Error>> { pub fn build(&mut self) -> Result<(), Box<Error>> {
debug!("[fn]: build"); debug!("[fn]: build");
try!(self.init()); self.init()?;
// Clean output directory // Clean output directory
try!(utils::fs::remove_dir_content(&self.dest)); utils::fs::remove_dir_content(&self.dest)?;
try!(self.renderer.render(&self)); self.renderer.render(&self)?;
Ok(()) Ok(())
} }
@ -269,55 +277,54 @@ impl MDBook {
if !theme_dir.exists() { if !theme_dir.exists() {
debug!("[*]: {:?} does not exist, trying to create directory", theme_dir); debug!("[*]: {:?} does not exist, trying to create directory", theme_dir);
try!(fs::create_dir(&theme_dir)); fs::create_dir(&theme_dir)?;
} }
// index.hbs // index.hbs
let mut index = try!(File::create(&theme_dir.join("index.hbs"))); let mut index = File::create(&theme_dir.join("index.hbs"))?;
try!(index.write_all(theme::INDEX)); index.write_all(theme::INDEX)?;
// book.css // book.css
let mut css = try!(File::create(&theme_dir.join("book.css"))); let mut css = File::create(&theme_dir.join("book.css"))?;
try!(css.write_all(theme::CSS)); css.write_all(theme::CSS)?;
// favicon.png // favicon.png
let mut favicon = try!(File::create(&theme_dir.join("favicon.png"))); let mut favicon = File::create(&theme_dir.join("favicon.png"))?;
try!(favicon.write_all(theme::FAVICON)); favicon.write_all(theme::FAVICON)?;
// book.js // book.js
let mut js = try!(File::create(&theme_dir.join("book.js"))); let mut js = File::create(&theme_dir.join("book.js"))?;
try!(js.write_all(theme::JS)); js.write_all(theme::JS)?;
// highlight.css // highlight.css
let mut highlight_css = try!(File::create(&theme_dir.join("highlight.css"))); let mut highlight_css = File::create(&theme_dir.join("highlight.css"))?;
try!(highlight_css.write_all(theme::HIGHLIGHT_CSS)); highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
// highlight.js // highlight.js
let mut highlight_js = try!(File::create(&theme_dir.join("highlight.js"))); let mut highlight_js = File::create(&theme_dir.join("highlight.js"))?;
try!(highlight_js.write_all(theme::HIGHLIGHT_JS)); highlight_js.write_all(theme::HIGHLIGHT_JS)?;
Ok(()) Ok(())
} }
pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<(), Box<Error>> { pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<(), Box<Error>> {
let path = self.get_dest().join(filename); let path = self.get_dest().join(filename);
try!(utils::fs::create_file(&path).and_then(|mut file| { utils::fs::create_file(&path)
file.write_all(content) .and_then(|mut file| file.write_all(content))
}).map_err(|e| { .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Could not create {}: {}", path.display(), e)))?;
io::Error::new(io::ErrorKind::Other, format!("Could not create {}: {}", path.display(), e))
}));
Ok(()) Ok(())
} }
/// Parses the `book.json` file (if it exists) to extract the configuration parameters. /// Parses the `book.json` file (if it exists) to extract
/// the configuration parameters.
/// The `book.json` file should be in the root directory of the book. /// 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` /// 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) -> Self {
let config = BookConfig::new(&self.root) let config = BookConfig::new(&self.root)
.read_config(&self.root) .read_config(&self.root)
.to_owned(); .to_owned();
self.title = config.title; self.title = config.title;
self.description = config.description; self.description = config.description;
@ -330,8 +337,10 @@ impl MDBook {
self self
} }
/// You can change the default renderer to another one by using this method. The only requirement /// You can change the default renderer to another one
/// is for your renderer to implement the [Renderer trait](../../renderer/renderer/trait.Renderer.html) /// by using this method. The only requirement
/// is for your renderer to implement the
/// [Renderer trait](../../renderer/renderer/trait.Renderer.html)
/// ///
/// ```no_run /// ```no_run
/// extern crate mdbook; /// extern crate mdbook;
@ -343,12 +352,14 @@ impl MDBook {
/// let mut book = MDBook::new(Path::new("mybook")) /// let mut book = MDBook::new(Path::new("mybook"))
/// .set_renderer(Box::new(HtmlHandlebars::new())); /// .set_renderer(Box::new(HtmlHandlebars::new()));
/// ///
/// // In this example we replace the default renderer by the default renderer... /// // In this example we replace the default renderer
/// // Don't forget to put your renderer in a Box /// // by the default renderer...
/// // Don't forget to put your renderer in a Box
/// } /// }
/// ``` /// ```
/// ///
/// **note:** Don't forget to put your renderer in a `Box` before passing it to `set_renderer()` /// **note:** Don't forget to put your renderer in a `Box`
/// before passing it to `set_renderer()`
pub fn set_renderer(mut self, renderer: Box<Renderer>) -> Self { pub fn set_renderer(mut self, renderer: Box<Renderer>) -> Self {
self.renderer = renderer; self.renderer = renderer;
@ -357,7 +368,7 @@ impl MDBook {
pub fn test(&mut self) -> Result<(), Box<Error>> { pub fn test(&mut self) -> Result<(), Box<Error>> {
// read in the chapters // read in the chapters
try!(self.parse_summary()); self.parse_summary()?;
for item in self.iter() { for item in self.iter() {
if let BookItem::Chapter(_, ref ch) = *item { if let BookItem::Chapter(_, ref ch) = *item {
@ -367,17 +378,15 @@ impl MDBook {
println!("[*]: Testing file: {:?}", path); println!("[*]: Testing file: {:?}", path);
let output_result = Command::new("rustdoc") let output_result = Command::new("rustdoc").arg(&path).arg("--test").output();
.arg(&path) let output = output_result?;
.arg("--test")
.output();
let output = try!(output_result);
if !output.status.success() { if !output.status.success() {
return Err(Box::new(io::Error::new(ErrorKind::Other, format!( return Err(Box::new(io::Error::new(ErrorKind::Other,
"{}\n{}", format!("{}\n{}",
String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)))) as Box<Error>); String::from_utf8_lossy(&output.stderr)))) as
Box<Error>);
} }
} }
} }
@ -480,7 +489,7 @@ impl MDBook {
// Construct book // Construct book
fn parse_summary(&mut self) -> Result<(), Box<Error>> { fn parse_summary(&mut self) -> Result<(), Box<Error>> {
// When append becomes stable, use self.content.append() ... // When append becomes stable, use self.content.append() ...
self.content = try!(parse::construct_bookitems(&self.src.join("SUMMARY.md"))); self.content = parse::construct_bookitems(&self.src.join("SUMMARY.md"))?;
Ok(()) Ok(())
} }
} }

View file

@ -76,7 +76,8 @@ extern crate handlebars;
extern crate pulldown_cmark; extern crate pulldown_cmark;
extern crate regex; extern crate regex;
#[macro_use] extern crate log; #[macro_use]
extern crate log;
pub mod book; pub mod book;
mod parse; mod parse;
pub mod renderer; pub mod renderer;

View file

@ -6,10 +6,10 @@ use book::bookitem::{BookItem, Chapter};
pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> { pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
debug!("[fn]: construct_bookitems"); debug!("[fn]: construct_bookitems");
let mut summary = String::new(); let mut summary = String::new();
try!(try!(File::open(path)).read_to_string(&mut summary)); File::open(path)?.read_to_string(&mut summary)?;
debug!("[*]: Parse SUMMARY.md"); debug!("[*]: Parse SUMMARY.md");
let top_items = try!(parse_level(&mut summary.split('\n').collect(), 0, vec![0])); let top_items = parse_level(&mut summary.split('\n').collect(), 0, vec![0])?;
debug!("[*]: Done parsing SUMMARY.md"); debug!("[*]: Done parsing SUMMARY.md");
Ok(top_items) Ok(top_items)
} }
@ -22,9 +22,10 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
while !summary.is_empty() { while !summary.is_empty() {
let item: BookItem; let item: BookItem;
// Indentation level of the line to parse // Indentation level of the line to parse
let level = try!(level(summary[0], 4)); let level = level(summary[0], 4)?;
// if level < current_level we remove the last digit of section, exit the current function, // if level < current_level we remove the last digit of section,
// exit the current function,
// and return the parsed level to the calling function. // and return the parsed level to the calling function.
if level < current_level { if level < current_level {
break; break;
@ -35,11 +36,13 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
// Level can not be root level !! // Level can not be root level !!
// Add a sub-number to section // Add a sub-number to section
section.push(0); section.push(0);
let last = items.pop().expect("There should be at least one item since this can't be the root level"); let last = items
.pop()
.expect("There should be at least one item since this can't be the root level");
if let BookItem::Chapter(ref s, ref ch) = last { if let BookItem::Chapter(ref s, ref ch) = last {
let mut ch = ch.clone(); let mut ch = ch.clone();
ch.sub_items = try!(parse_level(summary, level, section.clone())); ch.sub_items = parse_level(summary, level, section.clone())?;
items.push(BookItem::Chapter(s.clone(), ch)); items.push(BookItem::Chapter(s.clone(), ch));
// Remove the last number from the section, because we got back to our level.. // Remove the last number from the section, because we got back to our level..
@ -62,7 +65,8 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
// Eliminate possible errors and set section to -1 after suffix // Eliminate possible errors and set section to -1 after suffix
match parsed_item { match parsed_item {
// error if level != 0 and BookItem is != Chapter // error if level != 0 and BookItem is != Chapter
BookItem::Affix(_) | BookItem::Spacer if level > 0 => { BookItem::Affix(_) |
BookItem::Spacer if level > 0 => {
return Err(Error::new(ErrorKind::Other, return Err(Error::new(ErrorKind::Other,
"Your summary.md is messed up\n\n "Your summary.md is messed up\n\n
\ \
@ -98,7 +102,9 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
// Increment section // Increment section
let len = section.len() - 1; let len = section.len() - 1;
section[len] += 1; section[len] += 1;
let s = section.iter().fold("".to_owned(), |s, i| s + &i.to_string() + "."); let s = section
.iter()
.fold("".to_owned(), |s, i| s + &i.to_string() + ".");
BookItem::Chapter(s, ch) BookItem::Chapter(s, ch)
}, },
_ => parsed_item, _ => parsed_item,

View file

@ -37,7 +37,8 @@ impl Renderer for HtmlHandlebars {
// Register template // Register template
debug!("[*]: Register handlebars template"); debug!("[*]: Register handlebars template");
try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index)))); handlebars
.register_template_string("index", String::from_utf8(theme.index)?)?;
// Register helpers // Register helpers
debug!("[*]: Register handlebars helpers"); debug!("[*]: Register handlebars helpers");
@ -45,7 +46,7 @@ impl Renderer for HtmlHandlebars {
handlebars.register_helper("previous", Box::new(helpers::navigation::previous)); handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
handlebars.register_helper("next", Box::new(helpers::navigation::next)); handlebars.register_helper("next", Box::new(helpers::navigation::next));
let mut data = try!(make_data(book)); let mut data = make_data(book)?;
// Print version // Print version
let mut print_content: String = String::new(); let mut print_content: String = String::new();
@ -69,11 +70,11 @@ impl Renderer for HtmlHandlebars {
let path = book.get_src().join(&ch.path); let path = book.get_src().join(&ch.path);
debug!("[*]: Opening file: {:?}", path); debug!("[*]: Opening file: {:?}", path);
let mut f = try!(File::open(&path)); let mut f = File::open(&path)?;
let mut content: String = String::new(); let mut content: String = String::new();
debug!("[*]: Reading file"); debug!("[*]: Reading file");
try!(f.read_to_string(&mut content)); f.read_to_string(&mut content)?;
// Parse for playpen links // Parse for playpen links
if let Some(p) = path.parent() { if let Some(p) = path.parent() {
@ -85,8 +86,10 @@ impl Renderer for HtmlHandlebars {
print_content.push_str(&content); print_content.push_str(&content);
// Update the context with data for this file // Update the context with data for this file
let path = ch.path.to_str().ok_or_else(|| let path =
io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?; ch.path
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
data.insert("path".to_owned(), json!(path)); data.insert("path".to_owned(), json!(path));
data.insert("content".to_owned(), json!(content)); data.insert("content".to_owned(), json!(content));
data.insert("chapter_title".to_owned(), json!(ch.name)); data.insert("chapter_title".to_owned(), json!(ch.name));
@ -94,7 +97,7 @@ impl Renderer for HtmlHandlebars {
// Render the handlebars template with the data // Render the handlebars template with the data
debug!("[*]: Render template"); debug!("[*]: Render template");
let rendered = try!(handlebars.render("index", &data)); let rendered = handlebars.render("index", &data)?;
let filename = Path::new(&ch.path).with_extension("html"); let filename = Path::new(&ch.path).with_extension("html");
@ -106,24 +109,26 @@ impl Renderer for HtmlHandlebars {
// Write to file // Write to file
info!("[*] Creating {:?} ✓", filename.display()); info!("[*] Creating {:?} ✓", filename.display());
try!(book.write_file(filename, &rendered.into_bytes())); book.write_file(filename, &rendered.into_bytes())?;
// Create an index.html from the first element in SUMMARY.md // Create an index.html from the first element in SUMMARY.md
if index { if index {
debug!("[*]: index.html"); debug!("[*]: index.html");
let mut content = String::new(); let mut content = String::new();
let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html")))) let _source = File::open(book.get_dest().join(&ch.path.with_extension("html")))?
.read_to_string(&mut content); .read_to_string(&mut content);
// This could cause a problem when someone displays code containing <base href=...> // This could cause a problem when someone displays
// code containing <base href=...>
// on the front page, however this case should be very very rare... // on the front page, however this case should be very very rare...
content = content.lines() content = content
.lines()
.filter(|line| !line.contains("<base href=")) .filter(|line| !line.contains("<base href="))
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join("\n"); .join("\n");
try!(book.write_file("index.html", content.as_bytes())); book.write_file("index.html", content.as_bytes())?;
info!("[*] Creating index.html from {:?} ✓", info!("[*] Creating index.html from {:?} ✓",
book.get_dest().join(&ch.path.with_extension("html"))); book.get_dest().join(&ch.path.with_extension("html")));
@ -145,7 +150,7 @@ impl Renderer for HtmlHandlebars {
// Render the handlebars template with the data // Render the handlebars template with the data
debug!("[*]: Render template"); debug!("[*]: Render template");
let rendered = try!(handlebars.render("index", &data)); let rendered = handlebars.render("index", &data)?;
// do several kinds of post-processing // do several kinds of post-processing
let rendered = build_header_links(rendered, "print.html"); let rendered = build_header_links(rendered, "print.html");
@ -153,29 +158,29 @@ impl Renderer for HtmlHandlebars {
let rendered = fix_code_blocks(rendered); let rendered = fix_code_blocks(rendered);
let rendered = add_playpen_pre(rendered); let rendered = add_playpen_pre(rendered);
try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes())); book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes())?;
info!("[*] Creating print.html ✓"); info!("[*] Creating print.html ✓");
// Copy static files (js, css, images, ...) // Copy static files (js, css, images, ...)
debug!("[*] Copy static files"); debug!("[*] Copy static files");
try!(book.write_file("book.js", &theme.js)); book.write_file("book.js", &theme.js)?;
try!(book.write_file("book.css", &theme.css)); book.write_file("book.css", &theme.css)?;
try!(book.write_file("favicon.png", &theme.favicon)); book.write_file("favicon.png", &theme.favicon)?;
try!(book.write_file("jquery.js", &theme.jquery)); book.write_file("jquery.js", &theme.jquery)?;
try!(book.write_file("highlight.css", &theme.highlight_css)); book.write_file("highlight.css", &theme.highlight_css)?;
try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css)); book.write_file("tomorrow-night.css", &theme.tomorrow_night_css)?;
try!(book.write_file("highlight.js", &theme.highlight_js)); book.write_file("highlight.js", &theme.highlight_js)?;
try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME)); book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME)?;
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT)); book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT)?;
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG)); book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG)?;
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF)); book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF)?;
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF)); book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF)?;
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2)); book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2)?;
try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)); book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)?;
// Copy all remaining files // Copy all remaining files
try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"])); utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"])?;
Ok(()) Ok(())
} }
@ -207,15 +212,17 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
match *item { match *item {
BookItem::Affix(ref ch) => { BookItem::Affix(ref ch) => {
chapter.insert("name".to_owned(), json!(ch.name)); chapter.insert("name".to_owned(), json!(ch.name));
let path = ch.path.to_str().ok_or_else(|| let path = ch.path
io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?; .to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
chapter.insert("path".to_owned(), json!(path)); chapter.insert("path".to_owned(), json!(path));
}, },
BookItem::Chapter(ref s, ref ch) => { BookItem::Chapter(ref s, ref ch) => {
chapter.insert("section".to_owned(), json!(s)); chapter.insert("section".to_owned(), json!(s));
chapter.insert("name".to_owned(), json!(ch.name)); chapter.insert("name".to_owned(), json!(ch.name));
let path = ch.path.to_str().ok_or_else(|| let path = ch.path
io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?; .to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
chapter.insert("path".to_owned(), json!(path)); chapter.insert("path".to_owned(), json!(path));
}, },
BookItem::Spacer => { BookItem::Spacer => {
@ -237,42 +244,55 @@ fn build_header_links(html: String, filename: &str) -> String {
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap(); let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
let mut id_counter = HashMap::new(); let mut id_counter = HashMap::new();
regex.replace_all(&html, |caps: &Captures| { regex
let level = &caps[1]; .replace_all(&html, |caps: &Captures| {
let text = &caps[2]; let level = &caps[1];
let mut id = text.to_string(); let text = &caps[2];
let repl_sub = vec!["<em>", "</em>", "<code>", "</code>", let mut id = text.to_string();
"<strong>", "</strong>", let repl_sub = vec!["<em>",
"&lt;", "&gt;", "&amp;", "&#39;", "&quot;"]; "</em>",
for sub in repl_sub { "<code>",
id = id.replace(sub, ""); "</code>",
} "<strong>",
let id = id.chars().filter_map(|c| { "</strong>",
if c.is_alphanumeric() || c == '-' || c == '_' { "&lt;",
if c.is_ascii() { "&gt;",
Some(c.to_ascii_lowercase()) "&amp;",
} else { "&#39;",
Some(c) "&quot;"];
} for sub in repl_sub {
} else if c.is_whitespace() && c.is_ascii() { id = id.replace(sub, "");
Some('-')
} else {
None
} }
}).collect::<String>(); let id = id.chars()
.filter_map(|c| if c.is_alphanumeric() || c == '-' || c == '_' {
if c.is_ascii() {
Some(c.to_ascii_lowercase())
} else {
Some(c)
}
} else if c.is_whitespace() && c.is_ascii() {
Some('-')
} else {
None
})
.collect::<String>();
let id_count = *id_counter.get(&id).unwrap_or(&0); let id_count = *id_counter.get(&id).unwrap_or(&0);
id_counter.insert(id.clone(), id_count + 1); id_counter.insert(id.clone(), id_count + 1);
let id = if id_count > 0 { let id = if id_count > 0 {
format!("{}-{}", id, id_count) format!("{}-{}", id, id_count)
} else { } else {
id id
}; };
format!("<a class=\"header\" href=\"{filename}#{id}\" id=\"{id}\"><h{level}>{text}</h{level}></a>", format!("<a class=\"header\" href=\"{filename}#{id}\" id=\"{id}\"><h{level}>{text}</h{level}></a>",
level=level, id=id, text=text, filename=filename) level = level,
}).into_owned() id = id,
text = text,
filename = filename)
})
.into_owned()
} }
// anchors to the same page (href="#anchor") do not work because of // anchors to the same page (href="#anchor") do not work because of
@ -280,18 +300,24 @@ fn build_header_links(html: String, filename: &str) -> String {
// that in a very inelegant way // that in a very inelegant way
fn fix_anchor_links(html: String, filename: &str) -> String { fn fix_anchor_links(html: String, filename: &str) -> String {
let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap(); let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap();
regex.replace_all(&html, |caps: &Captures| { regex
let before = &caps[1]; .replace_all(&html, |caps: &Captures| {
let anchor = &caps[2]; let before = &caps[1];
let after = &caps[3]; let anchor = &caps[2];
let after = &caps[3];
format!("<a{before}href=\"{filename}#{anchor}\"{after}>", format!("<a{before}href=\"{filename}#{anchor}\"{after}>",
before=before, filename=filename, anchor=anchor, after=after) before = before,
}).into_owned() filename = filename,
anchor = anchor,
after = after)
})
.into_owned()
} }
// The rust book uses annotations for rustdoc to test code snippets, like the following: // The rust book uses annotations for rustdoc to test code snippets,
// like the following:
// ```rust,should_panic // ```rust,should_panic
// fn main() { // fn main() {
// // Code here // // Code here
@ -300,40 +326,48 @@ fn fix_anchor_links(html: String, filename: &str) -> String {
// This function replaces all commas by spaces in the code block classes // This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String { fn fix_code_blocks(html: String) -> String {
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap(); let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
regex.replace_all(&html, |caps: &Captures| { regex
let before = &caps[1]; .replace_all(&html, |caps: &Captures| {
let classes = &caps[2].replace(",", " "); let before = &caps[1];
let after = &caps[3]; let classes = &caps[2].replace(",", " ");
let after = &caps[3];
format!("<code{before}class=\"{classes}\"{after}>", before=before, classes=classes, after=after) format!("<code{before}class=\"{classes}\"{after}>", before = before, classes = classes, after = after)
}).into_owned() })
.into_owned()
} }
fn add_playpen_pre(html: String) -> String { fn add_playpen_pre(html: String) -> String {
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap(); let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
regex.replace_all(&html, |caps: &Captures| { regex
let text = &caps[1]; .replace_all(&html, |caps: &Captures| {
let classes = &caps[2]; let text = &caps[1];
let code = &caps[3]; let classes = &caps[2];
let code = &caps[3];
if classes.contains("language-rust") && !classes.contains("ignore") { if classes.contains("language-rust") && !classes.contains("ignore") {
// wrap the contents in an external pre block // wrap the contents in an external pre block
if text.contains("fn main") || text.contains("quick_main!") { if text.contains("fn main") || text.contains("quick_main!") {
format!("<pre class=\"playpen\">{}</pre>", text) format!("<pre class=\"playpen\">{}</pre>", text)
} else { } else {
// we need to inject our own main // we need to inject our own main
let (attrs, code) = partition_source(code); let (attrs, code) = partition_source(code);
format!("<pre class=\"playpen\"><code class=\"{}\"># #![allow(unused_variables)] format!("<pre class=\"playpen\"><code class=\"{}\"># #![allow(unused_variables)]
{}#fn main() {{ {}#fn main() {{
{} \
#}}</code></pre>", classes, attrs, code) {}
#}}</code></pre>",
classes,
attrs,
code)
}
} else {
// not language-rust, so no-op
format!("{}", text)
} }
} else { })
// not language-rust, so no-op .into_owned()
format!("{}", text)
}
}).into_owned()
} }
fn partition_source(s: &str) -> (String, String) { fn partition_source(s: &str) -> (String, String) {
@ -343,8 +377,7 @@ fn partition_source(s: &str) -> (String, String) {
for line in s.lines() { for line in s.lines() {
let trimline = line.trim(); let trimline = line.trim();
let header = trimline.chars().all(|c| c.is_whitespace()) || let header = trimline.chars().all(|c| c.is_whitespace()) || trimline.starts_with("#![");
trimline.starts_with("#![");
if !header || after_header { if !header || after_header {
after_header = true; after_header = true;
after.push_str(line); after.push_str(line);
@ -356,4 +389,4 @@ fn partition_source(s: &str) -> (String, String) {
} }
(before, after) (before, after)
} }

View file

@ -14,9 +14,12 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
// 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 = rc.context().navigate(rc.get_path(), &VecDeque::new(), "chapters").to_owned(); let chapters = rc.context()
.navigate(rc.get_path(), &VecDeque::new(), "chapters")
.to_owned();
let current = rc.context().navigate(rc.get_path(), &VecDeque::new(), "path") let current = rc.context()
.navigate(rc.get_path(), &VecDeque::new(), "path")
.to_string() .to_string()
.replace("\"", ""); .replace("\"", "");
@ -84,7 +87,7 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
match _h.template() { match _h.template() {
Some(t) => { Some(t) => {
*rc.context_mut() = updated_context; *rc.context_mut() = updated_context;
try!(t.render(r, rc)); t.render(r, rc)?;
}, },
None => return Err(RenderError::new("Error with the handlebars template")), None => return Err(RenderError::new("Error with the handlebars template")),
} }
@ -115,9 +118,12 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
// 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 = rc.context().navigate(rc.get_path(), &VecDeque::new(), "chapters").to_owned(); let chapters = rc.context()
.navigate(rc.get_path(), &VecDeque::new(), "chapters")
.to_owned();
let current = rc.context().navigate(rc.get_path(), &VecDeque::new(), "path") let current = rc.context()
.navigate(rc.get_path(), &VecDeque::new(), "path")
.to_string() .to_string()
.replace("\"", ""); .replace("\"", "");
@ -181,7 +187,7 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
match _h.template() { match _h.template() {
Some(t) => { Some(t) => {
*rc.context_mut() = updated_context; *rc.context_mut() = updated_context;
try!(t.render(r, rc)); t.render(r, rc)?;
}, },
None => return Err(RenderError::new("Error with the handlebars template")), None => return Err(RenderError::new("Error with the handlebars template")),
} }

View file

@ -4,8 +4,9 @@ use std::io::Read;
pub fn render_playpen(s: &str, path: &Path) -> String { pub fn render_playpen(s: &str, path: &Path) -> String {
// When replacing one thing in a string by something with a different length, the indices // When replacing one thing in a string by something with a different length,
// after that will not correspond, we therefore have to store the difference to correct this // the indices after that will not correspond,
// we therefore have to store the difference to correct this
let mut previous_end_index = 0; let mut previous_end_index = 0;
let mut replaced = String::new(); let mut replaced = String::new();
@ -35,13 +36,13 @@ pub fn render_playpen(s: &str, path: &Path) -> String {
continue; continue;
}; };
let replacement = String::new() + "<pre><code class=\"language-rust\">" + &file_content + let replacement = String::new() + "<pre><code class=\"language-rust\">" + &file_content + "</code></pre>";
"</code></pre>";
replaced.push_str(&s[previous_end_index..playpen.start_index]); replaced.push_str(&s[previous_end_index..playpen.start_index]);
replaced.push_str(&replacement); replaced.push_str(&replacement);
previous_end_index = playpen.end_index; previous_end_index = playpen.end_index;
// println!("Playpen{{ {}, {}, {:?}, {} }}", playpen.start_index, playpen.end_index, playpen.rust_file, // println!("Playpen{{ {}, {}, {:?}, {} }}", playpen.start_index,
// playpen.end_index, playpen.rust_file,
// playpen.editable); // playpen.editable);
} }
@ -100,12 +101,12 @@ fn find_playpens(s: &str, base_path: &Path) -> Vec<Playpen> {
.unwrap_or(false); .unwrap_or(false);
playpens.push(Playpen { playpens.push(Playpen {
start_index: i, start_index: i,
end_index: end_i, end_index: end_i,
rust_file: base_path.join(PathBuf::from(params[0])), rust_file: base_path.join(PathBuf::from(params[0])),
editable: editable, editable: editable,
escaped: escaped, escaped: escaped,
}) })
} }
playpens playpens
@ -189,7 +190,11 @@ fn test_find_playpens_escaped_playpen() {
println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new(""))); println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new("")));
assert!(find_playpens(s, Path::new("")) == assert!(find_playpens(s, Path::new("")) ==
vec![ vec![Playpen {
Playpen{start_index: 39, end_index: 68, rust_file: PathBuf::from("file.rs"), editable: true, escaped: true}, start_index: 39,
]); end_index: 68,
rust_file: PathBuf::from("file.rs"),
editable: true,
escaped: true,
}]);
} }

View file

@ -15,9 +15,14 @@ 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 = rc.context().navigate(rc.get_path(), &VecDeque::new(), "chapters").to_owned(); let chapters = rc.context()
let current = rc.context().navigate(rc.get_path(), &VecDeque::new(), "path").to_string().replace("\"", ""); .navigate(rc.get_path(), &VecDeque::new(), "chapters")
try!(rc.writer.write_all("<ul class=\"chapter\">".as_bytes())); .to_owned();
let current = rc.context()
.navigate(rc.get_path(), &VecDeque::new(), "path")
.to_string()
.replace("\"", "");
rc.writer.write_all("<ul class=\"chapter\">".as_bytes())?;
// Decode json format // Decode json format
let decoded: Vec<BTreeMap<String, String>> = serde_json::from_str(&chapters.to_string()).unwrap(); let decoded: Vec<BTreeMap<String, String>> = serde_json::from_str(&chapters.to_string()).unwrap();
@ -28,7 +33,8 @@ impl HelperDef for RenderToc {
// Spacer // Spacer
if item.get("spacer").is_some() { if item.get("spacer").is_some() {
try!(rc.writer.write_all("<li class=\"spacer\"></li>".as_bytes())); rc.writer
.write_all("<li class=\"spacer\"></li>".as_bytes())?;
continue; continue;
} }
@ -40,48 +46,47 @@ impl HelperDef for RenderToc {
if level > current_level { if level > current_level {
while level > current_level { while level > current_level {
try!(rc.writer.write_all("<li>".as_bytes())); rc.writer.write_all("<li>".as_bytes())?;
try!(rc.writer.write_all("<ul class=\"section\">".as_bytes())); rc.writer.write_all("<ul class=\"section\">".as_bytes())?;
current_level += 1; current_level += 1;
} }
try!(rc.writer.write_all("<li>".as_bytes())); rc.writer.write_all("<li>".as_bytes())?;
} else if level < current_level { } else if level < current_level {
while level < current_level { while level < current_level {
try!(rc.writer.write_all("</ul>".as_bytes())); rc.writer.write_all("</ul>".as_bytes())?;
try!(rc.writer.write_all("</li>".as_bytes())); rc.writer.write_all("</li>".as_bytes())?;
current_level -= 1; current_level -= 1;
} }
try!(rc.writer.write_all("<li>".as_bytes())); rc.writer.write_all("<li>".as_bytes())?;
} else { } else {
try!(rc.writer.write_all("<li".as_bytes())); rc.writer.write_all("<li".as_bytes())?;
if item.get("section").is_none() { if item.get("section").is_none() {
try!(rc.writer.write_all(" class=\"affix\"".as_bytes())); rc.writer.write_all(" class=\"affix\"".as_bytes())?;
} }
try!(rc.writer.write_all(">".as_bytes())); rc.writer.write_all(">".as_bytes())?;
} }
// Link // Link
let path_exists = if let Some(path) = item.get("path") { let path_exists = if let Some(path) = item.get("path") {
if !path.is_empty() { if !path.is_empty() {
try!(rc.writer.write_all("<a href=\"".as_bytes())); rc.writer.write_all("<a href=\"".as_bytes())?;
let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/");
// Add link // Add link
try!(rc.writer.write_all(Path::new(item.get("path") rc.writer.write_all(tmp.as_bytes())?;
.expect("Error: path should be Some(_)")) rc.writer.write_all("\"".as_bytes())?;
.with_extension("html")
.to_str()
.unwrap()
// Hack for windows who tends to use `\` as separator instead of `/`
.replace("\\", "/")
.as_bytes()));
try!(rc.writer.write_all("\"".as_bytes()));
if path == &current { if path == &current {
try!(rc.writer.write_all(" class=\"active\"".as_bytes())); rc.writer.write_all(" class=\"active\"".as_bytes())?;
} }
try!(rc.writer.write_all(">".as_bytes())); rc.writer.write_all(">".as_bytes())?;
true true
} else { } else {
false false
@ -92,47 +97,45 @@ impl HelperDef for RenderToc {
// Section does not necessarily exist // Section does not necessarily exist
if let Some(section) = item.get("section") { if let Some(section) = item.get("section") {
try!(rc.writer.write_all("<strong>".as_bytes())); rc.writer.write_all("<strong>".as_bytes())?;
try!(rc.writer.write_all(section.as_bytes())); rc.writer.write_all(section.as_bytes())?;
try!(rc.writer.write_all("</strong> ".as_bytes())); rc.writer.write_all("</strong> ".as_bytes())?;
} }
if let Some(name) = item.get("name") { if let Some(name) = item.get("name") {
// Render only inline code blocks // Render only inline code blocks
// filter all events that are not inline code blocks // filter all events that are not inline code blocks
let parser = Parser::new(name).filter(|event| { let parser = Parser::new(name).filter(|event| match *event {
match *event { Event::Start(Tag::Code) |
Event::Start(Tag::Code) | Event::End(Tag::Code) |
Event::End(Tag::Code) | Event::InlineHtml(_) |
Event::InlineHtml(_) | Event::Text(_) => true,
Event::Text(_) => true, _ => false,
_ => false, });
}
});
// render markdown to html // render markdown to html
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2); let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
html::push_html(&mut markdown_parsed_name, parser); html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template // write to the handlebars template
try!(rc.writer.write_all(markdown_parsed_name.as_bytes())); rc.writer.write_all(markdown_parsed_name.as_bytes())?;
} }
if path_exists { if path_exists {
try!(rc.writer.write_all("</a>".as_bytes())); rc.writer.write_all("</a>".as_bytes())?;
} }
try!(rc.writer.write_all("</li>".as_bytes())); rc.writer.write_all("</li>".as_bytes())?;
} }
while current_level > 1 { while current_level > 1 {
try!(rc.writer.write_all("</ul>".as_bytes())); rc.writer.write_all("</ul>".as_bytes())?;
try!(rc.writer.write_all("</li>".as_bytes())); rc.writer.write_all("</li>".as_bytes())?;
current_level -= 1; current_level -= 1;
} }
try!(rc.writer.write_all("</ul>".as_bytes())); rc.writer.write_all("</ul>".as_bytes())?;
Ok(()) Ok(())
} }
} }

View file

@ -19,11 +19,14 @@ pub static FONT_AWESOME_WOFF: &'static [u8] = include_bytes!("_FontAwesome/fonts
pub static FONT_AWESOME_WOFF2: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff2"); pub static FONT_AWESOME_WOFF2: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff2");
pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf"); pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf");
/// The `Theme` struct should be used instead of the static variables because the `new()` method /// The `Theme` struct should be used instead of the static variables because
/// will look if the user has a theme directory in his source folder and use the users theme instead /// the `new()` method
/// will look if the user has a theme directory in his source folder and use
/// the users theme instead
/// of the default. /// of the default.
/// ///
/// You should exceptionnaly use the static variables only if you need the default theme even if the /// You should exceptionnaly use the static variables only if you need the
/// default theme even if the
/// user has specified another theme. /// user has specified another theme.
pub struct Theme { pub struct Theme {
pub index: Vec<u8>, pub index: Vec<u8>,

View file

@ -24,10 +24,11 @@ pub fn file_to_string(path: &Path) -> Result<String, Box<Error>> {
Ok(content) Ok(content)
} }
/// Takes a path and returns a path containing just enough `../` to point to the root of the given path. /// Takes a path and returns a path containing just enough `../` to point to
/// the root of the given path.
/// ///
/// This is mostly interesting for a relative path to point back to the directory from where the /// This is mostly interesting for a relative path to point back to the
/// path starts. /// directory from where the path starts.
/// ///
/// ```ignore /// ```ignore
/// let mut path = Path::new("some/relative/path"); /// let mut path = Path::new("some/relative/path");
@ -41,9 +42,10 @@ pub fn file_to_string(path: &Path) -> Result<String, Box<Error>> {
/// "../../" /// "../../"
/// ``` /// ```
/// ///
/// **note:** it's not very fool-proof, if you find a situation where it doesn't return the correct /// **note:** it's not very fool-proof, if you find a situation where
/// path. Consider [submitting a new issue](https://github.com/azerupi/mdBook/issues) or a /// it doesn't return the correct path.
/// [pull-request](https://github.com/azerupi/mdBook/pulls) to improve it. /// Consider [submitting a new issue](https://github.com/azerupi/mdBook/issues)
/// or a [pull-request](https://github.com/azerupi/mdBook/pulls) to improve it.
pub fn path_to_root(path: &Path) -> String { pub fn path_to_root(path: &Path) -> String {
debug!("[fn]: path_to_root"); debug!("[fn]: path_to_root");
@ -66,8 +68,9 @@ pub fn path_to_root(path: &Path) -> String {
/// This function creates a file and returns it. But before creating the file it checks every /// This function creates a file and returns it. But before creating the file
/// directory in the path to see if it exists, and if it does not it will be created. /// it checks every directory in the path to see if it exists,
/// and if it does not it will be created.
pub fn create_file(path: &Path) -> io::Result<File> { pub fn create_file(path: &Path) -> io::Result<File> {
debug!("[fn]: create_file"); debug!("[fn]: create_file");
@ -76,7 +79,7 @@ pub fn create_file(path: &Path) -> io::Result<File> {
if let Some(p) = path.parent() { if let Some(p) = path.parent() {
debug!("Parent directory is: {:?}", p); debug!("Parent directory is: {:?}", p);
try!(fs::create_dir_all(p)); fs::create_dir_all(p)?;
} }
debug!("[*]: Create file: {:?}", path); debug!("[*]: Create file: {:?}", path);
@ -86,13 +89,13 @@ pub fn create_file(path: &Path) -> io::Result<File> {
/// Removes all the content of a directory but not the directory itself /// Removes all the content of a directory but not the directory itself
pub fn remove_dir_content(dir: &Path) -> Result<(), Box<Error>> { pub fn remove_dir_content(dir: &Path) -> Result<(), Box<Error>> {
for item in try!(fs::read_dir(dir)) { for item in fs::read_dir(dir)? {
if let Ok(item) = item { if let Ok(item) = item {
let item = item.path(); let item = item.path();
if item.is_dir() { if item.is_dir() {
try!(fs::remove_dir_all(item)); fs::remove_dir_all(item)?;
} else { } else {
try!(fs::remove_file(item)); fs::remove_file(item)?;
} }
} }
} }
@ -101,20 +104,21 @@ pub fn remove_dir_content(dir: &Path) -> Result<(), Box<Error>> {
/// ///
/// ///
/// Copies all files of a directory to another one except the files with the extensions given in the /// Copies all files of a directory to another one except the files
/// `ext_blacklist` array /// with the extensions given in the `ext_blacklist` array
pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blacklist: &[&str]) -> Result<(), Box<Error>> { pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blacklist: &[&str])
-> Result<(), Box<Error>> {
debug!("[fn] copy_files_except_ext"); debug!("[fn] copy_files_except_ext");
// Check that from and to are different // Check that from and to are different
if from == to { if from == to {
return Ok(()); return Ok(());
} }
debug!("[*] Loop"); debug!("[*] Loop");
for entry in try!(fs::read_dir(from)) { for entry in fs::read_dir(from)? {
let entry = try!(entry); let entry = entry?;
debug!("[*] {:?}", entry.path()); debug!("[*] {:?}", entry.path());
let metadata = try!(entry.metadata()); let metadata = entry.metadata()?;
// If the entry is a dir and the recursive option is enabled, call itself // If the entry is a dir and the recursive option is enabled, call itself
if metadata.is_dir() && recursive { if metadata.is_dir() && recursive {
@ -125,13 +129,10 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
// check if output dir already exists // check if output dir already exists
if !to.join(entry.file_name()).exists() { if !to.join(entry.file_name()).exists() {
try!(fs::create_dir(&to.join(entry.file_name()))); fs::create_dir(&to.join(entry.file_name()))?;
} }
try!(copy_files_except_ext(&from.join(entry.file_name()), copy_files_except_ext(&from.join(entry.file_name()), &to.join(entry.file_name()), true, ext_blacklist)?;
&to.join(entry.file_name()),
true,
ext_blacklist));
} else if metadata.is_file() { } else if metadata.is_file() {
// Check if it is in the blacklist // Check if it is in the blacklist
@ -141,13 +142,22 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
} }
} }
debug!("[*] creating path for file: {:?}", debug!("[*] creating path for file: {:?}",
&to.join(entry.path().file_name().expect("a file should have a file name..."))); &to.join(entry
.path()
.file_name()
.expect("a file should have a file name...")));
info!("[*] Copying file: {:?}\n to {:?}", info!("[*] Copying file: {:?}\n to {:?}",
entry.path(), entry.path(),
&to.join(entry.path().file_name().expect("a file should have a file name..."))); &to.join(entry
try!(fs::copy(entry.path(), .path()
&to.join(entry.path().file_name().expect("a file should have a file name...")))); .file_name()
.expect("a file should have a file name...")));
fs::copy(entry.path(),
&to.join(entry
.path()
.file_name()
.expect("a file should have a file name...")))?;
} }
} }
Ok(()) Ok(())