Compare commits

...

83 Commits

Author SHA1 Message Date
Mathieu David
80deac90d9 Merge branch 'master' of https://github.com/azerupi/mdBook 2016-03-01 18:33:55 +01:00
Mathieu David
625f5081fa update notify and change dependency version restrictions 2016-03-01 18:32:43 +01:00
Mathieu David
3e8151e8e3 Merge pull request #112 from jessestricker/feature-meta
Add description config option
2016-02-25 17:21:51 +01:00
Jesse Stricker
3c10a85735 Add documentation and example for description config 2016-02-25 15:01:16 +01:00
Jesse Stricker
330b1ad55d Add description config option 2016-02-25 14:32:49 +01:00
Mathieu David
f24eb59753 Bump version number, v0.0.9 has been published to Crates.io 2016-02-22 19:03:31 +01:00
Mathieu David
01c5085725 Add an entry about the favicon in the docs 2016-02-22 18:01:36 +01:00
Mathieu David
9f17be2c32 Merge pull request #109 from jessestricker/feature-favicon
Add theme support for favicon
2016-02-22 17:28:46 +01:00
Jesse Stricker
88fabd76f0 Copy favicon on 'init --theme' 2016-02-22 17:20:54 +01:00
Jesse Stricker
f508db6113 Add favicon support to theme 2016-02-22 17:17:07 +01:00
Jesse Stricker
1083d1822d Add default favicon.png 2016-02-22 16:59:53 +01:00
Mathieu David
fc86b963bb Merge pull request #108 from funkill/styles
add rounding for first and last items in theme selector
2016-02-16 10:30:01 +01:00
funkill
f2b913c9dd add rounding for first and last items in theme selector 2016-02-16 11:15:08 +03:00
Mathieu David
dd0cfc14d4 bump version number to 0.0.9, v0.0.8 has been published to crates.io 2016-02-16 08:57:55 +01:00
Mathieu David
5891e4b5db Fix bug where theme-popup was under the navigation arrows making it impossible to change the theme 2016-02-16 08:50:57 +01:00
Mathieu David
394023f617 Bump version number from 0.0.7 to 0.0.8, version 0.0.7 has been published to Crates.io 2016-02-15 21:20:07 +01:00
Mathieu David
39a6fe4b3c Fix wildcard dependency on crossbeam 2016-02-15 21:18:43 +01:00
Mathieu David
75b98d7019 Merge pull request #106 from funkill/pulldown-mark-update
pulldown-mark version bump
2016-02-15 21:01:21 +01:00
funkill
814b21ad94 pulldown-mark version bump 2016-02-15 21:25:46 +03:00
Mathieu David
7364d41f0c Style tables, different header bg, alternate row color and border 2016-02-05 18:09:35 +01:00
Mathieu David
f6be4a7d7e Merge branch 'master' of https://github.com/azerupi/mdBook 2016-02-03 18:02:23 +01:00
Mathieu David
0b00c270d5 Fix a style bug caused by the insertion of the theme-popup div inside font awesome icon <i>
The div is now inserted after the <i>, the text color has also been changed to the foreground color and the "(default)" text that indicates the default theme is now grey to contrast with the theme name

Fixes #97
2016-02-03 17:55:19 +01:00
Mathieu David
8d2ca521c0 Merge pull request #100 from yurrriq/patch-1
Fix "it's" typos in README.md
2016-01-28 11:58:17 +01:00
Eric Bailey
276cd8d490 Update README.md
Replace two contractions with possessive pronouns: it's => its.
2016-01-28 00:55:25 -06:00
Mathieu David
e958fc8605 update readme 2016-01-13 22:40:30 +01:00
Mathieu David
e286b208da Bump version from 0.0.6 to 0.0.7, v0.0.6 has been published to Crates.io 2016-01-03 14:18:36 +01:00
Mathieu David
3fd1d4606c Fix tests after removing PathExt from utils 2016-01-03 14:08:17 +01:00
Mathieu David
78b6148463 Basic formatting for tables + Styling for blockquotes
Added basic formatting for tables so that they have some padding and are aligned in the center of the page.
I did not add color or borders because I am not sure how tables should look like.

A lot of people in IntermezzOS want asides, blockquotes are probably the easiest way to do that. I have thus styled blockquotes for all the color themes.
2016-01-03 13:47:59 +01:00
Mathieu David
78e1897b47 Remove code that has better equivalent in std
Path_Ext has been stabilized in the Standard Library, the temporary copy I had can go.

