Add rustfmt configuration file and run rustfmt on the whole project

This commit is contained in:
Mathieu David 2016-02-16 23:11:29 +01:00
parent fe775850dc
commit 556258a6d8
14 changed files with 553 additions and 363 deletions

6
.gitignore vendored
View File

@ -1,5 +1,11 @@
Cargo.lock Cargo.lock
target target
# Test book to test new features
book-test book-test
# Output of example book
book-example/book book-example/book
# rustfmt backup files
*.rs.bk

13
rustfmt.toml Normal file
View File

@ -0,0 +1,13 @@
max_width = 120
ideal_width = 120
fn_call_width = 100
fn_args_density = "Compressed"
enum_trailing_comma = true
match_block_trailing_comma = true
struct_trailing_comma = "Always"
wrap_comments = true
report_todo = "Always"
report_fixme = "Always"

View File

@ -56,12 +56,12 @@ fn main() {
// Check which subcomamnd the user ran... // Check which subcomamnd the user ran...
let res = match matches.subcommand() { let res = match matches.subcommand() {
("init", Some(sub_matches)) => init(sub_matches), ("init", Some(sub_matches)) => init(sub_matches),
("build", Some(sub_matches)) => build(sub_matches), ("build", Some(sub_matches)) => build(sub_matches),
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
("watch", Some(sub_matches)) => watch(sub_matches), ("watch", Some(sub_matches)) => watch(sub_matches),
("test", Some(sub_matches)) => test(sub_matches), ("test", Some(sub_matches)) => test(sub_matches),
(_, _) => unreachable!() (_, _) => unreachable!(),
}; };
if let Err(e) = res { if let Err(e) = res {
@ -77,7 +77,7 @@ fn confirm() -> bool {
io::stdin().read_line(&mut s).ok(); io::stdin().read_line(&mut s).ok();
match &*s.trim() { match &*s.trim() {
"Y" | "y" | "yes" | "Yes" => true, "Y" | "y" | "yes" | "Yes" => true,
_ => false _ => false,
} }
} }
@ -151,66 +151,68 @@ fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
let book = MDBook::new(&book_dir).read_config(); let book = MDBook::new(&book_dir).read_config();
// Create a channel to receive the events. // Create a channel to receive the events.
let (tx, rx) = channel(); let (tx, rx) = channel();
let w: Result<notify::RecommendedWatcher, notify::Error> = notify::Watcher::new(tx); let w: Result<notify::RecommendedWatcher, notify::Error> = notify::Watcher::new(tx);
match w { match w {
Ok(mut watcher) => { Ok(mut watcher) => {
// Add the source directory to the watcher // Add the source directory to the watcher
if let Err(e) = watcher.watch(book.get_src()) { if let Err(e) = watcher.watch(book.get_src()) {
println!("Error while watching {:?}:\n {:?}", book.get_src(), e); println!("Error while watching {:?}:\n {:?}", book.get_src(), e);
::std::process::exit(0); ::std::process::exit(0);
}; };
// Add the book.json file to the watcher if it exists, because it's not // Add the book.json file to the watcher if it exists, because it's not
// located in the source directory // located in the source directory
if let Err(_) = watcher.watch(book_dir.join("book.json")) { if let Err(_) = watcher.watch(book_dir.join("book.json")) {
// do nothing if book.json is not found // do nothing if book.json is not found
} }
let previous_time = time::get_time().sec; let previous_time = time::get_time().sec;
crossbeam::scope(|scope| { crossbeam::scope(|scope| {
loop { loop {
match rx.recv() { match rx.recv() {
Ok(event) => { Ok(event) => {
// Skip the event if an event has already been issued in the last second // Skip the event if an event has already been issued in the last second
if time::get_time().sec - previous_time < 1 { continue } if time::get_time().sec - previous_time < 1 {
continue;
}
if let Some(path) = event.path { if let Some(path) = event.path {
// Trigger the build process in a new thread (to keep receiving events) // Trigger the build process in a new thread (to keep receiving events)
scope.spawn(move || { scope.spawn(move || {
println!("File changed: {:?}\nBuilding book...\n", path); println!("File changed: {:?}\nBuilding book...\n", path);
match build(args) { match build(args) {
Err(e) => println!("Error while building: {:?}", e), Err(e) => println!("Error while building: {:?}", e),
_ => {} _ => {},
} }
println!(""); println!("");
}); });
} else { } else {
continue; continue;
} }
}, },
Err(e) => { Err(e) => {
println!("An error occured: {:?}", e); println!("An error occured: {:?}", e);
} },
} }
} }
}); });
}, },
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);
} },
} }
Ok(()) Ok(())
} }
@ -230,9 +232,9 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
// Check if path is relative from current dir, or absolute... // Check if path is relative from current dir, or absolute...
let p = Path::new(dir); let p = Path::new(dir);
if p.is_relative() { if p.is_relative() {
env::current_dir().unwrap().join(dir) env::current_dir().unwrap().join(dir)
} else { } else {
p.to_path_buf() p.to_path_buf()
} }
} else { } else {
env::current_dir().unwrap() env::current_dir().unwrap()

View File

@ -26,7 +26,7 @@ impl BookConfig {
root: root.to_owned(), root: root.to_owned(),
dest: PathBuf::from("book"), dest: PathBuf::from("book"),
src: PathBuf::from("src"), src: PathBuf::from("src"),
indent_spaces: 4, // indentation used for SUMMARY.md indent_spaces: 4, // indentation used for SUMMARY.md
multilingual: false, multilingual: false,
} }
} }
@ -40,7 +40,7 @@ impl BookConfig {
Ok(f) => f, Ok(f) => f,
Err(_) => { Err(_) => {
debug!("[*]: Failed to open {:?}", root.join("book.json")); debug!("[*]: Failed to open {:?}", root.join("book.json"));
return self return self;
}, },
}; };
@ -49,17 +49,25 @@ impl BookConfig {
// Just return if an error occured. // Just return if an error occured.
// I would like to propagate the error, but I have to return `&self` // I would like to propagate the error, but I have to return `&self`
if let Err(_) = config_file.read_to_string(&mut data) { return self } if let Err(_) = config_file.read_to_string(&mut data) {
return self;
}
// Convert to JSON // Convert to JSON
if let Ok(config) = Json::from_str(&data) { if let Ok(config) = Json::from_str(&data) {
// Extract data // Extract data
debug!("[*]: Extracting data from config"); debug!("[*]: Extracting data from config");
// Title, author, description // Title & author
if let Some(a) = config.find_path(&["title"]) { self.title = a.to_string().replace("\"", "") } if let Some(a) = config.find_path(&["title"]) {
if let Some(a) = config.find_path(&["author"]) { self.author = a.to_string().replace("\"", "") } self.title = a.to_string().replace("\"", "")
if let Some(a) = config.find_path(&["description"]) { self.description = a.to_string().replace("\"", "") } }
if let Some(a) = config.find_path(&["author"]) {
self.author = a.to_string().replace("\"", "")
}
if let Some(a) = config.find_path(&["description"]) {
self.description = a.to_string().replace("\"", "")
}
// Destination // Destination
if let Some(a) = config.find_path(&["dest"]) { if let Some(a) = config.find_path(&["dest"]) {
@ -71,7 +79,9 @@ impl BookConfig {
let dest = self.get_root().join(&dest).to_owned(); let dest = self.get_root().join(&dest).to_owned();
self.set_dest(&dest); self.set_dest(&dest);
}, },
false => { self.set_dest(&dest); }, false => {
self.set_dest(&dest);
},
} }
} }
} }
@ -105,5 +115,4 @@ impl BookConfig {
self.src = src.to_owned(); self.src = src.to_owned();
self self
} }
} }

