Compare commits

..

49 Commits

Author SHA1 Message Date
steveklabnik
b45e5e4420 Release 0.0.26
Fixes #460
2017-10-02 09:10:17 -04:00
Mathieu David
a6d4881e00 Merge pull request #450 from Zengor/master
Call playground with /execute
2017-09-23 21:35:41 +02:00
Mathieu David
a0515bd104 Merge pull request #449 from steveklabnik/fix-regression
Change key for theme to not clobber old books
2017-09-23 20:42:31 +02:00
steveklabnik
9b64db908f prefix sidebar too 2017-09-22 13:58:45 -04:00
steveklabnik
f562878131 I forgot one theme, thanks budziq 2017-09-22 13:56:58 -04:00
Zengor
3823fc0e74 Call playground with /execute and not the legacy /evaluate.json
This commit changes the url used to call the playground, and the
request parameter format to go with it. The older evaluate is
available in the playground as a form of backwards compatibility
and swithcing now opens way for using newer features.
2017-09-21 00:24:47 -03:00
steveklabnik
793fb8f654 Change key for theme to not clobber old books
Fixes https://github.com/azerupi/mdBook/issues/448
2017-09-19 16:59:16 -04:00
Bartłomiej T. Listwon
911683d2cf Fix styling regression on print media in chromium
Forces 0px left padding on print view even if sidebar is visible
2017-09-18 22:10:31 +02:00
Mathieu David
2ae6e6a6e3 Merge pull request #445 from Listwon/master
Fix code snippet font size a little smaller in FF
2017-09-18 14:32:57 +02:00
Bartłomiej T. Listwon
91fd8a2865 Fix code snippet font size a little smaller in FF 2017-09-18 11:18:21 +02:00
Steve Klabnik
a3b6e549e2 Merge pull request #440 from budziq/force_runnable
added `mdbook-runnable` infostring support
2017-09-14 12:48:29 -04:00
Steve Klabnik
d450518292 Merge pull request #439 from azerupi/fix-print
Fix the issue with pages named print not at the root
2017-09-14 11:44:38 -04:00
Michal Budzynski
c056df597a added mdbook-runnable infostring support
makes `ignore`'d playpens runnable
2017-09-13 22:54:01 +02:00
Mathieu David
0d6adc5fc9 Fix the issue with pages named print not at the root 2017-09-13 22:17:23 +02:00
Mathieu David
0226da91e4 Fix shield in README 2017-09-11 19:47:39 +02:00
Mathieu David
ef5895fa78 Update all dependencies 2017-09-11 19:38:10 +02:00
Mathieu David
8e0abfb22f Fix test condition in Travis config 2017-09-10 09:06:46 +02:00
Mathieu David
c9bc13d786 Merge pull request #429 from azerupi/ci-infra
Revive Travis
2017-09-10 00:09:08 +02:00
Mathieu David
7ce78cbfea Fix missing && in travis config 2017-09-08 20:24:29 +02:00
Mathieu David
26544fa531 Fix Travis script 2017-09-08 20:24:29 +02:00
Mathieu David
0c93770f4a Update and simplify Travis CI 2017-09-08 20:24:29 +02:00
Mathieu David
743713ad3a Merge pull request #425 from dvberkel/correct-documentation-of-inline-mathematics
Correct inline mathematics delimiters
2017-09-08 20:23:08 +02:00
Mathieu David
e3f4bb5101 (cargo-release) start next development iteration 0.0.26-alpha.0 2017-09-08 20:21:49 +02:00
Mathieu David
441bcb5963 (cargo-release) version 0.0.25 2017-09-08 20:20:48 +02:00
Mathieu David
84ef4d2617 preserve dashes when generating anchors and trim whitespace 2017-09-08 19:59:04 +02:00
Daan van Berkel
bd30cae17e Correct inline mathematics delimiters
This fixes #424
2017-09-08 09:21:20 +02:00
Mathieu David
6f0b67f44f (cargo-release) start next development iteration 0.0.25-alpha.0 2017-09-07 23:32:57 +02:00
Mathieu David
abf86eefd9 (cargo-release) version 0.0.24 2017-09-07 23:31:57 +02:00
Mathieu David
016ec8836c Merge pull request #415 from azerupi/fix-print-title
Fix the print title that was using the title from the last rendered chapter
2017-09-07 23:29:54 +02:00
Mathieu David
881a1b39ff Remove the logic in handlebars and expose the 3 different titles in the handlebars variables 2017-09-07 23:19:22 +02:00
Mathieu David
a1e58229b2 Merge pull request #418 from behnam/manifest
[Cargo.toml] Fix package.exclude warnings
2017-09-07 22:46:49 +02:00
Mathieu David
276eab095c Merge pull request #427 from budziq/spurious_reloads
Do not trigger spurious watch events on Write and Remove
2017-09-07 22:45:12 +02:00
Mathieu David
f4513d3b5c Merge pull request #419 from behnam/nested
Fix heading links in nested pages
2017-09-07 22:39:51 +02:00
Michal Budzynski
570ce6681f Do not trigger spurious watch events on Write and Remove 2017-09-06 22:33:56 +02:00
Behnam Esfahbod
ddee839d9c [renderer] Err on bad file names, instead of panic
Addressing the review comments.
2017-09-06 02:25:10 -07:00
Behnam Esfahbod
99945542ca [renderer] Add normalize_path()
On the web, the normalized path separator is forward-slash (`/`), so we
use the built-in `is_separator()` method to replace any path separator
with the forward-slash, to ensure consistent output on unix and windows
machines.
2017-09-06 00:52:17 -07:00
Behnam Esfahbod
956a5cc7fd Fix heading links in nested pages
Plus fixing the whitespace chars not being replaced by hyphen.

Also expand tests for link creations, and add test for nested pages.

Fixes <https://github.com/azerupi/mdBook/issues/416>
Fixes <https://github.com/azerupi/mdBook/issues/417>
2017-09-06 00:52:17 -07:00
Behnam Esfahbod
cef62ec42e Fix build and test warnings
Move non-test test module files into their own directories to prevent
cargo from running them as tests. Then suppress the left-over warnings.