I found a fs::create_dir_all method that does exactly what create_path was doing, but better... create_path is thus replaced with that.
2016-01-03 13:02:04 +01:00
Mathieu David
d000fc8bac Updated pulldown-cmark to version 0.0.5
Version 0.0.5 contains table and footnotes support, both options are now enabled in mdBook.
2016-01-03 12:02:39 +01:00
Mathieu David
5170e6b675 Fix #89, bug introduced earlier where all headers are black in all color themes 2016-01-01 11:02:24 +01:00
Mathieu David
a7f329d337 Add href to heading anchors so that the url for the anchor is displayed in the url bar when clicking the header 2016-01-01 02:17:40 +01:00
Mathieu David
bb0c878e06 #29 update doc with an example of runnable rust code 2016-01-01 01:57:21 +01:00
Mathieu David
2a7463c45b #29 Add a way to escape {{#playpen ... } using a backslash in front: \{{#playpen ... }} 2016-01-01 01:40:37 +01:00
Mathieu David
db7424e947 Continue #29, playpens are now runnable 2016-01-01 00:32:12 +01:00
Mathieu David
0ac0301d72 Continue #29, Rust files can now be loaded with {{#playpen file.rs}}, they will be displayed as other code snippets included with markdown backticks except they have a playpen css class 2015-12-31 19:25:02 +01:00
Mathieu David
38b2dee17e Continue #29 Check that the rust file exists and read to string 2015-12-31 14:14:56 +01:00
Mathieu David
0cb234de5d Add tests for find_playpens 2015-12-31 12:02:25 +01:00
Mathieu David
ee4a7fb35c Start implementing #29 support for embedding playpen, implemented the function that parses the markdown to find playpen links 2015-12-30 22:40:23 +01:00
Mathieu David
9c5c8a6804 Bump version to 0.0.6, v0.0.5 has been published to Crates.io 2015-12-30 17:26:04 +01:00
Mathieu David
ae6334f358 Fix bug where we would not check if there was actually a page to navigate to when using arrow keys 2015-12-30 17:19:43 +01:00
Mathieu David
98cd2f0c27 Version bump to 0.0.5, v0.0.4 has been published to crates.io 2015-12-30 17:05:09 +01:00
Mathieu David
600eb02fee Fix bug introduced earlier, where navigation arrows would become blue when visited + make the tooltip on nvigation arrows a little clearer 2015-12-30 16:48:46 +01:00
Mathieu David
41462e8b2d Merge pull request #87 from funkill/keys_navigation
add navigation by arrows
2015-12-30 16:37:09 +01:00
Istratov D. S
43eef7637a add navigation by arrows 2015-12-30 18:30:08 +03:00
Mathieu David
dc8f6cd5e9 Add contributors to the doc + set visisted links to the same color as normal links 2015-12-30 15:59:18 +01:00
Mathieu David
5b9d8ee6ac Fix #83, spacing is reduced between two consecutive headings 2015-12-30 15:41:49 +01:00
Mathieu David
8a4d744dc1 Merge branch 'master' of https://github.com/azerupi/mdBook 2015-12-30 15:04:43 +01:00
Mathieu David
4f583dfea9 Update documentation, Closes #80 2015-12-30 15:04:24 +01:00
Mathieu David
678a0906db Merge pull request #86 from azerupi/revert-85-keys_navigation
Revert "Add navigation by keyboard using alt + left/right arrows"
2015-12-30 14:39:21 +01:00
Mathieu David
f47d420811 Revert "Add navigation by keyboard using alt + left/right arrows" 2015-12-30 14:38:00 +01:00
Mathieu David
5ffee8144b Merge pull request #85 from funkill/keys_navigation
Add navigation by keyboard using alt + left/right arrows
2015-12-30 14:32:54 +01:00
Istratov D. S
e9e8b4239e add navigation by keyboard using alt + left/right arrows 2015-12-30 16:09:59 +03:00
Mathieu David
0a0a96808d Merge branch 'master' of https://github.com/azerupi/mdBook 2015-12-30 00:51:05 +01:00
Mathieu David
2d00f40a24 Tweak css for inline code blocks in sidebar 2015-12-30 00:50:22 +01:00
Mathieu David
e40b293336 Fix #70 render inline code blocks in the sidebar 2015-12-30 00:46:55 +01:00
Mathieu David
6c94ba8a88 Merge pull request #82 from steveklabnik/docs
mdbook test documentation
2015-12-30 00:12:21 +01:00
Steve Klabnik
eed440ef5a fix mdbook test for the book 2015-12-29 17:59:08 -05:00
Steve Klabnik
e8436d2fd2 Add some documentation for mdbook test 2015-12-29 17:58:56 -05:00
Mathieu David
b40688c880 Merge branch 'master' into watch-command 2015-12-29 13:40:13 +01:00
Mathieu David
71213f40da Add expand/collapse button to show and hide the hidden code lines 2015-12-29 13:08:25 +01:00
Mathieu David
0620ef1f47 Hides rust code lines prepended with # 2015-12-29 12:26:32 +01:00
Mathieu David
d6d0979ecf The code on the lines prepended with a # are hidden, the space of the line remains because of the '\n' in <pre> tag 2015-12-28 23:52:05 +01:00
Mathieu David
159b300067 Merge branch 'master' into hide-rust-js 2015-12-28 16:40:56 +01:00
Mathieu David
0dd6a17187 Fix some small things in javascript 2015-12-28 16:39:14 +01:00
Mathieu David
f9b6e09c26 Merge pull request #79 from asolove/72-auto-anchor
Add anchors around all headers in the content.
2015-12-28 16:29:55 +01:00
asolove
4dfa15cffa Update .styl file. Ran the compile and it results in exactly what I did by hand, d'oh. 2015-12-27 21:13:31 -07:00
Mathieu David
7762475b33 Merge pull request #78 from asolove/76-newlines-in-index
Add newlines back in to generated index.html files.
2015-12-27 23:56:56 +01:00
asolove
0ab8a73ba2 Add anchors around all headers in the content.
- Just uses the header's text as its anchor name. Spaces work. Scrolling to the anchor works even when the anchor is added after the dom loads.
- Adjust theme css to only style links, not <a> tags used as anchors.
2015-12-27 15:17:59 -07:00
asolove
5b289c1303 Fix 0ffd638 with smarter way to join with linebreaks. 2015-12-27 14:24:42 -07:00
asolove
0ffd638904 Add newlines back in to generated index.html files. 2015-12-27 14:10:13 -07:00
Mathieu David
50504282fb Merge pull request #77 from asolove/scroll-sidebar-to-active-section
On page load, scroll sidebar to active section. Resolves #21.
2015-12-27 10:50:04 +01:00
asolove
1de00f9cd7 On page load, scroll sidebar to active section. 2015-12-26 20:45:50 -07:00
Mathieu David
a2b25232d3 Merge pull request #75 from mdinger/non_pre
Generalize inline code to all themes
2015-12-23 00:50:58 +01:00
mdinger
b1265862c7 Generalize inline code to all themes 2015-12-22 16:30:05 -05:00
Mathieu David
f1cd9f54c2 Fixes rust-lang/book#29 where the navigation arrow for next chapter was displayed on top of the scroll bar making it unusable 2015-12-17 17:34:24 +01:00
Mathieu David
95d82a924f Merge pull request #71 from steveklabnik/mdbook_test
Add initial support for running rustdoc to test rust code snippets, until a more generic test "system" is implemented
2015-12-15 22:00:36 +01:00
Steve Klabnik
6bcc592ed9 Implement 'mdbook test'
Fixes #69
2015-12-15 13:56:24 -05:00
Mathieu David
4ca6693a48 Update handlebars from 0.11.x to 0.12.x 2015-12-15 18:58:34 +01:00
Mathieu David
d376b0663a Bumped version to 0.0.4 (after publishing 0.0.3 to crates.io) + updated README to use cargo install for the installation 2015-12-11 22:17:05 +01:00
Mathieu David
22b6448381 Merge branch 'master' into watch-command 2015-11-10 16:33:25 +01:00
Mathieu David
cdbb2ee5fd Watch builds are now spawned in new threads (using crossbeam) and there is a timelock, preventing multiple builds being triggered in less than a second 2015-11-09 14:31:00 +01:00
Mathieu David
522eef9296 first implementation of the watch sub-command. #61 Needs refining, bug in notify made me use recursion, afraid of hitting the max recursion limit... 2015-09-27 14:38:37 +02:00
41 changed files with 1331 additions and 239 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook"
version = "0.0.3"
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"
@@ -11,22 +11,32 @@ readme = "README.md"
build = "build.rs"
exclude = [
"book-example/*",
"src/theme/stylus",
]
[dependencies]
clap = "~1.5.3"
handlebars = "=0.11.2"
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
notify = { version = "2.5.4", optional = true }
time = { version = "0.1.33", optional = true }
crossbeam = { version = "0.2.8", optional = true }
# Tests
[dev-dependencies]
tempdir = "~0.3.4"
tempdir = "0.3.4"
[features]
default = ["output"]
default = ["output", "watch"]
debug = []
output = []
regenerate-css = []
watch = ["notify", "time", "crossbeam"]
[[bin]]
doc = false

