Compare commits

..

125 Commits

Author SHA1 Message Date
Mathieu David
8b84b8fa82 (cargo-release) version 0.0.20 2017-04-26 19:22:37 +02:00
Corey Farwell
a4a708bdda Merge pull request #254 from frewsxcv/frewsxcv-no-create
Implement new 'no-create' build flag.
2017-04-21 23:03:23 -04:00
Steve Klabnik
a3b925e3ab Merge pull request #256 from steveklabnik/master
bump for 0.0.19
2017-04-18 10:52:33 -04:00
steveklabnik
cba988f009 bump for 0.0.19 2017-04-18 10:50:58 -04:00
Corey Farwell
4525810737 Rewrite an emptiness check. 2017-04-17 21:58:34 -04:00
Corey Farwell
5d72d966ad Wrap long line. 2017-04-17 21:56:01 -04:00
Corey Farwell
15dcca87d8 Refactor to prevent excessive indentation. 2017-04-17 21:55:32 -04:00
Corey Farwell
c6e81337fb Implement new 'no-create' build flag.
Fixes https://github.com/azerupi/mdBook/issues/253.
2017-04-17 21:53:27 -04:00
Steve Klabnik
9602acce80 Merge pull request #252 from crazymerlyn/fix-runnable-files
Remove the extra run button on runnable rust files
2017-04-16 11:58:07 -04:00
CrazyMerlyn
65d7e86024 Remove the extra run button on runnable rust files
The playpen helper now uses a simple pre block instead of a pre block
with class playpen as it led to nested playpens.
2017-04-16 18:17:59 +05:30
Mathieu David
b5ec813d2f Merge pull request #250 from regexident/master
Added monospace font with support for box-drawing chars
2017-04-15 20:37:17 +02:00
Vincent Esche
41735b4579 Added monospace font with support for box-drawing chars 2017-04-15 14:16:28 +02:00
Steve Klabnik
9cb232058b Merge pull request #243 from steveklabnik/gh241
Accept nightly examples.
2017-04-14 15:19:07 -04:00
Mathieu David
ef402c16e8 Merge pull request #248 from mthh/master
Fix alignement of chapters with three digit numbering
2017-04-14 20:25:21 +02:00
mthh
df5472ab5a Should fix sections created with chapter of more than two digits 2017-04-07 12:46:28 +02:00
mthh
d768963c30 Revert "should fix sections created with chapter of more than two digits"
This reverts commit 8e7ec6e1fd.
2017-04-07 10:47:45 +02:00
mthh
8e7ec6e1fd should fix sections created with chapter of more than two digits 2017-04-07 02:37:46 +02:00
steveklabnik
80f01d70c6 Accept nightly examples.
This also brings us to parity with rustdoc regarding attributes in
general; while this PR was focused on enabling nightly, that was a
happy accident.
2017-03-31 17:06:03 -04:00
Mathieu David
40f275bf21 Merge pull request #236 from steveklabnik/master
add a .gitattributes to ensure proper line ending settings
2017-03-31 15:27:11 +02:00
Mathieu David
af8300c0b4 Merge pull request #239 from tshepang/misplaced
move misplaced example
2017-03-31 15:26:00 +02:00
Tshepang Lekhonkhobe
793a88260c move misplaced example 2017-03-30 14:09:14 +02:00
Steve Klabnik
1ec776244d Merge pull request #238 from tshepang/misc
typos
2017-03-29 14:17:44 -04:00
Tshepang Lekhonkhobe
4af107b0ca typos 2017-03-29 16:42:55 +02:00
steveklabnik
35e2807138 add a .gitattributes to ensure proper line ending settings 2017-03-28 09:50:57 -04:00
Mathieu David
1632d2e339 Merge pull request #230 from crazymerlyn/ignore_arrow_keys_with_modifier
Fix keyboard navigation to trigger only if no modifier key is pressed
2017-03-26 18:49:27 +02:00
Steve Klabnik
7c3932cef9 Merge pull request #231 from crazymerlyn/fix-header-link-id
Fix header links
2017-03-24 10:24:53 -04:00
CrazyMerlyn
ed1a216121 Fix header links
Header fragment links now use "id" attribute instead of the depreciated
"name" attribute.

Similar headers are given numbered ids to avoid id collisions.
For instance, if there are three headers named "Example", their ids
would be "#example", "#example-1", and "#example-2" respectively.
2017-03-23 23:24:26 +05:30
CrazyMerlyn
f814e96459 Fix keyboard navigation to trigger only if no modifier key is pressed 2017-03-23 13:29:04 +05:30
Steve Klabnik
a7272e0ff5 Merge pull request #226 from steveklabnik/master
bump version
2017-03-10 10:40:21 -08:00
steveklabnik
1cf4774737 bump version 2017-03-10 13:39:35 -05:00
Steve Klabnik
c6a5d12002 Merge pull request #222 from steveklabnik/gh29
Implement playpen support for ```rust
2017-03-10 08:59:15 -08:00
steveklabnik
b120ce7397 inject allow(unused_variables) 2017-03-10 09:46:11 -05:00
Steve Klabnik
c7916c4818 Merge pull request #225 from integer32llc/update-hljs
Update to highlight.js 9.10.0
2017-03-10 05:53:10 -08:00
Carol (Nichols || Goulding)
56f597b90c Update to highlight.js 9.10.0 2017-03-09 22:45:59 -05:00
steveklabnik
c5f9625feb inject main 2017-03-06 13:27:25 -05:00
steveklabnik
79f00eeea3 Implement playpen support for ```rust
Fixes #29
2017-03-06 12:23:15 -05:00
Mathieu David
677fa42458 (cargo-release) start next development iteration 0.0.18-pre 2017-02-28 17:22:51 +01:00
Mathieu David
a8bba0b94d (cargo-release) version 0.0.17 2017-02-28 17:20:28 +01:00
Mathieu David
e5a973a18d Merge pull request #212 from azerupi/fix-anchors
Fix anchors #211
2017-02-28 14:50:40 +01:00
Mathieu David
e218257e42 fix anchor links 2017-02-28 12:42:11 +01:00
Mathieu David
1345c05b18 Fix anchors, Fixes #211 2017-02-28 12:40:05 +01:00
Mathieu David
5e3a3f3482 Merge pull request #214 from azerupi/fix-rust-hide
Fix code blocks with comma separated classes
2017-02-28 12:33:52 +01:00
Mathieu David
7f46071faa Merge pull request #215 from steveklabnik/travis
Try to fix Travis
2017-02-28 10:59:02 +01:00
Steve Klabnik
5674da2afb fix travis 2017-02-27 20:21:35 -05:00
Mathieu David
01341a7705 Fix code blocks with comma separated classes 2017-02-28 01:41:06 +01:00
Mathieu David
0c624d0f74 bump version 2017-02-20 16:16:21 +01:00
Mathieu David
58cfef00f2 Merge pull request #209 from steveklabnik/gh204
Print version: fix up header links
2017-02-20 16:12:00 +01:00
Steve Klabnik
6af3eea24b Print version: fix up header links 2017-02-20 09:28:49 -05:00
Mathieu David
c88656284c Regenerate css 2017-02-19 11:13:19 +01:00
Mathieu David
14a28080c1 Merge pull request #208 from frewsxcv/bump
Bump crates.
2017-02-19 11:10:46 +01:00
Corey Farwell
7fa36f82b0 Bump ws crate to 0.6. 2017-02-18 20:28:12 -05:00
Corey Farwell
3a30e65eef Bump staticfile crate to 0.4, iron to 0.5. 2017-02-18 20:26:12 -05:00
Corey Farwell
fab24f5224 Bump notify crate to 0.4. 2017-02-18 20:24:27 -05:00
Corey Farwell
cfa4295d79 Bump toml crate to 0.3. 2017-02-18 20:22:55 -05:00
Mathieu David
d7f38d08fd Merge pull request #205 from frewsxcv/clippy
Address warnings found by rust-clippy.
2017-02-17 11:05:33 +01:00
Mathieu David
864be6cf42 Merge pull request #207 from steveklabnik/gh204
Generate links at compile-time rather than use JS
2017-02-17 11:00:39 +01:00
Steve Klabnik
ec42e2f771 convert to one pass
thanks @burntsushi ❤️
2017-02-16 19:31:52 -05:00
Steve Klabnik
aba153a271 update env_logger 2017-02-16 17:17:26 -05:00
Steve Klabnik
280dabecd7 update regex dep 2017-02-16 17:11:16 -05:00
Steve Klabnik
38b3516b60 Implement links in section headers.
This project already had a transitive dependency on regex; let's use it.

This isn't the most efficient solution, but it should be fine. It ends
up doing five full scans of the text. There's probably an easier way but
I'm mostly just trying to get this to work for now.

This also implements the same algorithm that rustdoc does for generating
the name for the link.