View File

@ -27,7 +27,6 @@ pub struct BookItems<'a> {
impl Chapter { impl Chapter {
pub fn new(name: String, path: PathBuf) -> Self { pub fn new(name: String, path: PathBuf) -> Self {
Chapter { Chapter {
@ -40,13 +39,14 @@ impl Chapter {
impl ToJson for Chapter { impl ToJson for Chapter {
fn to_json(&self) -> Json { fn to_json(&self) -> Json {
let mut m: BTreeMap<String, Json> = BTreeMap::new(); let mut m: BTreeMap<String, Json> = BTreeMap::new();
m.insert("name".to_owned(), self.name.to_json()); m.insert("name".to_owned(), self.name.to_json());
m.insert("path".to_owned(),self.path.to_str() m.insert("path".to_owned(),
.expect("Json conversion failed for path").to_json() self.path
); .to_str()
.expect("Json conversion failed for path")
.to_json());
m.to_json() m.to_json()
} }
} }
@ -66,7 +66,7 @@ impl<'a> Iterator for BookItems<'a> {
Some((parent_items, parent_idx)) => { Some((parent_items, parent_idx)) => {
self.items = parent_items; self.items = parent_items;
self.current_index = parent_idx + 1; self.current_index = parent_idx + 1;
} },
} }
} else { } else {
let cur = self.items.get(self.current_index).unwrap(); let cur = self.items.get(self.current_index).unwrap();
@ -79,10 +79,10 @@ impl<'a> Iterator for BookItems<'a> {
}, },
BookItem::Spacer => { BookItem::Spacer => {
self.current_index += 1; self.current_index += 1;
} },
} }
return Some(cur) return Some(cur);
} }
} }
} }

View File

@ -18,7 +18,6 @@ pub struct MDBook {
} }
impl MDBook { impl MDBook {
/// Create a new `MDBook` struct with root directory `root` /// Create a new `MDBook` struct with root directory `root`
/// ///
/// - The default source directory is set to `root/src` /// - The default source directory is set to `root/src`
@ -148,11 +147,11 @@ impl MDBook {
try!(::std::fs::create_dir_all(path.parent().unwrap())); try!(::std::fs::create_dir_all(path.parent().unwrap()));
let mut f = try!(File::create(path)); let mut f = try!(File::create(path));
//debug!("[*]: Writing to {:?}", path); // debug!("[*]: Writing to {:?}", path);
try!(writeln!(f, "# {}", ch.name)); try!(writeln!(f, "# {}", ch.name));
} }
} }
} },
} }
} }
@ -310,9 +309,9 @@ 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(&path)
.arg("--test") .arg("--test")
.output(); .output();
let output = try!(output_result); let output = try!(output_result);
if !output.status.success() { if !output.status.success() {
@ -322,8 +321,8 @@ impl MDBook {
String::from_utf8_lossy(&output.stderr)))) as Box<Error>); String::from_utf8_lossy(&output.stderr)))) as Box<Error>);
} }
} }
} },
_ => {} _ => {},
} }
} }
Ok(()) Ok(())
@ -337,11 +336,13 @@ impl MDBook {
// Handle absolute and relative paths // Handle absolute and relative paths
match dest.is_absolute() { match dest.is_absolute() {
true => { self.config.set_dest(dest); }, true => {
self.config.set_dest(dest);
},
false => { false => {
let dest = self.config.get_root().join(dest).to_owned(); let dest = self.config.get_root().join(dest).to_owned();
self.config.set_dest(&dest); self.config.set_dest(&dest);
} },
} }
self self
@ -355,11 +356,13 @@ impl MDBook {
// Handle absolute and relative paths // Handle absolute and relative paths
match src.is_absolute() { match src.is_absolute() {
true => { self.config.set_src(src); }, true => {
self.config.set_src(src);
},
false => { false => {
let src = self.config.get_root().join(src).to_owned(); let src = self.config.get_root().join(src).to_owned();
self.config.set_src(&src); self.config.set_src(&src);
} },
} }
self self
@ -402,5 +405,4 @@ impl MDBook {
self.content = try!(parse::construct_bookitems(&self.config.get_src().join("SUMMARY.md"))); self.content = try!(parse::construct_bookitems(&self.config.get_src().join("SUMMARY.md")));
Ok(()) Ok(())
} }
} }

View File