View File

@@ -1,83 +1,92 @@
# mdBook [![Travis-CI](https://travis-ci.org/azerupi/mdBook.svg?branch=master)](https://travis-ci.org/azerupi/mdBook) [![Crates.io version](https://img.shields.io/crates/v/mdbook.svg)](https://crates.io/crates/mdbook) [![License](https://img.shields.io/crates/l/mdbook.svg)](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
```
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 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:
```
git clone --depth 1 https://github.com/azerupi/mdBook.git
```
Then `cd` into the directory and run:
```
git clone --depth=1 https://github.com/azerupi/mdBook.git
cd mdBook
cargo build --release
```
The executable `mdbook` will be in the `./target/release` folder, this should be added to the path.
The executable will be in `./target/release/mdbook`.
If you want to regenerate the css (stylesheet), make sure that you installed `stylus` and `nib` from `npm` because it is used to compile the stylesheets
Install [node.js](https://nodejs.org/en/)
```
npm install -g stylus nib
```
## Usage
Then build with the `regenerate-css` feature:
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.
```
cargo build --release --features="regenerate-css"
```
Here are the main commands you will want to run, for a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/).
## Structure
- `mdbook init`
There are two main parts of this project:
The init command will create a directory with the minimal boilerplate to start with.
- **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.
```
book-test/
├── book
└── src
├── chapter_1.md
└── SUMMARY.md
```
### Command line interface
`book` and `src` are both directories. `src` contains the markdown files that will be used to render the ouput to the `book` directory.
#### init
Please, take a look at the [**Documentation**](http://azerupi.github.io/mdBook/cli/init.html) for more information and some neat tricks.
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.
- `mdbook build`
Please, take a look at the [**Documentation**](http://azerupi.github.io/mdBook/cli/init.html) for more information.
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.
#### build
- `mdbook watch`
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)
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

View File

@@ -1,4 +1,5 @@
{
"title": "mdBook Documentation",
"description": "Create book from markdown files. Like Gitbook but implemented in Rust",
"author": "Mathieu David"
}

View File

@@ -4,12 +4,16 @@
- [Command Line Tool](cli/cli-tool.md)
- [init](cli/init.md)
- [build](cli/build.md)
- [watch](cli/watch.md)
- [test](cli/test.md)
- [Format](format/format.md)
- [SUMMARY.md](format/summary.md)
- [Configuration](format/config.md)
- [Theme](format/theme/theme.md)
- [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)

View File

@@ -2,7 +2,7 @@
The build command is used to render your book:
```
```bash
mdbook build
```
@@ -17,7 +17,7 @@ convenience. Large books will therefore remain structured when rendered.
Like `init`, the `build` command can take a directory as argument to use instead of the
current working directory.
```
```bash
mdbook build path/to/book
```

View File

@@ -5,14 +5,28 @@ Let's focus on the command line tool capabilities first.
## Install
At the moment, the only way to install mdBook is by downloading the source code from Github and building it yourself. Fortunately
this is made very easy with Cargo.
### Pre-requisite
If you haven't already, you should begin by installing [Rust](https://www.rust-lang.org/install.html) and [Git](https://git-scm.com/downloads)
mdBook is written in **[Rust](https://www.rust-lang.org/)** and therefore needs to be compiled with **Cargo**, because we don't yet offer ready-to-go binaries. If you haven't already installed Rust, please go ahead and [install it](https://www.rust-lang.org/downloads.html) now.
Open your terminal and navigate to the directory of you choice. We need to clone the git repository and then build it with Cargo.
### Install Crates.io version
Installing mdBook is relatively easy if you already have Rust and Cargo installed. You just have to type this snippet in your terminal:
```bash
cargo install mdbook
```
This will fetch the source code from [Crates.io](https://crates.io/) and compile it. You will have to add Cargo's `bin` directory to your `PATH`.
Run `mdbook help` in your terminal to verify if it works. Congratulations, you have installed mdBook!
### Install Git version
The **[git version](https://github.com/azerupi/mdBook)** contains all the latest bug-fixes and features, that will be released in the next version on **Crates.io**, if you can't wait until the next release. You can build the git version yourself. Open your terminal and navigate to the directory of you choice. We need to clone the git repository and then build it with Cargo.
```bash
git clone --depth=1 https://github.com/azerupi/mdBook.git
cd mdBook
cargo build --release