Fixes #204
2017-02-16 17:07:17 -05:00
Steve Klabnik
d609988264 remove js rendering 2017-02-16 17:07:16 -05:00
Corey Farwell
95fd292b4f Address warnings found by rust-clippy.
https://github.com/Manishearth/rust-clippy
2017-02-16 16:55:28 -05:00
Mathieu David
f3fb1f1e16 Merge pull request #206 from frewsxcv/serde
Bump serde, serde_json, and handlebars crates.
2017-02-16 22:51:37 +01:00
Corey Farwell
152ebba762 Bump serde, serde_json, and handlebars crates. 2017-02-15 23:31:05 -05:00
Mathieu David
23d25c853e Merge pull request #202 from paiv/paiv-widen-menu-hitregions
widen hit regions of menu buttons
2017-02-11 23:45:59 +01:00
Pavel Ivashkov
b97a8205f6 widen hit regions of menu buttons
![see here](http://i.imgur.com/jCZTCfr.png)
2017-02-11 21:20:12 +02:00
Mathieu David
82faec6b5a Merge pull request #201 from petehayes102/master
Add docs for --dest-dir option
2017-01-17 12:21:21 +01:00
Pete Hayes
32814f6f71 Remove blank ***note*** section 2017-01-17 00:19:09 +00:00
Pete Hayes
ac6f15cb27 Add docs for --dest-dir option 2017-01-17 00:19:09 +00:00
Mathieu David
0d6185ac96 Merge pull request #199 from petehayes102/master
Add --dest-dir option to build, watch and serve subcommands
2017-01-13 12:07:34 +01:00
Pete Hayes
4b31ae6789 Add --dest-dir arg to build, watch and serve subcommands 2017-01-12 12:26:22 +00:00
Pete Hayes
1afa2debc1 Fix spelling of omitted 2017-01-12 12:23:39 +00:00
Mathieu David
3a71371946 Merge pull request #198 from mbrubeck/watch
Update watch command to use `notify` 3.0
2017-01-02 19:58:47 +01:00
Mathieu David
9a318adc03 Merge pull request #197 from mbrubeck/cleanup
Clean up some Path code in bookconfig
2017-01-02 19:43:45 +01:00
Matt Brubeck
c7b4147ba7 Watch both book.json and book.toml 2017-01-01 16:03:49 -08:00
Matt Brubeck
1ac2602360 Update to notify 3.0
notify now does its own event debouncing, so it's no longer necessary
for mdbook to do this manually.
2017-01-01 16:03:49 -08:00
Matt Brubeck
09729aaca5 Clean up some Path code in bookconfig 2017-01-01 16:02:48 -08:00
Mathieu David
3ffd24df63 Merge pull request #196 from mbrubeck/open
Add a CLI option to open a web browser
2017-01-01 19:22:55 +01:00
Mathieu David
fe8d46b8e6 Merge pull request #195 from mbrubeck/refactor
Various refactoring and cleanup
2017-01-01 19:21:40 +01:00
Matt Brubeck
21bc3d47c8 Add a CLI option to open a web browser 2017-01-01 09:58:20 -08:00
Matt Brubeck
f2b87f7944 Factor common io error handling out of renderer 2016-12-31 23:12:38 -08:00
Matt Brubeck
894a03655e Simplify error handling in utils::fs 2016-12-31 23:12:38 -08:00
Matt Brubeck
6b2572e78d Simplify some as_str error handling code 2016-12-31 18:41:59 -08:00
Matt Brubeck
fe287a1eca Code cleanup: Remove unnecessary .remove() calls
`BTreeMap::insert` will replace any existing value, so there's no need
to remove the old value first.
2016-12-31 18:33:17 -08:00
Mathieu David
375502a6fa Merge pull request #194 from mbrubeck/warnings
Fix some rustc warnings.
2016-12-31 23:45:23 +01:00
Mathieu David
a6e1844aad Merge pull request #193 from mbrubeck/chapter-title
Add current chapter title to handlebars context
2016-12-31 23:43:39 +01:00
Mathieu David
d92852867b Merge pull request #192 from mbrubeck/docs
Fix a broken link in the documentation
2016-12-31 23:38:09 +01:00
Matt Brubeck
0f0750df52 Fix unreachable code warning in parse::summary::parse_level 2016-12-31 10:39:48 -08:00
Matt Brubeck
712adcf737 Fix cfg attribute in bookconfig_test 2016-12-31 10:36:19 -08:00
Matt Brubeck
3a0cfc87df Add current chapter title to handlebars context 2016-12-31 10:34:36 -08:00
Matt Brubeck
b1e384b03b Fix a broken link in the documentation
This fixes a broken link on http://azerupi.github.io/mdBook/cli/init.html

The `..` is redundant because the document's base URI is set to
`path_to_root`.  It breaks if the base URI is not at the server root.
2016-12-31 09:20:54 -08:00
Mathieu David
6410e792d7 Merge pull request #191 from jessestricker/readme-typos
Fix some minor typos and text inconsistencies
2016-12-29 18:02:15 +01:00
Jesse Stricker
b75243f1f5 Fix some minor typos 2016-12-29 16:25:51 +01:00
Mathieu David
f1df53a4bb Merge pull request #190 from gambhiro/parse-toml
Parse toml
2016-12-26 13:36:16 +01:00
Gambhiro
8a178e311d fix test 2016-12-24 13:44:24 +00:00
Gambhiro
53ec61ac70 upd example 2016-12-24 13:34:22 +00:00
Gambhiro
97d46e79b7 convert json to toml before config parsing 2016-12-24 13:22:01 +00:00
Gambhiro
552e39c897 update example to encourage using book.toml 2016-12-23 08:17:04 +00:00
Gambhiro
791487bc84 parse either book.toml or book.json 2016-12-23 08:15:32 +00:00
Gambhiro
f67ae7c71a update dependency versions 2016-12-23 08:10:42 +00:00
Mathieu David
e53dcdcf4d Merge pull request #186 from gambhiro/book-json-keys
Book json keys
2016-12-13 00:55:51 +01:00
Gambhiro
85d8e2ebd3 use theme_path key in book.json when given 2016-12-07 14:22:32 +00:00
Gambhiro
a9e5dc63f1 use src key in book.json when given 2016-12-07 09:38:56 +00:00
Mathieu David
cf35e08abc Merge pull request #181 from thomastanck/master
Use fixed positioning and remove overflow-x's for smoother scrolling …
2016-11-22 10:06:41 +01:00
Mathieu David
c986b3afc4 Merge pull request #182 from integer32llc/hljs-class
Add hljs class to all code blocks, regardless of highlighting
2016-11-14 07:48:07 +01:00
Carol (Nichols || Goulding)
08b5d14f7e Add hljs class to all code blocks, regardless of highlighting
Fixes #179.

Highlight.js does not apply syntax highlighting to code blocks marked
no-highlight, nohighlight, plain, or text. When it finds blocks of those
languages, it does not add the `hljs` class to those code blocks either.

highlight.css and tomorrow-night.css use the `hljs` class to give code
blocks their backrgound color and text color, and we want that to apply
even if the code doesn't get syntax highlighting markup.

This is a somewhat hacky solution to get just that behavior! After this
commit, code blocks with no-highlight, nohighlight, plain, or text
language set on them will indeed get the hljs colors.
2016-11-13 21:14:00 -05:00
Thomas Tan
f9101ca62c Use fixed positioning and remove overflow-x's for smoother scrolling experience in iOS 2016-11-09 16:18:40 +00:00
Mathieu David
f0c0d71326 Merge pull request #177 from azerupi/serde
Switch from rustc_serialize to serde
2016-11-03 11:44:34 +01:00
Mathieu David
d2f3eb5007 remove unused imports 2016-11-03 02:05:35 +01:00
Mathieu David
67aee5c192 Switch from rustc_serialize to serde. Closes #18 2016-11-03 01:58:42 +01:00
Mathieu David
30eb85711e Merge pull request #175 from DenisKolodin/metadata-remove
Remove unused metadata import
2016-11-01 12:44:23 +01:00
Denis Kolodin
b0d33e76ec Remove unused metadata import 2016-11-01 11:19:08 +03:00
Mathieu David
eb65f3fd1e Merge pull request #174 from rnkaufman/update-highlight-js
Highlight js update
2016-10-29 16:42:32 +02:00
rnkaufman
2600c62cf9 Highlight js update 2016-10-27 18:26:02 -07:00
Mathieu David
ecae442d25 Merge pull request #173 from HParker/slugify-section-anchors
slugify section headers
2016-10-21 22:12:40 +02:00
Adam Hess
f26f41fde3 slugify section headers
The current section headers are url encoded.  Because of that they
have some funny characters like %20.  We can clean that up by removing
all of the non-word characters before placing them in the anchor.
2016-10-20 22:02:16 -07:00
Mathieu David
b91f817bfd Merge pull request #171 from rzlourenco/master
Fix a bug where files without an extension were not copied
2016-09-23 22:13:30 +02:00
Rodrigo Lourenço
528945d67d Copy files with no extension too. 2016-09-23 15:09:16 +01:00
Mathieu David
4852e9e65a Merge branch 'master' of https://github.com/azerupi/mdBook 2016-09-12 22:50:03 +02:00
Mathieu David
e54b6643e1 regenerate css 2016-09-12 22:43:29 +02:00
Mathieu David
c7a95ccb8b Fix round corners in theme selector, changes were previously comitted directly to the css file causing them to be overwritten 2016-09-12 22:19:36 +02:00
Mathieu David
81a8f946b7 Fix print.styl, changes were previously comitted directly to the css file causing them to be overwritten 2016-09-12 22:10:33 +02:00
Mathieu David
04a643805a Merge pull request #170 from JIghtuse/master
Make line-height for chapter greater than section
2016-09-12 22:01:13 +02:00
Boris Egorov
49608b560b Make line-height for chapter greater than section
Fixes #166
2016-09-04 22:04:55 +07:00
Mathieu David
9e634a4e83 Bump version number to 0.0.15, 0.0.14 has been published to crates.io 2016-08-31 15:11:33 +02:00
42 changed files with 1235 additions and 643 deletions

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
[attr]rust text eol=lf whitespace=tab-in-indent,trailing-space,tabwidth=4
* text=auto eol=lf
*.rs rust

View File

@@ -26,7 +26,6 @@ matrix:
apt:
packages:
- nodejs
- npm
- os: linux
env: TARGET=x86_64-unknown-linux-musl CHANNEL=stable
# Beta channel

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook"
version = "0.0.14"
version = "0.0.20"
authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
description = "create books from markdown files (like Gitbook)"
documentation = "http://azerupi.github.io/mdBook/index.html"
@@ -15,29 +15,31 @@ exclude = [
]
[dependencies]
clap = "2.2.1"
handlebars = "0.20.0"
rustc-serialize = "0.3.18"
clap = "2.19.2"
handlebars = { version = "0.25.0", features = ["serde_type"] }
serde = "0.9"
serde_json = "0.9"
pulldown-cmark = "0.0.8"
log = "0.3"
env_logger = "0.3.4"
env_logger = "0.4.0"
toml = { version = "0.3", features = ["serde"] }
open = "1.1"
regex = "0.2.1"
# Watch feature
notify = { version = "2.5.5", optional = true }
notify = { version = "4.0", optional = true }
time = { version = "0.1.34", optional = true }
crossbeam = { version = "0.2.8", optional = true }
# Serve feature
iron = { version = "0.4", optional = true }
staticfile = { version = "0.3", optional = true }
ws = { version = "0.5.1", optional = true}
iron = { version = "0.5", optional = true }
staticfile = { version = "0.4", optional = true }
ws = { version = "0.6", optional = true}
# Tests
[dev-dependencies]
tempdir = "0.3.4"
[features]
default = ["output", "watch", "serve"]
debug = []

View File

@@ -21,7 +21,7 @@
</tr>
</table>
mdBook is a utility to create modern online books from markdown files.
mdBook is a utility to create modern online books from Markdown files.
**This project is still evolving.**
See [#90](https://github.com/azerupi/mdBook/issues/90)
@@ -29,7 +29,7 @@ See [#90](https://github.com/azerupi/mdBook/issues/90)
## What does it look like?
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.
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
@@ -47,12 +47,12 @@ There are multiple ways to install mdBook.
This will download and compile mdBook for you, the only thing left to do is to add the Cargo bin directory to your `PATH`.
3. **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***!
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***!
```
cargo install --git https://github.com/azerupi/mdBook.git
```
Again, make sure to add the Cargo bin directory to your `PATH`
Again, make sure to add the Cargo bin directory to your `PATH`.
4. **For Contributions**
If you want to contribute to mdBook you will have to clone the repository on your local machine:
@@ -66,15 +66,15 @@ There are multiple ways to install mdBook.
cargo build
```
the resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe`
The resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe`.
## Usage
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.
mdBook will primarily be used as a command line tool, even though it exposes all its functionality as a Rust crate for integration in other projects.
Here are the main commands you will want to run, for a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/).
Here are the main commands you will want to run. For a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/).
- `mdbook init`
@@ -88,7 +88,7 @@ Here are the main commands you will want to run, for a more exhaustive explanati
└── 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.
`book` and `src` are both directories. `src` contains the markdown files that will be used to render the output 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.
@@ -102,11 +102,11 @@ Here are the main commands you will want to run, for a more exhaustive explanati
- `mdbook serve`
Does the same thing as `mdbook watch` but additionally serves the book at `http://localhost:3000` (port is changeable) and reloads the browser when a change occures.
Does the same thing as `mdbook watch` but additionally serves the book at `http://localhost:3000` (port is changeable) and reloads the browser when a change occurs.
### 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 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!
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 an 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.
@@ -123,4 +123,4 @@ You can pick any issue you want to work on. Usually it's a good idea to ask if s
## License
All the code is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE](LICENSE) file
All the code is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE](LICENSE) file.

View File

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

3
book-example/book.toml Normal file
View File

@@ -0,0 +1,3 @@
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
author = "Mathieu David"

View File

@@ -1,5 +1,7 @@
# Summary
[Introduction](misc/introduction.md)
- [mdBook](README.md)
- [Command Line Tool](cli/cli-tool.md)
- [init](cli/init.md)

View File

@@ -21,6 +21,15 @@ current working directory.
mdbook build path/to/book
```
#### --open
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser after building it.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-------------------
***note:*** *make sure to run the build command in the root directory and not in the source directory*

View File

@@ -22,7 +22,7 @@ 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 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).
#### 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.

View File

@@ -26,8 +26,15 @@ mdbook server path/to/book -p 8000 -i 127.0.0.1 -a 192.168.1.100
If you were to want live reloading for this you would need to proxy the websocket calls through nginx as well from `192.168.1.100:<WS_PORT>` to `127.0.0.1:<WS_PORT>`. The `-w` flag allows for the websocket port to be configured.
#### --open
When you use the `--open` (`-o`) option, mdbook will open the book in your
your default web browser after starting the server.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-----
***note:*** *the `serve` 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)*
***note***:

View File

@@ -10,7 +10,7 @@ mdBook supports a `test` command that will run all available tests in mdBook. At
- 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.
In the future I would like the user to be able to enable / disable test from the `book.toml` configuration file and support custom tests.
**How to use it:**
```bash

View File

@@ -12,6 +12,14 @@ current working directory.
mdbook watch path/to/book
```
#### --open
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-----

View File

@@ -1,23 +1,28 @@
# Configuration
You can configure the parameters for your book in the ***book.json*** file.
You can configure the parameters for your book in the ***book.toml*** file.
Here is an example of what a ***book.json*** file might look like:
We encourage using the TOML format, but JSON is also recognized and parsed.
```json
{
"title": "Example book",
"author": "Name",
"description": "The example book covers examples.",
"dest": "output/my-book"
}
Here is an example of what a ***book.toml*** file might look like:
```toml
title = "Example book"
author = "Name"
description = "The example book covers examples."
dest = "output/my-book"
```
#### Supported variables
- **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
If relative paths are given, they will be relative to the book's root, i.e. the
parent directory of the source directory.
- **title:** The title of the book.
- **author:** The author of the book.
- **description:** The description, which is added as meta in the html head of each page.
- **src:** The path to the book's source files (chapters in Markdown, SUMMARY.md, etc.). Defaults to `root/src`.
- **dest:** The path to the directory where you want your book to be rendered. Defaults to `root/book`.
- **theme_path:** The path to a custom theme directory. Defaults to `root/theme`.
***note:*** *the supported configurable parameters are scarce at the moment, but more will be added in the future*

View File

@@ -4,5 +4,5 @@ In this section you will learn how to:
- Structure your book correctly
- Format your `SUMMARY.md` file
- Configure your book using `book.json`
- Configure your book using `book.toml`
- Customize your theme

View File

@@ -19,7 +19,8 @@ Here is a list of the properties that are exposed:
- ***language*** Language of the book in the form `en`. To use in <code class="language-html">\<html lang="{{ language }}"></code> for example.
At the moment it is hardcoded.
- ***title*** Title of the book, as specified in `book.json`
- ***title*** Title of the book, as specified in `book.toml`
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
- ***path*** Relative path to the original markdown file from the source directory
- ***content*** This is the rendered markdown.

View File

@@ -47,7 +47,7 @@ Will render as
# }
```
**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.**
**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.toml` so that everyone can benefit from it.**
## Improve default theme

View File

@@ -13,7 +13,7 @@ fn main() {
let mut book = MDBook::new(Path::new("my-book")) // Path to root
.set_src(Path::new("src")) // Path from root to source directory
.set_dest(Path::new("book")) // Path from root to output directory
.read_config(); // Parse book.json file for configuration
.read_config(); // Parse book.toml or book.json file for configuration
book.build().unwrap(); // Render the book
}

View File

@@ -0,0 +1,3 @@
# Introduction
A frontmatter chapter.

View File

@@ -1,10 +1,9 @@
#[macro_use]
extern crate mdbook;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate open;
// Dependencies for the Watch feature
#[cfg(feature = "watch")]
@@ -24,6 +23,7 @@ extern crate ws;
use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
@@ -33,6 +33,8 @@ use clap::{App, ArgMatches, SubCommand, AppSettings};
#[cfg(feature = "watch")]
use notify::Watcher;
#[cfg(feature = "watch")]
use std::time::Duration;
#[cfg(feature = "watch")]
use std::sync::mpsc::channel;
@@ -54,22 +56,29 @@ fn main() {
.subcommand(SubCommand::with_name("init")
.about("Create boilerplate structure and files in the directory")
// the {n} denotes a newline which will properly aligned in all help messages
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
.arg_from_usage("--theme 'Copies the default theme into your source folder'")
.arg_from_usage("--force 'skip confirmation prompts'"))
.subcommand(SubCommand::with_name("build")
.about("Build the book from the markdown files")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
.arg_from_usage("--no-create 'Will not create non-existent files linked from SUMMARY.md'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
.subcommand(SubCommand::with_name("watch")
.about("Watch the files for changes")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
.subcommand(SubCommand::with_name("serve")
.about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
.arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
.arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'")
.arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'")
.arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface addres)'"))
.arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface address)'")
.arg_from_usage("-o, --open 'Open the book server in a web browser'"))
.subcommand(SubCommand::with_name("test")
.about("Test that code samples compile"))
.get_matches();
@@ -159,10 +168,23 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
// 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();
let book = MDBook::new(&book_dir).read_config();
let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
None => book
};
if args.is_present("no-create") {
book.create_missing = false;
}
try!(book.build());
if args.is_present("open") {
open(book.get_dest().join("index.html"));
}
Ok(())
}
@@ -171,17 +193,24 @@ fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
#[cfg(feature = "watch")]
fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config();
let book = MDBook::new(&book_dir).read_config();
trigger_on_change(&mut book, |event, book| {
if let Some(path) = event.path {
println!("File changed: {:?}\nBuilding book...\n", path);
match book.build() {
Err(e) => println!("Error while building: {:?}", e),
_ => {},
}
println!("");
let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
None => book
};
if args.is_present("open") {
try!(book.build());
open(book.get_dest().join("index.html"));
}
trigger_on_change(&mut book, |path, book| {
println!("File changed: {:?}\nBuilding book...\n", path);
if let Err(e) = book.build() {
println!("Error while building: {:?}", e);
}
println!("");
});
Ok(())
@@ -194,11 +223,18 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
const RELOAD_COMMAND: &'static str = "reload";
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config();
let book = MDBook::new(&book_dir).read_config();
let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
None => book
};
let port = args.value_of("port").unwrap_or("3000");
let ws_port = args.value_of("ws-port").unwrap_or("3001");
let interface = args.value_of("interface").unwrap_or("localhost");
let public_address = args.value_of("address").unwrap_or(interface);
let open_browser = args.is_present("open");
let address = format!("{}:{}", interface, port);
let ws_address = format!("{}:{}", interface, ws_port);
@@ -239,15 +275,17 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
println!("\nServing on {}", address);
trigger_on_change(&mut book, move |event, book| {
if let Some(path) = event.path {
println!("File changed: {:?}\nBuilding book...\n", path);
match book.build() {
Err(e) => println!("Error while building: {:?}", e),
_ => broadcaster.send(RELOAD_COMMAND).unwrap(),
}
println!("");
if open_browser {
open(format!("http://{}", address));
}
trigger_on_change(&mut book, move |path, book| {
println!("File changed: {:?}\nBuilding book...\n", path);
match book.build() {
Err(e) => println!("Error while building: {:?}", e),
_ => broadcaster.send(RELOAD_COMMAND).unwrap(),
}
println!("");
});
Ok(())
@@ -278,57 +316,65 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
}
}
fn open<P: AsRef<OsStr>>(path: P) {
if let Err(e) = open::that(path) {
println!("Error opening web browser: {}", e);
}
}
// Calls the closure when a book source file is changed. This is blocking!
#[cfg(feature = "watch")]
fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
where F: Fn(notify::Event, &mut MDBook) -> ()
where F: Fn(&Path, &mut MDBook) -> ()
{
use notify::RecursiveMode::*;
use notify::DebouncedEvent::*;
// 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.get_root().join("book.json")) {
// do nothing if book.json is not found
}
let mut previous_time = time::get_time();
println!("\nListening for changes...\n");
loop {
match rx.recv() {
Ok(event) => {
// Skip the event if an event has already been issued in the last second
let time = time::get_time();
if time - previous_time < time::Duration::seconds(1) {
continue;
} else {
previous_time = time;
}
closure(event, book);
},
Err(e) => {
println!("An error occured: {:?}", e);
},
}
}
},
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
Ok(w) => w,
Err(e) => {
println!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(0);
},
}
};
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.get_src(), Recursive) {
println!("Error while watching {:?}:\n {:?}", book.get_src(), e);
::std::process::exit(0);
};
// Add the book.{json,toml} file to the watcher if it exists, because it's not
// located in the source directory
if watcher.watch(book.get_root().join("book.json"), NonRecursive).is_err() {
// do nothing if book.json is not found
}
if watcher.watch(book.get_root().join("book.toml"), NonRecursive).is_err() {
// do nothing if book.toml is not found
}
println!("\nListening for changes...\n");
loop {
match rx.recv() {
Ok(event) => match event {
NoticeWrite(path) |
NoticeRemove(path) |
Create(path) |
Write(path) |
Remove(path) |
Rename(_, path) => {
closure(&path, book);
}
_ => {}
},
Err(e) => {
println!("An error occured: {:?}", e);
},
}
}
}

