mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 15:01:45 -05:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b45e5e4420 | ||
|
|
a6d4881e00 | ||
|
|
a0515bd104 | ||
|
|
9b64db908f | ||
|
|
f562878131 | ||
|
|
3823fc0e74 | ||
|
|
793fb8f654 | ||
|
|
911683d2cf | ||
|
|
2ae6e6a6e3 | ||
|
|
91fd8a2865 | ||
|
|
a3b6e549e2 | ||
|
|
d450518292 | ||
|
|
c056df597a | ||
|
|
0d6adc5fc9 | ||
|
|
0226da91e4 | ||
|
|
ef5895fa78 | ||
|
|
8e0abfb22f | ||
|
|
c9bc13d786 | ||
|
|
7ce78cbfea | ||
|
|
26544fa531 | ||
|
|
0c93770f4a | ||
|
|
743713ad3a | ||
|
|
e3f4bb5101 | ||
|
|
441bcb5963 | ||
|
|
84ef4d2617 | ||
|
|
bd30cae17e | ||
|
|
6f0b67f44f | ||
|
|
abf86eefd9 | ||
|
|
016ec8836c | ||
|
|
881a1b39ff | ||
|
|
a1e58229b2 | ||
|
|
276eab095c | ||
|
|
f4513d3b5c | ||
|
|
570ce6681f | ||
|
|
ddee839d9c | ||
|
|
99945542ca | ||
|
|
956a5cc7fd | ||
|
|
cef62ec42e | ||
|
|
b1362bfa06 | ||
|
|
a529ca5e65 | ||
|
|
6bc3039b4f | ||
|
|
cd90fdd407 | ||
|
|
a6a7c95c78 | ||
|
|
0a4a2b66da | ||
|
|
2f3c14d609 | ||
|
|
ebcf1e495d | ||
|
|
40a4840867 | ||
|
|
313f9b9403 | ||
|
|
d0a6aea3aa |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ target
|
||||
|
||||
book-test
|
||||
book-example/book
|
||||
|
||||
.vscode
|
||||
102
.travis.yml
102
.travis.yml
@@ -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:
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -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"]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 \\)
|
||||
```
|
||||
|
||||
25
build.rs
25
build.rs
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
45
ci/script.sh
45
ci/script.sh
@@ -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
|
||||
@@ -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) |
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
"'",
|
||||
""",
|
||||
];
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
#page-wrapper.page-wrapper {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
#content {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# First Chapter
|
||||
|
||||
more text.
|
||||
5
tests/dummy/book/first/index.md
Normal file
5
tests/dummy/book/first/index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# First Chapter
|
||||
|
||||
more text.
|
||||
|
||||
## Some Section
|
||||
@@ -4,4 +4,6 @@ This file has some testable code.
|
||||
|
||||
```rust
|
||||
assert!($TEST_STATUS);
|
||||
```
|
||||
```
|
||||
|
||||
## Some Section
|
||||
@@ -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
24
tests/helpers/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user