Move *dummy book* code and data into a shared folder, and leave the rest
of helper utilities (one function) in the original module.
2017-09-06 00:52:17 -07:00
Behnam Esfahbod
b1362bfa06 [watch] Fix build warnings 2017-09-06 00:52:15 -07:00
Behnam Esfahbod
a529ca5e65 [Cargo.toml] Fix package.exclude warnings
IIUC, the existing exclude rule has meant to do what it will do in the
future (gitignore-like matching, not glob-only matching). This fix makes
the rule to do what it was expected to do, and is forward-compatible,
therefore fixing the warning messages.
2017-09-06 00:52:15 -07:00
Michal Budzynski
6bc3039b4f Both static and ACE editable snippets have optional play button
- list of available crates is dynamically loaded from play.rust-lang.org
- play button is enabled only if crates used in snippet are available on playground
- ACE editor's play button is dynamically updated on each text change
- `no_run` is honored by always disabling the play button
- minor cleanups
2017-09-06 00:18:24 +02:00
Michal Budzynski
cd90fdd407 first prototype of play-button enabling only if crate list supported
also minor refactor of clipboard handling
TODO:
- `no_run` support
- test with ACE
- disable play button with tooltip instead of hiding
2017-09-06 00:18:24 +02:00
Mathieu David
a6a7c95c78 Merge pull request #343 from budziq/appveyor_css
fixed `cargo build --features=regenerate-css` on win 10 / nightly
2017-09-05 19:33:57 +02:00
Mathieu David
0a4a2b66da Fix the print title that was using the title from the last rendered chapter. Fixes #414 2017-09-01 08:22:24 +02:00
Mathieu David
2f3c14d609 ignore vscode dir in git 2017-09-01 08:22:01 +02:00
Mathieu David
ebcf1e495d Merge pull request #413 from behnam/gitignore
[book] Prevent over-matching in gitignore rule
2017-08-31 19:27:38 +02:00
Behnam Esfahbod
40a4840867 [book] Prevent over-matching in gitignore rule
To only ignore the output destination (default: `book`) and no other
file/directory with the same name under the mdbook root, we should
prefix the gitignore rule with a leading slash (default: `/book`).
2017-08-30 16:01:45 -07:00
steveklabnik
313f9b9403 Bump version in Cargo.toml for next release 2017-08-29 12:53:32 -04:00
Michal Budzynski
d0a6aea3aa fixed cargo build --features=regenerate-css on windows 10
also added cargo build --features=regenerate-css to appveyor.yml
2017-08-12 13:20:26 +02:00
32 changed files with 388 additions and 380 deletions

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ target
book-test
book-example/book
.vscode

View File

@@ -1,110 +1,42 @@
language: rust
sudo: false
language: generic
cache: cargo
env:
global:
- PROJECT_NAME=mdBook
rust:
- stable
- beta
- nightly
matrix:
include:
# Stable channel
- os: osx
env: TARGET=i686-apple-darwin CHANNEL=stable
- os: linux
env: TARGET=i686-unknown-linux-gnu CHANNEL=stable
addons:
apt:
packages: &i686_unknown_linux_gnu
- gcc-multilib
- os: osx
env: TARGET=x86_64-apple-darwin CHANNEL=stable
- os: linux
env: TARGET=x86_64-unknown-linux-gnu CHANNEL=stable
addons:
apt:
packages:
- nodejs
- os: linux
env: TARGET=x86_64-unknown-linux-musl CHANNEL=stable
dist: trusty
addons:
apt:
packages: &musl_packages
- musl
- musl-dev
- musl-tools
# Beta channel
- os: osx
env: TARGET=i686-apple-darwin CHANNEL=beta
- os: linux
env: TARGET=i686-unknown-linux-gnu CHANNEL=beta
addons:
apt:
packages: *i686_unknown_linux_gnu
- os: osx
env: TARGET=x86_64-apple-darwin CHANNEL=beta
- os: linux
env: TARGET=x86_64-unknown-linux-gnu CHANNEL=beta
- os: linux
env: TARGET=x86_64-unknown-linux-musl CHANNEL=beta
dist: trusty
addons:
apt:
packages: &musl_packages
- musl
- musl-dev
- musl-tools
# Nightly channel
- os: osx
env: TARGET=i686-apple-darwin CHANNEL=nightly
- os: linux
env: TARGET=i686-unknown-linux-gnu CHANNEL=nightly
addons:
apt:
packages: *i686_unknown_linux_gnu
- os: osx
env: TARGET=x86_64-apple-darwin CHANNEL=nightly
- os: linux
env: TARGET=x86_64-unknown-linux-gnu CHANNEL=nightly
- os: linux
env: TARGET=x86_64-unknown-linux-musl CHANNEL=nightly
dist: trusty
addons:
apt:
packages: &musl_packages
- musl
- musl-dev
- musl-tools
install:
- export PATH="$PATH:$HOME/.cargo/bin"
- bash ci/install.sh
os:
- linux
- osx
script:
- bash ci/script.sh
- cargo build --verbose
- cargo test --verbose
after_success:
# Deploy the docs if the commit is on master
- test "$TRAVIS_PULL_REQUEST" == "false" &&
test "$TRAVIS_BRANCH" == "master" &&
test "$TARGET" == "x86_64-unknown-linux-gnu" &&
test "$CHANNEL" = "stable" &&
test "$TRAVIS_RUST_VERSION" == "stable" &&
npm install stylus nib &&
bash deploy.sh
bash ci/deploy.sh
before_deploy:
# Script to create packages from the build artefacts to upload to GitHub
- bash ci/before_deploy.sh
deploy:
provider: releases
api_key:
secure: Z1k7WqX7z+tT4+SzTh4tBBzf11VaADB4AWuEczHtylaEb/0hRs8gaiHCNSVHm/QTp0QPWQR2Vw7uKMhVuxG7I8X7h31j3A7ulYBh/iVk0DVIrtrn2Q4WOED9CpoXLuLtk2nxo9MBViFW7mw4nJe9H2Tn9o/9oEYBuwzekvW5mh4muqUuCVTr8eQVYbs3jbC9pQy5oYjOLeUnlL9Cey5VN/nAhzAtyFP+6lIMri0PKit4JtkFou/O1MEpFYlP3VGC2lFiWuByocPKBT/L45FecS9qoHq+i6+ZCPDH2eu46nuYsDbLKAkPdGvf1MdPBPwoj0vSnZbgaTisQ4hIoBngQQQPZlPaGtcdd6g6asxSfnbA9cQhClI5oZJmg+ksxQE+peE8pnbmZ10Ix0PpIkkfWdQeMdUUCQarOTkTK54Munw+X+kp1lH19j6+krQPLBYr95fPRd4b5tWsJD2+pb/UOYFEEJxMNoUHyLCrtdCO7imOwrSUcv51+Z8UudqfPpKQeszrJcntL4owip35r3sF5TsE9YfW5qssLC164IylvP32y1AcfL1jqg8b+zrqLZKanjvDOJ1dtHHuwKqxcwf7PhAf0YjAtVSH9OIYcDzmDa0EMLrq7EK0fs6NAeb5qt6CML7pZrRS3fmOxN53Fbmj81qm6TmjQjDe4dmZlELgNow=
file: ${PROJECT_NAME}-${TRAVIS_TAG}-${TARGET}.tar.gz
file: ${PROJECT_NAME}-${TRAVIS_TAG}-${TRAVIS_OS_NAME}.tar.gz
# don't delete the artifacts from previous phases
skip_cleanup: true
# deploy when a new tag is pushed
on:
condition: $CHANNEL = stable
condition: $TRAVIS_RUST_VERSION = stable
tags: true
notifications:

