mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 10:16:33 -05:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80deac90d9 | ||
|
|
625f5081fa | ||
|
|
3e8151e8e3 | ||
|
|
3c10a85735 | ||
|
|
330b1ad55d | ||
|
|
f24eb59753 | ||
|
|
01c5085725 | ||
|
|
9f17be2c32 | ||
|
|
88fabd76f0 | ||
|
|
f508db6113 | ||
|
|
1083d1822d | ||
|
|
fc86b963bb | ||
|
|
f2b913c9dd | ||
|
|
dd0cfc14d4 | ||
|
|
5891e4b5db | ||
|
|
394023f617 | ||
|
|
39a6fe4b3c | ||
|
|
75b98d7019 | ||
|
|
814b21ad94 | ||
|
|
7364d41f0c | ||
|
|
f6be4a7d7e | ||
|
|
0b00c270d5 | ||
|
|
8d2ca521c0 | ||
|
|
276cd8d490 | ||
|
|
e958fc8605 | ||
|
|
e286b208da | ||
|
|
3fd1d4606c | ||
|
|
78b6148463 | ||
|
|
78e1897b47 | ||
|
|
d000fc8bac | ||
|
|
5170e6b675 | ||
|
|
a7f329d337 | ||
|
|
bb0c878e06 | ||
|
|
2a7463c45b | ||
|
|
db7424e947 | ||
|
|
0ac0301d72 | ||
|
|
38b2dee17e | ||
|
|
0cb234de5d | ||
|
|
ee4a7fb35c | ||
|
|
9c5c8a6804 | ||
|
|
ae6334f358 | ||
|
|
98cd2f0c27 |
26
Cargo.toml
26
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.0.4"
|
||||
version = "0.0.10"
|
||||
authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
|
||||
description = "create books from markdown files (like Gitbook)"
|
||||
documentation = "http://azerupi.github.io/mdBook/index.html"
|
||||
@@ -15,28 +15,20 @@ exclude = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
clap = "~1.5.3"
|
||||
handlebars = "~0.12.0"
|
||||
rustc-serialize = "~0.3.16"
|
||||
pulldown-cmark = "~0.0.3"
|
||||
|
||||
clap = "1.5.3"
|
||||
handlebars = "0.12.0"
|
||||
rustc-serialize = "0.3.16"
|
||||
pulldown-cmark = "0.0.6"
|
||||
|
||||
# Watch feature
|
||||
[dependencies.notify]
|
||||
notify = "^2.4.1"
|
||||
optional = true
|
||||
notify = { version = "2.5.4", optional = true }
|
||||
time = { version = "0.1.33", optional = true }
|
||||
crossbeam = { version = "0.2.8", optional = true }
|
||||
|
||||
[dependencies.time]
|
||||
time = "^0.1.33"
|
||||
optional = true
|
||||
|
||||
[dependencies.crossbeam]
|
||||
time = "^0.2.0"
|
||||
optional = true
|
||||
|
||||
# Tests
|
||||
[dev-dependencies]
|
||||
tempdir = "~0.3.4"
|
||||
tempdir = "0.3.4"
|
||||
|
||||
|
||||
[features]
|
||||
|
||||
85
README.md
85
README.md
@@ -1,79 +1,92 @@
|
||||
# mdBook [](https://travis-ci.org/azerupi/mdBook) [](https://crates.io/crates/mdbook) [](LICENSE)
|
||||
|
||||
Personal implementation of Gitbook in Rust
|
||||
mdBook is a utility to create modern online books from markdown files.
|
||||
|
||||
**This project is still in it's early days.**
|
||||
**This project is still in its early days.**
|
||||
For more information about what is left on my to-do list, check the issue tracker
|
||||
|
||||
|
||||
## Example
|
||||
## What does it look like?
|
||||
|
||||
To have an idea of what a rendered book looks like,take a look at the [**Documentation**](http://azerupi.github.io/mdBook/). It is rendered by the latest version of mdBook.
|
||||
The [**Documentation**](http://azerupi.github.io/mdBook/) for mdBook has been written in markdown and is using mdBook to generate the online book-like website you can read. The documentation uses the latest version on github and showcases the available features.
|
||||
|
||||
## Installation
|
||||
|
||||
There are 2 ways to install mdBook but both require [Rust and Cargo](https://www.rust-lang.org/) to be installed.
|
||||
|
||||
##### Install from Crates.io
|
||||
|
||||
Once you have installed Rust, type the following in the terminal:
|
||||
```
|
||||
cargo install mdbook
|
||||
```
|
||||
|
||||
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
|
||||
This will download and compile mdBook for you, the only thing you that will be left to do is add the Cargo bin directory to your path.
|
||||
|
||||
Install [node.js](https://nodejs.org/en/)
|
||||
##### Install from git
|
||||
|
||||
The version published to Crates.io will ever so slightly be behind the version hosted here on Github. If you need the latest version you can build the git version of mdBook yourself. Cargo makes this ***super easy***!
|
||||
|
||||
First, clone the repository on your computer:
|
||||
|
||||
```
|
||||
npm install -g stylus nib
|
||||
git clone --depth 1 https://github.com/azerupi/mdBook.git
|
||||
```
|
||||
|
||||
Then build with the `regenerate-css` feature:
|
||||
Then `cd` into the directory and run:
|
||||
|
||||
```
|
||||
cargo build --release --features="regenerate-css"
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Structure
|
||||
The executable will be in `./target/release/mdbook`.
|
||||
|
||||
There are two main parts of this project:
|
||||
|
||||
- **The library:** The crate is structured so that all the code that actually does something is part of the library. You could therefore easily hook mdbook into your existing project, extend it's functionality by wrapping it in some other code, etc.
|
||||
- **The binary:** Is just a wrapper around the library functionality providing a nice and easy command line interface.
|
||||
|
||||
### Command line interface
|
||||
## Usage
|
||||
|
||||
#### init
|
||||
mdBook will primaraly be used as a command line tool, even though it exposes all its functionality as a Rust crate for integration in other projects.
|
||||
|
||||
If you run `mdbook init` in a directory, it will create a couple of folders and files you can start with.
|
||||
This is the strucutre it creates at the moment:
|
||||
```
|
||||
book-test/
|
||||
├── book
|
||||
└── src
|
||||
├── chapter_1.md
|
||||
└── SUMMARY.md
|
||||
```
|
||||
`book` and `src` are both directories. `src` contains the markdown files that will be used to render the ouput to the `book` directory.
|
||||
Here are the main commands you will want to run, for a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/).
|
||||
|
||||
Please, take a look at the [**Documentation**](http://azerupi.github.io/mdBook/cli/init.html) for more information.
|
||||
- `mdbook init`
|
||||
|
||||
#### build
|
||||
The init command will create a directory with the minimal boilerplate to start with.
|
||||
|
||||
Use `mdbook build` in the directory to render the book. You can find more information in the [**Documentation**](http://azerupi.github.io/mdBook/cli/build.html)
|
||||
```
|
||||
book-test/
|
||||
├── book
|
||||
└── src
|
||||
├── chapter_1.md
|
||||
└── SUMMARY.md
|
||||
```
|
||||
|
||||
`book` and `src` are both directories. `src` contains the markdown files that will be used to render the ouput to the `book` directory.
|
||||
|
||||
Please, take a look at the [**Documentation**](http://azerupi.github.io/mdBook/cli/init.html) for more information and some neat tricks.
|
||||
|
||||
- `mdbook build`
|
||||
|
||||
This is the command you will run to render your book, it reads the `SUMMARY.md` file to understand the structure of your book, takes the markdown files in the source directory as input and outputs static html pages that you can upload to a server.
|
||||
|
||||
- `mdbook watch`
|
||||
|
||||
When you run this command, mdbook will watch your markdown files to rebuild the book on every change. This avoids having to come back to the terminal to type `mdbook build` over and over again.
|
||||
|
||||
### As a library
|
||||
|
||||
Aside from the command line interface, this crate can also be used as a library. This means that you could integrate it in an existing project, like a webapp for example. Since the command line interface is just a wrapper around the library functionality, when you use this crate as a library you have full access to all the functionality of the command line interface with and easy to use API and more!
|
||||
Aside from the command line interface, this crate can also be used as a library. This means that you could integrate it in an existing project, like a web-app for example. Since the command line interface is just a wrapper around the library functionality, when you use this crate as a library you have full access to all the functionality of the command line interface with and easy to use API and more!
|
||||
|
||||
See the [**Documentation**](http://azerupi.github.io/mdBook/lib/lib.html) and the [**API docs**](http://azerupi.github.io/mdBook/mdbook/index.html) for more information.
|
||||
See the [Documentation](http://azerupi.github.io/mdBook/lib/lib.html) and the [API docs](http://azerupi.github.io/mdBook/mdbook/index.html) for more information.
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are highly apreciated. Here are some ideas:
|
||||
Contributions are highly appreciated and encouraged! Don't hesitate to participate to discussions in the issues, propose new features and ask for help.
|
||||
|
||||
- **Create new renderers**, at the moment I have only created a renderer that uses [handlebars](https://github.com/sunng87/handlebars-rust), [pulldown-cmark](https://github.com/google/pulldown-cmark) and renders to html. But you could create a renderer that uses another template engine, markdown parser or even outputs to another format like pdf.
|
||||
- **Add tests** I have not much experience in writing tests, all help to write meaningful tests is thus very welcome
|
||||
- **write documentation** documentation can always be improved
|
||||
- **Smaller tasks** I try to add a lot of the remaining tasks on the issue tracker with the label: [`Enhancement`](https://github.com/azerupi/mdBook/issues?q=is%3Aopen+is%3Aissue+label%3AEnhancement). Just pick one that looks interesting. The majority of the tasks are small enough to be tackled by people who are unfamiliar with the project.
|
||||
People who are not familiar with the code can look at [issues that are tagged **easy**](https://github.com/azerupi/mdBook/labels/Easy). A lot of issues are also related to web development, so people that are not comfortable with Rust can also participate! :wink:
|
||||
|
||||
You can pick any issue you want to work on. Usually it's a good idea to ask if someone is already working on it and if not to claim the issue.
|
||||
|
||||
If you have an idea for improvement, create a new issue. Or a pull request if you can :)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"title": "mdBook Documentation",
|
||||
"description": "Create book from markdown files. Like Gitbook but implemented in Rust",
|
||||
"author": "Mathieu David"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,6 +8,7 @@ Here is an example of what a ***book.json*** file might look like:
|
||||
{
|
||||
"title": "Example book",
|
||||
"author": "Name",
|
||||
"description": "The example book covers examples.",
|
||||
"dest": "output/my-book"
|
||||
}
|
||||
```
|
||||
@@ -16,6 +17,7 @@ Here is an example of what a ***book.json*** file might look like:
|
||||
|
||||
- **title:** title of the book
|
||||
- **author:** author of the book
|
||||
- **description:** description, which is added as meta in the html head of each page.
|
||||
- **dest:** path to the directory where you want your book to be rendered. If a relative path is given it will be relative to the parent directory of the source directory
|
||||
|
||||
***note:*** *the supported configurable parameters are scarce at the moment, but more will be added in the future*
|
||||
|
||||
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}}
|
||||
@@ -14,6 +14,7 @@ Here are the files you can override:
|
||||
- ***book.js*** is mostly used to add client side functionality, like hiding / un-hiding the sidebar, changing the theme, ...
|
||||
- ***highlight.js*** is the JavaScript that is used to highlight code snippets, you should not need to modify this.
|
||||
- ***highlight.css*** is the theme used for the code highlighting
|
||||
- ***favicon.png*** the favicon that will be used
|
||||
|
||||
Generally, when you want to tweak the theme, you don't need to override all the files. If you only need changes in the stylesheet,
|
||||
there is no point in overriding all the other files. Because custom files take precedence over built-in ones, they will not get updated with new fixes / features.
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::path::{Path, PathBuf};
|
||||
pub struct BookConfig {
|
||||
pub title: String,
|
||||
pub author: String,
|
||||
pub description: String,
|
||||
root: PathBuf,
|
||||
dest: PathBuf,
|
||||
src: PathBuf,
|
||||
@@ -21,6 +22,7 @@ impl BookConfig {
|
||||
BookConfig {
|
||||
title: String::new(),
|
||||
author: String::new(),
|
||||
description: String::new(),
|
||||
root: root.to_owned(),
|
||||
dest: PathBuf::from("book"),
|
||||
src: PathBuf::from("src"),
|
||||
@@ -54,9 +56,10 @@ impl BookConfig {
|
||||
// Extract data
|
||||
|
||||
debug!("[*]: Extracting data from config");
|
||||
// Title & author
|
||||
// Title, author, description
|
||||
if let Some(a) = config.find_path(&["title"]) { self.title = 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
|
||||
if let Some(a) = config.find_path(&["dest"]) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -198,6 +198,10 @@ impl MDBook {
|
||||
let mut css = try!(File::create(&theme_dir.join("book.css")));
|
||||
try!(css.write_all(theme::CSS));
|
||||
|
||||
// favicon.png
|
||||
let mut favicon = try!(File::create(&theme_dir.join("favicon.png")));
|
||||
try!(favicon.write_all(theme::FAVICON));
|
||||
|
||||
// book.js
|
||||
let mut js = try!(File::create(&theme_dir.join("book.js")));
|
||||
try!(js.write_all(theme::JS));
|
||||
@@ -347,6 +351,15 @@ impl MDBook {
|
||||
&self.config.author
|
||||
}
|
||||
|
||||
pub fn set_description(mut self, description: &str) -> Self {
|
||||
self.config.description = description.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_description(&self) -> &str {
|
||||
&self.config.description
|
||||
}
|
||||
|
||||
// Construct book
|
||||
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
|
||||
// When append becomes stable, use self.content.append() ...
|
||||
|
||||
@@ -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,74 @@ 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));
|
||||
|
||||
// Favicon
|
||||
let mut favicon_file = if let Ok(f) = File::create(book.get_dest().join("favicon.png")) { f } else {
|
||||
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create favicon.png")))
|
||||
};
|
||||
try!(favicon_file.write_all(&theme.favicon));
|
||||
|
||||
// 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"]));
|
||||
@@ -203,6 +241,8 @@ fn make_data(book: &MDBook) -> Result<BTreeMap<String,Json>, Box<Error>> {
|
||||
let mut data = BTreeMap::new();
|
||||
data.insert("language".to_owned(), "en".to_json());
|
||||
data.insert("title".to_owned(), book.get_title().to_json());
|
||||
data.insert("description".to_owned(), book.get_description().to_json());
|
||||
data.insert("favicon".to_owned(), "favicon.png".to_json());
|
||||
|
||||
let mut chapters = vec![];
|
||||
|
||||
|
||||
@@ -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,17 @@ h5 {
|
||||
.header + .header h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
table {
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table td {
|
||||
padding: 3px 20px;
|
||||
border: 1px solid;
|
||||
}
|
||||
table thead td {
|
||||
font-weight: 700;
|
||||
}
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -240,8 +251,9 @@ h5 {
|
||||
right: 15px;
|
||||
}
|
||||
.theme-popup {
|
||||
position: fixed;
|
||||
left: -40px;
|
||||
position: relative;
|
||||
left: 10px;
|
||||
z-index: 1000;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
@@ -252,6 +264,15 @@ h5 {
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.theme-popup .theme:hover:first-child {
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
}
|
||||
.theme-popup .theme:hover:last-child {
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1250px) {
|
||||
.nav-chapters {
|
||||
display: none;
|
||||
@@ -285,29 +306,18 @@ h5 {
|
||||
}
|
||||
}
|
||||
.light {
|
||||
/* Inline code */
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
/* 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;
|
||||
@@ -351,36 +361,78 @@ h5 {
|
||||
color: #4183c4;
|
||||
}
|
||||
.light .theme-popup {
|
||||
color: #333;
|
||||
background: #fafafa;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.light .theme-popup .theme:hover {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
.coal {
|
||||
/* Inline code */
|
||||
color: #98a3ad;
|
||||
background-color: #141617;
|
||||
.light .theme-popup .default {
|
||||
color: #ccc;
|
||||
}
|
||||
.coal :not(pre) > .hljs {
|
||||
.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;
|
||||
}
|
||||
.light table td {
|
||||
border-color: #f2f2f2;
|
||||
}
|
||||
.light table tbody tr:nth-child(2n) {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
.light table thead {
|
||||
background: #ccc;
|
||||
}
|
||||
.light table thead td {
|
||||
border: none;
|
||||
}
|
||||
.light table thead tr {
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
.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;
|
||||
/* 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;
|
||||
@@ -424,36 +476,78 @@ h5 {
|
||||
color: #2b79a2;
|
||||
}
|
||||
.coal .theme-popup {
|
||||
color: #98a3ad;
|
||||
background: #141617;
|
||||
border: 1px solid #43484d;
|
||||
}
|
||||
.coal .theme-popup .theme:hover {
|
||||
background-color: #1f2124;
|
||||
}
|
||||
.navy {
|
||||
/* Inline code */
|
||||
color: #bcbdd0;
|
||||
background-color: #161923;
|
||||
.coal .theme-popup .default {
|
||||
color: #43484d;
|
||||
}
|
||||
.navy :not(pre) > .hljs {
|
||||
.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;
|
||||
}
|
||||
.coal table td {
|
||||
border-color: #1f2223;
|
||||
}
|
||||
.coal table tbody tr:nth-child(2n) {
|
||||
background: #1b1d1e;
|
||||
}
|
||||
.coal table thead {
|
||||
background: #3f4649;
|
||||
}
|
||||
.coal table thead td {
|
||||
border: none;
|
||||
}
|
||||
.coal table thead tr {
|
||||
border: 1px #3f4649 solid;
|
||||
}
|
||||
.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;
|
||||
/* 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;
|
||||
@@ -497,36 +591,78 @@ h5 {
|
||||
color: #2b79a2;
|
||||
}
|
||||
.navy .theme-popup {
|
||||
color: #bcbdd0;
|
||||
background: #161923;
|
||||
border: 1px solid #737480;
|
||||
}
|
||||
.navy .theme-popup .theme:hover {
|
||||
background-color: #282e40;
|
||||
}
|
||||
.rust {
|
||||
/* Inline code */
|
||||
color: #262625;
|
||||
background-color: #e1e1db;
|
||||
.navy .theme-popup .default {
|
||||
color: #737480;
|
||||
}
|
||||
.rust :not(pre) > .hljs {
|
||||
.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;
|
||||
}
|
||||
.navy table td {
|
||||
border-color: #1f2331;
|
||||
}
|
||||
.navy table tbody tr:nth-child(2n) {
|
||||
background: #1b1f2b;
|
||||
}
|
||||
.navy table thead {
|
||||
background: #39415b;
|
||||
}
|
||||
.navy table thead td {
|
||||
border: none;
|
||||
}
|
||||
.navy table thead tr {
|
||||
border: 1px #39415b solid;
|
||||
}
|
||||
.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;
|
||||
/* 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;
|
||||
@@ -570,9 +706,62 @@ h5 {
|
||||
color: #2b79a2;
|
||||
}
|
||||
.rust .theme-popup {
|
||||
color: #262625;
|
||||
background: #e1e1db;
|
||||
border: 1px solid #b38f6b;
|
||||
}
|
||||
.rust .theme-popup .theme:hover {
|
||||
background-color: #99908a;
|
||||
}
|
||||
.rust .theme-popup .default {
|
||||
color: #737480;
|
||||
}
|
||||
.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 table td {
|
||||
border-color: #d7d7cf;
|
||||
}
|
||||
.rust table tbody tr:nth-child(2n) {
|
||||
background: #dbdbd4;
|
||||
}
|
||||
.rust table thead {
|
||||
background: #b3a497;
|
||||
}
|
||||
.rust table thead td {
|
||||
border: none;
|
||||
}
|
||||
.rust table thead tr {
|
||||
border: 1px #b3a497 solid;
|
||||
}
|
||||
.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;
|
||||
});
|
||||
|
||||
@@ -99,13 +105,13 @@ $( document ).ready(function() {
|
||||
$('.theme-popup').remove();
|
||||
} else {
|
||||
var popup = $('<div class="theme-popup"></div>')
|
||||
.append($('<div class="theme" id="light">Light (default)<div>'))
|
||||
.append($('<div class="theme" id="light">Light <span class="default">(default)</span><div>'))
|
||||
.append($('<div class="theme" id="rust">Rust</div>'))
|
||||
.append($('<div class="theme" id="coal">Coal</div>'))
|
||||
.append($('<div class="theme" id="navy">Navy</div>'));
|
||||
|
||||
|
||||
$(this).append(popup);
|
||||
popup.insertAfter(this);
|
||||
|
||||
$('.theme').click(function(){
|
||||
var theme = $(this).attr('id');
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BIN
src/theme/favicon.png
Normal file
BIN
src/theme/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta name="description" content="{% block description %}{% endblock %}">
|
||||
<meta name="description" content="{{ description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<base href="{{ path_to_root }}">
|
||||
@@ -12,6 +12,8 @@
|
||||
<link rel="stylesheet" href="book.css">
|
||||
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
|
||||
|
||||
<link rel="shortcut icon" href="{{ favicon }}">
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ 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");
|
||||
pub static FAVICON: &'static [u8] = include_bytes!("favicon.png");
|
||||
pub static JS: &'static [u8] = include_bytes!("book.js");
|
||||
pub static HIGHLIGHT_JS: &'static [u8] = include_bytes!("highlight.js");
|
||||
pub static TOMORROW_NIGHT_CSS: &'static [u8] = include_bytes!("tomorrow-night.css");
|
||||
@@ -28,6 +28,7 @@ pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/
|
||||
pub struct Theme {
|
||||
pub index: Vec<u8>,
|
||||
pub css: Vec<u8>,
|
||||
pub favicon: Vec<u8>,
|
||||
pub js: Vec<u8>,
|
||||
pub highlight_css: Vec<u8>,
|
||||
pub tomorrow_night_css: Vec<u8>,
|
||||
@@ -42,6 +43,7 @@ impl Theme {
|
||||
let mut theme = Theme {
|
||||
index: INDEX.to_owned(),
|
||||
css: CSS.to_owned(),
|
||||
favicon: FAVICON.to_owned(),
|
||||
js: JS.to_owned(),
|
||||
highlight_css: HIGHLIGHT_CSS.to_owned(),
|
||||
tomorrow_night_css: TOMORROW_NIGHT_CSS.to_owned(),
|
||||
@@ -80,6 +82,12 @@ impl Theme {
|
||||
let _ = f.read_to_end(&mut theme.css);
|
||||
}
|
||||
|
||||
// favicon.png
|
||||
if let Ok(mut f) = File::open(&src.join("favicon.png")) {
|
||||
theme.favicon.clear();
|
||||
let _ = f.read_to_end(&mut theme.favicon);
|
||||
}
|
||||
|
||||
// highlight.js
|
||||
if let Ok(mut f) = File::open(&src.join("highlight.js")) {
|
||||
theme.highlight_js.clear();
|
||||
|
||||
@@ -19,3 +19,17 @@ 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;
|
||||
border-collapse: collapse;
|
||||
|
||||
td {
|
||||
padding: 3px 20px;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
thead {
|
||||
td { font-weight: 700; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.theme-popup {
|
||||
position: fixed
|
||||
left: -40px
|
||||
position: relative
|
||||
left: 10px
|
||||
|
||||
z-index: 1000;
|
||||
|
||||
border-radius: 4px
|
||||
font-size: 0.7em
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -75,9 +61,68 @@
|
||||
}
|
||||
|
||||
.theme-popup {
|
||||
color: $fg
|
||||
background: $theme-popup-bg
|
||||
border: 1px solid $theme-popup-border
|
||||
|
||||
.theme:hover { background-color: $theme-hover }
|
||||
|
||||
.default { color: $icons }
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
td {
|
||||
border-color: $table-border-color;
|
||||
}
|
||||
|
||||
// Alternate background colors for rows
|
||||
tbody tr:nth-child(2n) {
|
||||
background: $table-alternate-bg;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: $table-header-bg;
|
||||
td { border: none; }
|
||||
tr { border: 1px $table-header-bg solid; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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,11 @@ $theme-popup-bg = #141617
|
||||
$theme-popup-border = #43484d
|
||||
$theme-hover = #1f2124
|
||||
|
||||
$quote-bg = #242637
|
||||
$quote-border = lighten($quote-bg, 5%)
|
||||
|
||||
$table-border-color = lighten($bg, 5%)
|
||||
$table-header-bg = lighten($bg, 20%)
|
||||
$table-alternate-bg = lighten($bg, 3%)
|
||||
|
||||
@import 'base'
|
||||
|
||||
@@ -18,4 +18,11 @@ $theme-popup-bg = #fafafa
|
||||
$theme-popup-border = #cccccc
|
||||
$theme-hover = #e6e6e6
|
||||
|
||||
$quote-bg = #f2f7f9
|
||||
$quote-border = darken($quote-bg, 5%)
|
||||
|
||||
$table-border-color = darken($bg, 5%)
|
||||
$table-header-bg = darken($bg, 20%)
|
||||
$table-alternate-bg = darken($bg, 3%)
|
||||
|
||||
@import 'base'
|
||||
|
||||
@@ -18,4 +18,11 @@ $theme-popup-bg = #161923
|
||||
$theme-popup-border = #737480
|
||||
$theme-hover = #282e40
|
||||
|
||||
$quote-bg = #262933
|
||||
$quote-border = lighten($quote-bg, 5%)
|
||||
|
||||
$table-border-color = lighten($bg, 5%)
|
||||
$table-header-bg = lighten($bg, 20%)
|
||||
$table-alternate-bg = lighten($bg, 3%)
|
||||
|
||||
@import 'base'
|
||||
|
||||
@@ -18,4 +18,11 @@ $theme-popup-bg = #e1e1db
|
||||
$theme-popup-border = #b38f6b
|
||||
$theme-hover = #99908a
|
||||
|
||||
$quote-bg = #c1c1bb
|
||||
$quote-border = darken($quote-bg, 5%)
|
||||
|
||||
$table-border-color = darken($bg, 5%)
|
||||
$table-header-bg = #b3a497
|
||||
$table-alternate-bg = darken($bg, 3%)
|
||||
|
||||
@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