@ -26,7 +26,9 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
// 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 { break } if level < current_level {
break;
}
// if level > current_level we call ourselves to go one level deeper // if level > current_level we call ourselves to go one level deeper
if level > current_level { if level > current_level {
@ -42,13 +44,15 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
// 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..
section.pop(); section.pop();
continue continue;
} else { } else {
return Err(Error::new( ErrorKind::Other, format!( return Err(Error::new(ErrorKind::Other,
"Your summary.md is messed up\n\n format!("Your summary.md is messed up\n\n
Prefix, Suffix and Spacer elements can only exist on the root level.\n Prefix, \
Prefix elements can only exist before any chapter and there can be no chapters after suffix elements." Suffix and Spacer elements can only exist on the root level.\n
))) \
Prefix elements can only exist before any chapter and there can be \
no chapters after suffix elements.")));
}; };
} else { } else {
@ -59,26 +63,32 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
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, format!( return Err(Error::new(ErrorKind::Other,
"Your summary.md is messed up\n\n format!("Your summary.md is messed up\n\n
Prefix, Suffix and Spacer elements can only exist on the root level.\n \
Prefix elements can only exist before any chapter and there can be no chapters after suffix elements." Prefix, Suffix and Spacer elements can only exist on the \
))) root level.\n
Prefix \
elements can only exist before any chapter and there can be \
no chapters after suffix elements.")))
}, },
// error if BookItem == Chapter and section == -1 // error if BookItem == Chapter and section == -1
BookItem::Chapter(_, _) if section[0] == -1 => { BookItem::Chapter(_, _) if section[0] == -1 => {
return Err(Error::new( ErrorKind::Other, format!( return Err(Error::new(ErrorKind::Other,
"Your summary.md is messed up\n\n format!("Your summary.md is messed up\n\n
Prefix, Suffix and Spacer elements can only exist on the root level.\n \
Prefix elements can only exist before any chapter and there can be no chapters after suffix elements." Prefix, Suffix and Spacer elements can only exist on the \
))) root level.\n
Prefix \
elements can only exist before any chapter and there can be \
no chapters after suffix elements.")))
}, },
// Set section = -1 after suffix // Set section = -1 after suffix
BookItem::Affix(_) if section[0] > 0 => { BookItem::Affix(_) if section[0] > 0 => {
section[0] = -1; section[0] = -1;
} },
_ => {}, _ => {},
} }
@ -86,12 +96,12 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
match parsed_item { match parsed_item {
BookItem::Chapter(_, ch) => { BookItem::Chapter(_, ch) => {
// 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,
} }
} else { } else {
@ -131,11 +141,7 @@ fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
debug!("[SUMMARY.md]:"); debug!("[SUMMARY.md]:");
debug!("\t[line]: {}", line); debug!("\t[line]: {}", line);
debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces", spaces_in_tab); debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces", spaces_in_tab);
return Err(Error::new( return Err(Error::new(ErrorKind::Other, format!("Indentation error on line:\n\n{}", line)));
ErrorKind::Other,
format!("Indentation error on line:\n\n{}", line)
)
)
} }
Ok(level) Ok(level)
@ -146,12 +152,12 @@ fn parse_line(l: &str) -> Option<BookItem> {
debug!("[fn]: parse_line"); debug!("[fn]: parse_line");
// Remove leading and trailing spaces or tabs // Remove leading and trailing spaces or tabs
let line = l.trim_matches(|c: char| { c == ' ' || c == '\t' }); let line = l.trim_matches(|c: char| c == ' ' || c == '\t');
// Spacers are "------" // Spacers are "------"
if line.starts_with("--") { if line.starts_with("--") {
debug!("[*]: Line is spacer"); debug!("[*]: Line is spacer");
return Some(BookItem::Spacer) return Some(BookItem::Spacer);
} }
if let Some(c) = line.chars().nth(0) { if let Some(c) = line.chars().nth(0) {
@ -161,18 +167,22 @@ fn parse_line(l: &str) -> Option<BookItem> {
debug!("[*]: Line is list element"); debug!("[*]: Line is list element");
if let Some((name, path)) = read_link(line) { if let Some((name, path)) = read_link(line) {
return Some(BookItem::Chapter("0".to_owned(), Chapter::new(name, path))) return Some(BookItem::Chapter("0".to_owned(), Chapter::new(name, path)));
} else { return None } } else {
return None;
}
}, },
// Non-list element // Non-list element
'[' => { '[' => {
debug!("[*]: Line is a link element"); debug!("[*]: Line is a link element");
if let Some((name, path)) = read_link(line) { if let Some((name, path)) = read_link(line) {
return Some(BookItem::Affix(Chapter::new(name, path))) return Some(BookItem::Affix(Chapter::new(name, path)));
} else { return None } } else {
} return None;
_ => {} }
},
_ => {},
} }
} }
@ -185,32 +195,31 @@ fn read_link(line: &str) -> Option<(String, PathBuf)> {
// In the future, support for list item that is not a link // In the future, support for list item that is not a link
// Not sure if I should error on line I can't parse or just ignore them... // Not sure if I should error on line I can't parse or just ignore them...
if let Some(i) = line.find('[') { start_delimitor = i; } if let Some(i) = line.find('[') {
else { start_delimitor = i;
} else {
debug!("[*]: '[' not found, this line is not a link. Ignoring..."); debug!("[*]: '[' not found, this line is not a link. Ignoring...");
return None return None;
} }
if let Some(i) = line[start_delimitor..].find("](") { if let Some(i) = line[start_delimitor..].find("](") {
end_delimitor = start_delimitor +i; end_delimitor = start_delimitor + i;
} } else {
else {
debug!("[*]: '](' not found, this line is not a link. Ignoring..."); debug!("[*]: '](' not found, this line is not a link. Ignoring...");
return None return None;
} }
let name = line[start_delimitor + 1 .. end_delimitor].to_owned(); let name = line[start_delimitor + 1..end_delimitor].to_owned();
start_delimitor = end_delimitor + 1; start_delimitor = end_delimitor + 1;
if let Some(i) = line[start_delimitor..].find(')') { if let Some(i) = line[start_delimitor..].find(')') {
end_delimitor = start_delimitor + i; end_delimitor = start_delimitor + i;
} } else {
else {
debug!("[*]: ')' not found, this line is not a link. Ignoring..."); debug!("[*]: ')' not found, this line is not a link. Ignoring...");
return None return None;
} }
let path = PathBuf::from(line[start_delimitor + 1 .. end_delimitor].to_owned()); let path = PathBuf::from(line[start_delimitor + 1..end_delimitor].to_owned());
Some((name, path)) Some((name, path))
} }

View File

@ -51,7 +51,8 @@ impl Renderer for HtmlHandlebars {
// Check if dest directory exists // Check if dest directory exists
debug!("[*]: Check if destination directory exists"); debug!("[*]: Check if destination directory exists");
if let Err(_) = fs::create_dir_all(book.get_dest()) { if let Err(_) = fs::create_dir_all(book.get_dest()) {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Unexpected error when constructing destination path"))) return Err(Box::new(io::Error::new(io::ErrorKind::Other,
"Unexpected error when constructing destination path")));
} }
// Render a file for every entry in the book // Render a file for every entry in the book
@ -83,8 +84,13 @@ impl Renderer for HtmlHandlebars {
// Remove content from previous file and render content for this one // Remove content from previous file and render content for this one
data.remove("path"); data.remove("path");
match ch.path.to_str() { match ch.path.to_str() {
Some(p) => { data.insert("path".to_owned(), p.to_json()); }, Some(p) => {
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))), data.insert("path".to_owned(), p.to_json());
},
None => {
return Err(Box::new(io::Error::new(io::ErrorKind::Other,
"Could not convert path to str")))
},
} }
@ -102,7 +108,9 @@ impl Renderer for HtmlHandlebars {
debug!("[*]: Create file {:?}", &book.get_dest().join(&ch.path).with_extension("html")); debug!("[*]: Create file {:?}", &book.get_dest().join(&ch.path).with_extension("html"));
// Write to file // Write to file
let mut file = try!(utils::create_file(&book.get_dest().join(&ch.path).with_extension("html"))); let mut file = try!(utils::fs::create_file(&book.get_dest()
.join(&ch.path)
.with_extension("html")));
output!("[*] Creating {:?} ✓", &book.get_dest().join(&ch.path).with_extension("html")); output!("[*] Creating {:?} ✓", &book.get_dest().join(&ch.path).with_extension("html"));
try!(file.write_all(&rendered.into_bytes())); try!(file.write_all(&rendered.into_bytes()));
@ -114,23 +122,24 @@ impl Renderer for HtmlHandlebars {
let mut index_file = try!(File::create(book.get_dest().join("index.html"))); let mut index_file = try!(File::create(book.get_dest().join("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 = try!(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().filter(|line| !line.contains("<base href=")).collect::<Vec<&str>>().join("\n"); content = content.lines()
.filter(|line| !line.contains("<base href="))
.collect::<Vec<&str>>()
.join("\n");
try!(index_file.write_all(content.as_bytes())); try!(index_file.write_all(content.as_bytes()));
output!( output!("[*] Creating index.html from {:?} ✓",
"[*] Creating index.html from {:?} ✓", book.get_dest().join(&ch.path.with_extension("html")));
book.get_dest().join(&ch.path.with_extension("html"))
);
index = false; index = false;
} }
} }
} },
_ => {} _ => {},
} }
} }
@ -159,14 +168,18 @@ impl Renderer for HtmlHandlebars {
debug!("[*] Copy static files"); debug!("[*] Copy static files");
// JavaScript // JavaScript
let mut js_file = if let Ok(f) = File::create(book.get_dest().join("book.js")) { f } else { let mut js_file = if let Ok(f) = File::create(book.get_dest().join("book.js")) {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create book.js"))) f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create book.js")));
}; };
try!(js_file.write_all(&theme.js)); try!(js_file.write_all(&theme.js));
// Css // Css
let mut css_file = if let Ok(f) = File::create(book.get_dest().join("book.css")) { f } else { let mut css_file = if let Ok(f) = File::create(book.get_dest().join("book.css")) {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create book.css"))) f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create book.css")));
}; };
try!(css_file.write_all(&theme.css)); try!(css_file.write_all(&theme.css));
@ -177,54 +190,89 @@ impl Renderer for HtmlHandlebars {
try!(favicon_file.write_all(&theme.favicon)); try!(favicon_file.write_all(&theme.favicon));
// JQuery local fallback // JQuery local fallback
let mut jquery = if let Ok(f) = File::create(book.get_dest().join("jquery.js")) { f } else { let mut jquery = if let Ok(f) = File::create(book.get_dest().join("jquery.js")) {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create jquery.js"))) f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create jquery.js")));
}; };
try!(jquery.write_all(&theme.jquery)); try!(jquery.write_all(&theme.jquery));
// syntax highlighting // syntax highlighting
let mut highlight_css = if let Ok(f) = File::create(book.get_dest().join("highlight.css")) { f } else { let mut highlight_css = if let Ok(f) = File::create(book.get_dest().join("highlight.css")) {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create highlight.css"))) f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create highlight.css")));
}; };
try!(highlight_css.write_all(&theme.highlight_css)); try!(highlight_css.write_all(&theme.highlight_css));
let mut tomorrow_night_css = if let Ok(f) = File::create(book.get_dest().join("tomorrow-night.css")) { f } else { let mut tomorrow_night_css = if let Ok(f) = File::create(book.get_dest().join("tomorrow-night.css")) {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create tomorrow-night.css"))) f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create tomorrow-night.css")));
}; };
try!(tomorrow_night_css.write_all(&theme.tomorrow_night_css)); try!(tomorrow_night_css.write_all(&theme.tomorrow_night_css));
let mut highlight_js = if let Ok(f) = File::create(book.get_dest().join("highlight.js")) { f } else { let mut highlight_js = if let Ok(f) = File::create(book.get_dest().join("highlight.js")) {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create highlight.js"))) f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create highlight.js")));
}; };
try!(highlight_js.write_all(&theme.highlight_js)); try!(highlight_js.write_all(&theme.highlight_js));
// Font Awesome local fallback // Font Awesome local fallback
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/css/font-awesome.css")) { f } else { let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest()
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create font-awesome.css"))) .join("_FontAwesome/css/font-awesome.css")) {
f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create font-awesome.css")));
}; };
try!(font_awesome.write_all(theme::FONT_AWESOME)); try!(font_awesome.write_all(theme::FONT_AWESOME));
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.eot")) { f } else { let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest()
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.eot"))) .join("_FontAwesome/fonts/fontawesome-we\
bfont.eot")) {
f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.eot")));
}; };
try!(font_awesome.write_all(theme::FONT_AWESOME_EOT)); try!(font_awesome.write_all(theme::FONT_AWESOME_EOT));
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.svg")) { f } else { let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest()
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.svg"))) .join("_FontAwesome/fonts/fontawesome-we\
bfont.svg")) {
f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.svg")));
}; };
try!(font_awesome.write_all(theme::FONT_AWESOME_SVG)); try!(font_awesome.write_all(theme::FONT_AWESOME_SVG));
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.ttf")) { f } else { let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest()
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.ttf"))) .join("_FontAwesome/fonts/fontawesome-we\
bfont.ttf")) {
f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.ttf")));
}; };
try!(font_awesome.write_all(theme::FONT_AWESOME_TTF)); try!(font_awesome.write_all(theme::FONT_AWESOME_TTF));
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.woff")) { f } else { let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest()
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.woff"))) .join("_FontAwesome/fonts/fontawesome-we\
bfont.woff")) {
f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.woff")));
}; };
try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF)); try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF));
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.woff2")) { f } else { let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest()
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.woff2"))) .join("_FontAwesome/fonts/fontawesome-we\
bfont.woff2")) {
f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.woff2")));
}; };
try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF2)); try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF2));
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/FontAwesome.ttf")) { f } else { let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest()
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create FontAwesome.ttf"))) .join("_FontAwesome/fonts/FontAwesome.\
ttf")) {
f
} else {
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create FontAwesome.ttf")));
}; };
try!(font_awesome.write_all(theme::FONT_AWESOME_TTF)); try!(font_awesome.write_all(theme::FONT_AWESOME_TTF));
@ -235,10 +283,10 @@ impl Renderer for HtmlHandlebars {
} }
} }
fn make_data(book: &MDBook) -> Result<BTreeMap<String,Json>, Box<Error>> { fn make_data(book: &MDBook) -> Result<BTreeMap<String, Json>, Box<Error>> {
debug!("[fn]: make_data"); debug!("[fn]: make_data");
let mut data = BTreeMap::new(); let mut data = BTreeMap::new();
data.insert("language".to_owned(), "en".to_json()); data.insert("language".to_owned(), "en".to_json());
data.insert("title".to_owned(), book.get_title().to_json()); data.insert("title".to_owned(), book.get_title().to_json());
data.insert("description".to_owned(), book.get_description().to_json()); data.insert("description".to_owned(), book.get_description().to_json());
@ -254,7 +302,9 @@ fn make_data(book: &MDBook) -> Result<BTreeMap<String,Json>, Box<Error>> {
BookItem::Affix(ref ch) => { BookItem::Affix(ref ch) => {
chapter.insert("name".to_owned(), ch.name.to_json()); chapter.insert("name".to_owned(), ch.name.to_json());
match ch.path.to_str() { match ch.path.to_str() {
Some(p) => { chapter.insert("path".to_owned(), p.to_json()); }, Some(p) => {
chapter.insert("path".to_owned(), p.to_json());
},
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))), None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))),
} }
}, },
@ -262,13 +312,15 @@ fn make_data(book: &MDBook) -> Result<BTreeMap<String,Json>, Box<Error>> {
chapter.insert("section".to_owned(), s.to_json()); chapter.insert("section".to_owned(), s.to_json());
chapter.insert("name".to_owned(), ch.name.to_json()); chapter.insert("name".to_owned(), ch.name.to_json());
match ch.path.to_str() { match ch.path.to_str() {
Some(p) => { chapter.insert("path".to_owned(), p.to_json()); }, Some(p) => {
chapter.insert("path".to_owned(), p.to_json());
},
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))), None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))),
} }
}, },
BookItem::Spacer => { BookItem::Spacer => {
chapter.insert("spacer".to_owned(), "_spacer_".to_json()); chapter.insert("spacer".to_owned(), "_spacer_".to_json());
} },
} }