View File

@@ -1,6 +1,6 @@
[package]
name = "mdbook"
version = "0.0.23"
version = "0.0.26"
authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
description = "create books from markdown files (like Gitbook)"
documentation = "http://azerupi.github.io/mdBook/index.html"
@@ -11,17 +11,17 @@ readme = "README.md"
build = "build.rs"
exclude = [
"book-example/*",
"src/theme/stylus",
"src/theme/stylus/**",
]
[dependencies]
clap = "2.24"
handlebars = "0.27"
handlebars = "0.29"
serde = "1.0"
serde_derive = "1.0"
error-chain = "0.10.0"
error-chain = "0.11.0"
serde_json = "1.0"
pulldown-cmark = "0.0.14"
pulldown-cmark = "0.1"
lazy_static = "0.2"
log = "0.3"
env_logger = "0.4.0"
@@ -33,7 +33,7 @@ tempdir = "0.3.4"
# Watch feature
notify = { version = "4.0", optional = true }
time = { version = "0.1.34", optional = true }
crossbeam = { version = "0.2.8", optional = true }
crossbeam = { version = "0.3", optional = true }
# Serve feature
iron = { version = "0.5", optional = true }
@@ -41,7 +41,7 @@ staticfile = { version = "0.4", optional = true }
ws = { version = "0.7", optional = true}
[build-dependencies]
error-chain = "0.10"
error-chain = "0.11"
[features]
default = ["output", "watch", "serve"]

View File

@@ -16,7 +16,7 @@
<tr>
<td colspan="2">
<a href="https://crates.io/crates/mdbook"><img src="https://img.shields.io/crates/v/mdbook.svg"></a>
<a href="LICENSE"><img src="https://img.shields.io/crates/l/mdbook.svg"></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/azerupi/mdBook.svg"></a>
</td>
</tr>
</table>

View File

@@ -1,6 +1,7 @@
environment:
global:
PROJECT_NAME: mdBook
nodejs_version: "6"
matrix:
# Stable channel
- TARGET: i686-pc-windows-msvc
@@ -31,12 +32,17 @@ install:
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
- ps: Install-Product node $env:nodejs_version
- node --version
- npm --version
- npm install -g stylus nib
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo build --verbose
- cargo build --verbose --features=regenerate-css
- cargo test --verbose
before_deploy:

View File

@@ -13,7 +13,7 @@ mathjax-support = true
The usual delimiters MathJax uses are not yet supported. You can't currently use `$$ ... $$` as delimiters and the `\[ ... \]` delimiters need an extra backslash to work. Hopefully this limitation will be lifted soon.
### Inline equations
Inline equations are delimited by `\\[` and `\\]`. So for example, to render the following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write the following:
Inline equations are delimited by `\\(` and `\\)`. So for example, to render the following inline equation \\( \int x dx = \frac{x^2}{2} + C \\) you would write the following:
```
\\( \int x dx = \frac{x^2}{2} + C \\)
```

View File

