mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 11:24:57 -05:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
18
Cargo.toml
18
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.0.4"
|
||||
version = "0.0.8"
|
||||
authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
|
||||
description = "create books from markdown files (like Gitbook)"
|
||||
documentation = "http://azerupi.github.io/mdBook/index.html"
|
||||
@@ -18,21 +18,13 @@ exclude = [
|
||||
clap = "~1.5.3"
|
||||
handlebars = "~0.12.0"
|
||||
rustc-serialize = "~0.3.16"
|
||||
pulldown-cmark = "~0.0.3"
|
||||
|
||||
pulldown-cmark = "~0.0.6"
|
||||
|
||||
# Watch feature
|
||||
[dependencies.notify]
|
||||
notify = "^2.4.1"
|
||||
optional = true
|
||||
notify = { version = "~2.4.1", 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]
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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,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,8 @@ h5 {
|
||||
right: 15px;
|
||||
}
|
||||
.theme-popup {
|
||||
position: fixed;
|
||||
left: -40px;
|
||||
position: relative;
|
||||
left: 10px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
@@ -285,29 +296,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 +351,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 +466,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 +581,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 +696,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,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,6 +1,6 @@
|
||||
.theme-popup {
|
||||
position: fixed
|
||||
left: -40px
|
||||
position: relative
|
||||
left: 10px
|
||||
|
||||
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