Merge branch 'master' into watch-command

This commit is contained in:
Mathieu David 2015-12-29 13:40:13 +01:00
commit b40688c880
12 changed files with 279 additions and 46 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "mdbook"
version = "0.0.3"
version = "0.0.4"
authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
description = "create books from markdown files (like Gitbook)"
documentation = "http://azerupi.github.io/mdBook/index.html"
@ -15,11 +15,11 @@ exclude = [
]
[dependencies]
clap = "*"
handlebars = "*"
rustc-serialize = "*"
pulldown-cmark = "*"
crossbeam = "^0.1.5"
clap = "~1.5.3"
handlebars = "~0.12.0"
rustc-serialize = "~0.3.16"
pulldown-cmark = "~0.0.3"
# Watch feature
[dependencies.notify]
@ -30,9 +30,13 @@ optional = true
time = "^0.1.33"
optional = true
[dependencies.crossbeam]
time = "^0.2.0"
optional = true
# Tests
[dev-dependencies]
tempdir = "*"
tempdir = "~0.3.4"
[features]
@ -40,7 +44,7 @@ default = ["output", "watch"]
debug = []
output = []
regenerate-css = []
watch = ["notify", "time"]
watch = ["notify", "time", "crossbeam"]
[[bin]]
doc = false

View File

@ -13,14 +13,10 @@ To have an idea of what a rendered book looks like,take a look at the [**Documen
## Installation
```
git clone --depth=1 https://github.com/azerupi/mdBook.git
cd mdBook
cargo build --release
cargo install mdbook
```
The executable `mdbook` will be in the `./target/release` folder, this should be added to the path.
If you want to regenerate the css (stylesheet), make sure that you installed `stylus` and `nib` from `npm` because it is used to compile the stylesheets
If you want to regenerate the css (stylesheet), clone the git repo locally and make sure that you installed `stylus` and `nib` from `npm` because it is used to compile the stylesheets
Install [node.js](https://nodejs.org/en/)

View File

@ -1,3 +1,4 @@
#[macro_use]
extern crate mdbook;
#[macro_use]
extern crate clap;
@ -49,6 +50,8 @@ fn main() {
.subcommand(SubCommand::with_name("watch")
.about("Watch the files for changes")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
.subcommand(SubCommand::with_name("test")
.about("Test that code samples compile"))
.get_matches();
// Check which subcomamnd the user ran...
@ -57,6 +60,7 @@ fn main() {
("build", Some(sub_matches)) => build(sub_matches),
#[cfg(feature = "watch")]
("watch", Some(sub_matches)) => watch(sub_matches),
("test", Some(sub_matches)) => test(sub_matches),
(_, _) => unreachable!()
};
@ -142,8 +146,17 @@ fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
match w {
Ok(mut watcher) => {
watcher.watch(book.get_src()).unwrap();
watcher.watch(book_dir.join("book.json")).unwrap();
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.get_src()) {
println!("Error while watching {:?}:\n {:?}", book.get_src(), e);
::std::process::exit(0);
};
// Add the book.json file to the watcher if it exists, because it's not
// located in the source directory
if let Err(_) = watcher.watch(book_dir.join("book.json")) {
// do nothing if book.json is not found
}
let previous_time = time::get_time().sec;
@ -184,11 +197,22 @@ fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
}
}
Ok(())
}
fn test(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config();
try!(book.test());
Ok(())
}
// Helper function that returns the right path if either a relative or absolute path is passed
fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(dir) = args.value_of("dir") {
// Check if path is relative from current dir, or absolute...

View File

@ -1,7 +1,10 @@
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::io::Write;
use std::error::Error;
use std::io;
use std::io::Write;
use std::io::ErrorKind;
use std::process::Command;
use {BookConfig, BookItem, theme, parse, utils};
use book::BookItems;
@ -257,6 +260,39 @@ impl MDBook {
self
}
pub fn test(&mut self) -> Result<(), Box<Error>> {
// read in the chapters
try!(self.parse_summary());
for item in self.iter() {
match *item {
BookItem::Chapter(_, ref ch) => {
if ch.path != PathBuf::new() {
let path = self.get_src().join(&ch.path);
println!("[*]: Testing file: {:?}", path);
let output_result = Command::new("rustdoc")
.arg(&path)
.arg("--test")
.output();
let output = try!(output_result);
if !output.status.success() {
return Err(Box::new(io::Error::new(ErrorKind::Other, format!(
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)))) as Box<Error>);
}
}
}
_ => {}
}
}
Ok(())
}
pub fn set_dest(mut self, dest: &Path) -> Self {
// Handle absolute and relative paths

View File

@ -114,7 +114,7 @@ impl Renderer for HtmlHandlebars {
// This could cause a problem when someone displays code containing <base href=...>
// on the front page, however this case should be very very rare...
content = content.lines().filter(|line| !line.contains("<base href=")).collect();
content = content.lines().filter(|line| !line.contains("<base href=")).collect::<Vec<&str>>().join("\n");
try!(index_file.write_all(content.as_bytes()));

View File

@ -27,7 +27,7 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
// Decode json format
let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) {
Ok(data) => data,
Err(_) => return Err(RenderError{ desc: "Could not decode the JSON data"}),
Err(_) => return Err(RenderError{ desc: "Could not decode the JSON data".to_owned()}),
};
let mut previous: Option<BTreeMap<String, String>> = None;
@ -55,7 +55,7 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
},
None => {
debug!("[*]: No title found for chapter");
return Err(RenderError{ desc: "No title found for chapter in JSON data" })
return Err(RenderError{ desc: "No title found for chapter in JSON data".to_owned() })
}
};
@ -68,10 +68,10 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
match path.to_str() {
Some(p) => { previous_chapter.insert("link".to_owned(), p.to_json()); },
None => return Err(RenderError{ desc: "Link could not be converted to str" })
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" })
None => return Err(RenderError{ desc: "No path found for chapter in JSON data".to_owned() })
}
debug!("[*]: Inject in context");
@ -84,7 +84,7 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
Some(t) => {
try!(t.render(&updated_context, r, rc));
},
None => return Err(RenderError{ desc: "Error with the handlebars template" })
None => return Err(RenderError{ desc: "Error with the handlebars template".to_owned() })
}
}
@ -124,7 +124,7 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
// Decode json format
let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) {
Ok(data) => data,
Err(_) => return Err(RenderError{ desc: "Could not decode the JSON data"}),
Err(_) => return Err(RenderError{ desc: "Could not decode the JSON data".to_owned() }),
};
let mut previous: Option<BTreeMap<String, String>> = None;
@ -140,7 +140,7 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
let previous_path = match previous.get("path") {
Some(p) => p,
None => return Err(RenderError{ desc: "No path found for chapter in JSON data"})
None => return Err(RenderError{ desc: "No path found for chapter in JSON data".to_owned() })
};
if previous_path == &current {
@ -155,7 +155,7 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
debug!("[*]: Inserting title: {}", n);
next_chapter.insert("title".to_owned(), n.to_json());
}
None => return Err(RenderError{ desc: "No title found for chapter in JSON data"})
None => return Err(RenderError{ desc: "No title found for chapter in JSON data".to_owned() })
}
@ -164,7 +164,7 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
match link.to_str() {
Some(l) => { next_chapter.insert("link".to_owned(), l.to_json()); },
None => return Err(RenderError{ desc: "Link could not converted to str"})
None => return Err(RenderError{ desc: "Link could not converted to str".to_owned() })
}
debug!("[*]: Inject in context");
@ -178,7 +178,7 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
Some(t) => {
try!(t.render(&updated_context, r, rc));
},
None => return Err(RenderError{ desc: "Error with the handlebars template" })
None => return Err(RenderError{ desc: "Error with the handlebars template".to_owned() })
}
break