View File

@ -19,15 +19,15 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
let chapters = c.navigate(rc.get_path(), "chapters"); let chapters = c.navigate(rc.get_path(), "chapters");
let current = c.navigate(rc.get_path(), "path") let current = c.navigate(rc.get_path(), "path")
.to_string() .to_string()
.replace("\"", ""); .replace("\"", "");
debug!("[*]: Decode chapters from JSON"); debug!("[*]: Decode chapters from JSON");
// Decode json format // Decode json format
let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) { let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) {
Ok(data) => data, Ok(data) => data,
Err(_) => return Err(RenderError{ desc: "Could not decode the JSON data".to_owned()}), Err(_) => return Err(RenderError { desc: "Could not decode the JSON data".to_owned() }),
}; };
let mut previous: Option<BTreeMap<String, String>> = None; let mut previous: Option<BTreeMap<String, String>> = None;
@ -41,7 +41,7 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
if path == &current { if path == &current {
debug!("[*]: Found current chapter"); debug!("[*]: Found current chapter");
if let Some(previous) = previous{ if let Some(previous) = previous {
debug!("[*]: Creating BTreeMap to inject in context"); debug!("[*]: Creating BTreeMap to inject in context");
// Create new BTreeMap to extend the context: 'title' and 'link' // Create new BTreeMap to extend the context: 'title' and 'link'
@ -55,8 +55,8 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
}, },
None => { None => {
debug!("[*]: No title found for chapter"); debug!("[*]: No title found for chapter");
return Err(RenderError{ desc: "No title found for chapter in JSON data".to_owned() }) return Err(RenderError { desc: "No title found for chapter in JSON data".to_owned() });
} },
}; };
// Chapter link // Chapter link
@ -67,11 +67,19 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
debug!("[*]: Inserting link: {:?}", path); debug!("[*]: Inserting link: {:?}", path);
match path.to_str() { match path.to_str() {
Some(p) => { previous_chapter.insert("link".to_owned(), p.to_json()); }, Some(p) => {
None => return Err(RenderError{ desc: "Link could not be converted to str".to_owned() }) previous_chapter.insert("link".to_owned(), p.to_json());
},
None => {
return Err(RenderError {
desc: "Link could not be converted to str".to_owned(),
})
},
} }
}, },
None => return Err(RenderError{ desc: "No path found for chapter in JSON data".to_owned() }) None => {
return Err(RenderError { desc: "No path found for chapter in JSON data".to_owned() })
},
} }
debug!("[*]: Inject in context"); debug!("[*]: Inject in context");
@ -84,14 +92,13 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
Some(t) => { Some(t) => {
try!(t.render(&updated_context, r, rc)); try!(t.render(&updated_context, r, rc));
}, },
None => return Err(RenderError{ desc: "Error with the handlebars template".to_owned() }) None => return Err(RenderError { desc: "Error with the handlebars template".to_owned() }),
} }
} }
break; break;
} } else {
else {
previous = Some(item.clone()); previous = Some(item.clone());
} }
}, },
@ -117,14 +124,14 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
let chapters = c.navigate(rc.get_path(), "chapters"); let chapters = c.navigate(rc.get_path(), "chapters");
let current = c.navigate(rc.get_path(), "path") let current = c.navigate(rc.get_path(), "path")
.to_string() .to_string()
.replace("\"", ""); .replace("\"", "");
debug!("[*]: Decode chapters from JSON"); debug!("[*]: Decode chapters from JSON");
// Decode json format // Decode json format
let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) { let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) {
Ok(data) => data, Ok(data) => data,
Err(_) => return Err(RenderError{ desc: "Could not decode the JSON data".to_owned() }), Err(_) => return Err(RenderError { desc: "Could not decode the JSON data".to_owned() }),
}; };
let mut previous: Option<BTreeMap<String, String>> = None; let mut previous: Option<BTreeMap<String, String>> = None;
@ -140,7 +147,7 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
let previous_path = match previous.get("path") { let previous_path = match previous.get("path") {
Some(p) => p, Some(p) => p,
None => return Err(RenderError{ desc: "No path found for chapter in JSON data".to_owned() }) None => return Err(RenderError { desc: "No path found for chapter in JSON data".to_owned() }),
}; };
if previous_path == &current { if previous_path == &current {
@ -154,8 +161,10 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
Some(n) => { Some(n) => {
debug!("[*]: Inserting title: {}", n); debug!("[*]: Inserting title: {}", n);
next_chapter.insert("title".to_owned(), n.to_json()); next_chapter.insert("title".to_owned(), n.to_json());
} },
None => return Err(RenderError{ desc: "No title found for chapter in JSON data".to_owned() }) None => {
return Err(RenderError { desc: "No title found for chapter in JSON data".to_owned() })
},
} }
@ -163,8 +172,10 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
debug!("[*]: Inserting link: {:?}", link); debug!("[*]: Inserting link: {:?}", link);
match link.to_str() { match link.to_str() {
Some(l) => { next_chapter.insert("link".to_owned(), l.to_json()); }, Some(l) => {
None => return Err(RenderError{ desc: "Link could not converted to str".to_owned() }) next_chapter.insert("link".to_owned(), l.to_json());
},
None => return Err(RenderError { desc: "Link could not converted to str".to_owned() }),
} }
debug!("[*]: Inject in context"); debug!("[*]: Inject in context");
@ -178,10 +189,10 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
Some(t) => { Some(t) => {
try!(t.render(&updated_context, r, rc)); try!(t.render(&updated_context, r, rc));
}, },
None => return Err(RenderError{ desc: "Error with the handlebars template".to_owned() }) None => return Err(RenderError { desc: "Error with the handlebars template".to_owned() }),
} }
break break;
} }
} }