View File

@@ -1,17 +1,14 @@
# The init command
There is some minimal boilerplate that is the same for every new book. It's for this purpose that mdBook includes an `init` command.
The `init` command is used like this:
```
```bash
mdbook init
```
It will create a couple of files and directories in the working directory so that you can
spend more time writing your book and less setting it up.
The files set up for you are the following:
```
When using the `init` command for the first time, a couple of files will be set up for you:
```bash
book-test/
├── book
└── src
@@ -19,22 +16,23 @@ book-test/
└── SUMMARY.md
```
The `src` directory is were you write your book in markdown. It contains all the source files,
- The `src` directory is were you write your book in markdown. It contains all the source files,
configuration files, etc.
The `book` directory is where your book is rendered. All the output is ready to be uploaded
to a server to be seen by the internet.
- The `book` directory is where your book is rendered. All the output is ready to be uploaded
to a server to be seen by your audience.
The `SUMMARY.md` file is the most important file, it's the skeleton of your book and is discussed in more detail in another [chapter](../format/summary.html).
- The `SUMMARY.md` file is the most important file, it's the skeleton of your book and is discussed in more detail in another [chapter](../format/summary.html).
When a `SUMMARY.md` file already exists, the `init` command will generate the files according to the paths used in the `SUMMARY.md`
#### Tip & Trick: Hidden Feature
When a `SUMMARY.md` file already exists, the `init` command will first parse it and generate the missing files according to the paths used in the `SUMMARY.md`. This allows you to think and create the whole structure of your book and then let mdBook generate it for you.
#### Specify a directory
When using the `init` command, you can also specify a directory, instead of using the current working directory,
by appending a path to the command:
```
```bash
mdbook init path/to/book
```

View File

@@ -0,0 +1,19 @@
# The test command
When writing a book, you sometimes need to automate some tests. For example, [The Rust Programming Book](https://doc.rust-lang.org/stable/book/) uses a lot of code examples that could get outdated.
Therefore it is very important for them to be able to automatically test these code examples.
mdBook supports a `test` command that will run all available tests in mdBook. At the moment, only one test is available:
*"Test Rust code examples using Rustdoc"*, but I hope this will be expanded in the future to include more tests like:
- checking for broken links
- checking for unused files
- ...
In the future I would like the user to be able to enable / disable test from the `book.json` configuration file and support custom tests.
**How to use it:**
```bash
$ mdbook test
[*]: Testing file: "/mdBook/book-example/src/README.md”
```

View File

@@ -0,0 +1,18 @@
# The watch command
The `watch` command is useful when you want your book to be rendered on every file change.
You could repeatedly issue `mdbook build` every time a file is changed. But using `mdbook watch` once will watch your files and will trigger a build automatically whenever you modify a file.
#### Specify a directory
Like `init` and `build`, `watch` can take a directory as argument to use instead of the
current working directory.
```bash
mdbook watch path/to/book
```
-----
***note:*** *the `watch` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/azerupi/mdBook/issues)*

View File

@@ -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*

View 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");
}

View File

@@ -0,0 +1,21 @@
# MathJax Support
mdBook supports math equations through [MathJax](https://www.mathjax.org/).
**However the normal method for indication math equations with `$$` does not work (yet?).**
To indicate an inline equation \\( \int x = \frac{x^2}{2} \\) use
```
\\( \int x = \frac{x^2}{2} \\)
```
To indicate a block equation
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
use
```
\\[ \mu = \frac{1}{N} \sum_{i=0} x_i \\]
```

View 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}}

View File

@@ -5,12 +5,51 @@ For syntax highlighting I use [Highlight.js](https://highlightjs.org) with a cus
Automatic language detection has been turned off, so you will probably want to
specify the programming language you use like this
<pre class="language-markdown"><code class="language-markdown">```rust
<pre><code class="language-markdown">```rust
fn main() {
// Some code
}
```</code></pre>
## Custom theme
Like the rest of the theme, the files used for syntax highlighting can be overridden with your own.
- ***highlight.js*** normally you shouldn't have to overwrite this file, unless you want to use a more recent version.
- ***highlight.css*** theme used by highlight.js for syntax highlighting.
If you want to use another theme for `highlight.js` download it from their website, or make it yourself,
rename it to `highlight.css` and put it in `src/theme` (or the equivalent if you changed your source folder)
Now your theme will be used instead of the default theme.
## 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);
#}
```
**At the moment, this only works for code examples that are annotated with `rust`. Because it would collide with semantics of some programming languages. In the future, we want to make this configurable through the `book.json` so that everyone can benefit from it.**
## Improve default theme
If you think the default theme doesn't look quite right for a specific language, or could be improved.
@@ -19,15 +58,3 @@ Feel free to [submit a new issue](https://github.com/azerupi/mdBook/issues) expl
You could also create a pull-request with the proposed improvements.
Overall the theme should be light and sober, without to many flashy colors.
## Custom theme
Like the rest of the theme, the files used for syntax highlighting can be overwritten with your own.
- ***highlight.js*** normally you shouldn't have to overwrite this file. But if you need to, you can.
- ***highlight.css*** theme used by highlight.js for syntax highlighting.
If you want to use another theme for `highlight.js` download it from their website, or make it yourself,
rename it to `highlight.css` and put it in `src/theme` (or the equivalent if you changed your source folder)
Now your theme will be used instead of the default theme.

View File

@@ -1,18 +1,22 @@
# Theme
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to render your markdown files in and comes with a default theme
The default renderer uses a [handlebars](http://handlebarsjs.com/) template to render your markdown files and comes with a default theme
included in the mdBook binary.
But the theme is totally customizable, you can replace every file from the theme by your own by adding a
`theme` directory in your source folder. Create a new file with the name of the file you want to overwrite
The theme is totally customizable, you can selectively replace every file from the theme by your own by adding a
`theme` directory in your source folder. Create a new file with the name of the file you want to override
and now that file will be used instead of the default file.
Here are the files you can overwrite:
Here are the files you can override:
- ***index.hbs*** is the handlebars template.
- ***book.css*** is the style used in the output. If you want to change the design of your book, this is probably the file you want to modify. Sometimes in conjunction with `index.hbs` when you want to radically change the layout.
- ***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
***Note:*** *When you overwrite a file, it is possible that you break some functionality. Therefore I recommend to use the file from the default theme as template and only add / modify what you need. You can copy the default theme into your source directory automatically by using `mdbook init --theme`.*
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.
**Note:** When you override a file, it is possible that you break some functionality. Therefore I recommend to use the file from the default theme as template and only add / modify what you need. You can copy the default theme into your source directory automatically by using `mdbook init --theme` just remove the files you don't want to override.

View File

@@ -3,7 +3,7 @@
mdBook is not only a command line tool, it can be used as a crate. You can extend it,
integrate it in current projects. Here is a short example:
```rust
```rust,ignore
extern crate mdbook;
use mdbook::MDBook;

View File

@@ -1 +1,13 @@
# Contributors
Here is a list of the contributors who have helped improving mdBook. Big shout-out to them!
If you have contributed to mdBook and I forgot to add you, don't hesitate to add yourself to the list. If you are in the list, feel free to add your real name & contact information if you wish.
- [mdinger](https://github.com/mdinger)
- Kevin ([kbknapp](https://github.com/kbknapp))
- Steve Klabnik ([steveklabnik](https://github.com/steveklabnik))
- Adam Solove ([asolove](https://github.com/asolove))
- Wayne Nilsen ([waynenilsen](https://github.com/waynenilsen))
- [funnkill](https://github.com/funkill)
- Fu Gangqiang ([FuGangqiang](https://github.com/FuGangqiang))

View File

@@ -1,6 +1,15 @@
#[macro_use]
extern crate mdbook;
#[macro_use]
extern crate clap;
extern crate crossbeam;
// Dependencies for the Watch feature
#[cfg(feature = "watch")]
extern crate notify;
#[cfg(feature = "watch")]
extern crate time;
use std::env;
use std::error::Error;
@@ -9,6 +18,13 @@ use std::path::{Path, PathBuf};
use clap::{App, ArgMatches, SubCommand};
// Uses for the Watch feature
#[cfg(feature = "watch")]
use notify::Watcher;
#[cfg(feature = "watch")]
use std::sync::mpsc::channel;
use mdbook::MDBook;
const NAME: &'static str = "mdbook";
@@ -32,14 +48,19 @@ fn main() {
.about("Build the book from the markdown files")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
.subcommand(SubCommand::with_name("watch")
.about("Watch the files for changes"))
.about("Watch the files for changes")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
.subcommand(SubCommand::with_name("test")
.about("Test that code samples compile"))
.get_matches();
// Check which subcomamnd the user ran...
let res = match matches.subcommand() {
("init", Some(sub_matches)) => init(sub_matches),
("build", Some(sub_matches)) => build(sub_matches),
("watch", _) => unimplemented!(),
#[cfg(feature = "watch")]
("watch", Some(sub_matches)) => watch(sub_matches),
("test", Some(sub_matches)) => test(sub_matches),
(_, _) => unreachable!()
};
@@ -48,6 +69,8 @@ fn main() {
}
}
// Simple function that user comfirmation
fn confirm() -> bool {
io::stdout().flush().unwrap();
let mut s = String::new();
@@ -58,6 +81,8 @@ fn confirm() -> bool {
}
}
// Init command implementation
fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
@@ -95,6 +120,8 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
Ok(())
}
// Build command implementation
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config();
@@ -104,6 +131,88 @@ fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
Ok(())
}
// Watch command implementation
#[cfg(feature = "watch")]
fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let book = MDBook::new(&book_dir).read_config();
// Create a channel to receive the events.
let (tx, rx) = channel();
let w: Result<notify::RecommendedWatcher, notify::Error> = notify::Watcher::new(tx);
match w {
Ok(mut watcher) => {
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.get_src()) {
println!("Error while watching {:?}:\n {:?}", book.get_src(), e);
::std::process::exit(0);
};
// Add the book.json file to the watcher if it exists, because it's not
// located in the source directory
if let Err(_) = watcher.watch(book_dir.join("book.json")) {
// do nothing if book.json is not found
}
let previous_time = time::get_time().sec;
crossbeam::scope(|scope| {
loop {
match rx.recv() {
Ok(event) => {
// Skip the event if an event has already been issued in the last second
if time::get_time().sec - previous_time < 1 { continue }
if let Some(path) = event.path {
// Trigger the build process in a new thread (to keep receiving events)
scope.spawn(move || {
println!("File changed: {:?}\nBuilding book...\n", path);
match build(args) {
Err(e) => println!("Error while building: {:?}", e),
_ => {}
}
println!("");
});
} else {
continue;
}
},
Err(e) => {
println!("An error occured: {:?}", e);
}
}
}
});
},
Err(e) => {
println!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(0);
}
}
Ok(())
}
fn test(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config();
try!(book.test());
Ok(())
}
fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(dir) = args.value_of("dir") {
// Check if path is relative from current dir, or absolute...

View File

@@ -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"]) {

View File

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

View File

@@ -1,6 +1,5 @@
extern crate handlebars;
extern crate rustc_serialize;
extern crate pulldown_cmark;
use renderer::html_handlebars::helpers;
use renderer::Renderer;
@@ -9,14 +8,14 @@ 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;
use self::handlebars::{Handlebars, JsonRender};
use self::rustc_serialize::json::{Json, ToJson};
use self::pulldown_cmark::{Parser, html};
pub struct HtmlHandlebars;
@@ -51,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")))
}
@@ -72,8 +71,13 @@ 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 = render_html(&content);
content = utils::render_markdown(&content);
print_content.push_str(&content);
// Remove content from previous file and render content for this one
@@ -114,7 +118,7 @@ impl Renderer for HtmlHandlebars {
// This could cause a problem when someone displays code containing <base href=...>
// on the front page, however this case should be very very rare...
content = content.lines().filter(|line| !line.contains("<base href=")).collect();
content = content.lines().filter(|line| !line.contains("<base href=")).collect::<Vec<&str>>().join("\n");
try!(index_file.write_all(content.as_bytes()));
@@ -155,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"]));
@@ -204,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![];
@@ -241,10 +280,3 @@ fn make_data(book: &MDBook) -> Result<BTreeMap<String,Json>, Box<Error>> {
debug!("[*]: JSON constructed");
Ok(data)
}
fn render_html(text: &str) -> String {
let mut s = String::with_capacity(text.len() * 3 / 2);
let p = Parser::new(&text);
html::push_html(&mut s, p);
s
}

View File

@@ -1,2 +1,3 @@
pub mod navigation;
pub mod toc;
pub mod playpen;

View File

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

View File

@@ -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},
]);
}

View File

@@ -1,11 +1,13 @@
extern crate handlebars;
extern crate rustc_serialize;
extern crate pulldown_cmark;
use std::path::Path;
use std::collections::BTreeMap;
use self::rustc_serialize::json;
use self::handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper, Context};
use self::pulldown_cmark::{Parser, html, Event, Tag};
// Handlebars helper to construct TOC
#[derive(Clone, Copy)]
@@ -93,7 +95,24 @@ impl HelperDef for RenderToc {
}
if let Some(name) = item.get("name") {
try!(rc.writer.write(name.as_bytes()));
// Render only inline code blocks
// filter all events that are not inline code blocks
let parser = Parser::new(&name).filter(|event|{
match event {
&Event::Start(Tag::Code) | &Event::End(Tag::Code) => true,
&Event::InlineHtml(_) => true,
&Event::Text(_) => true,
_ => false,
}
});
// render markdown to html
let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
try!(rc.writer.write(markdown_parsed_name.as_bytes()));
}
if path_exists {

View File

@@ -9,10 +9,33 @@ body {
.right {
float: right;
}
.hidden {
display: none;
}
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;
}
table td {
padding: 3px 20px;
border: 1px solid;
}
table thead td {
font-weight: 700;
}
.sidebar {
position: absolute;
left: 0;
@@ -37,6 +60,9 @@ h3 {
left: -300px;
}
}
.sidebar code {
line-height: 2em;
}
.sidebar-hidden .sidebar {
left: -300px;
}
@@ -222,11 +248,12 @@ h3 {
left: 0;
}
.next {
right: 0;
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;
@@ -237,6 +264,15 @@ h3 {
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;
@@ -272,6 +308,16 @@ h3 {
.light {
color: #333;
background-color: #fff;
/* Inline code */
}
.light .content .header:link,
.light .content .header:visited {
color: #333;
pointer: cursor;
}
.light .content .header:link:hover,
.light .content .header:visited:hover {
text-decoration: none;
}
.light .sidebar {
background-color: #fafafa;
@@ -292,8 +338,11 @@ h3 {
background-color: #f4f4f4;
}
.light .menu-bar,
.light .menu-bar:visited,
.light .nav-chapters,
.light .mobile-nav-chapters {
.light .nav-chapters:visited,
.light .mobile-nav-chapters,
.light .mobile-nav-chapters:visited {
color: #ccc;
}
.light .menu-bar i:hover,
@@ -307,19 +356,83 @@ h3 {
.light .mobile-nav-chapters {
background-color: #fafafa;
}
.light .content a {
.light .content a:link,
.light a:visited {
color: #4183c4;
}
.light .theme-popup {
color: #333;
background: #fafafa;
border: 1px solid #ccc;
}
.light .theme-popup .theme:hover {
background-color: #e6e6e6;
}
.light .theme-popup .default {
color: #ccc;
}
.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;
}
.light pre {
position: relative;
}
.light pre > .buttons {
position: absolute;
right: 5px;
top: 5px;
color: #364149;
cursor: pointer;
}
.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;
@@ -340,8 +453,11 @@ h3 {
background-color: #393939;
}
.coal .menu-bar,
.coal .menu-bar:visited,
.coal .nav-chapters,
.coal .mobile-nav-chapters {
.coal .nav-chapters:visited,
.coal .mobile-nav-chapters,
.coal .mobile-nav-chapters:visited {
color: #43484d;
}
.coal .menu-bar i:hover,
@@ -355,19 +471,83 @@ h3 {
.coal .mobile-nav-chapters {
background-color: #292c2f;
}
.coal .content a {
.coal .content a:link,
.coal a:visited {
color: #2b79a2;
}
.coal .theme-popup {
color: #98a3ad;
background: #141617;
border: 1px solid #43484d;
}
.coal .theme-popup .theme:hover {
background-color: #1f2124;
}
.coal .theme-popup .default {
color: #43484d;
}
.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;
}
.coal pre {
position: relative;
}
.coal pre > .buttons {
position: absolute;
right: 5px;
top: 5px;
color: #a1adb8;
cursor: pointer;
}
.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;
@@ -388,8 +568,11 @@ h3 {
background-color: #2d334f;
}
.navy .menu-bar,
.navy .menu-bar:visited,
.navy .nav-chapters,
.navy .mobile-nav-chapters {
.navy .nav-chapters:visited,
.navy .mobile-nav-chapters,
.navy .mobile-nav-chapters:visited {
color: #737480;
}
.navy .menu-bar i:hover,
@@ -403,19 +586,83 @@ h3 {
.navy .mobile-nav-chapters {
background-color: #282d3f;
}
.navy .content a {
.navy .content a:link,
.navy a:visited {
color: #2b79a2;
}
.navy .theme-popup {
color: #bcbdd0;
background: #161923;
border: 1px solid #737480;
}
.navy .theme-popup .theme:hover {
background-color: #282e40;
}
.navy .theme-popup .default {
color: #737480;
}
.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;
}
.navy pre {
position: relative;
}
.navy pre > .buttons {
position: absolute;
right: 5px;
top: 5px;
color: #c8c9db;
cursor: pointer;
}
.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;
@@ -436,8 +683,11 @@ h3 {
background-color: #45373a;
}
.rust .menu-bar,
.rust .menu-bar:visited,
.rust .nav-chapters,
.rust .mobile-nav-chapters {
.rust .nav-chapters:visited,
.rust .mobile-nav-chapters,
.rust .mobile-nav-chapters:visited {
color: #737480;
}
.rust .menu-bar i:hover,
@@ -451,13 +701,67 @@ h3 {
.rust .mobile-nav-chapters {
background-color: #3b2e2a;
}
.rust .content a {
.rust .content a:link,
.rust a:visited {
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;
}

View File

@@ -8,7 +8,7 @@ $( document ).ready(function() {
// Set theme
var theme = localStorage.getItem('theme');
if (theme == null) { theme = 'light'; }
if (theme === null) { theme = 'light'; }
set_theme(theme);
@@ -23,11 +23,43 @@ $( document ).ready(function() {
hljs.highlightBlock(block);
});
var KEY_CODES = {
PREVIOUS_KEY: 37,
NEXT_KEY: 39
};
$(document).on('keydown', function (e) {
switch (e.keyCode) {
case KEY_CODES.NEXT_KEY:
e.preventDefault();
if($('.nav-chapters.next').length) {
window.location.href = $('.nav-chapters.next').attr('href');
}
break;
case KEY_CODES.PREVIOUS_KEY:
e.preventDefault();
if($('.nav-chapters.previous').length) {
window.location.href = $('.nav-chapters.previous').attr('href');
}
break;
}
});
// Interesting DOM Elements
var html = $("html");
var sidebar = $("#sidebar");
var page_wrapper = $("#page-wrapper");
var content = $("#content");
// Add anchors for all content headers
content.find("h1, h2, h3, h4, h5").wrap(function(){
var wrapper = $("<a 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;
});
// Toggle sidebar
@@ -50,6 +82,13 @@ $( document ).ready(function() {
});
// Scroll sidebar to current active section
var activeSection = sidebar.find(".active");
if(activeSection.length) {
sidebar.scrollTop(activeSection.offset().top);
}
// Print button
$("#print-button").click(function(){
var printWindow = window.open("print.html");
@@ -66,18 +105,18 @@ $( 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');
set_theme(theme)
set_theme(theme);
});
}
@@ -96,4 +135,96 @@ $( document ).ready(function() {
$('body').removeClass().addClass(theme);
}
// Hide Rust code lines prepended with a specific character
var hiding_character = "#";
$("code.language-rust").each(function(i, block){
var code_block = $(this);
var pre_block = $(this).parent();
// hide lines
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].replace(/(\s*)#/, "$1") + "</span>";
}
else {
lines[n] = "<span class=\"hidden\">" + lines[n].replace(/(\s*)#/, "$1") + "\n" + "</span>";
}
lines_hidden = true;
}
else if(first_non_hidden_line) {
lines[n] = "\n" + lines[n];
}
else {
first_non_hidden_line = true;
}
}
code_block.html(lines.join(""));
// If no lines were hidden, return
if(!lines_hidden) { return; }
// add expand button
pre_block.prepend("<div class=\"buttons\"><i class=\"fa fa-expand\"></i></div>");
pre_block.find("i").click(function(e){
if( $(this).hasClass("fa-expand") ) {
$(this).removeClass("fa-expand").addClass("fa-compress");
pre_block.find("span.hidden").removeClass("hidden").addClass("unhidden");
}
else {
$(this).removeClass("fa-compress").addClass("fa-expand");
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

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

View File

@@ -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">
@@ -84,13 +86,13 @@
</div>
{{#previous}}
<a href="{{link}}" class="nav-chapters previous">
<a href="{{link}}" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a href="{{link}}" class="nav-chapters next">
<a href="{{link}}" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}

View File

@@ -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();

View File

@@ -11,4 +11,25 @@ html, body {
float: right
}
.hidden {
display: none;
}
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; }
}
}

View File

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

View File

@@ -18,6 +18,10 @@
@media only screen and (max-width: 1060px) {
left: - $sidebar-width
}
code {
line-height: 2em;
}
}
.sidebar-hidden .sidebar {

View File

@@ -1,7 +1,9 @@
.theme-popup {
position: fixed
left: -40px
position: relative
left: 10px
z-index: 1000;
border-radius: 4px
font-size: 0.7em

View File

@@ -1,7 +1,17 @@
.{unquote($theme-name)} {
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
@@ -13,7 +23,7 @@
a { color: $sidebar-fg }
.active,
a:hover {
a:hover, {
/* Animate color change */
color: $sidebar-active
}
@@ -24,8 +34,11 @@
}
.menu-bar,
.menu-bar:visited,
.nav-chapters,
.mobile-nav-chapters {
.nav-chapters:visited,
.mobile-nav-chapters,
.mobile-nav-chapters:visited {
color: $icons
}
@@ -43,14 +56,73 @@
background-color: $sidebar-bg
}
.content a {
.content a:link, a:visited {
color: $links
}
.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; }
}
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -1,28 +1,11 @@
use std::path::{Path, PathBuf, Component};
extern crate pulldown_cmark;
use std::path::{Path, Component};
use std::error::Error;
use std::io;
use std::fs::{self, metadata, File};
/// 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.
///
@@ -61,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.
@@ -110,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)
}
@@ -131,7 +83,7 @@ pub fn remove_dir_content(dir: &Path) -> Result<(), Box<Error>> {
Ok(())
}
/// **Untested!**
///
///
/// Copies all files of a directory to another one except the files with the extensions given in the
/// `ext_blacklist` array
@@ -168,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..."))));
}
@@ -178,6 +130,23 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
}
///
///
/// Wrapper around the pulldown-cmark parser and renderer to render markdown
pub fn render_markdown(text: &str) -> String {
let mut s = String::with_capacity(text.len() * 3 / 2);
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
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
@@ -189,7 +158,6 @@ mod tests {
extern crate tempdir;
use super::copy_files_except_ext;
use super::PathExt;
use std::fs;
#[test]