@@ -1,6 +1,5 @@
// build.rs
use std::process::Command;
use std::env;
use std::path::Path;
#[macro_use]
@@ -8,13 +7,21 @@ extern crate error_chain;
#[cfg(windows)]
mod execs {
pub const NPM: &'static str = "npm.cmd";
pub const STYLUS: &'static str = "stylus.cmd";
use std::process::Command;
pub fn cmd(program: &str) -> Command {
let mut cmd = Command::new("cmd");
cmd.args(&["/c", program]);
cmd
}
}
#[cfg(not(windows))]
mod execs {
pub const NPM: &'static str = "npm";
pub const STYLUS: &'static str = "stylus";
use std::process::Command;
pub fn cmd(program: &str) -> Command {
Command::new(program)
}
}
@@ -25,7 +32,7 @@ error_chain!{
}
fn program_exists(program: &str) -> Result<()> {
Command::new(program)
execs::cmd(program)
.arg("-v")
.output()
.chain_err(|| format!("Please install '{}'!", program))?;
@@ -33,7 +40,7 @@ fn program_exists(program: &str) -> Result<()> {
}
fn npm_package_exists(package: &str) -> Result<()> {
let status = Command::new(execs::NPM)
let status = execs::cmd("npm")
.args(&["list", "-g"])
.arg(package)
.output();
@@ -67,7 +74,7 @@ fn run() -> Result<()> {
if let Ok(_) = env::var("CARGO_FEATURE_REGENERATE_CSS") {
// Check dependencies
Program(execs::NPM).exists()?;
Program("npm").exists()?;
Program("node").exists().or(Program("nodejs").exists())?;
Package("nib").exists()?;
Package("stylus").exists()?;
@@ -78,7 +85,7 @@ fn run() -> Result<()> {
let theme_dir = Path::new(&manifest_dir).join("src/theme/");
let stylus_dir = theme_dir.join("stylus/book.styl");
if !Command::new(execs::STYLUS)
if !execs::cmd("stylus")
.arg(stylus_dir)
.arg("--out")
.arg(theme_dir)

View File

@@ -18,7 +18,7 @@ mk_tarball() {
pushd $td
tar czf $out_dir/${PROJECT_NAME}-${TRAVIS_TAG}-${TARGET}.tar.gz *
tar czf $out_dir/${PROJECT_NAME}-${TRAVIS_TAG}-${TRAVIS_OS_NAME}.tar.gz *
popd $td
rm -r $td

View File

@@ -1,59 +0,0 @@
# `install` phase: install stuff needed for the `script` phase
set -ex
case "$TRAVIS_OS_NAME" in
linux)
host=x86_64-unknown-linux-gnu
;;
osx)
host=x86_64-apple-darwin
;;
esac
mktempd() {
echo $(mktemp -d 2>/dev/null || mktemp -d -t tmp)
}
install_rustup() {
local td=$(mktempd)
pushd $td
curl -O https://static.rust-lang.org/rustup/dist/$host/rustup-setup
chmod +x rustup-setup
./rustup-setup -y
popd
rm -r $td
rustup self update
rustup install "$CHANNEL"
rustup default "$CHANNEL"
rustc -V
cargo -V
}
install_standard_crates() {
if [ "$host" != "$TARGET" ]; then
if [ ! "$CHANNEL" = "stable" ]; then
rustup target add $TARGET
else
local version=$(rustc -V | cut -d' ' -f2)
local tarball=rust-std-${version}-${TARGET}
local td=$(mktempd)
curl -s https://static.rust-lang.org/dist/${tarball}.tar.gz | \
tar --strip-components 1 -C $td -xz
$td/install.sh --prefix=$(rustc --print sysroot)
rm -r $td
fi
fi
}
main() {
install_rustup
install_standard_crates
}
main

View File

@@ -1,45 +0,0 @@
# `script` phase: you usually build, test and generate docs in this phase
set -ex
# NOTE Workaround for rust-lang/rust#31907 - disable doc tests when cross compiling
# This has been fixed in the nightly channel but it would take a while to reach the other channels
disable_cross_doctests() {
local host
case "$TRAVIS_OS_NAME" in
linux)
host=x86_64-unknown-linux-gnu
;;
osx)
host=x86_64-apple-darwin
;;
esac
if [ "$host" != "$TARGET" ] && [ "$CHANNEL" != "nightly" ]; then
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
brew install gnu-sed --default-names
fi
find src -name '*.rs' -type f | xargs sed -i -e 's:\(//.\s*```\):\1 ignore,:g'
fi
}
run_test_suite() {
# Extra test without default features to avoid bitrot. We only test on a single target (but with
# all the channels) to avoid significantly increasing the build times
if [ $TARGET = x86_64-unknown-linux-gnu ]; then
cargo build --target $TARGET --no-default-features --verbose
cargo test --target $TARGET --no-default-features --verbose
cargo clean
fi
cargo build --target $TARGET --verbose
cargo test --target $TARGET --verbose
}
main() {
disable_cross_doctests
run_test_suite
}
main

View File

@@ -1,6 +1,4 @@
extern crate notify;
extern crate time;
extern crate crossbeam;
use std::path::Path;
use self::notify::Watcher;
@@ -98,8 +96,6 @@ pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
match rx.recv() {
Ok(event) => {
match event {
NoticeWrite(path) |
NoticeRemove(path) |
Create(path) |
Write(path) |
Remove(path) |

View File

@@ -223,7 +223,7 @@ impl MDBook {
debug!("[*]: Writing to .gitignore");
writeln!(f, "{}", relative).expect("Could not write to file.");
writeln!(f, "/{}", relative).expect("Could not write to file.");
}
}

View File

@@ -51,9 +51,17 @@ impl HtmlHandlebars {
io::Error::new(io::ErrorKind::Other, "Could not convert path to str")
})?;
// Non-lexical lifetimes needed :'(
let title: String;
{
let book_title = ctx.data.get("book_title").and_then(serde_json::Value::as_str).unwrap_or("");
title = ch.name.clone() + " - " + book_title;
}
ctx.data.insert("path".to_owned(), json!(path));
ctx.data.insert("content".to_owned(), json!(content));
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert(
"path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)),
@@ -63,14 +71,16 @@ impl HtmlHandlebars {
debug!("[*]: Render template");
let rendered = ctx.handlebars.render("index", &ctx.data)?;
let filename = Path::new(&ch.path).with_extension("html");
let filepath = Path::new(&ch.path).with_extension("html");
let rendered = self.post_process(rendered,
filename.file_name().unwrap().to_str().unwrap_or(""),
ctx.book.get_html_config().get_playpen_config());
&normalize_path(filepath.to_str()
.ok_or(Error::from(format!("Bad file name: {}", filepath.display())))?),
ctx.book.get_html_config().get_playpen_config()
);
// Write to file
info!("[*] Creating {:?} ✓", filename.display());
ctx.book.write_file(filename, &rendered.into_bytes())?;
info!("[*] Creating {:?} ✓", filepath.display());
ctx.book.write_file(filepath, &rendered.into_bytes())?;
if ctx.is_index {
self.render_index(ctx.book, ch, &ctx.destination)?;
@@ -111,9 +121,9 @@ impl HtmlHandlebars {
Ok(())
}
fn post_process(&self, rendered: String, filename: &str, playpen_config: &PlaypenConfig) -> String {
let rendered = build_header_links(&rendered, filename);
let rendered = fix_anchor_links(&rendered, filename);
fn post_process(&self, rendered: String, filepath: &str, playpen_config: &PlaypenConfig) -> String {
let rendered = build_header_links(&rendered, &filepath);
let rendered = fix_anchor_links(&rendered, &filepath);
let rendered = fix_code_blocks(&rendered);
let rendered = add_playpen_pre(&rendered, playpen_config);
@@ -182,7 +192,7 @@ impl HtmlHandlebars {
Ok(())
}
/// Helper function to write a file to the build directory, normalizing
/// Helper function to write a file to the build directory, normalizing
/// the path to be relative to the book root.
fn write_custom_file(&self, custom_file: &Path, book: &MDBook) -> Result<()> {
let mut data = Vec::new();
@@ -207,6 +217,10 @@ impl HtmlHandlebars {
/// Update the context with data for this file
fn configure_print_version(&self, data: &mut serde_json::Map<String, serde_json::Value>, print_content: &str) {
// Make sure that the Print chapter does not display the title from
// the last rendered chapter by removing it from its context
data.remove("title");
data.insert("is_print".to_owned(), json!(true));
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"))));
@@ -276,6 +290,7 @@ impl Renderer for HtmlHandlebars {
// Print version
self.configure_print_version(&mut data, &print_content);
data.insert("title".to_owned(), json!(book.get_title()));
// Render the handlebars template with the data
debug!("[*]: Render template");
@@ -284,7 +299,7 @@ impl Renderer for HtmlHandlebars {
let rendered = self.post_process(rendered, "print.html",
book.get_html_config().get_playpen_config());
book.write_file(
Path::new("print").with_extension("html"),
&rendered.into_bytes(),
@@ -308,7 +323,7 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
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("book_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() {
@@ -412,7 +427,7 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
/// Goes through the rendered HTML, making sure all header tags are wrapped in
/// an anchor so people can link to sections directly.
fn build_header_links(html: &str, filename: &str) -> String {
fn build_header_links(html: &str, filepath: &str) -> String {
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
let mut id_counter = HashMap::new();
@@ -422,14 +437,14 @@ fn build_header_links(html: &str, filename: &str) -> String {
"Regex should ensure we only ever get numbers here",
);
wrap_header_with_link(level, &caps[2], &mut id_counter, filename)
wrap_header_with_link(level, &caps[2], &mut id_counter, filepath)
})
.into_owned()
}
/// Wraps a single header tag with a link, making sure each tag gets its own
/// unique ID by appending an auto-incremented number (if necessary).
fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<String, usize>, filename: &str)
fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<String, usize>, filepath: &str)
-> String {
let raw_id = id_from_content(content);
@@ -443,21 +458,21 @@ fn wrap_header_with_link(level: usize, content: &str, id_counter: &mut HashMap<S
*id_count += 1;
format!(
r#"<a class="header" href="{filename}#{id}" id="{id}"><h{level}>{text}</h{level}></a>"#,
r##"<a class="header" href="{filepath}#{id}" id="{id}"><h{level}>{text}</h{level}></a>"##,
level = level,
id = id,
text = content,
filename = filename
filepath = filepath
)
}
/// Generate an id for use with anchors which is derived from a "normalised"
/// Generate an id for use with anchors which is derived from a "normalised"
/// string.
fn id_from_content(content: &str) -> String {
let mut content = content.to_string();
// Skip any tags or html-encoded stuff
let repl_sub = vec![
const REPL_SUB: &[&str] = &[
"<em>",
"</em>",
"<code>",
@@ -470,27 +485,20 @@ fn id_from_content(content: &str) -> String {
"&#39;",
"&quot;",
];
for sub in repl_sub {
for sub in REPL_SUB {
content = content.replace(sub, "");
}
let mut id = String::new();
// Remove spaces and hastags indicating a header
let trimmed = content.trim().trim_left_matches("#").trim();
for c in content.chars() {
if c.is_alphanumeric() || c == '-' || c == '_' {
id.push(c.to_ascii_lowercase());
} else if c.is_whitespace() {
id.push(c);
}
}
id
normalize_id(trimmed)
}
// 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: &str, filename: &str) -> String {
fn fix_anchor_links(html: &str, filepath: &str) -> String {
let regex = Regex::new(r##"<a([^>]+)href="#([^"]+)"([^>]*)>"##).unwrap();
regex
.replace_all(html, |caps: &Captures| {
@@ -499,9 +507,9 @@ fn fix_anchor_links(html: &str, filename: &str) -> String {
let after = &caps[3];
format!(
"<a{before}href=\"{filename}#{anchor}\"{after}>",
"<a{before}href=\"{filepath}#{anchor}\"{after}>",
before = before,
filename = filename,
filepath = filepath,
anchor = anchor,
after = after
)
@@ -539,7 +547,7 @@ fn add_playpen_pre(html: &str, playpen_config: &PlaypenConfig) -> String {
let classes = &caps[2];
let code = &caps[3];
if classes.contains("language-rust") && !classes.contains("ignore") {
if (classes.contains("language-rust") && !classes.contains("ignore")) || classes.contains("mdbook-runnable") {
// wrap the contents in an external pre block
if playpen_config.is_editable() &&
classes.contains("editable") || text.contains("fn main") || text.contains("quick_main!") {
@@ -592,6 +600,26 @@ struct RenderItemContext<'a> {
is_index: bool,
}
pub fn normalize_path(path: &str) -> String {
use std::path::is_separator;
path.chars()
.map(|ch| if is_separator(ch) { '/' } else { ch })
.collect::<String>()
}
pub fn normalize_id(content: &str) -> String {
content.chars()
.filter_map(|ch|
if ch.is_alphanumeric() || ch == '_' || ch == '-' {
Some(ch.to_ascii_lowercase())
} else if ch.is_whitespace() {
Some('-')
} else {
None
}
)
.collect::<String>()
}
#[cfg(test)]
@@ -601,18 +629,46 @@ mod tests {
#[test]
fn original_build_header_links() {
let inputs = vec![
("blah blah <h1>Foo</h1>", r#"blah blah <a class="header" href="bar.rs#foo" id="foo"><h1>Foo</h1></a>"#),
("<h1>Foo</h1>", r#"<a class="header" href="bar.rs#foo" id="foo"><h1>Foo</h1></a>"#),
("<h3>Foo^bar</h3>", r#"<a class="header" href="bar.rs#foobar" id="foobar"><h3>Foo^bar</h3></a>"#),
("<h4></h4>", r#"<a class="header" href="bar.rs#" id=""><h4></h4></a>"#),
("<h4><em>Hï</em></h4>", r#"<a class="header" href="bar.rs#hï" id="hï"><h4><em>Hï</em></h4></a>"#),
("<h1>Foo</h1><h3>Foo</h3>",
r#"<a class="header" href="bar.rs#foo" id="foo"><h1>Foo</h1></a><a class="header" href="bar.rs#foo-1" id="foo-1"><h3>Foo</h3></a>"#),
(
"blah blah <h1>Foo</h1>",
r##"blah blah <a class="header" href="./some_chapter/some_section.html#foo" id="foo"><h1>Foo</h1></a>"##,
),
(
"<h1>Foo</h1>",
r##"<a class="header" href="./some_chapter/some_section.html#foo" id="foo"><h1>Foo</h1></a>"##,
),
(
"<h3>Foo^bar</h3>",
r##"<a class="header" href="./some_chapter/some_section.html#foobar" id="foobar"><h3>Foo^bar</h3></a>"##,
),
(
"<h4></h4>",
r##"<a class="header" href="./some_chapter/some_section.html#" id=""><h4></h4></a>"##
),
(
"<h4><em>Hï</em></h4>",
r##"<a class="header" href="./some_chapter/some_section.html#hï" id="hï"><h4><em>Hï</em></h4></a>"##
),
(
"<h1>Foo</h1><h3>Foo</h3>",
r##"<a class="header" href="./some_chapter/some_section.html#foo" id="foo"><h1>Foo</h1></a><a class="header" href="./some_chapter/some_section.html#foo-1" id="foo-1"><h3>Foo</h3></a>"##
),
];
for (src, should_be) in inputs {
let got = build_header_links(src, "bar.rs");
let filepath = "./some_chapter/some_section.html";
let got = build_header_links(&src, filepath);
assert_eq!(got, should_be);
// This is redundant for most cases
let got = fix_anchor_links(&got, filepath);
assert_eq!(got, should_be);
}
}
#[test]
fn anchor_generation() {
assert_eq!(id_from_content("## `--passes`: add more rustdoc passes"), "--passes-add-more-rustdoc-passes");
assert_eq!(id_from_content("## Method-call expressions"), "method-call-expressions");
}
}

View File

@@ -64,9 +64,10 @@ pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(
debug!("[*]: Render template");
// Render template
_h.template().ok_or_else(|| RenderError::new("Error with the handlebars template"))
_h.template()
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.with_context(Context::wraps(&previous_chapter));
let mut local_rc = rc.with_context(Context::wraps(&previous_chapter)?);
t.render(r, &mut local_rc)
})?;
}
@@ -142,7 +143,7 @@ pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), R
// Render template
_h.template().ok_or_else(|| RenderError::new("Error with the handlebars template"))
.and_then(|t| {
let mut local_rc = rc.with_context(Context::wraps(&next_chapter));
let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
t.render(r, &mut local_rc)
})?;
break;

View File

@@ -5,6 +5,7 @@ body {
}
body {
margin: 0;
font-size: 1rem;
}
code {
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
@@ -19,6 +20,9 @@ code {
.hidden {
display: none;
}
.play-button.hidden {
display: none;
}
h2,
h3 {
margin-top: 2.5em;
@@ -348,7 +352,8 @@ table thead td {
.light .nav-chapters,
.light .nav-chapters:visited,
.light .mobile-nav-chapters,
.light .mobile-nav-chapters:visited {
.light .mobile-nav-chapters:visited,
.light .menu-bar a i {
color: #ccc;
}
.light .menu-bar i:hover,
@@ -468,7 +473,8 @@ table thead td {
.coal .nav-chapters,
.coal .nav-chapters:visited,
.coal .mobile-nav-chapters,
.coal .mobile-nav-chapters:visited {
.coal .mobile-nav-chapters:visited,
.coal .menu-bar a i {
color: #43484d;
}
.coal .menu-bar i:hover,
@@ -588,7 +594,8 @@ table thead td {
.navy .nav-chapters,
.navy .nav-chapters:visited,
.navy .mobile-nav-chapters,
.navy .mobile-nav-chapters:visited {
.navy .mobile-nav-chapters:visited,
.navy .menu-bar a i {
color: #737480;
}
.navy .menu-bar i:hover,
@@ -708,7 +715,8 @@ table thead td {
.rust .nav-chapters,
.rust .nav-chapters:visited,
.rust .mobile-nav-chapters,
.rust .mobile-nav-chapters:visited {
.rust .mobile-nav-chapters:visited,
.rust .menu-bar a i {
color: #737480;
}
.rust .menu-bar i:hover,
@@ -828,7 +836,8 @@ table thead td {
.ayu .nav-chapters,
.ayu .nav-chapters:visited,
.ayu .mobile-nav-chapters,
.ayu .mobile-nav-chapters:visited {
.ayu .mobile-nav-chapters:visited,
.ayu .menu-bar a i {
color: #737480;
}
.ayu .menu-bar i:hover,
@@ -922,6 +931,9 @@ table thead td {
left: 0;
overflow-y: initial;
}
#page-wrapper.page-wrapper {
padding-left: 0px;
}
#content {
max-width: none;
margin: 0;

View File

@@ -7,7 +7,7 @@ $( document ).ready(function() {
window.onunload = function(){};
// Set theme
var theme = store.get('theme');
var theme = store.get('mdbook-theme');
if (theme === null || theme === undefined) { theme = 'light'; }
set_theme(theme);
@@ -17,7 +17,7 @@ $( document ).ready(function() {
tabReplace: ' ', // 4 spaces
languages: [], // Languages used for auto-detection
});
if (window.ace) {
// language-rust class needs to be removed for editable
// blocks or highlightjs will capture events
@@ -31,7 +31,7 @@ $( document ).ready(function() {
hljs.highlightBlock(block);
});
}
// Adding the hljs class gives code blocks the color css
// even if highlighting doesn't apply
$('code').addClass('hljs');
@@ -83,16 +83,6 @@ $( document ).ready(function() {
}
// Print button
$("#print-button").click(function(){
var printWindow = window.open("print.html");
});
if( url.substring(url.lastIndexOf('/')+1) == "print.html" ) {
window.print();
}
// Theme button
$("#theme-toggle").click(function(){
if($('.theme-popup').length) {
@@ -128,34 +118,34 @@ $( document ).ready(function() {
function set_theme(theme) {
let ace_theme;
if (theme == 'coal' || theme == 'navy') {
$("[href='ayu-highlight.css']").prop('disabled', true);
$("[href='tomorrow-night.css']").prop('disabled', false);
$("[href='highlight.css']").prop('disabled', true);
ace_theme = "ace/theme/tomorrow_night";
} else if (theme == 'ayu') {
$("[href='ayu-highlight.css']").prop('disabled', false);
$("[href='tomorrow-night.css']").prop('disabled', true);
$("[href='highlight.css']").prop('disabled', true);
ace_theme = "ace/theme/tomorrow_night";
} else {
$("[href='ayu-highlight.css']").prop('disabled', true);
$("[href='tomorrow-night.css']").prop('disabled', true);
$("[href='highlight.css']").prop('disabled', false);
ace_theme = "ace/theme/dawn";
}
if (window.ace && window.editors) {
window.editors.forEach(function(editor) {
editor.setTheme(ace_theme);
});
}
store.set('theme', theme);
store.set('mdbook-theme', theme);
$('body').removeClass().addClass(theme);
}
@@ -219,7 +209,7 @@ $( document ).ready(function() {
pre_block.prepend("<div class=\"buttons\"></div>");
buttons = pre_block.find(".buttons");
}
buttons.prepend("<i class=\"fa fa-play play-button\"></i>");
buttons.prepend("<i class=\"fa fa-play play-button hidden\"></i>");
buttons.prepend("<i class=\"fa fa-copy clip-button\"><i class=\"tooltiptext\"></i></i>");
let code_block = pre_block.find("code").first();
@@ -245,14 +235,7 @@ $( document ).ready(function() {
text: function(trigger) {
hideTooltip(trigger);
let playpen = $(trigger).parents(".playpen");
let code_block = playpen.find("code").first();
if (window.ace && code_block.hasClass("editable")) {
let editor = window.ace.edit(code_block.get(0));
return editor.getValue();
} else {
return code_block.get(0).textContent;
}
return playpen_text(playpen);
}
});
clipboardSnippets.on('success', function(e) {
@@ -262,8 +245,83 @@ $( document ).ready(function() {
clipboardSnippets.on('error', function(e) {
showTooltip(e.trigger, "Clipboard error!");
});
$.ajax({
url: "https://play.rust-lang.org/meta/crates",
method: "POST",
crossDomain: true,
dataType: "json",
contentType: "application/json",
success: function(response){
// get list of crates available in the rust playground
let playground_crates = response.crates.map(function(item) {return item["id"];} );
$(".playpen").each(function(block) {
handle_crate_list_update($(this), playground_crates);
});
},
});
});
function playpen_text(playpen) {
let code_block = playpen.find("code").first();
if (window.ace && code_block.hasClass("editable")) {
let editor = window.ace.edit(code_block.get(0));
return editor.getValue();
} else {
return code_block.get(0).textContent;
}
}
function handle_crate_list_update(playpen_block, playground_crates) {
// update the play buttons after receiving the response
update_play_button(playpen_block, playground_crates);
// and install on change listener to dynamically update ACE editors
if (window.ace) {
let code_block = playpen_block.find("code").first();
if (code_block.hasClass("editable")) {
let editor = window.ace.edit(code_block.get(0));
editor.on("change", function(e){
update_play_button(playpen_block, playground_crates);
});
}
}
}
// updates the visibility of play button based on `no_run` class and
// used crates vs ones available on http://play.rust-lang.org
function update_play_button(pre_block, playground_crates) {
var play_button = pre_block.find(".play-button");
var classes = pre_block.find("code").attr("class").split(" ");
// skip if code is `no_run`
if (classes.indexOf("no_run") > -1) {
play_button.addClass("hidden");
return;
}
// get list of `extern crate`'s from snippet
var txt = playpen_text(pre_block);
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
var snippet_crates = [];
while (item = re.exec(txt)) {
snippet_crates.push(item[1]);
}
// check if all used crates are available on play.rust-lang.org
var all_available = snippet_crates.every(function(elem) {
return playground_crates.indexOf(elem) > -1;
});
if (all_available) {
play_button.removeClass("hidden");
} else {
play_button.addClass("hidden");
}
}
function hideTooltip(elem) {
elem.firstChild.innerText="";
elem.setAttribute('class', 'fa fa-copy clip-button');
@@ -278,17 +336,17 @@ function sidebarToggle() {
var html = $("html");
if ( html.hasClass("sidebar-hidden") ) {
html.removeClass("sidebar-hidden").addClass("sidebar-visible");
store.set('sidebar', 'visible');
store.set('mdbook-sidebar', 'visible');
} else if ( html.hasClass("sidebar-visible") ) {
html.removeClass("sidebar-visible").addClass("sidebar-hidden");
store.set('sidebar', 'hidden');
store.set('mdbook-sidebar', 'hidden');
} else {
if($("#sidebar").position().left === 0){
html.addClass("sidebar-hidden");
store.set('sidebar', 'hidden');
store.set('mdbook-sidebar', 'hidden');
} else {
html.addClass("sidebar-visible");
store.set('sidebar', 'visible');
store.set('mdbook-sidebar', 'visible');
}
}
}
@@ -300,30 +358,24 @@ function run_rust_code(code_block) {
result_block = code_block.find(".result");
}
let text;
let inner_code_block = code_block.find("code").first();
if (window.ace && inner_code_block.hasClass("editable")) {
let editor = window.ace.edit(inner_code_block.get(0));
text = editor.getValue();
} else {
text = inner_code_block.text();
let text = playpen_text(code_block);
var params = {
channel: "stable",
mode: "debug",
crateType: "bin",
tests: false,
code: text,
}
var params = {
version: "stable",
optimize: "0",
code: text,
};
if(text.indexOf("#![feature") !== -1) {
params.version = "nightly";
params.channel = "nightly";
}
result_block.text("Running...");
$.ajax({
url: "https://play.rust-lang.org/evaluate.json",
url: "https://play.rust-lang.org/execute",
method: "POST",
crossDomain: true,
dataType: "json",
@@ -331,7 +383,7 @@ function run_rust_code(code_block) {
data: JSON.stringify(params),
timeout: 15000,
success: function(response){
result_block.text(response.result);
result_block.text(response.success ? response.stdout : response.stderr);
},
error: function(qXHR, textStatus, errorThrown){
result_block.text("Playground communication " + textStatus);

View File

@@ -2,7 +2,7 @@
<html lang="{{ language }}">
<head>
<meta charset="UTF-8">
<title>{{ chapter_title }} - {{ title }}</title>
<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">
@@ -60,14 +60,14 @@
<body class="light">
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme = store.get('theme');
var theme = store.get('mdbook-theme');
if (theme === null || theme === undefined) { theme = 'light'; }
$('body').removeClass().addClass(theme);
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var sidebar = store.get('sidebar');
var sidebar = store.get('mdbook-sidebar');
if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") }
else if (sidebar === "visible") { $("html").addClass("sidebar-visible") }
</script>
@@ -85,10 +85,12 @@
<i id="theme-toggle" class="fa fa-paint-brush"></i>
</div>
<h1 class="menu-title">{{ title }}</h1>
<h1 class="menu-title">{{ book_title }}</h1>
<div class="right-buttons">
<i id="print-button" class="fa fa-print" title="Print this book"></i>
<a href="print.html">
<i id="print-button" class="fa fa-print" title="Print this book"></i>
</a>
</div>
</div>
@@ -156,6 +158,14 @@
<script src="{{ theme_tomorrow_night_js }}" type="text/javascript" charset="utf-8"></script>
{{/if}}
{{#if is_print}}
<script>
$(document).ready(function() {
window.print();
})
</script>
{{/if}}
<script src="highlight.js"></script>
<script src="book.js"></script>
</body>

View File

@@ -5,6 +5,7 @@ html, body {
body {
margin: 0;
font-size: 1rem;
}
code {
@@ -24,6 +25,10 @@ code {
display: none;
}
.play-button.hidden {
display: none;
}
h2, h3 { margin-top: 2.5em }
h4, h5 { margin-top: 2em }

View File

@@ -12,6 +12,10 @@
overflow-y: initial;
}
#page-wrapper.page-wrapper {
padding-left: 0px;
}
#content {
max-width: none;
margin: 0;

View File

@@ -38,7 +38,8 @@
.nav-chapters,
.nav-chapters:visited,
.mobile-nav-chapters,
.mobile-nav-chapters:visited {
.mobile-nav-chapters:visited,
.menu-bar a i {
color: $icons
}

View File

@@ -1,3 +0,0 @@
# First Chapter
more text.

View File

@@ -0,0 +1,5 @@
# First Chapter
more text.
## Some Section

View File

@@ -4,4 +4,6 @@ This file has some testable code.
```rust
assert!($TEST_STATUS);
```
```
## Some Section

View File

@@ -1,26 +1,23 @@
//! Helpers for tests which exercise the overall application, in particular
//! the `MDBook` initialization and build/rendering process.
//!
//! This will create an entire book in a temporary directory using some
//! dummy contents from the `tests/dummy-book/` directory.
// Not all features are used in all test crates, so...
#![allow(dead_code, unused_extern_crates)]
#![allow(dead_code, unused_variables, unused_imports)]
extern crate tempdir;
use std::path::Path;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::fs::{create_dir_all, File};
use std::io::Write;
use tempdir::TempDir;
const SUMMARY_MD: &'static str = include_str!("dummy-book/SUMMARY.md");
const INTRO: &'static str = include_str!("dummy-book/intro.md");
const FIRST: &'static str = include_str!("dummy-book/first/index.md");
const NESTED: &'static str = include_str!("dummy-book/first/nested.md");
const SECOND: &'static str = include_str!("dummy-book/second.md");
const CONCLUSION: &'static str = include_str!("dummy-book/conclusion.md");
const SUMMARY_MD: &'static str = include_str!("book/SUMMARY.md");
const INTRO: &'static str = include_str!("book/intro.md");
const FIRST: &'static str = include_str!("book/first/index.md");
const NESTED: &'static str = include_str!("book/first/nested.md");
const SECOND: &'static str = include_str!("book/second.md");
const CONCLUSION: &'static str = include_str!("book/conclusion.md");
/// Create a dummy book in a temporary directory, using the contents of
@@ -58,10 +55,10 @@ impl DummyBook {
let temp = TempDir::new("dummy_book").unwrap();
let src = temp.path().join("src");
fs::create_dir_all(&src).unwrap();
create_dir_all(&src).unwrap();
let first = src.join("first");
fs::create_dir_all(&first).unwrap();
create_dir_all(&first).unwrap();
let to_substitute = if self.passing_test { "true" } else { "false" };
let nested_text = NESTED.replace("$TEST_STATUS", to_substitute);
@@ -91,20 +88,3 @@ impl Default for DummyBook {
DummyBook { passing_test: true }
}
}
/// Read the contents of the provided file into memory and then iterate through
/// the list of strings asserting that the file contains all of them.
pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
let filename = filename.as_ref();
let mut content = String::new();
File::open(&filename)
.expect("Couldn't open the provided file")
.read_to_string(&mut content)
.expect("Couldn't read the file's contents");
for s in strings {
assert!(content.contains(s), "Searching for {:?} in {}\n\n{}", s, filename.display(), content);
}
}

24
tests/helpers/mod.rs Normal file
View File

@@ -0,0 +1,24 @@
//! Helpers for tests which exercise the overall application, in particular
//! the `MDBook` initialization and build/rendering process.
use std::path::Path;
use std::fs::File;
use std::io::Read;
/// Read the contents of the provided file into memory and then iterate through
/// the list of strings asserting that the file contains all of them.
pub fn assert_contains_strings<P: AsRef<Path>>(filename: P, strings: &[&str]) {
let filename = filename.as_ref();
let mut content = String::new();
File::open(&filename)
.expect("Couldn't open the provided file")
.read_to_string(&mut content)
.expect("Couldn't read the file's contents");
for s in strings {
assert!(content.contains(s), "Searching for {:?} in {}\n\n{}", s, filename.display(), content);
}
}

View File

@@ -1,14 +1,18 @@
extern crate mdbook;
extern crate tempdir;
mod dummy;
mod helpers;
use dummy::DummyBook;
use helpers::assert_contains_strings;
use mdbook::MDBook;
/// Make sure you can load the dummy book and build it without panicking.
#[test]
fn build_the_dummy_book() {
let temp = helpers::DummyBook::default().build();
let temp = DummyBook::default().build();
let mut md = MDBook::new(temp.path());
md.build().unwrap();
@@ -16,7 +20,7 @@ fn build_the_dummy_book() {
#[test]
fn by_default_mdbook_generates_rendered_content_in_the_book_directory() {
let temp = helpers::DummyBook::default().build();
let temp = DummyBook::default().build();
let mut md = MDBook::new(temp.path());
assert!(!temp.path().join("book").exists());
@@ -28,62 +32,76 @@ fn by_default_mdbook_generates_rendered_content_in_the_book_directory() {
#[test]
fn make_sure_bottom_level_files_contain_links_to_chapters() {
let temp = helpers::DummyBook::default().build();
let temp = DummyBook::default().build();
let mut md = MDBook::new(temp.path());
md.build().unwrap();
let dest = temp.path().join("book");
let links = vec![
"intro.html",
"first/index.html",
"first/nested.html",
"second.html",
"conclusion.html",
r#"href="intro.html""#,
r#"href="./first/index.html""#,
r#"href="./first/nested.html""#,
r#"href="./second.html""#,
r#"href="./conclusion.html""#,
];
let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"];
for filename in files_in_bottom_dir {
helpers::assert_contains_strings(dest.join(filename), &links);
assert_contains_strings(dest.join(filename), &links);
}
}
#[test]
fn check_correct_cross_links_in_nested_dir() {
let temp = helpers::DummyBook::default().build();
let temp = DummyBook::default().build();
let mut md = MDBook::new(temp.path());
md.build().unwrap();
let first = temp.path().join("book").join("first");
let links = vec![
r#"<base href="../">"#,
"intro.html",
"first/index.html",
"first/nested.html",
"second.html",
"conclusion.html",
r#"href="intro.html""#,
r#"href="./first/index.html""#,
r#"href="./first/nested.html""#,
r#"href="./second.html""#,
r#"href="./conclusion.html""#,
];
let files_in_nested_dir = vec!["index.html", "nested.html"];
for filename in files_in_nested_dir {
helpers::assert_contains_strings(first.join(filename), &links);
assert_contains_strings(first.join(filename), &links);
}
assert_contains_strings(
first.join("index.html"),
&[
r##"href="./first/index.html#some-section" id="some-section""##
],
);
assert_contains_strings(
first.join("nested.html"),
&[
r##"href="./first/nested.html#some-section" id="some-section""##
],
);
}
#[test]
fn rendered_code_has_playpen_stuff() {
let temp = helpers::DummyBook::default().build();
let temp = DummyBook::default().build();
let mut md = MDBook::new(temp.path());
md.build().unwrap();
let nested = temp.path().join("book/first/nested.html");
let playpen_class = vec![r#"class="playpen""#];
helpers::assert_contains_strings(nested, &playpen_class);
assert_contains_strings(nested, &playpen_class);
let book_js = temp.path().join("book/book.js");
helpers::assert_contains_strings(book_js, &[".playpen"]);
assert_contains_strings(book_js, &[".playpen"]);
}
#[test]
@@ -96,7 +114,7 @@ fn chapter_content_appears_in_rendered_document() {
("conclusion.html", "Conclusion"),
];
let temp = helpers::DummyBook::default().build();
let temp = DummyBook::default().build();
let mut md = MDBook::new(temp.path());
md.build().unwrap();
@@ -104,6 +122,6 @@ fn chapter_content_appears_in_rendered_document() {
for (filename, text) in content {
let path = destination.join(filename);
helpers::assert_contains_strings(path, &[text]);
assert_contains_strings(path, &[text]);
}
}
}

View File

@@ -1,13 +1,15 @@
extern crate tempdir;
extern crate mdbook;
extern crate tempdir;
mod helpers;
mod dummy;
use dummy::DummyBook;
use mdbook::MDBook;
#[test]
fn mdbook_can_correctly_test_a_passing_book() {
let temp = helpers::DummyBook::default()
let temp = DummyBook::default()
.with_passing_test(true)
.build();
let mut md = MDBook::new(temp.path());
@@ -17,10 +19,10 @@ fn mdbook_can_correctly_test_a_passing_book() {
#[test]
fn mdbook_detects_book_with_failing_tests() {
let temp = helpers::DummyBook::default()
let temp = DummyBook::default()
.with_passing_test(false)
.build();
let mut md: MDBook = MDBook::new(temp.path());
assert!(md.test(vec![]).is_err());
}
}