View File

@ -14,29 +14,37 @@ pub fn render_playpen(s: &str, path: &Path) -> String {
for playpen in find_playpens(s, path) { for playpen in find_playpens(s, path) {
if playpen.escaped { if playpen.escaped {
replaced.push_str(&s[previous_end_index..playpen.start_index-1]); replaced.push_str(&s[previous_end_index..playpen.start_index - 1]);
replaced.push_str(&s[playpen.start_index..playpen.end_index]); replaced.push_str(&s[playpen.start_index..playpen.end_index]);
previous_end_index = playpen.end_index; previous_end_index = playpen.end_index;
continue continue;
} }
// Check if the file exists // Check if the file exists
if !playpen.rust_file.exists() || !playpen.rust_file.is_file() { if !playpen.rust_file.exists() || !playpen.rust_file.is_file() {
output!("[-] No file exists for {{{{#playpen }}}}\n {}", playpen.rust_file.to_str().unwrap()); output!("[-] No file exists for {{{{#playpen }}}}\n {}", playpen.rust_file.to_str().unwrap());
continue continue;
} }
// Open file & read file // Open file & read file
let mut file = if let Ok(f) = File::open(&playpen.rust_file) { f } else { continue }; let mut file = if let Ok(f) = File::open(&playpen.rust_file) {
f
} else {
continue;
};
let mut file_content = String::new(); let mut file_content = String::new();
if let Err(_) = file.read_to_string(&mut file_content) { continue }; if let Err(_) = file.read_to_string(&mut file_content) {
continue;
};
let replacement = String::new() + "<pre class=\"playpen\"><code class=\"language-rust\">" + &file_content + "</code></pre>"; let replacement = String::new() + "<pre class=\"playpen\"><code class=\"language-rust\">" + &file_content +
"</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, playpen.editable); // println!("Playpen{{ {}, {}, {:?}, {} }}", playpen.start_index, playpen.end_index, playpen.rust_file,
// playpen.editable);
} }
replaced.push_str(&s[previous_end_index..]); replaced.push_str(&s[previous_end_index..]);
@ -45,7 +53,7 @@ pub fn render_playpen(s: &str, path: &Path) -> String {
} }
#[derive(PartialOrd, PartialEq, Debug)] #[derive(PartialOrd, PartialEq, Debug)]
struct Playpen{ struct Playpen {
start_index: usize, start_index: usize,
end_index: usize, end_index: usize,
rust_file: PathBuf, rust_file: PathBuf,
@ -61,38 +69,50 @@ fn find_playpens(s: &str, base_path: &Path) -> Vec<Playpen> {
let mut escaped = false; let mut escaped = false;
if i > 0 { if i > 0 {
if let Some(c) = s[i-1..].chars().nth(0) { if let Some(c) = s[i - 1..].chars().nth(0) {
if c == '\\' { escaped = true } if c == '\\' {
escaped = true
}
} }
} }
// DON'T forget the "+ i" else you have an index out of bounds error !! // DON'T forget the "+ i" else you have an index out of bounds error !!
let end_i = if let Some(n) = s[i..].find("}}") { n } else { continue } + i + 2; let end_i = if let Some(n) = s[i..].find("}}") {
n
} else {
continue;
} + i + 2;
debug!("s[{}..{}] = {}", i, end_i, s[i..end_i].to_string()); debug!("s[{}..{}] = {}", i, end_i, s[i..end_i].to_string());
// If there is nothing between "{{#playpen" and "}}" skip // If there is nothing between "{{#playpen" and "}}" skip
if end_i-2 - (i+10) < 1 { continue } if end_i - 2 - (i + 10) < 1 {
if s[i+10..end_i-2].trim().len() == 0 { continue } continue;
}
if s[i + 10..end_i - 2].trim().len() == 0 {
continue;
}
debug!("{}", s[i+10..end_i-2].to_string()); debug!("{}", s[i + 10..end_i - 2].to_string());
// Split on whitespaces // Split on whitespaces
let params: Vec<&str> = s[i+10..end_i-2].split_whitespace().collect(); let params: Vec<&str> = s[i + 10..end_i - 2].split_whitespace().collect();
let mut editable = false; let mut editable = false;
if params.len() > 1 { if params.len() > 1 {
editable = if let Some(_) = params[1].find("editable") {true} else {false}; editable = if let Some(_) = params[1].find("editable") {
true
} else {
false
};
} }
playpens.push( playpens.push(Playpen {
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
@ -101,8 +121,7 @@ fn find_playpens(s: &str, base_path: &Path) -> Vec<Playpen> {
// // ---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
// Tests // Tests
// //
@ -130,10 +149,21 @@ fn test_find_playpens_simple_playpen() {
println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new(""))); println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new("")));
assert!(find_playpens(s, Path::new("")) == vec![ assert!(find_playpens(s, Path::new("")) ==
Playpen{start_index: 22, end_index: 42, rust_file: PathBuf::from("file.rs"), editable: false, escaped: false}, vec![Playpen {
Playpen{start_index: 47, end_index: 68, rust_file: PathBuf::from("test.rs"), editable: false, escaped: false} start_index: 22,
]); end_index: 42,
rust_file: PathBuf::from("file.rs"),
editable: false,
escaped: false,
},
Playpen {
start_index: 47,
end_index: 68,
rust_file: PathBuf::from("test.rs"),
editable: false,
escaped: false,
}]);
} }
#[test] #[test]
@ -142,10 +172,21 @@ fn test_find_playpens_complex_playpen() {
println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new("dir"))); println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new("dir")));
assert!(find_playpens(s, Path::new("dir")) == vec![ assert!(find_playpens(s, Path::new("dir")) ==
Playpen{start_index: 22, end_index: 51, rust_file: PathBuf::from("dir/file.rs"), editable: true, escaped: false}, vec![Playpen {
Playpen{start_index: 56, end_index: 86, rust_file: PathBuf::from("dir/test.rs"), editable: true, escaped: false} start_index: 22,
]); end_index: 51,
rust_file: PathBuf::from("dir/file.rs"),
editable: true,
escaped: false,
},
Playpen {
start_index: 56,
end_index: 86,
rust_file: PathBuf::from("dir/test.rs"),
editable: true,
escaped: false,
}]);
} }
#[test] #[test]
@ -154,7 +195,8 @@ 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("")) == vec![ assert!(find_playpens(s, Path::new("")) ==
vec![
Playpen{start_index: 39, end_index: 68, rust_file: PathBuf::from("file.rs"), editable: true, escaped: true}, Playpen{start_index: 39, end_index: 68, rust_file: PathBuf::from("file.rs"), editable: true, escaped: true},
]); ]);
} }