View File

@ -9,6 +9,9 @@ body {
.right {
float: right;
}
.hidden {
display: none;
}
h2,
h3 {
margin-top: 2.5em;
@ -222,7 +225,7 @@ h3 {
left: 0;
}
.next {
right: 0;
right: 15px;
}
.theme-popup {
position: fixed;
@ -270,9 +273,30 @@ h3 {
}
}
.light {
/* Inline code */
color: #333;
background-color: #fff;
}
.light :not(pre) > .hljs {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.light pre {
position: relative;
}
.light pre > i {
position: absolute;
right: 5px;
top: 5px;
color: #364149;
cursor: pointer;
}
.light pre > i :hover {
color: #008cff;
}
.light .sidebar {
background-color: #fafafa;
color: #364149;
@ -307,7 +331,7 @@ h3 {
.light .mobile-nav-chapters {
background-color: #fafafa;
}
.light .content a {
.light .content a:link {
color: #4183c4;
}
.light .theme-popup {
@ -318,9 +342,30 @@ h3 {
background-color: #e6e6e6;
}
.coal {
/* Inline code */
color: #98a3ad;
background-color: #141617;
}
.coal :not(pre) > .hljs {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.coal pre {
position: relative;
}
.coal pre > i {
position: absolute;
right: 5px;
top: 5px;
color: #a1adb8;
cursor: pointer;
}
.coal pre > i :hover {
color: #3473ad;
}
.coal .sidebar {
background-color: #292c2f;
color: #a1adb8;
@ -355,7 +400,7 @@ h3 {
.coal .mobile-nav-chapters {
background-color: #292c2f;
}
.coal .content a {
.coal .content a:link {
color: #2b79a2;
}
.coal .theme-popup {
@ -366,9 +411,30 @@ h3 {
background-color: #1f2124;
}
.navy {
/* Inline code */
color: #bcbdd0;
background-color: #161923;
}
.navy :not(pre) > .hljs {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.navy pre {
position: relative;
}
.navy pre > i {
position: absolute;
right: 5px;
top: 5px;
color: #c8c9db;
cursor: pointer;
}
.navy pre > i :hover {
color: #2b79a2;
}
.navy .sidebar {
background-color: #282d3f;
color: #c8c9db;
@ -403,7 +469,7 @@ h3 {
.navy .mobile-nav-chapters {
background-color: #282d3f;
}
.navy .content a {
.navy .content a:link {
color: #2b79a2;
}
.navy .theme-popup {
@ -414,9 +480,30 @@ h3 {
background-color: #282e40;
}
.rust {
/* Inline code */
color: #262625;
background-color: #e1e1db;
}
.rust :not(pre) > .hljs {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.rust pre {
position: relative;
}
.rust pre > i {
position: absolute;
right: 5px;
top: 5px;
color: #c8c9db;
cursor: pointer;
}
.rust pre > i :hover {
color: #e69f67;
}
.rust .sidebar {
background-color: #3b2e2a;
color: #c8c9db;
@ -451,7 +538,7 @@ h3 {
.rust .mobile-nav-chapters {
background-color: #3b2e2a;
}
.rust .content a {
.rust .content a:link {
color: #2b79a2;
}
.rust .theme-popup {

View File

@ -8,7 +8,7 @@ $( document ).ready(function() {
// Set theme
var theme = localStorage.getItem('theme');
if (theme == null) { theme = 'light'; }
if (theme === null) { theme = 'light'; }
set_theme(theme);
@ -28,6 +28,15 @@ $( document ).ready(function() {
var html = $("html");
var sidebar = $("#sidebar");
var page_wrapper = $("#page-wrapper");
var content = $("#content");
// Add anchors for all content headers
content.find("h1, h2, h3, h4, h5").wrap(function(){
var wrapper = $("<a>");
wrapper.attr("name", $(this).text());
return wrapper;
});
// Toggle sidebar
@ -50,6 +59,13 @@ $( document ).ready(function() {
});
// Scroll sidebar to current active section
var activeSection = sidebar.find(".active");
if(activeSection.length) {
sidebar.scrollTop(activeSection.offset().top);
}
// Print button
$("#print-button").click(function(){
var printWindow = window.open("print.html");
@ -77,7 +93,7 @@ $( document ).ready(function() {
$('.theme').click(function(){
var theme = $(this).attr('id');
set_theme(theme)
set_theme(theme);
});
}
@ -96,4 +112,54 @@ $( document ).ready(function() {
$('body').removeClass().addClass(theme);
}
// Hide Rust code lines prepended with a specific character
var hiding_character = "#";
$("code.language-rust").each(function(i, block){
// hide lines
var lines = $(this).html().split("\n");
var first_non_hidden_line = false;
var lines_hidden = false;
for(var n = 0; n < lines.length; n++){
if($.trim(lines[n])[0] == hiding_character){
if(first_non_hidden_line){
lines[n] = "<span class=\"hidden\">" + "\n" + lines[n].substr(1) + "</span>";
}
else {
lines[n] = "<span class=\"hidden\">" + lines[n].substr(1) + "\n" + "</span>";
}
lines_hidden = true;
}
else if(first_non_hidden_line) {
lines[n] = "\n" + lines[n];
}
else {
first_non_hidden_line = true;
}
}
$(this).html(lines.join(""));
// If no lines were hidden, return
if(!lines_hidden) { return; }
// add expand button
$(this).parent().prepend("<i class=\"fa fa-expand\"></i>");
$(this).parent().find("i").click(function(e){
if( $(this).hasClass("fa-expand") ) {
$(this).removeClass("fa-expand").addClass("fa-compress");
$(this).parent().find("span.hidden").removeClass("hidden").addClass("unhidden");
}
else {
$(this).removeClass("fa-compress").addClass("fa-expand");
$(this).parent().find("span.unhidden").removeClass("unhidden").addClass("hidden");
}
});
});
});

View File

@ -10,14 +10,6 @@
-webkit-text-size-adjust: none;
}
/* Inline code */
:not(pre) > .hljs {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
border-radius: 3px;
}
/* Atelier-Dune Comment */
.hljs-comment {

View File

@ -11,4 +11,8 @@ html, body {
float: right
}
.hidden {
display: none;
}
h2, h3 { margin-top: 2.5em }

View File

@ -20,4 +20,4 @@
.mobile-nav-chapters { display: none }
.nav-chapters:hover { text-decoration: none }
.previous { left: 0 }
.next { right: 0 }
.next { right: 15px }

View File

@ -1,4 +1,28 @@
.{unquote($theme-name)} {
/* Inline code */
:not(pre) > .hljs {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
border-radius: 3px;
}
pre {
position: relative;
}
pre > i {
position: absolute;
right: 5px;
top: 5px;
color: $sidebar-fg;
cursor: pointer;
:hover {
color: $sidebar-active;
}
}
color: $fg
background-color: $bg
@ -43,7 +67,7 @@
background-color: $sidebar-bg
}
.content a {
.content a:link {
color: $links
}