mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 16:12:33 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fd1d4606c | ||
|
|
78b6148463 | ||
|
|
78e1897b47 | ||
|
|
d000fc8bac | ||
|
|
5170e6b675 | ||
|
|
a7f329d337 | ||
|
|
bb0c878e06 | ||
|
|
2a7463c45b | ||
|
|
db7424e947 | ||
|
|
0ac0301d72 | ||
|
|
38b2dee17e | ||
|
|
0cb234de5d | ||
|
|
ee4a7fb35c | ||
|
|
9c5c8a6804 | ||
|
|
ae6334f358 | ||
|
|
98cd2f0c27 |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.0.4"
|
||||
version = "0.0.6"
|
||||
authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
|
||||
description = "create books from markdown files (like Gitbook)"
|
||||
documentation = "http://azerupi.github.io/mdBook/index.html"
|
||||
@@ -18,7 +18,7 @@ exclude = [
|
||||
clap = "~1.5.3"
|
||||
handlebars = "~0.12.0"
|
||||
rustc-serialize = "~0.3.16"
|
||||
pulldown-cmark = "~0.0.3"
|
||||
pulldown-cmark = "~0.0.5"
|
||||
|
||||
|
||||
# Watch feature
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
- [index.hbs](format/theme/index-hbs.md)
|
||||
- [Syntax highlighting](format/theme/syntax-highlighting.md)
|
||||
- [MathJax Support](format/mathjax.md)
|
||||
- [Rust code specific features](format/rust.md)
|
||||
- [Rust Library](lib/lib.md)
|
||||
-----------
|
||||
[Contributors](misc/contributors.md)
|
||||
|
||||
6
book-example/src/format/example.rs
Normal file
6
book-example/src/format/example.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
println!("Hello World!");
|
||||
#
|
||||
# // You can even hide lines! :D
|
||||
# println!("I am hidden! Expand the code snippet to see me");
|
||||
}
|
||||
42
book-example/src/format/rust.md
Normal file
42
book-example/src/format/rust.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Rust code specific features
|
||||
|
||||
## Hiding code lines
|
||||
|
||||
There is a feature in mdBook that let's you hide code lines by prepending them with a `#`.
|
||||
|
||||
```bash
|
||||
#fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
|
||||
println!("{}", x + y);
|
||||
#}
|
||||
```
|
||||
|
||||
Will render as
|
||||
|
||||
```rust
|
||||
#fn main() {
|
||||
let x = 5;
|
||||
let y = 7;
|
||||
|
||||
println!("{}", x + y);
|
||||
#}
|
||||
```
|
||||
|
||||
|
||||
## Inserting runnable Rust files
|
||||
|
||||
With the following syntax, you can insert runnable Rust files into your book:
|
||||
|
||||
```hbs
|
||||
\{{#playpen file.rs}}
|
||||
```
|
||||
|
||||
The path to the Rust file has to be relative from the current source file.
|
||||
|
||||
When play is clicked, the code snippet will be send to the [Rust Playpen]() to be compiled and run. The result is send back and displayed directly underneath the code.
|
||||
|
||||
Here is what a rendered code snippet looks like:
|
||||
|
||||
{{#playpen example.rs}}
|
||||
@@ -9,7 +9,7 @@ use std::process::Command;
|
||||
use {BookConfig, BookItem, theme, parse, utils};
|
||||
use book::BookItems;
|
||||
use renderer::{Renderer, HtmlHandlebars};
|
||||
use utils::{PathExt, create_path};
|
||||
|
||||
|
||||
pub struct MDBook {
|
||||
config: BookConfig,
|
||||
@@ -96,7 +96,7 @@ impl MDBook {
|
||||
debug!("[fn]: init");
|
||||
|
||||
if !self.config.get_root().exists() {
|
||||
create_path(self.config.get_root()).unwrap();
|
||||
fs::create_dir_all(self.config.get_root()).unwrap();
|
||||
output!("{:?} created", self.config.get_root());
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use book::bookitem::BookItem;
|
||||
use {utils, theme};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs::File;
|
||||
use std::fs::{self, File};
|
||||
use std::error::Error;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::collections::BTreeMap;
|
||||
@@ -50,7 +50,7 @@ impl Renderer for HtmlHandlebars {
|
||||
|
||||
// Check if dest directory exists
|
||||
debug!("[*]: Check if destination directory exists");
|
||||
if let Err(_) = utils::create_path(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")))
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ impl Renderer for HtmlHandlebars {
|
||||
debug!("[*]: Reading file");
|
||||
try!(f.read_to_string(&mut content));
|
||||
|
||||
// Parse for playpen links
|
||||
if let Some(p) = path.parent() {
|
||||
content = helpers::playpen::render_playpen(&content, p);
|
||||
}
|
||||
|
||||
// Render markdown using the pulldown-cmark crate
|
||||
content = utils::render_markdown(&content);
|
||||
print_content.push_str(&content);
|
||||
@@ -154,41 +159,68 @@ impl Renderer for HtmlHandlebars {
|
||||
|
||||
debug!("[*] Copy static files");
|
||||
// JavaScript
|
||||
let mut js_file = try!(File::create(book.get_dest().join("book.js")));
|
||||
let mut js_file = if let Ok(f) = File::create(book.get_dest().join("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));
|
||||
|
||||
// Css
|
||||
let mut css_file = try!(File::create(book.get_dest().join("book.css")));
|
||||
let mut css_file = if let Ok(f) = File::create(book.get_dest().join("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));
|
||||
|
||||
// JQuery local fallback
|
||||
let mut jquery = try!(File::create(book.get_dest().join("jquery.js")));
|
||||
let mut jquery = if let Ok(f) = File::create(book.get_dest().join("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));
|
||||
|
||||
// Font Awesome local fallback
|
||||
let mut font_awesome = try!(utils::create_file(&book.get_dest().join("_FontAwesome/css/font-awesome").with_extension("css")));
|
||||
try!(font_awesome.write_all(theme::FONT_AWESOME));
|
||||
let mut font_awesome = try!(utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.eot")));
|
||||
try!(font_awesome.write_all(theme::FONT_AWESOME_EOT));
|
||||
let mut font_awesome = try!(utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.svg")));
|
||||
try!(font_awesome.write_all(theme::FONT_AWESOME_SVG));
|
||||
let mut font_awesome = try!(utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.ttf")));
|
||||
try!(font_awesome.write_all(theme::FONT_AWESOME_TTF));
|
||||
let mut font_awesome = try!(utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.woff")));
|
||||
try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF));
|
||||
let mut font_awesome = try!(utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.woff2")));
|
||||
try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF2));
|
||||
let mut font_awesome = try!(utils::create_file(&book.get_dest().join("_FontAwesome/fonts/FontAwesome.ttf")));
|
||||
try!(font_awesome.write_all(theme::FONT_AWESOME_TTF));
|
||||
|
||||
// syntax highlighting
|
||||
let mut highlight_css = try!(File::create(book.get_dest().join("highlight.css")));
|
||||
let mut highlight_css = if let Ok(f) = File::create(book.get_dest().join("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));
|
||||
let mut tomorrow_night_css = try!(File::create(book.get_dest().join("tomorrow-night.css")));
|
||||
|
||||
let mut tomorrow_night_css = if let Ok(f) = File::create(book.get_dest().join("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));
|
||||
let mut highlight_js = try!(File::create(book.get_dest().join("highlight.js")));
|
||||
|
||||
let mut highlight_js = if let Ok(f) = File::create(book.get_dest().join("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));
|
||||
|
||||
// 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 {
|
||||
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create font-awesome.css")))
|
||||
};
|
||||
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 {
|
||||
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));
|
||||
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.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));
|
||||
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.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));
|
||||
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.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));
|
||||
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().join("_FontAwesome/fonts/fontawesome-webfont.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));
|
||||
let mut font_awesome = if let Ok(f) = utils::create_file(&book.get_dest().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));
|
||||
|
||||
// Copy all remaining files
|
||||
try!(utils::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod navigation;
|
||||
pub mod toc;
|
||||
pub mod playpen;
|
||||
|
||||
160
src/renderer/html_handlebars/helpers/playpen.rs
Normal file
160
src/renderer/html_handlebars/helpers/playpen.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
extern crate handlebars;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
|
||||
pub fn render_playpen(s: &str, path: &Path) -> String {
|
||||
// When replacing one thing in a string by something with a different length, 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 replaced = String::new();
|
||||
|
||||
for playpen in find_playpens(s, path) {
|
||||
|
||||
if playpen.escaped {
|
||||
replaced.push_str(&s[previous_end_index..playpen.start_index-1]);
|
||||
replaced.push_str(&s[playpen.start_index..playpen.end_index]);
|
||||
previous_end_index = playpen.end_index;
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the file exists
|
||||
if !playpen.rust_file.exists() || !playpen.rust_file.is_file() {
|
||||
output!("[-] No file exists for {{{{#playpen }}}}\n {}", playpen.rust_file.to_str().unwrap());
|
||||
continue
|
||||
}
|
||||
|
||||
// Open file & read file
|
||||
let mut file = if let Ok(f) = File::open(&playpen.rust_file) { f } else { continue };
|
||||
let mut file_content = String::new();
|
||||
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>";
|
||||
|
||||
replaced.push_str(&s[previous_end_index..playpen.start_index]);
|
||||
replaced.push_str(&replacement);
|
||||
previous_end_index = playpen.end_index;
|
||||
//println!("Playpen{{ {}, {}, {:?}, {} }}", playpen.start_index, playpen.end_index, playpen.rust_file, playpen.editable);
|
||||
}
|
||||
|
||||
replaced.push_str(&s[previous_end_index..]);
|
||||
|
||||
replaced
|
||||
}
|
||||
|
||||
#[derive(PartialOrd, PartialEq, Debug)]
|
||||
struct Playpen{
|
||||
start_index: usize,
|
||||
end_index: usize,
|
||||
rust_file: PathBuf,
|
||||
editable: bool,
|
||||
escaped: bool,
|
||||
}
|
||||
|
||||
fn find_playpens(s: &str, base_path: &Path) -> Vec<Playpen> {
|
||||
let mut playpens = vec![];
|
||||
for (i, _) in s.match_indices("{{#playpen") {
|
||||
debug!("[*]: find_playpen");
|
||||
|
||||
let mut escaped = false;
|
||||
|
||||
if i > 0 {
|
||||
if let Some(c) = s[i-1..].chars().nth(0) {
|
||||
if c == '\\' { escaped = true }
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
|
||||
debug!("s[{}..{}] = {}", i, end_i, s[i..end_i].to_string());
|
||||
|
||||
// If there is nothing between "{{#playpen" and "}}" skip
|
||||
if end_i-2 - (i+10) < 1 { continue }
|
||||
if s[i+10..end_i-2].trim().len() == 0 { continue }
|
||||
|
||||
debug!("{}", s[i+10..end_i-2].to_string());
|
||||
|
||||
// Split on whitespaces
|
||||
let params: Vec<&str> = s[i+10..end_i-2].split_whitespace().collect();
|
||||
let mut editable = false;
|
||||
|
||||
if params.len() > 1 {
|
||||
editable = if let Some(_) = params[1].find("editable") {true} else {false};
|
||||
}
|
||||
|
||||
playpens.push(
|
||||
Playpen{
|
||||
start_index: i,
|
||||
end_index: end_i,
|
||||
rust_file: base_path.join(PathBuf::from(params[0])),
|
||||
editable: editable,
|
||||
escaped: escaped
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
playpens
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------------------
|
||||
// Tests
|
||||
//
|
||||
|
||||
#[test]
|
||||
fn test_find_playpens_no_playpen() {
|
||||
let s = "Some random text without playpen...";
|
||||
assert!(find_playpens(s, Path::new("")) == vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_playpens_partial_playpen() {
|
||||
let s = "Some random text with {{#playpen...";
|
||||
assert!(find_playpens(s, Path::new("")) == vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_playpens_empty_playpen() {
|
||||
let s = "Some random text with {{#playpen}} and {{#playpen }}...";
|
||||
assert!(find_playpens(s, Path::new("")) == vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_playpens_simple_playpen() {
|
||||
let s = "Some random text with {{#playpen file.rs}} and {{#playpen test.rs }}...";
|
||||
|
||||
println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new("")));
|
||||
|
||||
assert!(find_playpens(s, Path::new("")) == vec![
|
||||
Playpen{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]
|
||||
fn test_find_playpens_complex_playpen() {
|
||||
let s = "Some random text with {{#playpen file.rs editable}} and {{#playpen test.rs editable }}...";
|
||||
|
||||
println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new("dir")));
|
||||
|
||||
assert!(find_playpens(s, Path::new("dir")) == vec![
|
||||
Playpen{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]
|
||||
fn test_find_playpens_escaped_playpen() {
|
||||
let s = "Some random text with escaped playpen \\{{#playpen file.rs editable}} ...";
|
||||
|
||||
println!("\nOUTPUT: {:?}\n", find_playpens(s, Path::new("")));
|
||||
|
||||
assert!(find_playpens(s, Path::new("")) == vec![
|
||||
Playpen{start_index: 39, end_index: 68, rust_file: PathBuf::from("file.rs"), editable: true, escaped: true},
|
||||
]);
|
||||
}
|
||||
@@ -25,6 +25,15 @@ h5 {
|
||||
.header + .header h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
table thead td {
|
||||
font-weight: 700;
|
||||
}
|
||||
table td {
|
||||
padding: 3px 20px;
|
||||
}
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -285,29 +294,26 @@ h5 {
|
||||
}
|
||||
}
|
||||
.light {
|
||||
/* Inline code */
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
/*
|
||||
table {
|
||||
thead td {
|
||||
color: $table-header-fg;
|
||||
backrgound: $table-header-bg;
|
||||
}
|
||||
}
|
||||
*/
|
||||
/* Inline code */
|
||||
}
|
||||
.light :not(pre) > .hljs {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0.1em 0.3em;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
.light .content .header:link,
|
||||
.light .content .header:visited {
|
||||
color: #333;
|
||||
pointer: cursor;
|
||||
}
|
||||
.light pre {
|
||||
position: relative;
|
||||
}
|
||||
.light pre > i {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
color: #364149;
|
||||
cursor: pointer;
|
||||
}
|
||||
.light pre > i :hover {
|
||||
color: #008cff;
|
||||
.light .content .header:link:hover,
|
||||
.light .content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.light .sidebar {
|
||||
background-color: #fafafa;
|
||||
@@ -357,30 +363,61 @@ h5 {
|
||||
.light .theme-popup .theme:hover {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
.coal {
|
||||
/* Inline code */
|
||||
color: #98a3ad;
|
||||
background-color: #141617;
|
||||
.light blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: #333;
|
||||
background-color: #f2f7f9;
|
||||
border-top: 0.1em solid #e1edf1;
|
||||
border-bottom: 0.1em solid #e1edf1;
|
||||
}
|
||||
.coal :not(pre) > .hljs {
|
||||
.light :not(pre) > .hljs {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0.1em 0.3em;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.coal pre {
|
||||
.light pre {
|
||||
position: relative;
|
||||
}
|
||||
.coal pre > i {
|
||||
.light pre > .buttons {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
color: #a1adb8;
|
||||
color: #364149;
|
||||
cursor: pointer;
|
||||
}
|
||||
.coal pre > i :hover {
|
||||
color: #3473ad;
|
||||
.light pre > .buttons :hover {
|
||||
color: #008cff;
|
||||
}
|
||||
.light pre > .buttons i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.light pre > .result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.coal {
|
||||
color: #98a3ad;
|
||||
background-color: #141617;
|
||||
/*
|
||||
table {
|
||||
thead td {
|
||||
color: $table-header-fg;
|
||||
backrgound: $table-header-bg;
|
||||
}
|
||||
}
|
||||
*/
|
||||
/* Inline code */
|
||||
}
|
||||
.coal .content .header:link,
|
||||
.coal .content .header:visited {
|
||||
color: #98a3ad;
|
||||
pointer: cursor;
|
||||
}
|
||||
.coal .content .header:link:hover,
|
||||
.coal .content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.coal .sidebar {
|
||||
background-color: #292c2f;
|
||||
@@ -430,30 +467,61 @@ h5 {
|
||||
.coal .theme-popup .theme:hover {
|
||||
background-color: #1f2124;
|
||||
}
|
||||
.navy {
|
||||
/* Inline code */
|
||||
color: #bcbdd0;
|
||||
background-color: #161923;
|
||||
.coal blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: #98a3ad;
|
||||
background-color: #242637;
|
||||
border-top: 0.1em solid #2c2f44;
|
||||
border-bottom: 0.1em solid #2c2f44;
|
||||
}
|
||||
.navy :not(pre) > .hljs {
|
||||
.coal :not(pre) > .hljs {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0.1em 0.3em;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.navy pre {
|
||||
.coal pre {
|
||||
position: relative;
|
||||
}
|
||||
.navy pre > i {
|
||||
.coal pre > .buttons {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
color: #c8c9db;
|
||||
color: #a1adb8;
|
||||
cursor: pointer;
|
||||
}
|
||||
.navy pre > i :hover {
|
||||
color: #2b79a2;
|
||||
.coal pre > .buttons :hover {
|
||||
color: #3473ad;
|
||||
}
|
||||
.coal pre > .buttons i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.coal pre > .result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.navy {
|
||||
color: #bcbdd0;
|
||||
background-color: #161923;
|
||||
/*
|
||||
table {
|
||||
thead td {
|
||||
color: $table-header-fg;
|
||||
backrgound: $table-header-bg;
|
||||
}
|
||||
}
|
||||
*/
|
||||
/* Inline code */
|
||||
}
|
||||
.navy .content .header:link,
|
||||
.navy .content .header:visited {
|
||||
color: #bcbdd0;
|
||||
pointer: cursor;
|
||||
}
|
||||
.navy .content .header:link:hover,
|
||||
.navy .content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.navy .sidebar {
|
||||
background-color: #282d3f;
|
||||
@@ -503,30 +571,61 @@ h5 {
|
||||
.navy .theme-popup .theme:hover {
|
||||
background-color: #282e40;
|
||||
}
|
||||
.rust {
|
||||
/* Inline code */
|
||||
color: #262625;
|
||||
background-color: #e1e1db;
|
||||
.navy blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: #bcbdd0;
|
||||
background-color: #262933;
|
||||
border-top: 0.1em solid #2f333f;
|
||||
border-bottom: 0.1em solid #2f333f;
|
||||
}
|
||||
.rust :not(pre) > .hljs {
|
||||
.navy :not(pre) > .hljs {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0.1em 0.3em;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.rust pre {
|
||||
.navy pre {
|
||||
position: relative;
|
||||
}
|
||||
.rust pre > i {
|
||||
.navy pre > .buttons {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
color: #c8c9db;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rust pre > i :hover {
|
||||
color: #e69f67;
|
||||
.navy pre > .buttons :hover {
|
||||
color: #2b79a2;
|
||||
}
|
||||
.navy pre > .buttons i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.navy pre > .result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.rust {
|
||||
color: #262625;
|
||||
background-color: #e1e1db;
|
||||
/*
|
||||
table {
|
||||
thead td {
|
||||
color: $table-header-fg;
|
||||
backrgound: $table-header-bg;
|
||||
}
|
||||
}
|
||||
*/
|
||||
/* Inline code */
|
||||
}
|
||||
.rust .content .header:link,
|
||||
.rust .content .header:visited {
|
||||
color: #262625;
|
||||
pointer: cursor;
|
||||
}
|
||||
.rust .content .header:link:hover,
|
||||
.rust .content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.rust .sidebar {
|
||||
background-color: #3b2e2a;
|
||||
@@ -576,3 +675,37 @@ h5 {
|
||||
.rust .theme-popup .theme:hover {
|
||||
background-color: #99908a;
|
||||
}
|
||||
.rust blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: #262625;
|
||||
background-color: #c1c1bb;
|
||||
border-top: 0.1em solid #b8b8b1;
|
||||
border-bottom: 0.1em solid #b8b8b1;
|
||||
}
|
||||
.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 > .buttons {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
color: #c8c9db;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rust pre > .buttons :hover {
|
||||
color: #e69f67;
|
||||
}
|
||||
.rust pre > .buttons i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.rust pre > .result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -32,11 +32,15 @@ $( document ).ready(function() {
|
||||
switch (e.keyCode) {
|
||||
case KEY_CODES.NEXT_KEY:
|
||||
e.preventDefault();
|
||||
window.location.href = $('.nav-chapters.next').attr('href');
|
||||
if($('.nav-chapters.next').length) {
|
||||
window.location.href = $('.nav-chapters.next').attr('href');
|
||||
}
|
||||
break;
|
||||
case KEY_CODES.PREVIOUS_KEY:
|
||||
e.preventDefault();
|
||||
window.location.href = $('.nav-chapters.previous').attr('href');
|
||||
if($('.nav-chapters.previous').length) {
|
||||
window.location.href = $('.nav-chapters.previous').attr('href');
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -52,6 +56,8 @@ $( document ).ready(function() {
|
||||
content.find("h1, h2, h3, h4, h5").wrap(function(){
|
||||
var wrapper = $("<a class=\"header\">");
|
||||
wrapper.attr("name", $(this).text());
|
||||
// Add so that when you click the link actually shows up in the url bar...
|
||||
wrapper.attr("href", $(location).attr('href') + "#" + $(this).text());
|
||||
return wrapper;
|
||||
});
|
||||
|
||||
@@ -136,18 +142,20 @@ $( document ).ready(function() {
|
||||
|
||||
$("code.language-rust").each(function(i, block){
|
||||
|
||||
var code_block = $(this);
|
||||
var pre_block = $(this).parent();
|
||||
// hide lines
|
||||
var lines = $(this).html().split("\n");
|
||||
var lines = code_block.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>";
|
||||
lines[n] = "<span class=\"hidden\">" + "\n" + lines[n].replace(/(\s*)#/, "$1") + "</span>";
|
||||
}
|
||||
else {
|
||||
lines[n] = "<span class=\"hidden\">" + lines[n].substr(1) + "\n" + "</span>";
|
||||
lines[n] = "<span class=\"hidden\">" + lines[n].replace(/(\s*)#/, "$1") + "\n" + "</span>";
|
||||
}
|
||||
lines_hidden = true;
|
||||
}
|
||||
@@ -158,25 +166,65 @@ $( document ).ready(function() {
|
||||
first_non_hidden_line = true;
|
||||
}
|
||||
}
|
||||
$(this).html(lines.join(""));
|
||||
code_block.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>");
|
||||
pre_block.prepend("<div class=\"buttons\"><i class=\"fa fa-expand\"></i></div>");
|
||||
|
||||
$(this).parent().find("i").click(function(e){
|
||||
pre_block.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");
|
||||
pre_block.find("span.hidden").removeClass("hidden").addClass("unhidden");
|
||||
}
|
||||
else {
|
||||
$(this).removeClass("fa-compress").addClass("fa-expand");
|
||||
$(this).parent().find("span.unhidden").removeClass("unhidden").addClass("hidden");
|
||||
pre_block.find("span.unhidden").removeClass("unhidden").addClass("hidden");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Process playpen code blocks
|
||||
$(".playpen").each(function(block){
|
||||
var pre_block = $(this);
|
||||
// Add play button
|
||||
var buttons = pre_block.find(".buttons");
|
||||
if( buttons.length === 0 ) {
|
||||
pre_block.prepend("<div class=\"buttons\"></div>");
|
||||
buttons = pre_block.find(".buttons");
|
||||
}
|
||||
buttons.prepend("<i class=\"fa fa-play play-button\"></i>");
|
||||
|
||||
buttons.find(".play-button").click(function(e){
|
||||
run_rust_code(pre_block);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
function run_rust_code(code_block) {
|
||||
var result_block = code_block.find(".result");
|
||||
if(result_block.length === 0) {
|
||||
code_block.append("<code class=\"result hljs language-bash\"></code>");
|
||||
result_block = code_block.find(".result");
|
||||
}
|
||||
|
||||
result_block.text("Running...");
|
||||
|
||||
$.ajax({
|
||||
url: "https://play.rust-lang.org/evaluate.json",
|
||||
method: "POST",
|
||||
crossDomain: true,
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({version: "stable", optimize: "0", code: code_block.find(".language-rust").text() }),
|
||||
success: function(response){
|
||||
result_block.text(response.result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use utils::{PathExt};
|
||||
|
||||
pub static INDEX: &'static [u8] = include_bytes!("index.hbs");
|
||||
pub static CSS: &'static [u8] = include_bytes!("book.css");
|
||||
|
||||
@@ -19,3 +19,10 @@ h2, h3 { margin-top: 2.5em }
|
||||
h4, h5 { margin-top: 2em }
|
||||
|
||||
.header + .header h3, .header + .header h4, .header + .header h5 { margin-top: 1em }
|
||||
|
||||
table {
|
||||
margin: 0 auto;
|
||||
|
||||
thead td { font-weight: 700; }
|
||||
td { padding: 3px 20px; }
|
||||
}
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
.{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
|
||||
|
||||
.content .header:link, .content .header:visited {
|
||||
color: $fg;
|
||||
pointer: cursor;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: $sidebar-bg
|
||||
color: $sidebar-fg
|
||||
@@ -80,4 +66,49 @@
|
||||
|
||||
.theme:hover { background-color: $theme-hover }
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: $fg;
|
||||
background-color: $quote-bg;
|
||||
border-top: .1em solid $quote-border;
|
||||
border-bottom: .1em solid $quote-border;
|
||||
}
|
||||
|
||||
/*
|
||||
table {
|
||||
thead td {
|
||||
color: $table-header-fg;
|
||||
backrgound: $table-header-bg;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > .hljs {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
|
||||
& > .buttons {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
|
||||
color: $sidebar-fg;
|
||||
cursor: pointer;
|
||||
|
||||
:hover { color: $sidebar-active; }
|
||||
i { margin-left: 8px; }
|
||||
}
|
||||
|
||||
& > .result { margin-top: 10px; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,7 @@ $theme-popup-bg = #141617
|
||||
$theme-popup-border = #43484d
|
||||
$theme-hover = #1f2124
|
||||
|
||||
$quote-bg = #242637
|
||||
$quote-border = lighten($quote-bg, 5%)
|
||||
|
||||
@import 'base'
|
||||
|
||||
@@ -18,4 +18,7 @@ $theme-popup-bg = #fafafa
|
||||
$theme-popup-border = #cccccc
|
||||
$theme-hover = #e6e6e6
|
||||
|
||||
$quote-bg = #f2f7f9
|
||||
$quote-border = darken($quote-bg, 5%)
|
||||
|
||||
@import 'base'
|
||||
|
||||
@@ -18,4 +18,7 @@ $theme-popup-bg = #161923
|
||||
$theme-popup-border = #737480
|
||||
$theme-hover = #282e40
|
||||
|
||||
$quote-bg = #262933
|
||||
$quote-border = lighten($quote-bg, 5%)
|
||||
|
||||
@import 'base'
|
||||
|
||||
@@ -18,4 +18,7 @@ $theme-popup-bg = #e1e1db
|
||||
$theme-popup-border = #b38f6b
|
||||
$theme-hover = #99908a
|
||||
|
||||
$quote-bg = #c1c1bb
|
||||
$quote-border = darken($quote-bg, 5%)
|
||||
|
||||
@import 'base'
|
||||
|
||||
@@ -1,32 +1,11 @@
|
||||
extern crate pulldown_cmark;
|
||||
|
||||
use std::path::{Path, PathBuf, Component};
|
||||
use std::path::{Path, Component};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::fs::{self, metadata, File};
|
||||
|
||||
use self::pulldown_cmark::{Parser, html};
|
||||
|
||||
/// This is copied from the rust source code until Path_ Ext stabilizes.
|
||||
/// You can use it, but be aware that it will be removed when those features go to rust stable
|
||||
pub trait PathExt {
|
||||
fn exists(&self) -> bool;
|
||||
fn is_file(&self) -> bool;
|
||||
fn is_dir(&self) -> bool;
|
||||
}
|
||||
|
||||
impl PathExt for Path {
|
||||
fn exists(&self) -> bool {
|
||||
metadata(self).is_ok()
|
||||
}
|
||||
|
||||
fn is_file(&self) -> bool {
|
||||
metadata(self).map(|s| s.is_file()).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_dir(&self) -> bool {
|
||||
metadata(self).map(|s| s.is_dir()).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
use self::pulldown_cmark::{Parser, html, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
|
||||
|
||||
/// Takes a path and returns a path containing just enough `../` to point to the root of the given path.
|
||||
///
|
||||
@@ -65,46 +44,7 @@ pub fn path_to_root(path: &Path) -> String {
|
||||
})
|
||||
}
|
||||
|
||||
/// This function checks for every component in a path if the directory exists,
|
||||
/// if it does not it is created.
|
||||
|
||||
pub fn create_path(path: &Path) -> Result<(), Box<Error>> {
|
||||
debug!("[fn]: create_path");
|
||||
|
||||
// Create directories if they do not exist
|
||||
let mut constructed_path = PathBuf::new();
|
||||
|
||||
for component in path.components() {
|
||||
|
||||
let dir;
|
||||
match component {
|
||||
Component::Normal(_) => { dir = PathBuf::from(component.as_os_str()); },
|
||||
Component::RootDir => {
|
||||
debug!("[*]: Root directory");
|
||||
// This doesn't look very compatible with Windows...
|
||||
constructed_path.push("/");
|
||||
continue
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
constructed_path.push(&dir);
|
||||
debug!("[*]: {:?}", constructed_path);
|
||||
|
||||
if !constructed_path.exists() || !constructed_path.is_dir() {
|
||||
try!(fs::create_dir(&constructed_path));
|
||||
debug!("[*]: Directory created {:?}", constructed_path);
|
||||
} else {
|
||||
debug!("[*]: Directory exists {:?}", constructed_path);
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
debug!("[*]: Constructed path: {:?}", constructed_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function creates a file and returns it. But before creating the file it checks every
|
||||
/// directory in the path to see if it exists, and if it does not it will be created.
|
||||
@@ -114,11 +54,19 @@ pub fn create_file(path: &Path) -> Result<File, Box<Error>> {
|
||||
|
||||
// Construct path
|
||||
if let Some(p) = path.parent() {
|
||||
try!(create_path(p));
|
||||
debug!("Parent directory is: {:?}", p);
|
||||
|
||||
try!(fs::create_dir_all(p));
|
||||
}
|
||||
|
||||
debug!("[*]: Create file: {:?}", path);
|
||||
let f = try!(File::create(path));
|
||||
let f = match File::create(path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
debug!("File::create: {}", e);
|
||||
return Err(Box::new(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
|
||||
},
|
||||
};
|
||||
|
||||
Ok(f)
|
||||
}
|
||||
@@ -172,7 +120,7 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
|
||||
if let Some(ext) = entry.path().extension() {
|
||||
if ext_blacklist.contains(&ext.to_str().unwrap()) { continue }
|
||||
debug!("[*] creating path for file: {:?}", &to.join(entry.path().file_name().expect("a file should have a file name...")));
|
||||
//try!(create_path(&to.join(entry.path())));
|
||||
|
||||
output!("[*] copying file: {:?}\n to {:?}", 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..."))));
|
||||
}
|
||||
@@ -188,7 +136,12 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
|
||||
|
||||
pub fn render_markdown(text: &str) -> String {
|
||||
let mut s = String::with_capacity(text.len() * 3 / 2);
|
||||
let p = Parser::new(&text);
|
||||
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(OPTION_ENABLE_TABLES);
|
||||
opts.insert(OPTION_ENABLE_FOOTNOTES);
|
||||
|
||||
let p = Parser::new_ext(&text, opts);
|
||||
html::push_html(&mut s, p);
|
||||
s
|
||||
}
|
||||
@@ -205,7 +158,6 @@ mod tests {
|
||||
extern crate tempdir;
|
||||
|
||||
use super::copy_files_except_ext;
|
||||
use super::PathExt;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user