View File

@ -14,117 +14,119 @@ use self::pulldown_cmark::{Parser, html, Event, Tag};
pub struct RenderToc; pub struct RenderToc;
impl HelperDef for RenderToc { impl HelperDef for RenderToc {
fn call(&self, c: &Context, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { fn call(&self, c: &Context, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
// get value from context data // get value from context data
// rc.get_path() is current json parent path, you should always use it like this // rc.get_path() is current json parent path, you should always use it like this
// param is the key of value you want to display // param is the key of value you want to display
let chapters = c.navigate(rc.get_path(), "chapters"); let chapters = c.navigate(rc.get_path(), "chapters");
let current = c.navigate(rc.get_path(), "path").to_string().replace("\"", ""); let current = c.navigate(rc.get_path(), "path").to_string().replace("\"", "");
try!(rc.writer.write("<ul class=\"chapter\">".as_bytes())); try!(rc.writer.write("<ul class=\"chapter\">".as_bytes()));
// Decode json format // Decode json format
let decoded: Vec<BTreeMap<String,String>> = json::decode(&chapters.to_string()).unwrap(); let decoded: Vec<BTreeMap<String, String>> = json::decode(&chapters.to_string()).unwrap();
let mut current_level = 1; let mut current_level = 1;
for item in decoded { for item in decoded {
// Spacer // Spacer
if let Some(_) = item.get("spacer") { if let Some(_) = item.get("spacer") {
try!(rc.writer.write("<li class=\"spacer\"></li>".as_bytes())); try!(rc.writer.write("<li class=\"spacer\"></li>".as_bytes()));
continue continue;
}
let level = if let Some(s) = item.get("section") { s.len() / 2 } else { 1 };
if level > current_level {
try!(rc.writer.write("<li>".as_bytes()));
try!(rc.writer.write("<ul class=\"section\">".as_bytes()));
try!(rc.writer.write("<li>".as_bytes()));
} else if level < current_level {
while level < current_level {
try!(rc.writer.write("</ul>".as_bytes()));
try!(rc.writer.write("</li>".as_bytes()));
current_level = current_level - 1;
} }
try!(rc.writer.write("<li>".as_bytes()));
}
else {
try!(rc.writer.write("<li".as_bytes()));
if let None = item.get("section") {
try!(rc.writer.write(" class=\"affix\"".as_bytes()));
}
try!(rc.writer.write(">".as_bytes()));
}
// Link let level = if let Some(s) = item.get("section") {
let path_exists = if let Some(path) = item.get("path") { s.len() / 2
if !path.is_empty() { } else {
try!(rc.writer.write("<a href=\"".as_bytes())); 1
};
// Add link if level > current_level {
try!(rc.writer.write( try!(rc.writer.write("<li>".as_bytes()));
Path::new( try!(rc.writer.write("<ul class=\"section\">".as_bytes()));
item.get("path") try!(rc.writer.write("<li>".as_bytes()));
.expect("Error: path should be Some(_)") } else if level < current_level {
).with_extension("html") while level < current_level {
.to_str().unwrap().as_bytes() try!(rc.writer.write("</ul>".as_bytes()));
)); try!(rc.writer.write("</li>".as_bytes()));
current_level = current_level - 1;
try!(rc.writer.write("\"".as_bytes())); }
try!(rc.writer.write("<li>".as_bytes()));
if path == &current { } else {
try!(rc.writer.write(" class=\"active\"".as_bytes())); try!(rc.writer.write("<li".as_bytes()));
if let None = item.get("section") {
try!(rc.writer.write(" class=\"affix\"".as_bytes()));
} }
try!(rc.writer.write(">".as_bytes())); try!(rc.writer.write(">".as_bytes()));
true }
// Link
let path_exists = if let Some(path) = item.get("path") {
if !path.is_empty() {
try!(rc.writer.write("<a href=\"".as_bytes()));
// Add link
try!(rc.writer.write(Path::new(item.get("path")
.expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
.unwrap()
.as_bytes()));
try!(rc.writer.write("\"".as_bytes()));
if path == &current {
try!(rc.writer.write(" class=\"active\"".as_bytes()));
}
try!(rc.writer.write(">".as_bytes()));
true
} else {
false
}
} else { } else {
false false
};
// Section does not necessarily exist
if let Some(section) = item.get("section") {
try!(rc.writer.write("<strong>".as_bytes()));
try!(rc.writer.write(section.as_bytes()));
try!(rc.writer.write("</strong> ".as_bytes()));
} }
}else {
false
};
// Section does not necessarily exist if let Some(name) = item.get("name") {
if let Some(section) = item.get("section") { // Render only inline code blocks
try!(rc.writer.write("<strong>".as_bytes()));
try!(rc.writer.write(section.as_bytes())); // filter all events that are not inline code blocks
try!(rc.writer.write("</strong> ".as_bytes())); let parser = Parser::new(&name).filter(|event| {
match event {
&Event::Start(Tag::Code) | &Event::End(Tag::Code) => true,
&Event::InlineHtml(_) => true,
&Event::Text(_) => true,
_ => false,
}
});
// render markdown to html
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
try!(rc.writer.write(markdown_parsed_name.as_bytes()));
}
if path_exists {
try!(rc.writer.write("</a>".as_bytes()));
}
try!(rc.writer.write("</li>".as_bytes()));
current_level = level;
} }
if let Some(name) = item.get("name") { try!(rc.writer.write("</ul>".as_bytes()));
// Render only inline code blocks Ok(())
// filter all events that are not inline code blocks
let parser = Parser::new(&name).filter(|event|{
match event {
&Event::Start(Tag::Code) | &Event::End(Tag::Code) => true,
&Event::InlineHtml(_) => true,
&Event::Text(_) => true,
_ => false,
}
});
// render markdown to html
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
try!(rc.writer.write(markdown_parsed_name.as_bytes()));
}
if path_exists {
try!(rc.writer.write("</a>".as_bytes()));
}
try!(rc.writer.write("</li>".as_bytes()));
current_level = level;
} }
try!(rc.writer.write("</ul>".as_bytes()));
Ok(())
}
} }