View File

@@ -1,30 +1,40 @@
use rustc_serialize::json::Json;
extern crate toml;
use std::process::exit;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::collections::BTreeMap;
use std::str::FromStr;
use serde_json;
#[derive(Debug, Clone)]
pub struct BookConfig {
pub title: String,
pub author: String,
pub description: String,
root: PathBuf,
pub dest: PathBuf,
pub src: PathBuf,
pub theme_path: PathBuf,
pub title: String,
pub author: String,
pub description: String,
pub indent_spaces: i32,
multilingual: bool,
}
impl BookConfig {
pub fn new(root: &Path) -> Self {
BookConfig {
title: String::new(),
author: String::new(),
description: String::new(),
root: root.to_owned(),
dest: root.join("book"),
src: root.join("src"),
theme_path: root.join("theme"),
title: String::new(),
author: String::new(),
description: String::new(),
indent_spaces: 4, // indentation used for SUMMARY.md
multilingual: false,
}
@@ -34,55 +44,114 @@ impl BookConfig {
debug!("[fn]: read_config");
// If the file does not exist, return early
let mut config_file = match File::open(root.join("book.json")) {
Ok(f) => f,
Err(_) => {
debug!("[*]: Failed to open {:?}", root.join("book.json"));
return self;
},
let read_file = |path: PathBuf| -> String {
let mut data = String::new();
let mut f: File = match File::open(&path) {
Ok(x) => x,
Err(_) => {
error!("[*]: Failed to open {:?}", &path);
exit(2);
}
};
if f.read_to_string(&mut data).is_err() {
error!("[*]: Failed to read {:?}", &path);
exit(2);
}
data
};
debug!("[*]: Reading config");
let mut data = String::new();
// Read book.toml or book.json if exists
// Just return if an error occured.
// I would like to propagate the error, but I have to return `&self`
if let Err(_) = config_file.read_to_string(&mut data) {
return self;
if root.join("book.toml").exists() {
debug!("[*]: Reading config");
let data = read_file(root.join("book.toml"));
self.parse_from_toml_string(&data);
} else if root.join("book.json").exists() {
debug!("[*]: Reading config");
let data = read_file(root.join("book.json"));
self.parse_from_json_string(&data);
} else {
debug!("[*]: No book.toml or book.json was found, using defaults.");
}
// Convert to JSON
if let Ok(config) = Json::from_str(&data) {
// Extract data
self
}
debug!("[*]: Extracting data from config");
// 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("\"", "")
pub fn parse_from_toml_string(&mut self, data: &str) -> &mut Self {
let config = match toml::from_str(data) {
Ok(x) => {x},
Err(e) => {
error!("[*]: Toml parse errors in book.toml: {:?}", e);
exit(2);
}
};
// Destination
if let Some(a) = config.find_path(&["dest"]) {
let dest = PathBuf::from(&a.to_string().replace("\"", ""));
self.parse_from_btreemap(&config);
// If path is relative make it absolute from the parent directory of src
match dest.is_relative() {
true => {
let dest = self.get_root().join(&dest).to_owned();
self.set_dest(&dest);
},
false => {
self.set_dest(&dest);
},
}
self
}
/// Parses the string to JSON and converts it to BTreeMap<String, toml::Value>.
pub fn parse_from_json_string(&mut self, data: &str) -> &mut Self {
let c: serde_json::Value = match serde_json::from_str(data) {
Ok(x) => x,
Err(e) => {
error!("[*]: JSON parse errors in book.json: {:?}", e);
exit(2);
}
};
let config = json_object_to_btreemap(c.as_object().unwrap());
self.parse_from_btreemap(&config);
self
}
pub fn parse_from_btreemap(&mut self, config: &BTreeMap<String, toml::Value>) -> &mut Self {
// Title, author, description
if let Some(a) = config.get("title") {
self.title = a.to_string().replace("\"", "");
}
if let Some(a) = config.get("author") {
self.author = a.to_string().replace("\"", "");
}
if let Some(a) = config.get("description") {
self.description = a.to_string().replace("\"", "");
}
// Destination folder
if let Some(a) = config.get("dest") {
let mut dest = PathBuf::from(&a.to_string().replace("\"", ""));
// If path is relative make it absolute from the parent directory of src
if dest.is_relative() {
dest = self.get_root().join(&dest);
}
self.set_dest(&dest);
}
// Source folder
if let Some(a) = config.get("src") {
let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
if src.is_relative() {
src = self.get_root().join(&src);
}
self.set_src(&src);
}
// Theme path folder
if let Some(a) = config.get("theme_path") {
let mut theme_path = PathBuf::from(&a.to_string().replace("\"", ""));
if theme_path.is_relative() {
theme_path = self.get_root().join(&theme_path);
}
self.set_theme_path(&theme_path);
}
self
@@ -114,4 +183,43 @@ impl BookConfig {
self.src = src.to_owned();
self
}
pub fn get_theme_path(&self) -> &Path {
&self.theme_path
}
pub fn set_theme_path(&mut self, theme_path: &Path) -> &mut Self {
self.theme_path = theme_path.to_owned();
self
}
}
pub fn json_object_to_btreemap(json: &serde_json::Map<String, serde_json::Value>) -> BTreeMap<String, toml::Value> {
let mut config: BTreeMap<String, toml::Value> = BTreeMap::new();
for (key, value) in json.iter() {
config.insert(
String::from_str(key).unwrap(),
json_value_to_toml_value(value.to_owned())
);
}
config
}
pub fn json_value_to_toml_value(json: serde_json::Value) -> toml::Value {
match json {
serde_json::Value::Null => toml::Value::String("".to_string()),
serde_json::Value::Bool(x) => toml::Value::Boolean(x),
serde_json::Value::Number(ref x) if x.is_i64() => toml::Value::Integer(x.as_i64().unwrap()),
serde_json::Value::Number(ref x) if x.is_u64() => toml::Value::Integer(x.as_i64().unwrap()),
serde_json::Value::Number(x) => toml::Value::Float(x.as_f64().unwrap()),
serde_json::Value::String(x) => toml::Value::String(x),
serde_json::Value::Array(x) => {
toml::Value::Array(x.iter().map(|v| json_value_to_toml_value(v.to_owned())).collect())
},
serde_json::Value::Object(x) => {
toml::Value::Table(json_object_to_btreemap(&x))
},
}
}

349
src/book/bookconfig_test.rs Normal file
View File

@@ -0,0 +1,349 @@
#![cfg(test)]
use std::path::Path;
use serde_json;
use book::bookconfig::*;
#[test]
fn it_parses_json_config() {
let text = r#"
{
"title": "mdBook Documentation",
"description": "Create book from markdown files. Like Gitbook but implemented in Rust",
"author": "Mathieu David"
}"#;
// TODO don't require path argument, take pwd
let mut config = BookConfig::new(Path::new("."));
config.parse_from_json_string(&text.to_string());
let mut expected = BookConfig::new(Path::new("."));
expected.title = "mdBook Documentation".to_string();
expected.author = "Mathieu David".to_string();
expected.description = "Create book from markdown files. Like Gitbook but implemented in Rust".to_string();
assert_eq!(format!("{:#?}", config), format!("{:#?}", expected));
}
#[test]
fn it_parses_toml_config() {
let text = r#"
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
author = "Mathieu David"
"#;
// TODO don't require path argument, take pwd
let mut config = BookConfig::new(Path::new("."));
config.parse_from_toml_string(&text.to_string());
let mut expected = BookConfig::new(Path::new("."));
expected.title = "mdBook Documentation".to_string();
expected.author = "Mathieu David".to_string();
expected.description = "Create book from markdown files. Like Gitbook but implemented in Rust".to_string();
assert_eq!(format!("{:#?}", config), format!("{:#?}", expected));
}
#[test]
fn it_parses_json_nested_array_to_toml() {
// Example from:
// toml-0.2.1/tests/valid/arrays-nested.json
let text = r#"
{
"nest": {
"type": "array",
"value": [
{"type": "array", "value": [
{"type": "string", "value": "a"}
]},
{"type": "array", "value": [
{"type": "string", "value": "b"}
]}
]
}
}"#;
let c: serde_json::Value = serde_json::from_str(&text).unwrap();
let result = json_object_to_btreemap(&c.as_object().unwrap());
let expected = r#"{
"nest": Table(
{
"type": String(
"array"
),
"value": Array(
[
Table(
{
"type": String(
"array"
),
"value": Array(
[
Table(
{
"type": String(
"string"
),
"value": String(
"a"
)
}
)
]
)
}
),
Table(
{
"type": String(
"array"
),
"value": Array(
[
Table(
{
"type": String(
"string"
),
"value": String(
"b"
)
}
)
]
)
}
)
]
)
}
)
}"#;
assert_eq!(format!("{:#?}", result), expected);
}
#[test]
fn it_parses_json_arrays_to_toml() {
// Example from:
// toml-0.2.1/tests/valid/arrays.json
let text = r#"
{
"ints": {
"type": "array",
"value": [
{"type": "integer", "value": "1"},
{"type": "integer", "value": "2"},
{"type": "integer", "value": "3"}
]
},
"floats": {
"type": "array",
"value": [
{"type": "float", "value": "1.1"},
{"type": "float", "value": "2.1"},
{"type": "float", "value": "3.1"}
]
},
"strings": {
"type": "array",
"value": [
{"type": "string", "value": "a"},
{"type": "string", "value": "b"},
{"type": "string", "value": "c"}
]
},
"dates": {
"type": "array",
"value": [
{"type": "datetime", "value": "1987-07-05T17:45:00Z"},
{"type": "datetime", "value": "1979-05-27T07:32:00Z"},
{"type": "datetime", "value": "2006-06-01T11:00:00Z"}
]
}
}"#;
let c: serde_json::Value = serde_json::from_str(&text).unwrap();
let result = json_object_to_btreemap(&c.as_object().unwrap());
let expected = r#"{
"dates": Table(
{
"type": String(
"array"
),
"value": Array(
[
Table(
{
"type": String(
"datetime"
),
"value": String(
"1987-07-05T17:45:00Z"
)
}
),
Table(
{
"type": String(
"datetime"
),
"value": String(
"1979-05-27T07:32:00Z"
)
}
),
Table(
{
"type": String(
"datetime"
),
"value": String(
"2006-06-01T11:00:00Z"
)
}
)
]
)
}
),
"floats": Table(
{
"type": String(
"array"
),
"value": Array(
[
Table(
{
"type": String(
"float"
),
"value": String(
"1.1"
)
}
),
Table(
{
"type": String(
"float"
),
"value": String(
"2.1"
)
}
),
Table(
{
"type": String(
"float"
),
"value": String(
"3.1"
)
}
)
]
)
}
),
"ints": Table(
{
"type": String(
"array"
),
"value": Array(
[
Table(
{
"type": String(
"integer"
),
"value": String(
"1"
)
}
),
Table(
{
"type": String(
"integer"
),
"value": String(
"2"
)
}
),
Table(
{
"type": String(
"integer"
),
"value": String(
"3"
)
}
)
]
)
}
),
"strings": Table(
{
"type": String(
"array"
),
"value": Array(
[
Table(
{
"type": String(
"string"
),
"value": String(
"a"
)
}
),
Table(
{
"type": String(
"string"
),
"value": String(
"b"
)
}
),
Table(
{
"type": String(
"string"
),
"value": String(
"c"
)
}
)
]
)
}
)
}"#;
assert_eq!(format!("{:#?}", result), expected);
}

View File

@@ -1,6 +1,6 @@
use rustc_serialize::json::{Json, ToJson};
use serde::{Serialize, Serializer};
use serde::ser::SerializeStruct;
use std::path::PathBuf;
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub enum BookItem {
@@ -36,16 +36,12 @@ impl Chapter {
}
impl ToJson for Chapter {
fn to_json(&self) -> Json {
let mut m: BTreeMap<String, Json> = BTreeMap::new();
m.insert("name".to_owned(), self.name.to_json());
m.insert("path".to_owned(),
self.path
.to_str()
.expect("Json conversion failed for path")
.to_json());
m.to_json()
impl Serialize for Chapter {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
let mut struct_ = try!(serializer.serialize_struct("Chapter", 2));
try!(struct_.serialize_field("name", &self.name));
try!(struct_.serialize_field("path", &self.path));
struct_.end()
}
}
@@ -67,7 +63,7 @@ impl<'a> Iterator for BookItems<'a> {
},
}
} else {
let cur = self.items.get(self.current_index).unwrap();
let cur = &self.items[self.current_index];
match *cur {
BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => {

View File

@@ -1,6 +1,8 @@
pub mod bookitem;
pub mod bookconfig;
pub mod bookconfig_test;
pub use self::bookitem::{BookItem, BookItems};
pub use self::bookconfig::BookConfig;
@@ -20,6 +22,7 @@ pub struct MDBook {
root: PathBuf,
dest: PathBuf,
src: PathBuf,
theme_path: PathBuf,
pub title: String,
pub author: String,
@@ -29,13 +32,34 @@ pub struct MDBook {
renderer: Box<Renderer>,
livereload: Option<String>,
/// Should `mdbook build` create files referenced from SUMMARY.md if they
/// don't exist
pub create_missing: bool,
}
impl MDBook {
/// Create a new `MDBook` struct with root directory `root`
///
/// - The default source directory is set to `root/src`
/// - The default output directory is set to `root/book`
/// # Examples
///
/// ```no_run
/// # extern crate mdbook;
/// # use mdbook::MDBook;
/// # use std::path::Path;
/// # fn main() {
/// let book = MDBook::new(Path::new("root_dir"));
/// # }
/// ```
///
/// In this example, `root_dir` will be the root directory of our book and is specified in function
/// of the current working directory by using a relative path instead of an absolute path.
///
/// Default directory paths:
///
/// - source: `root/src`
/// - output: `root/book`
/// - theme: `root/theme`
///
/// They can both be changed by using [`set_src()`](#method.set_src) and [`set_dest()`](#method.set_dest)
@@ -49,6 +73,7 @@ impl MDBook {
root: root.to_owned(),
dest: root.join("book"),
src: root.join("src"),
theme_path: root.join("theme"),
title: String::new(),
author: String::new(),
@@ -58,6 +83,7 @@ impl MDBook {
renderer: Box::new(HtmlHandlebars::new()),
livereload: None,
create_missing: true,
}
}
@@ -154,23 +180,27 @@ impl MDBook {
debug!("[*]: constructing paths for missing files");
for item in self.iter() {
debug!("[*]: item: {:?}", item);
match *item {
let ch = match *item {
BookItem::Spacer => continue,
BookItem::Chapter(_, ref ch) |
BookItem::Affix(ref ch) => {
if ch.path != PathBuf::new() {
let path = self.src.join(&ch.path);
BookItem::Affix(ref ch) => ch,
};
if ch.path.as_os_str().is_empty() {
let path = self.src.join(&ch.path);
if !path.exists() {
debug!("[*]: {:?} does not exist, trying to create file", path);
try!(::std::fs::create_dir_all(path.parent().unwrap()));
let mut f = try!(File::create(path));
// debug!("[*]: Writing to {:?}", path);
try!(writeln!(f, "# {}", ch.name));
}
if !path.exists() {
if !self.create_missing {
return Err(format!(
"'{}' referenced from SUMMARY.md does not exist.",
path.to_string_lossy()).into());
}
},
debug!("[*]: {:?} does not exist, trying to create file", path);
try!(::std::fs::create_dir_all(path.parent().unwrap()));
let mut f = try!(File::create(path));
// debug!("[*]: Writing to {:?}", path);
try!(writeln!(f, "# {}", ch.name));
}
}
}
@@ -266,21 +296,19 @@ impl MDBook {
Ok(())
}
pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<(), Box<Error>> {
let path = self.get_dest().join(filename);
try!(utils::fs::create_file(&path).and_then(|mut file| {
file.write_all(content)
}).map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Could not create {}: {}", path.display(), e))
}));
Ok(())
}
/// Parses the `book.json` file (if it exists) to extract the configuration parameters.
/// The `book.json` file should be in the root directory of the book.
/// The root directory is the one specified when creating a new `MDBook`
///
/// ```no_run
/// # extern crate mdbook;
/// # use mdbook::MDBook;
/// # use std::path::Path;
/// # fn main() {
/// let mut book = MDBook::new(Path::new("root_dir"));
/// # }
/// ```
///
/// In this example, `root_dir` will be the root directory of our book and is specified in function
/// of the current working directory by using a relative path instead of an absolute path.
pub fn read_config(mut self) -> Self {
@@ -294,6 +322,7 @@ impl MDBook {
self.dest = config.dest;
self.src = config.src;
self.theme_path = config.theme_path;
self
}
@@ -328,29 +357,26 @@ impl MDBook {
try!(self.parse_summary());
for item in self.iter() {
match *item {
BookItem::Chapter(_, ref ch) => {
if ch.path != PathBuf::new() {
if let BookItem::Chapter(_, ref ch) = *item {
if ch.path != PathBuf::new() {
let path = self.get_src().join(&ch.path);
let path = self.get_src().join(&ch.path);
println!("[*]: Testing file: {:?}", path);
println!("[*]: Testing file: {:?}", path);
let output_result = Command::new("rustdoc")
.arg(&path)
.arg("--test")
.output();
let output = try!(output_result);
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>);
}
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(())
@@ -363,14 +389,11 @@ impl MDBook {
pub fn set_dest(mut self, dest: &Path) -> Self {
// Handle absolute and relative paths
match dest.is_absolute() {
true => {
self.dest = dest.to_owned();
},
false => {
let dest = self.root.join(dest).to_owned();
self.dest = dest;
},
if dest.is_absolute() {
self.dest = dest.to_owned();
} else {
let dest = self.root.join(dest).to_owned();
self.dest = dest;
}
self
@@ -383,14 +406,11 @@ impl MDBook {
pub fn set_src(mut self, src: &Path) -> Self {
// Handle absolute and relative paths
match src.is_absolute() {
true => {
self.src = src.to_owned();
},
false => {
let src = self.root.join(src).to_owned();
self.src = src;
},
if src.is_absolute() {
self.src = src.to_owned();
} else {
let src = self.root.join(src).to_owned();
self.src = src;
}
self
@@ -438,10 +458,20 @@ impl MDBook {
}
pub fn get_livereload(&self) -> Option<&String> {
match self.livereload {
Some(ref livereload) => Some(&livereload),
None => None,
}
self.livereload.as_ref()
}
pub fn set_theme_path(mut self, theme_path: &Path) -> Self {
self.theme_path = if theme_path.is_absolute() {
theme_path.to_owned()
} else {
self.root.join(theme_path).to_owned()
};
self
}
pub fn get_theme_path(&self) -> &Path {
&self.theme_path
}
// Construct book

View File

@@ -69,9 +69,12 @@
//!
//! Make sure to take a look at it.
extern crate rustc_serialize;
extern crate serde;
#[macro_use]
extern crate serde_json;
extern crate handlebars;
extern crate pulldown_cmark;
extern crate regex;
#[macro_use] extern crate log;
pub mod book;

View File

@@ -37,7 +37,7 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
section.push(0);
let last = items.pop().expect("There should be at least one item since this can't be the root level");
item = if let BookItem::Chapter(ref s, ref ch) = last {
if let BookItem::Chapter(ref s, ref ch) = last {
let mut ch = ch.clone();
ch.sub_items = try!(parse_level(summary, level, section.clone()));
items.push(BookItem::Chapter(s.clone(), ch));
@@ -47,12 +47,12 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
continue;
} else {
return Err(Error::new(ErrorKind::Other,
format!("Your summary.md is messed up\n\n
"Your summary.md is messed up\n\n
Prefix, \
Suffix and Spacer elements can only exist on the root level.\n
Suffix and Spacer elements can only exist on the root level.\n
\
Prefix elements can only exist before any chapter and there can be \
no chapters after suffix elements.")));
Prefix elements can only exist before any chapter and there can be \
no chapters after suffix elements."));
};
} else {
@@ -64,25 +64,25 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32
// error if level != 0 and BookItem is != Chapter
BookItem::Affix(_) | BookItem::Spacer if level > 0 => {
return Err(Error::new(ErrorKind::Other,
format!("Your summary.md is messed up\n\n
"Your summary.md is messed up\n\n
\
Prefix, Suffix and Spacer elements can only exist on the \
root level.\n
Prefix, Suffix and Spacer elements can only exist on the \
root level.\n
Prefix \
elements can only exist before any chapter and there can be \
no chapters after suffix elements.")))
elements can only exist before any chapter and there can be \
no chapters after suffix elements."))
},
// error if BookItem == Chapter and section == -1
BookItem::Chapter(_, _) if section[0] == -1 => {
return Err(Error::new(ErrorKind::Other,
format!("Your summary.md is messed up\n\n
"Your summary.md is messed up\n\n
\
Prefix, Suffix and Spacer elements can only exist on the \
root level.\n
Prefix, Suffix and Spacer elements can only exist on the \
root level.\n
Prefix \
elements can only exist before any chapter and there can be \
no chapters after suffix elements.")))
elements can only exist before any chapter and there can be \
no chapters after suffix elements."))
},
// Set section = -1 after suffix

View File

@@ -3,17 +3,22 @@ use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};
use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read, Write};
use std::io::{self, Read};
use std::collections::BTreeMap;
use std::collections::HashMap;
use handlebars::Handlebars;
use rustc_serialize::json::{Json, ToJson};
use serde_json;
#[derive(Default)]
pub struct HtmlHandlebars;
impl HtmlHandlebars {
@@ -28,7 +33,7 @@ impl Renderer for HtmlHandlebars {
let mut handlebars = Handlebars::new();
// Load theme
let theme = theme::Theme::new(book.get_src());
let theme = theme::Theme::new(book.get_theme_path());
// Register template
debug!("[*]: Register handlebars template");
@@ -47,7 +52,7 @@ impl Renderer for HtmlHandlebars {
// Check if dest directory exists
debug!("[*]: Check if destination directory exists");
if let Err(_) = fs::create_dir_all(book.get_dest()) {
if fs::create_dir_all(book.get_dest()).is_err() {
return Err(Box::new(io::Error::new(io::ErrorKind::Other,
"Unexpected error when constructing destination path")));
}
@@ -79,44 +84,34 @@ impl Renderer for HtmlHandlebars {
content = utils::render_markdown(&content);
print_content.push_str(&content);
// Remove content from previous file and render content for this one
data.remove("path");
match ch.path.to_str() {
Some(p) => {
data.insert("path".to_owned(), p.to_json());
},
None => {
return Err(Box::new(io::Error::new(io::ErrorKind::Other,
"Could not convert path to str")))
},
}
// Update the context with data for this file
let path = ch.path.to_str().ok_or_else(||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
data.insert("path".to_owned(), json!(path));
data.insert("content".to_owned(), json!(content));
data.insert("chapter_title".to_owned(), json!(ch.name));
data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));
// Remove content from previous file and render content for this one
data.remove("content");
data.insert("content".to_owned(), content.to_json());
// Remove path to root from previous file and render content for this one
data.remove("path_to_root");
data.insert("path_to_root".to_owned(), utils::fs::path_to_root(&ch.path).to_json());
// Rendere the handlebars template with the data
// Render the handlebars template with the data
debug!("[*]: Render template");
let rendered = try!(handlebars.render("index", &data));
debug!("[*]: Create file {:?}", &book.get_dest().join(&ch.path).with_extension("html"));
// Write to file
let mut file =
try!(utils::fs::create_file(&book.get_dest().join(&ch.path).with_extension("html")));
info!("[*] Creating {:?} ✓", &book.get_dest().join(&ch.path).with_extension("html"));
let filename = Path::new(&ch.path).with_extension("html");
try!(file.write_all(&rendered.into_bytes()));
// Do several kinds of post-processing
let rendered = build_header_links(rendered, filename.to_str().unwrap_or(""));
let rendered = fix_anchor_links(rendered, filename.to_str().unwrap_or(""));
let rendered = fix_code_blocks(rendered);
let rendered = add_playpen_pre(rendered);
// Write to file
info!("[*] Creating {:?} ✓", filename.display());
try!(book.write_file(filename, &rendered.into_bytes()));
// Create an index.html from the first element in SUMMARY.md
if index {
debug!("[*]: index.html");
let mut index_file = try!(File::create(book.get_dest().join("index.html")));
let mut content = String::new();
let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
.read_to_string(&mut content);
@@ -128,7 +123,7 @@ impl Renderer for HtmlHandlebars {
.collect::<Vec<&str>>()
.join("\n");
try!(index_file.write_all(content.as_bytes()));
try!(book.write_file("index.html", content.as_bytes()));
info!("[*] Creating index.html from {:?} ✓",
book.get_dest().join(&ch.path.with_extension("html")));
@@ -142,132 +137,42 @@ impl Renderer for HtmlHandlebars {
// Print version
// Remove content from previous file and render content for this one
data.remove("path");
data.insert("path".to_owned(), "print.md".to_json());
// Update the context with data for this file
data.insert("path".to_owned(), json!("print.md"));
data.insert("content".to_owned(), json!(print_content));
data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));
// Remove content from previous file and render content for this one
data.remove("content");
data.insert("content".to_owned(), print_content.to_json());
// Remove path to root from previous file and render content for this one
data.remove("path_to_root");
data.insert("path_to_root".to_owned(), utils::fs::path_to_root(Path::new("print.md")).to_json());
// Rendere the handlebars template with the data
// Render the handlebars template with the data
debug!("[*]: Render template");
let rendered = try!(handlebars.render("index", &data));
let mut file = try!(utils::fs::create_file(&book.get_dest().join("print").with_extension("html")));
try!(file.write_all(&rendered.into_bytes()));
// do several kinds of post-processing
let rendered = build_header_links(rendered, "print.html");
let rendered = fix_anchor_links(rendered, "print.html");
let rendered = fix_code_blocks(rendered);
let rendered = add_playpen_pre(rendered);
try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
info!("[*] Creating print.html ✓");
// Copy static files (js, css, images, ...)
debug!("[*] Copy static files");
// JavaScript
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 = 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 = 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));
// syntax highlighting
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 = 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 = 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::fs::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::fs::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::fs::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::fs::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::fs::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::fs::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::fs::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));
try!(book.write_file("book.js", &theme.js));
try!(book.write_file("book.css", &theme.css));
try!(book.write_file("favicon.png", &theme.favicon));
try!(book.write_file("jquery.js", &theme.jquery));
try!(book.write_file("highlight.css", &theme.highlight_css));
try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
try!(book.write_file("highlight.js", &theme.highlight_js));
try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));
// Copy all remaining files
try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));
@@ -276,16 +181,16 @@ impl Renderer for HtmlHandlebars {
}
}
fn make_data(book: &MDBook) -> Result<BTreeMap<String, Json>, Box<Error>> {
fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
debug!("[fn]: make_data");
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 data = serde_json::Map::new();
data.insert("language".to_owned(), json!("en"));
data.insert("title".to_owned(), json!(book.get_title()));
data.insert("description".to_owned(), json!(book.get_description()));
data.insert("favicon".to_owned(), json!("favicon.png"));
if let Some(livereload) = book.get_livereload() {
data.insert("livereload".to_owned(), livereload.to_json());
data.insert("livereload".to_owned(), json!(livereload));
}
let mut chapters = vec![];
@@ -296,26 +201,20 @@ fn make_data(book: &MDBook) -> Result<BTreeMap<String, Json>, Box<Error>> {
match *item {
BookItem::Affix(ref ch) => {
chapter.insert("name".to_owned(), ch.name.to_json());
match ch.path.to_str() {
Some(p) => {
chapter.insert("path".to_owned(), p.to_json());
},
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))),
}
chapter.insert("name".to_owned(), json!(ch.name));
let path = ch.path.to_str().ok_or_else(||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
chapter.insert("path".to_owned(), json!(path));
},
BookItem::Chapter(ref s, ref ch) => {
chapter.insert("section".to_owned(), s.to_json());
chapter.insert("name".to_owned(), ch.name.to_json());
match ch.path.to_str() {
Some(p) => {
chapter.insert("path".to_owned(), p.to_json());
},
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))),
}
chapter.insert("section".to_owned(), json!(s));
chapter.insert("name".to_owned(), json!(ch.name));
let path = ch.path.to_str().ok_or_else(||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
chapter.insert("path".to_owned(), json!(path));
},
BookItem::Spacer => {
chapter.insert("spacer".to_owned(), "_spacer_".to_json());
chapter.insert("spacer".to_owned(), json!("_spacer_"));
},
}
@@ -323,8 +222,133 @@ fn make_data(book: &MDBook) -> Result<BTreeMap<String, Json>, Box<Error>> {
chapters.push(chapter);
}
data.insert("chapters".to_owned(), chapters.to_json());
data.insert("chapters".to_owned(), json!(chapters));
debug!("[*]: JSON constructed");
Ok(data)
}
fn build_header_links(html: String, filename: &str) -> String {
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
let mut id_counter = HashMap::new();
regex.replace_all(&html, |caps: &Captures| {
let level = &caps[1];
let text = &caps[2];
let mut id = text.to_string();
let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
"<strong>", "</strong>",
"&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
for sub in repl_sub {
id = id.replace(sub, "");
}
let id = id.chars().filter_map(|c| {
if c.is_alphanumeric() || c == '-' || c == '_' {
if c.is_ascii() {
Some(c.to_ascii_lowercase())
} else {
Some(c)
}
} else if c.is_whitespace() && c.is_ascii() {
Some('-')
} else {
None
}
}).collect::<String>();
let id_count = *id_counter.get(&id).unwrap_or(&0);
id_counter.insert(id.clone(), id_count + 1);
let id = if id_count > 0 {
format!("{}-{}", id, id_count)
} else {
id
};
format!("<a class=\"header\" href=\"{filename}#{id}\" id=\"{id}\"><h{level}>{text}</h{level}></a>",
level=level, id=id, text=text, filename=filename)
}).into_owned()
}
// anchors to the same page (href="#anchor") do not work because of
// <base href="../"> pointing to the root folder. This function *fixes*
// that in a very inelegant way
fn fix_anchor_links(html: String, filename: &str) -> String {
let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap();
regex.replace_all(&html, |caps: &Captures| {
let before = &caps[1];
let anchor = &caps[2];
let after = &caps[3];
format!("<a{before}href=\"{filename}#{anchor}\"{after}>",
before=before, filename=filename, anchor=anchor, after=after)
}).into_owned()
}
// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
// // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
regex.replace_all(&html, |caps: &Captures| {
let before = &caps[1];
let classes = &caps[2].replace(",", " ");
let after = &caps[3];
format!("<code{before}class=\"{classes}\"{after}>", before=before, classes=classes, after=after)
}).into_owned()
}
fn add_playpen_pre(html: String) -> String {
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
regex.replace_all(&html, |caps: &Captures| {
let text = &caps[1];
let classes = &caps[2];
let code = &caps[3];
if classes.contains("language-rust") && !classes.contains("ignore") {
// wrap the contents in an external pre block
if text.contains("fn main") {
format!("<pre class=\"playpen\">{}</pre>", text)
} else {
// we need to inject our own main
let (attrs, code) = partition_source(code);
format!("<pre class=\"playpen\"><code class=\"{}\"># #![allow(unused_variables)]
{}#fn main() {{
{}
#}}</code></pre>", classes, attrs, code)
}
} else {
// not language-rust, so no-op
format!("{}", text)
}
}).into_owned()
}
fn partition_source(s: &str) -> (String, String) {
let mut after_header = false;
let mut before = String::new();
let mut after = String::new();
for line in s.lines() {
let trimline = line.trim();
let header = trimline.chars().all(|c| c.is_whitespace()) ||
trimline.starts_with("#![");
if !header || after_header {
after_header = true;
after.push_str(line);
after.push_str("\n");
} else {
before.push_str(line);
before.push_str("\n");
}
}
(before, after)
}

View File

@@ -1,28 +1,29 @@
use std::path::Path;
use std::collections::BTreeMap;
use std::collections::{VecDeque, BTreeMap};
use serde_json;
use handlebars::{Handlebars, RenderError, RenderContext, Helper, Renderable};
use rustc_serialize::json::{self, ToJson};
use handlebars::{Handlebars, RenderError, RenderContext, Helper, Context, Renderable};
// Handlebars helper for navigation
pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
debug!("[fn]: previous (handlebars helper)");
debug!("[*]: Get data from context");
// get value from context data
// rc.get_path() is current json parent path, you should always use it like this
// param is the key of value you want to display
let chapters = c.navigate(rc.get_path(), "chapters");
let chapters = rc.context().navigate(rc.get_path(), &VecDeque::new(), "chapters").to_owned();
let current = c.navigate(rc.get_path(), "path")
let current = rc.context().navigate(rc.get_path(), &VecDeque::new(), "path")
.to_string()
.replace("\"", "");
debug!("[*]: Decode chapters from JSON");
// Decode json format
let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) {
let decoded: Vec<BTreeMap<String, String>> = match serde_json::from_str(&chapters.to_string()) {
Ok(data) => data,
Err(_) => return Err(RenderError::new("Could not decode the JSON data")),
};
@@ -48,7 +49,7 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
match previous.get("name") {
Some(n) => {
debug!("[*]: Inserting title: {}", n);
previous_chapter.insert("title".to_owned(), n.to_json())
previous_chapter.insert("title".to_owned(), json!(n))
},
None => {
debug!("[*]: No title found for chapter");
@@ -66,7 +67,7 @@ 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.replace("\\", "/").to_json());
previous_chapter.insert("link".to_owned(), json!(p.replace("\\", "/")));
},
None => return Err(RenderError::new("Link could not be converted to str")),
}
@@ -76,13 +77,14 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
debug!("[*]: Inject in context");
// Inject in current context
let updated_context = c.extend(&previous_chapter);
let updated_context = rc.context().extend(&previous_chapter);
debug!("[*]: Render template");
// Render template
match _h.template() {
Some(t) => {
try!(t.render(&updated_context, r, rc));
*rc.context_mut() = updated_context;
try!(t.render(r, rc));
},
None => return Err(RenderError::new("Error with the handlebars template")),
}
@@ -106,22 +108,22 @@ pub fn previous(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext
pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
debug!("[fn]: next (handlebars helper)");
debug!("[*]: Get data from context");
// get value from context data
// rc.get_path() is current json parent path, you should always use it like this
// param is the key of value you want to display
let chapters = c.navigate(rc.get_path(), "chapters");
let chapters = rc.context().navigate(rc.get_path(), &VecDeque::new(), "chapters").to_owned();
let current = c.navigate(rc.get_path(), "path")
let current = rc.context().navigate(rc.get_path(), &VecDeque::new(), "path")
.to_string()
.replace("\"", "");
debug!("[*]: Decode chapters from JSON");
// Decode json format
let decoded: Vec<BTreeMap<String, String>> = match json::decode(&chapters.to_string()) {
let decoded: Vec<BTreeMap<String, String>> = match serde_json::from_str(&chapters.to_string()) {
Ok(data) => data,
Err(_) => return Err(RenderError::new("Could not decode the JSON data")),
};
@@ -152,7 +154,7 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
match item.get("name") {
Some(n) => {
debug!("[*]: Inserting title: {}", n);
next_chapter.insert("title".to_owned(), n.to_json());
next_chapter.insert("title".to_owned(), json!(n));
},
None => return Err(RenderError::new("No title found for chapter in JSON data")),
}
@@ -164,21 +166,22 @@ pub fn next(c: &Context, _h: &Helper, r: &Handlebars, rc: &mut RenderContext) ->
match link.to_str() {
Some(l) => {
// Hack for windows who tends to use `\` as separator instead of `/`
next_chapter.insert("link".to_owned(), l.replace("\\", "/").to_json());
next_chapter.insert("link".to_owned(), json!(l.replace("\\", "/")));
},
None => return Err(RenderError::new("Link could not converted to str")),
}
debug!("[*]: Inject in context");
// Inject in current context
let updated_context = c.extend(&next_chapter);
let updated_context = rc.context().extend(&next_chapter);
debug!("[*]: Render template");
// Render template
match _h.template() {
Some(t) => {
try!(t.render(&updated_context, r, rc));
*rc.context_mut() = updated_context;
try!(t.render(r, rc));
},
None => return Err(RenderError::new("Error with the handlebars template")),
}

View File

@@ -31,11 +31,11 @@ pub fn render_playpen(s: &str, path: &Path) -> String {
continue;
};
let mut file_content = String::new();
if let Err(_) = file.read_to_string(&mut file_content) {
if file.read_to_string(&mut file_content).is_err() {
continue;
};
let replacement = String::new() + "<pre class=\"playpen\"><code class=\"language-rust\">" + &file_content +
let replacement = String::new() + "<pre><code class=\"language-rust\">" + &file_content +
"</code></pre>";
replaced.push_str(&s[previous_end_index..playpen.start_index]);
@@ -86,7 +86,7 @@ fn find_playpens(s: &str, base_path: &Path) -> Vec<Playpen> {
if end_i - 2 - (i + 10) < 1 {
continue;
}
if s[i + 10..end_i - 2].trim().len() == 0 {
if s[i + 10..end_i - 2].trim().is_empty() {
continue;
}
@@ -94,15 +94,10 @@ fn find_playpens(s: &str, base_path: &Path) -> Vec<Playpen> {
// 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
};
}
let editable = params
.get(1)
.map(|p| p.find("editable").is_some())
.unwrap_or(false);
playpens.push(Playpen {
start_index: i,

View File

@@ -1,8 +1,8 @@
use std::path::Path;
use std::collections::BTreeMap;
use std::collections::{VecDeque, BTreeMap};
use rustc_serialize::json;
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper, Context};
use serde_json;
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper};
use pulldown_cmark::{Parser, html, Event, Tag};
// Handlebars helper to construct TOC
@@ -10,63 +10,63 @@ use pulldown_cmark::{Parser, html, Event, Tag};
pub struct RenderToc;
impl HelperDef for RenderToc {
fn call(&self, c: &Context, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
fn call(&self, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
// get value from context data
// rc.get_path() is current json parent path, you should always use it like this
// param is the key of value you want to display
let chapters = c.navigate(rc.get_path(), "chapters");
let current = c.navigate(rc.get_path(), "path").to_string().replace("\"", "");
try!(rc.writer.write("<ul class=\"chapter\">".as_bytes()));
let chapters = rc.context().navigate(rc.get_path(), &VecDeque::new(), "chapters").to_owned();
let current = rc.context().navigate(rc.get_path(), &VecDeque::new(), "path").to_string().replace("\"", "");
try!(rc.writer.write_all("<ul class=\"chapter\">".as_bytes()));
// Decode json format
let decoded: Vec<BTreeMap<String, String>> = json::decode(&chapters.to_string()).unwrap();
let decoded: Vec<BTreeMap<String, String>> = serde_json::from_str(&chapters.to_string()).unwrap();
let mut current_level = 1;
for item in decoded {
// Spacer
if let Some(_) = item.get("spacer") {
try!(rc.writer.write("<li class=\"spacer\"></li>".as_bytes()));
if item.get("spacer").is_some() {
try!(rc.writer.write_all("<li class=\"spacer\"></li>".as_bytes()));
continue;
}
let level = if let Some(s) = item.get("section") {
s.len() / 2
s.matches(".").count()
} else {
1
};
if level > current_level {
while level > current_level {
try!(rc.writer.write("<li>".as_bytes()));
try!(rc.writer.write("<ul class=\"section\">".as_bytes()));
try!(rc.writer.write_all("<li>".as_bytes()));
try!(rc.writer.write_all("<ul class=\"section\">".as_bytes()));
current_level += 1;
}
try!(rc.writer.write("<li>".as_bytes()));
try!(rc.writer.write_all("<li>".as_bytes()));
} else if level < current_level {
while level < current_level {
try!(rc.writer.write("</ul>".as_bytes()));
try!(rc.writer.write("</li>".as_bytes()));
try!(rc.writer.write_all("</ul>".as_bytes()));
try!(rc.writer.write_all("</li>".as_bytes()));
current_level -= 1;
}
try!(rc.writer.write("<li>".as_bytes()));
try!(rc.writer.write_all("<li>".as_bytes()));
} else {
try!(rc.writer.write("<li".as_bytes()));
if let None = item.get("section") {
try!(rc.writer.write(" class=\"affix\"".as_bytes()));
try!(rc.writer.write_all("<li".as_bytes()));
if item.get("section").is_none() {
try!(rc.writer.write_all(" class=\"affix\"".as_bytes()));
}
try!(rc.writer.write(">".as_bytes()));
try!(rc.writer.write_all(">".as_bytes()));
}
// Link
let path_exists = if let Some(path) = item.get("path") {
if !path.is_empty() {
try!(rc.writer.write("<a href=\"".as_bytes()));
try!(rc.writer.write_all("<a href=\"".as_bytes()));
// Add link
try!(rc.writer.write(Path::new(item.get("path")
try!(rc.writer.write_all(Path::new(item.get("path")
.expect("Error: path should be Some(_)"))
.with_extension("html")
.to_str()
@@ -75,13 +75,13 @@ impl HelperDef for RenderToc {
.replace("\\", "/")
.as_bytes()));
try!(rc.writer.write("\"".as_bytes()));
try!(rc.writer.write_all("\"".as_bytes()));
if path == &current {
try!(rc.writer.write(" class=\"active\"".as_bytes()));
try!(rc.writer.write_all(" class=\"active\"".as_bytes()));
}
try!(rc.writer.write(">".as_bytes()));
try!(rc.writer.write_all(">".as_bytes()));
true
} else {
false
@@ -92,21 +92,21 @@ impl HelperDef for RenderToc {
// Section does not necessarily exist
if let Some(section) = item.get("section") {
try!(rc.writer.write("<strong>".as_bytes()));
try!(rc.writer.write(section.as_bytes()));
try!(rc.writer.write("</strong> ".as_bytes()));
try!(rc.writer.write_all("<strong>".as_bytes()));
try!(rc.writer.write_all(section.as_bytes()));
try!(rc.writer.write_all("</strong> ".as_bytes()));
}
if let Some(name) = item.get("name") {
// 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,
let parser = Parser::new(name).filter(|event| {
match *event {
Event::Start(Tag::Code) |
Event::End(Tag::Code) |
Event::InlineHtml(_) |
Event::Text(_) => true,
_ => false,
}
});
@@ -116,23 +116,23 @@ impl HelperDef for RenderToc {
html::push_html(&mut markdown_parsed_name, parser);
// write to the handlebars template
try!(rc.writer.write(markdown_parsed_name.as_bytes()));
try!(rc.writer.write_all(markdown_parsed_name.as_bytes()));
}
if path_exists {
try!(rc.writer.write("</a>".as_bytes()));
try!(rc.writer.write_all("</a>".as_bytes()));
}
try!(rc.writer.write("</li>".as_bytes()));
try!(rc.writer.write_all("</li>".as_bytes()));
}
while current_level > 1 {
try!(rc.writer.write("</ul>".as_bytes()));
try!(rc.writer.write("</li>".as_bytes()));
try!(rc.writer.write_all("</ul>".as_bytes()));
try!(rc.writer.write_all("</li>".as_bytes()));
current_level -= 1;
}
try!(rc.writer.write("</ul>".as_bytes()));
try!(rc.writer.write_all("</ul>".as_bytes()));
Ok(())
}
}

View File

@@ -3,6 +3,10 @@ body {
font-family: "Open Sans", sans-serif;
color: #333;
}
code {
font-family: "Source Code Pro", "Menlo", "DejaVu Sans Mono", monospace;
font-size: 0.875em;
}
.left {
float: left;
}
@@ -72,7 +76,7 @@ table thead td {
.chapter {
list-style: none outside none;
padding-left: 0;
line-height: 1.9em;
line-height: 2.2em;
}
.chapter li a {
padding: 5px 0;
@@ -89,7 +93,7 @@ table thead td {
.section {
list-style: none outside none;
padding-left: 20px;
line-height: 2.5em;
line-height: 1.9em;
}
.section li {
-o-text-overflow: ellipsis;
@@ -264,15 +268,11 @@ table thead td {
line-height: 25px;
white-space: nowrap;
}
.theme-popup .theme:hover:first-child {
.theme-popup .theme:hover:first-child,
.theme-popup .theme:hover:last-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;
@@ -765,9 +765,11 @@ table thead td {
.rust pre > .result {
margin-top: 10px;
}
@media print {
#sidebar {
@media only print {
#sidebar,
#menu-bar,
.nav-chapters,
.mobile-nav-chapters {
display: none;
}
#page-wrapper {
@@ -779,16 +781,40 @@ table thead td {
margin: 0;
padding: 0;
}
#menu-bar {
display: none;
}
.page {
overflow-y: initial;
}
.nav-chapters {
display: none;
code {
background-color: #666;
-webkit-border-radius: 5px;
border-radius: 5px;
/* Force background to be printed in Chrome */
-webkit-print-color-adjust: exact;
}
.mobile-nav-chapters {
display: none;
a,
a:visited,
a:active,
a:hover {
color: #4183c4;
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
page-break-inside: avoid;
page-break-after: avoid;
/*break-after: avoid*/
}
pre,
code {
page-break-inside: avoid;
white-space: pre-wrap /* CSS 3 */;
white-space: -moz-pre-wrap /* Mozilla, since 1999 */;
white-space: -pre-wrap /* Opera 4-6 */;
white-space: -o-pre-wrap /* Opera 7 */;
word-wrap: break-word /* Internet Explorer 5.5+ */;
}
}

View File

@@ -22,6 +22,10 @@ $( document ).ready(function() {
$('code').each(function(i, block) {
hljs.highlightBlock(block);
});
// Adding the hljs class gives code blocks the color css
// even if highlighting doesn't apply
$('code').addClass('hljs');
var KEY_CODES = {
PREVIOUS_KEY: 37,
@@ -29,6 +33,7 @@ $( document ).ready(function() {
};
$(document).on('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
switch (e.keyCode) {
case KEY_CODES.NEXT_KEY:
e.preventDefault();
@@ -51,19 +56,6 @@ $( document ).ready(function() {
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...
// Remove any existing anchor then append the new one
// ensuring eg. no spaces are present within it ie. they become %20
wrapper.attr("href", $(location).attr('href').split("#")[0] + "#" + encodeURIComponent($(this).text().trim()) );
return wrapper;
});
// Toggle sidebar
$("#sidebar-toggle").click(function(event){
if ( html.hasClass("sidebar-hidden") ) {
@@ -216,6 +208,18 @@ function run_rust_code(code_block) {
result_block = code_block.find(".result");
}
let text = code_block.find(".language-rust").text();
let params = {
version: "stable",
optimize: "0",
code: text,
};
if(text.includes("#![feature")) {
params.version = "nightly";
}
result_block.text("Running...");
$.ajax({
@@ -224,7 +228,7 @@ function run_rust_code(code_block) {
crossDomain: true,
dataType: "json",
contentType: "application/json",
data: JSON.stringify({version: "stable", optimize: "0", code: code_block.find(".language-rust").text() }),
data: JSON.stringify(params),
success: function(response){
result_block.text(response.result);
}

View File

@@ -12,96 +12,59 @@
/* Atelier-Dune Comment */
.hljs-comment {
.hljs-comment,
.hljs-quote {
color: #AAA;
}
/* Atelier-Dune Red */
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-regexp,
.hljs-name,
.ruby .hljs-constant,
.xml .hljs-tag .hljs-title,
.xml .hljs-pi,
.xml .hljs-doctype,
.html .hljs-doctype,
.css .hljs-id,
.css .hljs-class,
.css .hljs-pseudo {
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #d73737;
}
/* Atelier-Dune Orange */
.hljs-number,
.hljs-preprocessor,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-params,
.hljs-attribute,
.hljs-constant {
.hljs-type,
.hljs-params {
color: #b65611;
}
/* Atelier-Dune Yellow */
.ruby .hljs-class .hljs-title,
.css .hljs-rule .hljs-attribute {
color: #ae9513;
}
/* Atelier-Dune Green */
.hljs-string,
.hljs-value,
.hljs-inheritance,
.ruby .hljs-symbol,
.xml .hljs-cdata {
color: #2a9292;
}
/* Atelier-Dune Aqua */
.hljs-title,
.css .hljs-hexcolor {
color: #1fad83;
.hljs-symbol,
.hljs-bullet {
color: #60ac39;
}
/* Atelier-Dune Blue */
.hljs-function,
.python .hljs-decorator,
.python .hljs-title,
.ruby .hljs-function .hljs-title,
.ruby .hljs-title .hljs-keyword,
.perl .hljs-sub,
.javascript .hljs-title,
.coffeescript .hljs-title {
.hljs-title,
.hljs-section {
color: #6684e1;
}
/* Atelier-Dune Purple */
.hljs-keyword,
.javascript .hljs-function {
.hljs-selector-tag {
color: #b854d4;
}
.coffeescript .javascript,
.javascript .xml,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
.hljs-emphasis {
font-style: italic;
}
/* markdown */
.hljs-header {
color: #A30000;
}
.hljs-link_label {
color: #33CCCC;
}
.hljs-link_url {
color: #CC66FF;
.hljs-strong {
font-weight: bold;
}

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
<html lang="{{ language }}">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<title>{{ chapter_title }} - {{ title }}</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -10,7 +10,8 @@
<base href="{{ path_to_root }}">
<link rel="stylesheet" href="book.css">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="{{ favicon }}">

View File

@@ -56,12 +56,6 @@ impl Theme {
return theme;
}
let src = src.join("theme");
// If src does exist, check if there is a theme directory in it
if !src.exists() || !src.is_dir() {
return theme;
}
// Check for individual files if they exist
// index.hbs

View File

@@ -7,3 +7,4 @@
@import 'nav-icons'
@import 'theme-popup'
@import 'themes'
@import 'print'

View File

@@ -3,6 +3,11 @@ html, body {
color: #333
}
code {
font-family: "Source Code Pro", "Menlo", "DejaVu Sans Mono", monospace;
font-size: 0.875em;
}
.left {
float: left
}

View File

@@ -1,31 +1,38 @@
@media only print {
.sidebar,
.menu-bar,
#sidebar,
#menu-bar,
.nav-chapters,
.mobile-nav-chapters {
display: none
display: none
}
.page-wrapper {
left: 0
#page-wrapper {
left: 0;
overflow-y: initial;
}
.content {
max-width: 100%
#content {
max-width: none;
margin: 0;
padding: 0;
}
.page {
overflow-y: initial;
}
code {
background-color: #666666
border-radius: 5px
background-color: #666666
border-radius: 5px
/* Force background to be printed in Chrome */
-webkit-print-color-adjust: exact
/* Force background to be printed in Chrome */
-webkit-print-color-adjust: exact
}
a, a:visited, a:active, a:hover {
color: #4183c4
text-decoration: none
color: #4183c4
text-decoration: none
}
h1, h2, h3, h4, h5, h6 {

View File

@@ -35,7 +35,7 @@
.chapter {
list-style: none outside none
padding-left: 0
line-height: 1.9em
line-height: 2.2em
li a {
padding: 5px 0
@@ -54,7 +54,7 @@
.section {
list-style: none outside none
padding-left: 20px
line-height: 2.5em
line-height: 1.9em
li {
text-overflow: ellipsis

View File

@@ -3,7 +3,7 @@
left: 10px
z-index: 1000;
border-radius: 4px
font-size: 0.7em
@@ -12,7 +12,14 @@
padding: 2px 10px
line-height: 25px
white-space: nowrap
&:hover:first-child,
&:hover:last-child {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
}
}
}
@media only screen and (max-width: 1250px) {

View File

@@ -1,7 +1,7 @@
use std::path::{Path, Component};
use std::error::Error;
use std::io::{self, Read};
use std::fs::{self, metadata, File};
use std::fs::{self, File};
/// Takes a path to a file and try to read the file into a String
@@ -69,7 +69,7 @@ pub fn path_to_root(path: &Path) -> String {
/// 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.
pub fn create_file(path: &Path) -> Result<File, Box<Error>> {
pub fn create_file(path: &Path) -> io::Result<File> {
debug!("[fn]: create_file");
// Construct path
@@ -80,15 +80,7 @@ pub fn create_file(path: &Path) -> Result<File, Box<Error>> {
}
debug!("[*]: Create file: {:?}", 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)
File::create(path)
}
/// Removes all the content of a directory but not the directory itself
@@ -147,15 +139,15 @@ pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blackl
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...")));
info!("[*] 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..."))));
}
debug!("[*] creating path for file: {:?}",
&to.join(entry.path().file_name().expect("a file should have a file name...")));
info!("[*] 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..."))));
}
}
Ok(())

View File

@@ -14,7 +14,7 @@ pub fn render_markdown(text: &str) -> String {
opts.insert(OPTION_ENABLE_TABLES);
opts.insert(OPTION_ENABLE_FOOTNOTES);
let p = Parser::new_ext(&text, opts);
let p = Parser::new_ext(text, opts);
html::push_html(&mut s, p);
s
}