View File

@ -1,5 +1,5 @@
use std::error::Error; use std::error::Error;
pub trait Renderer { pub trait Renderer {
fn render(&self, book: &::book::MDBook) -> Result<(), Box<Error>>; fn render(&self, book: &::book::MDBook) -> Result<(), Box<Error>>;
} }

View File

@ -53,13 +53,13 @@ impl Theme {
// Check if the given path exists // Check if the given path exists
if !src.exists() || !src.is_dir() { if !src.exists() || !src.is_dir() {
return theme return theme;
} }
let src = src.join("theme"); let src = src.join("theme");
// If src does exist, check if there is a theme directory in it // If src does exist, check if there is a theme directory in it
if !src.exists() || !src.is_dir() { if !src.exists() || !src.is_dir() {
return theme return theme;
} }
// Check for individual files if they exist // Check for individual files if they exist
@ -73,7 +73,7 @@ impl Theme {
// book.js // book.js
if let Ok(mut f) = File::open(&src.join("book.js")) { if let Ok(mut f) = File::open(&src.join("book.js")) {
theme.js.clear(); theme.js.clear();
let _ = f.read_to_end(&mut theme.js); let _ = f.read_to_end(&mut theme.js);
} }
// book.css // book.css

View File

@ -32,13 +32,16 @@ pub fn path_to_root(path: &Path) -> String {
debug!("[fn]: path_to_root"); debug!("[fn]: path_to_root");
// Remove filename and add "../" for every directory // Remove filename and add "../" for every directory
path.to_path_buf().parent().expect("") path.to_path_buf()
.components().fold(String::new(), |mut s, c| { .parent()
.expect("")
.components()
.fold(String::new(), |mut s, c| {
match c { match c {
Component::Normal(_) => s.push_str("../"), Component::Normal(_) => s.push_str("../"),
_ => { _ => {
debug!("[*]: Other path component... {:?}", c); debug!("[*]: Other path component... {:?}", c);
} },
} }
s s
}) })
@ -64,7 +67,7 @@ pub fn create_file(path: &Path) -> Result<File, Box<Error>> {
Ok(f) => f, Ok(f) => f,
Err(e) => { Err(e) => {
debug!("File::create: {}", e); debug!("File::create: {}", e);
return Err(Box::new(io::Error::new(io::ErrorKind::Other, format!("{}", e)))) return Err(Box::new(io::Error::new(io::ErrorKind::Other, format!("{}", e))));
}, },
}; };
@ -77,7 +80,11 @@ pub fn remove_dir_content(dir: &Path) -> Result<(), Box<Error>> {
for item in try!(fs::read_dir(dir)) { for item in try!(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() { try!(fs::remove_dir_all(item)); } else { try!(fs::remove_file(item)); } if item.is_dir() {
try!(fs::remove_dir_all(item));
} else {
try!(fs::remove_file(item));
}
} }
} }
Ok(()) Ok(())
@ -91,7 +98,9 @@ pub fn remove_dir_content(dir: &Path) -> Result<(), Box<Error>> {
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 { return Ok(()) } if from == to {
return Ok(());
}
debug!("[*] Loop"); debug!("[*] Loop");
for entry in try!(fs::read_dir(from)) { for entry in try!(fs::read_dir(from)) {
let entry = try!(entry); let entry = try!(entry);
@ -100,7 +109,9 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
// 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 {
if entry.path() == to.to_path_buf() { continue } if entry.path() == to.to_path_buf() {
continue;
}
debug!("[*] is dir"); debug!("[*] is dir");
// check if output dir already exists // check if output dir already exists
@ -108,28 +119,31 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
try!(fs::create_dir(&to.join(entry.file_name()))); try!(fs::create_dir(&to.join(entry.file_name())));
} }
try!(copy_files_except_ext( try!(copy_files_except_ext(&from.join(entry.file_name()),
&from.join(entry.file_name()), &to.join(entry.file_name()),
&to.join(entry.file_name()), true,
true, ext_blacklist));
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
if let Some(ext) = entry.path().extension() { if let Some(ext) = entry.path().extension() {
if ext_blacklist.contains(&ext.to_str().unwrap()) { continue } if ext_blacklist.contains(&ext.to_str().unwrap()) {
debug!("[*] creating path for file: {:?}", &to.join(entry.path().file_name().expect("a file should have a file name..."))); continue;
}
debug!("[*] creating path for file: {:?}",
&to.join(entry.path().file_name().expect("a file should have a file name...")));
output!("[*] copying file: {:?}\n to {:?}", entry.path(), &to.join(entry.path().file_name().expect("a file should have a file name..."))); output!("[*] copying file: {:?}\n to {:?}",
try!(fs::copy(entry.path(), &to.join(entry.path().file_name().expect("a file should have a file name...")))); entry.path(),
&to.join(entry.path().file_name().expect("a file should have a file name...")));
try!(fs::copy(entry.path(),
&to.join(entry.path().file_name().expect("a file should have a file name..."))));
} }
} }
} }
Ok(()) Ok(())
} }
/// ///
/// ///
/// Wrapper around the pulldown-cmark parser and renderer to render markdown /// Wrapper around the pulldown-cmark parser and renderer to render markdown
@ -168,17 +182,35 @@ mod tests {
}; };
// Create a couple of files // Create a couple of files
if let Err(_) = fs::File::create(&tmp.path().join("file.txt")) { panic!("Could not create file.txt") } if let Err(_) = fs::File::create(&tmp.path().join("file.txt")) {
if let Err(_) = fs::File::create(&tmp.path().join("file.md")) { panic!("Could not create file.md") } panic!("Could not create file.txt")
if let Err(_) = fs::File::create(&tmp.path().join("file.png")) { panic!("Could not create file.png") } }
if let Err(_) = fs::create_dir(&tmp.path().join("sub_dir")) { panic!("Could not create sub_dir") } if let Err(_) = fs::File::create(&tmp.path().join("file.md")) {
if let Err(_) = fs::File::create(&tmp.path().join("sub_dir/file.png")) { panic!("Could not create sub_dir/file.png") } panic!("Could not create file.md")
if let Err(_) = fs::create_dir(&tmp.path().join("sub_dir_exists")) { panic!("Could not create sub_dir_exists") } }
if let Err(_) = fs::File::create(&tmp.path().join("sub_dir_exists/file.txt")) { panic!("Could not create sub_dir_exists/file.txt") } if let Err(_) = fs::File::create(&tmp.path().join("file.png")) {
panic!("Could not create file.png")
}
if let Err(_) = fs::create_dir(&tmp.path().join("sub_dir")) {
panic!("Could not create sub_dir")
}
if let Err(_) = fs::File::create(&tmp.path().join("sub_dir/file.png")) {
panic!("Could not create sub_dir/file.png")
}
if let Err(_) = fs::create_dir(&tmp.path().join("sub_dir_exists")) {
panic!("Could not create sub_dir_exists")
}
if let Err(_) = fs::File::create(&tmp.path().join("sub_dir_exists/file.txt")) {
panic!("Could not create sub_dir_exists/file.txt")
}
// Create output dir // Create output dir
if let Err(_) = fs::create_dir(&tmp.path().join("output")) { panic!("Could not create output") } if let Err(_) = fs::create_dir(&tmp.path().join("output")) {
if let Err(_) = fs::create_dir(&tmp.path().join("output/sub_dir_exists")) { panic!("Could not create output/sub_dir_exists") } panic!("Could not create output")
}
if let Err(_) = fs::create_dir(&tmp.path().join("output/sub_dir_exists")) {
panic!("Could not create output/sub_dir_exists")
}
match copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, &["md"]) { match copy_files_except_ext(&tmp.path(), &tmp.path().join("output"), true, &["md"]) {
Err(e) => panic!("Error while executing the function:\n{:?}", e), Err(e) => panic!("Error while executing the function:\n{:?}", e),
@ -186,11 +218,21 @@ mod tests {
} }
// Check if the correct files where created // Check if the correct files where created
if !(&tmp.path().join("output/file.txt")).exists() { panic!("output/file.txt should exist") } if !(&tmp.path().join("output/file.txt")).exists() {
if (&tmp.path().join("output/file.md")).exists() { panic!("output/file.md should not exist") } panic!("output/file.txt should exist")
if !(&tmp.path().join("output/file.png")).exists() { panic!("output/file.png should exist") } }
if !(&tmp.path().join("output/sub_dir/file.png")).exists() { panic!("output/sub_dir/file.png should exist") } if (&tmp.path().join("output/file.md")).exists() {
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() { panic!("output/sub_dir/file.png should exist") } panic!("output/file.md should not exist")
}
if !(&tmp.path().join("output/file.png")).exists() {
panic!("output/file.png should exist")
}
if !(&tmp.path().join("output/sub_dir/file.png")).exists() {
panic!("output/sub_dir/file.png should exist")
}
if !(&tmp.path().join("output/sub_dir_exists/file.txt")).exists() {
panic!("output/sub_dir/file.png should exist")
}
} }
} }