From 65849b051e601c1b786f1ee33cb5eec5e5f3b3e0 Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 24 Nov 2018 16:13:36 +1300 Subject: [PATCH 01/17] Rewrite the book! --- .gitignore | 1 + Cargo.toml | 1 + docs/Cargo.toml | 17 +++++ docs/content/getting-started.md | 56 +++++++++++++++ docs/content/index.md | 33 +++++++++ docs/src/main.rs | 123 ++++++++++++++++++++++++++++++++ docs/src/views.rs | 104 +++++++++++++++++++++++++++ docs/styles.css | 75 +++++++++++++++++++ docs/watch.sh | 12 ++++ 9 files changed, 422 insertions(+) create mode 100644 docs/Cargo.toml create mode 100644 docs/content/getting-started.md create mode 100644 docs/content/index.md create mode 100644 docs/src/main.rs create mode 100644 docs/src/views.rs create mode 100644 docs/styles.css create mode 100755 docs/watch.sh diff --git a/.gitignore b/.gitignore index a9d37c5..9adeb4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +docs/site diff --git a/Cargo.toml b/Cargo.toml index 13092b4..0b4badd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ ] exclude = [ "benchmarks", + "docs", ] diff --git a/docs/Cargo.toml b/docs/Cargo.toml new file mode 100644 index 0000000..4771728 --- /dev/null +++ b/docs/Cargo.toml @@ -0,0 +1,17 @@ +[package] + +name = "docs" +version = "0.0.0" +authors = ["Chris Wong <lambda.fairy@gmail.com>"] + +license = "CC-BY-SA-4.0" +repository = "https://github.com/lfairy/maud" +description = "Documentation for Maud." + +edition = "2018" + +[dependencies] +comrak = "*" +indexmap = "*" +maud = { path = "../maud" } +syntect = "*" diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md new file mode 100644 index 0000000..86b42f7 --- /dev/null +++ b/docs/content/getting-started.md @@ -0,0 +1,56 @@ +# Getting started + +## Install nightly Rust + +Maud requires the nightly version of Rust. +If you're using `rustup`, +see the [documentation][rustup] +for how to install this version. + +[rustup]: https://github.com/rust-lang/rustup.rs/blob/master/README.md#working-with-nightly-rust + +## Add Maud to your project + +Once Rust is set up, +create a new project with Cargo: + +```sh +cargo new --bin pony-greeter +cd pony-greeter +``` + +Add `maud` to your `Cargo.toml`: + +```toml +[dependencies] +maud = "*" +``` + +Then save the following to `src/main.rs`: + +```rust +#![feature(proc_macro_hygiene)] + +extern crate maud; +use maud::html; + +fn main() { + let name = "Lyra"; + let markup = html! { + p { "Hi, " (name) "!" } + }; + println!("{}", markup.into_string()); +} +``` + +`html!` takes a single argument: a template using Maud's custom syntax. This call expands to an expression of type [`Markup`][Markup], which can then be converted to a `String` using `.into_string()`. + +[Markup]: https://docs.rs/maud/*/maud/type.Markup.html + +Run this program with `cargo run`, and you should get the following: + +``` +<p>Hi, Lyra!</p> +``` + +Congrats – you've written your first Maud program! diff --git a/docs/content/index.md b/docs/content/index.md new file mode 100644 index 0000000..9f26d61 --- /dev/null +++ b/docs/content/index.md @@ -0,0 +1,33 @@ +```rust +html! { + h1 { "Hello, world!" } + p.intro { + "This is an example of the " + a href="https://github.com/lfairy/maud" { "Maud" } + " template language." + } +} +``` + +Maud is an HTML [template engine] for Rust. It's implemented as a macro, `html!`, which compiles your markup to specialized Rust code. This unique approach makes Maud templates blazing fast, super type-safe, and easy to deploy. + +[template engine]: https://www.simple-is-better.org/template/ + +## Tight integration with Rust + +Since Maud is a Rust macro, it can borrow most of its features from the host language. Pattern matching and `for` loops work as they do in Rust. There is no need to derive JSON conversions, as your templates can work with Rust values directly. + +## Type safety + +Your templates are checked by the compiler, just like the code around them. Any typos will be caught at compile time, not after your app has already started. + +## Minimal runtime + +Since most of the work happens at compile time, the runtime footprint is small. The Maud runtime library, including integration with the [Rocket] and [Actix] web frameworks, is around 100 SLoC. + +[Rocket]: https://rocket.rs/ +[Actix]: https://actix.rs/ + +## Simple deployment + +There is no need to track separate template files, since all relevant code is linked into the final executable. diff --git a/docs/src/main.rs b/docs/src/main.rs new file mode 100644 index 0000000..53cd9bc --- /dev/null +++ b/docs/src/main.rs @@ -0,0 +1,123 @@ +#![feature(crate_visibility_modifier)] +#![feature(proc_macro_hygiene)] + +use comrak::{self, Arena, ComrakOptions}; +use comrak::nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeValue}; +use indexmap::IndexMap; +use std::error::Error; +use std::fs; +use std::io; +use std::mem; +use std::path::Path; +use std::string::FromUtf8Error; +use syntect::parsing::SyntaxSet; +use syntect::highlighting::{Color, ThemeSet}; +use syntect::html::highlighted_html_for_string; + +mod views; + +const BOOK_FILES: &[&str] = &[ + "index", + "getting-started", +]; + +fn main() -> Result<(), Box<dyn Error>> { + fs::create_dir_all("site")?; + + let arena = Arena::new(); + let options = ComrakOptions { + ext_header_ids: Some("".to_string()), + ..ComrakOptions::default() + }; + + let mut pages = IndexMap::<&str, _>::new(); + + for path in BOOK_FILES { + let mut input_path = Path::new("content").join(path); + input_path.set_extension("md"); + + let page = load_page(&arena, &options, &input_path)?; + + pages.insert(path, page); + } + + for path in pages.keys() { + let mut output_path = Path::new("site").join(path); + output_path.set_extension("html"); + println!("{}", output_path.display()); + let markup = views::main(&options, path, &pages); + fs::write(output_path, markup.into_string())?; + } + + fs::copy("styles.css", "site/styles.css")?; + + Ok(()) +} + +struct Page<'a> { + title: Option<&'a AstNode<'a>>, + content: &'a AstNode<'a>, +} + +fn load_page<'a>( + arena: &'a Arena<AstNode<'a>>, + options: &ComrakOptions, + path: &Path, +) -> io::Result<Page<'a>> { + let buffer = fs::read_to_string(path)?; + let content = comrak::parse_document(arena, &buffer, options); + + let title = content + .first_child() + .filter(|node| { + let mut data = node.data.borrow_mut(); + if let NodeValue::Heading(NodeHeading { level: 1, .. }) = data.value { + node.detach(); + data.value = NodeValue::Document; + true + } else { + false + } + }); + + lower_headings(content); + highlight_code(content) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + + Ok(Page { title, content }) +} + +fn lower_headings<'a>(root: &'a AstNode<'a>) { + for node in root.descendants() { + let mut data = node.data.borrow_mut(); + if let NodeValue::Heading(NodeHeading { level, .. }) = &mut data.value { + *level += 1; + } + } +} + +fn highlight_code<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> { + let ss = SyntaxSet::load_defaults_newlines(); + let ts = ThemeSet::load_defaults(); + let mut theme = ts.themes["InspiredGitHub"].clone(); + theme.settings.background = Some(Color { r: 0xee, g: 0xee, b: 0xee, a: 0xff }); + for node in root.descendants() { + let mut data = node.data.borrow_mut(); + if let NodeValue::CodeBlock(NodeCodeBlock { info, literal, .. }) = &mut data.value { + let info = String::from_utf8(mem::replace(info, Vec::new()))?; + let syntax = ss.find_syntax_by_token(&info) + .unwrap_or_else(|| ss.find_syntax_plain_text()); + let mut literal = String::from_utf8(mem::replace(literal, Vec::new()))?; + if !literal.ends_with('\n') { + // Syntect expects a trailing newline + literal.push('\n'); + } + let html = highlighted_html_for_string(&literal, &ss, syntax, &theme); + data.value = NodeValue::HtmlBlock(NodeHtmlBlock { + block_type: 0, + literal: html.into_bytes(), + }); + } + } + Ok(()) +} diff --git a/docs/src/views.rs b/docs/src/views.rs new file mode 100644 index 0000000..949299b --- /dev/null +++ b/docs/src/views.rs @@ -0,0 +1,104 @@ +use comrak::{self, ComrakOptions}; +use comrak::nodes::AstNode; +use crate::Page; +use indexmap::IndexMap; +use maud::{DOCTYPE, Markup, Render, html}; +use std::io; +use std::str; + +struct StringWriter<'a>(&'a mut String); + +impl<'a> io::Write for StringWriter<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + str::from_utf8(buf) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) + .map(|s| { + self.0.push_str(s); + buf.len() + }) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +struct Comrak<'a>(&'a AstNode<'a>, &'a ComrakOptions); + +impl<'a> Render for Comrak<'a> { + fn render_to(&self, buffer: &mut String) { + comrak::format_html(self.0, self.1, &mut StringWriter(buffer)).unwrap(); + } +} + +struct ComrakText<'a>(&'a AstNode<'a>, &'a ComrakOptions); + +impl<'a> Render for ComrakText<'a> { + fn render_to(&self, buffer: &mut String) { + comrak::format_commonmark(self.0, self.1, &mut StringWriter(buffer)).unwrap(); + } +} + +crate fn main<'a>( + options: &'a ComrakOptions, + path: &str, + pages: &IndexMap<&str, Page<'a>>, +) -> Markup { + let page = &pages[path]; + html! { + (DOCTYPE) + meta charset="utf-8"; + title { + @if let Some(title) = page.title { + (ComrakText(title, options)) + " \u{2013} " + } + "Maud, a macro for writing HTML" + } + link rel="stylesheet" href="styles.css"; + + header { + h1 { + a href="." { + "maud" + } + } + p { "a macro for writing html" } + } + + nav { + ul { + @for (other_path, other_page) in pages { + @if let Some(title) = other_page.title { + li { + a href={ (other_path) ".html" } { + (Comrak(title, options)) + } + } + } + } + } + ul { + li { + a href="https://docs.rs/maud/" { + "API documentation" + } + } + li { + a href="https://github.com/lfairy/maud" { + "GitHub" + } + } + } + } + + main { + @if let Some(title) = page.title { + h2 { + (Comrak(title, options)) + } + } + (Comrak(page.content, options)) + } + } +} diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 0000000..4a2c76f --- /dev/null +++ b/docs/styles.css @@ -0,0 +1,75 @@ +html { + box-sizing: border-box; + font: 16px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +*, *::before, *::after { + box-sizing: inherit; +} + +body { + margin: 0 auto; + padding: 2rem; + max-width: 40rem; +} + +header { + display: flex; + flex-wrap: wrap; + align-items: baseline; +} + +header h1 { + margin: 0; +} + +header p { + margin: 0 0 0 1rem; +} + +nav ul { + padding: 0; + list-style: none; +} + +pre { + padding: 1rem; + font: 15px Consolas, monospace; + background: #eee; +} + +a:link { + text-decoration: none; +} + +a:focus, a:hover, a:active { + background: #fdd; +} + +@media (min-width: 60rem) { + body { + max-width: 56rem; /* 15 + 1 + 40 */ + display: grid; + grid-template-columns: 15rem auto; + grid-gap: 1rem; + } + + header { + grid-column: 1 / 3; + grid-row: 1; + } + + nav { + grid-column: 1; + grid-row: 2; + } + + main { + grid-column: 2; + grid-row: 2; + } + + main h2:first-child { + margin-top: .5rem; + } +} diff --git a/docs/watch.sh b/docs/watch.sh new file mode 100755 index 0000000..0bf2ab6 --- /dev/null +++ b/docs/watch.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +python3 -m http.server -d site & +server_pid=$! +trap 'kill $server_pid' EXIT + +while true +do + find . -name '*.rs' -o -name '*.md' | entr -d cargo run +done From f8b19960dd9e50836801ad692492d590f1b71f2b Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 24 Nov 2018 16:44:16 +1300 Subject: [PATCH 02/17] Rewrite .md links to .html --- docs/src/main.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/src/main.rs b/docs/src/main.rs index 53cd9bc..d88c526 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -2,7 +2,7 @@ #![feature(proc_macro_hygiene)] use comrak::{self, Arena, ComrakOptions}; -use comrak::nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeValue}; +use comrak::nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink, NodeValue}; use indexmap::IndexMap; use std::error::Error; use std::fs; @@ -81,6 +81,8 @@ fn load_page<'a>( }); lower_headings(content); + rewrite_md_links(content) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; highlight_code(content) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; @@ -96,6 +98,21 @@ fn lower_headings<'a>(root: &'a AstNode<'a>) { } } +fn rewrite_md_links<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> { + for node in root.descendants() { + let mut data = node.data.borrow_mut(); + if let NodeValue::Link(NodeLink { url, .. }) = &mut data.value { + let mut url_string = String::from_utf8(mem::replace(url, Vec::new()))?; + if url_string.ends_with(".md") { + url_string.truncate(url_string.len() - ".md".len()); + url_string.push_str(".html"); + } + *url = url_string.into_bytes(); + } + } + Ok(()) +} + fn highlight_code<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> { let ss = SyntaxSet::load_defaults_newlines(); let ts = ThemeSet::load_defaults(); From 00a38a32b3410203746ca225135c0b49eba7677f Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 24 Nov 2018 16:44:53 +1300 Subject: [PATCH 03/17] Add files from existing book --- docs/content/basic-syntax.md | 149 +++++++++++++++++++++++++++ docs/content/control-structures.md | 109 ++++++++++++++++++++ docs/content/dynamic-content.md | 71 +++++++++++++ docs/content/faq.md | 37 +++++++ docs/content/partials.md | 59 +++++++++++ docs/content/traits.md | 83 +++++++++++++++ docs/content/web-frameworks.md | 160 +++++++++++++++++++++++++++++ docs/src/main.rs | 7 ++ 8 files changed, 675 insertions(+) create mode 100644 docs/content/basic-syntax.md create mode 100644 docs/content/control-structures.md create mode 100644 docs/content/dynamic-content.md create mode 100644 docs/content/faq.md create mode 100644 docs/content/partials.md create mode 100644 docs/content/traits.md create mode 100644 docs/content/web-frameworks.md diff --git a/docs/content/basic-syntax.md b/docs/content/basic-syntax.md new file mode 100644 index 0000000..03a67fe --- /dev/null +++ b/docs/content/basic-syntax.md @@ -0,0 +1,149 @@ +# Basic syntax + +The next few sections will outline the syntax used by Maud templates. + +## Literals `""` + +Literal strings use the same syntax as Rust. Wrap them in double quotes, and use a backslash for escapes. + +```rust +html! { + "Oatmeal, are you crazy?" +} +``` + +### Escaping and `PreEscaped` + +By default, HTML special characters are escaped automatically. Wrap the string in `(PreEscaped())` to disable this escaping. (See the section on [dynamic content] to learn more about how this works.) + +```rust +use maud::PreEscaped; +html! { + "<script>alert(\"XSS\")</script>" // <script>... + (PreEscaped("<script>alert(\"XSS\")</script>")) // <script>... +} +``` + +[dynamic content]: dynamic-content.md + +If the string is long, or contains many special characters, then it may be worth using [raw strings] instead: + +```rust +use maud::PreEscaped; +html! { + (PreEscaped(r#" + <script> + alert("Look ma, no backslashes!"); + </script> + "#)) +} +``` + +[raw strings]: https://doc.rust-lang.org/reference/tokens.html#raw-string-literals + +If you want to add a `<!DOCTYPE html>` declaration to your page, you may use the `maud::DOCTYPE` constant instead of writing it out by hand: + +```rust +use maud::DOCTYPE; +html! { + (DOCTYPE) // <!DOCTYPE html> +} +``` + +## Elements `p` + +Write an element using curly braces: `p { ... }`. + +Terminate a void element using a semicolon: `br;`. Note that the result will be rendered with HTML syntax – `<br>` not `<br />`. + +```rust +html! { + h1 { "Poem" } + p { + "Rock, you are a rock." + br; + "Gray, you are gray," + br; + "Like a rock, which you are." + br; + "Rock." + } +} +``` + +Maud also supports ending a void element with a slash: `br /`. This syntax is [deprecated][#96] and should not be used in new code. + +[#96]: https://github.com/lfairy/maud/pull/96 + +Before version 0.18, Maud allowed the curly braces to be omitted. This syntax was [removed][#137] and now causes an error instead. + +[#137]: https://github.com/lfairy/maud/pull/137 + +## Non-empty attributes `id="yay"` + +```rust +html! { + ul { + li { + a href="about:blank" { "Apple Bloom" } + } + li class="lower-middle" { + "Sweetie Belle" + } + li dir="rtl" { + "Scootaloo " + small { "(also a chicken)" } + } + } +} +``` + +Add attributes using the syntax: `attr="value"`. You can attach any number of attributes to an element. The values must be quoted: they are parsed as string literals. + +## Empty attributes `checked?` `disabled?[foo]` + +Declare an empty attribute using a `?` suffix: `checked?`. + +```rust +html! { + form { + input type="checkbox" name="cupcakes" checked?; + " " + label for="cupcakes" { "Do you like cupcakes?" } + } +} +``` + +To toggle an attribute based on a boolean flag, use a `?[]` suffix instead: `checked?[foo]`. This will check the value of `foo` at runtime, inserting the attribute only if `foo` equals `true`. + +```rust +let allow_editing = true; +html! { + p contenteditable?[allow_editing] { + "Edit me, I " + em { "dare" } + " you." + } +} +``` + +## Classes and IDs `.foo` `#bar` + +Add classes and IDs to an element using `.foo` and `#bar` syntax. You can chain multiple classes and IDs together, and mix and match them with other attributes: + +```rust +html! { + div.container#main { + input.big.scary.bright-red type="button" value="Launch Party Cannon"; + } +} +``` + +To toggle a class based on a boolean flag, use a `[]` suffix: `.foo[is_foo]`. This will check the value of `is_foo` at runtime, inserting that class value `foo` in the class attribute only if `is_foo` is `true`. + +```rust +let cuteness = 95; +html! { + p.cute[cuteness > 50] { "Squee!" } +} +``` diff --git a/docs/content/control-structures.md b/docs/content/control-structures.md new file mode 100644 index 0000000..b71114d --- /dev/null +++ b/docs/content/control-structures.md @@ -0,0 +1,109 @@ +# Control structures + +Maud provides various control structures for adding dynamic elements to your templates. + +## Branching with `@if` and `@else` + +Use `@if` and `@else` to branch on a boolean expression. As with Rust, braces are mandatory and the `@else` clause is optional. + +```rust +#[derive(PartialEq)] +enum Princess { Celestia, Luna, Cadance, TwilightSparkle } + +let user = Princess::Celestia; + +html! { + @if user == Princess::Luna { + h1 { "Super secret woona to-do list" } + ul { + li { "Nuke the Crystal Empire" } + li { "Kick a puppy" } + li { "Evil laugh" } + } + } @else if user == Princess::Celestia { + p { "Sister, please stop reading my diary. It's rude." } + } @else { + p { "Nothing to see here; move along." } + } +} +``` + +`@if let` is supported as well. It works as you'd expect: + +```rust +let user = Some("Pinkie Pie"); +html! { + p { + "Hello, " + @if let Some(name) = user { + (name) + } @else { + "stranger" + } + "!" + } +} +``` + +## Looping with `@for` + +Use `@for .. in ..` to loop over the elements of an iterator. + +```rust +let names = ["Applejack", "Rarity", "Fluttershy"]; +html! { + p { "My favorite ponies are:" } + ol { + @for name in &names { + li { (name) } + } + } +} +``` + +## Declaring variables with `@let` + +Declare a new variable within a template using `@let`. This can be useful when working with values in a for loop. + +```rust +let names = ["Applejack", "Rarity", "Fluttershy"]; +html! { + @for name in &names { + @let first_letter = name.chars().next().unwrap(); + p { + "The first letter of " + b { (name) } + " is " + b { (first_letter) } + "." + } + } +} +``` + +## Matching with `@match` + +Pattern matching is supported with `@match`. + +```rust +enum Princess { Celestia, Luna, Cadance, TwilightSparkle } + +let user = Princess::Celestia; + +html! { + @match user { + Princess::Luna => { + h1 { "Super secret woona to-do list" } + ul { + li { "Nuke the Crystal Empire" } + li { "Kick a puppy" } + li { "Evil laugh" } + } + }, + Princess::Celestia => { + p { "Sister, please stop reading my diary. It's rude." } + }, + _ => p { "Nothing to see here; move along." } + } +} +``` diff --git a/docs/content/dynamic-content.md b/docs/content/dynamic-content.md new file mode 100644 index 0000000..521daf5 --- /dev/null +++ b/docs/content/dynamic-content.md @@ -0,0 +1,71 @@ +# Dynamic content + +Use `(foo)` syntax to splice in the value of `foo` at runtime. Any HTML special characters are escaped by default. + +```rust +let best_pony = "Pinkie Pie"; +let numbers = [1, 2, 3, 4]; +html! { + p { "Hi, " (best_pony) "!" } + p { + "I have " (numbers.len()) " numbers, " + "and the first one is " (numbers[0]) + } +} +``` + +Arbitrary Rust code can be included in a splice by using a [block](https://doc.rust-lang.org/reference.html#block-expressions). This can be helpful for complex expressions that would be difficult to read otherwise. + +```rust +html! { + p { + ({ + let f: Foo = something_convertible_to_foo()?; + f.time().format("%H%Mh") + }) + } +} +``` + +## Splices in attributes + +Splices work in attributes as well. + +```rust +let secret_message = "Surprise!"; +html! { + p title=(secret_message) { + "Nothing to see here, move along." + } +} +``` + +To concatenate multiple values within an attribute, wrap the whole thing in braces. This syntax is useful for building URLs. + +```rust +const GITHUB: &'static str = "https://github.com"; +html! { + a href={ (GITHUB) "/lfairy/maud" } { + "Fork me on GitHub" + } +} +``` + +## What can be spliced? + +You can splice any value that implements [`std::fmt::Display`][Display]. Most primitive types (such as `str` and `i32`) implement this trait, so they should work out of the box. + +To change this behavior for some type, you can implement the [`Render`][Render] trait by hand. The [`PreEscaped`][PreEscaped] wrapper type, which outputs its argument without escaping, works this way. See the [traits](./traits.md) section for details. + +```rust +use maud::PreEscaped; +let post = "<p>Pre-escaped</p>"; +html! { + h1 { "My super duper blog post" } + (PreEscaped(post)) +} +``` + +[Display]: http://doc.rust-lang.org/std/fmt/trait.Display.html +[Render]: https://docs.rs/maud/*/maud/trait.Render.html +[PreEscaped]: https://docs.rs/maud/*/maud/struct.PreEscaped.html diff --git a/docs/content/faq.md b/docs/content/faq.md new file mode 100644 index 0000000..6f610c0 --- /dev/null +++ b/docs/content/faq.md @@ -0,0 +1,37 @@ +# Frequently asked questions + +## What is the origin of the name "Maud"? + +Maud is named after a [character](http://mlp.wikia.com/wiki/Maud_Pie) from *My Little Pony: Friendship is Magic*. It does not refer to the [poem](https://en.wikipedia.org/wiki/Maud_and_other_poems) by Alfred Tennyson, though other people have brought that up in the past. + +Here are some reasons why I chose this name: + +* "Maud" shares three letters with "markup"; + +* The library is efficient and austere, like the character; + +* Google used to maintain a site called ["HTML5 Rocks"](https://techcrunch.com/2010/06/22/html5rocks-google/), and Maud (the character) is a geologist. + +## Why does `html!` always allocate a `String`? Wouldn't it be more efficient if it wrote to a handle directly? + +Good question! In fact, Maud did work this way in the past. + +Sadly, that kind of thing didn't work out that well in practice. Having to pass the handle around made templates hard to compose, which is important in any non-trivial project. Furthermore, Iron (and other middleware frameworks) likes to take ownership of the response body, so we'd need to do some closure gymnastics to get everything to work together. To put the nail in the coffin, benchmarks showed that a `String` based solution is actually faster than one which avoids allocations. + +For these reasons, I changed `html!` to return a `String` in version 0.11. + +## Why is Maud written as a procedural macro? Can't it use `macro_rules!` instead? + +This is certainly possible, and in fact the [Horrorshow](https://github.com/Stebalien/horrorshow-rs) library works this way. + +I use procedural macros because they are more flexible. There are some syntax constructs in Maud that cannot be parsed with `macro_rules!`; better diagnostics are a bonus as well. + +## Maud has had a lot of releases so far. When will it reach 1.0? + +I plan to make a 1.0 release when the library can be used on stable Rust. + +## Why doesn't Maud implement [context-aware escaping](https://security.googleblog.com/2009/03/reducing-xss-by-way-of-automatic.html)? + +If a project follows best practices in separating HTML and CSS/JavaScript, then context-aware escaping is unnecessary. + +Google uses context-aware escaping because it has a large, decades-old code base, much of it written before these best practices were well known. Any project that uses Maud is neither large nor decades-old, and so should not have the need for this feature. diff --git a/docs/content/partials.md b/docs/content/partials.md new file mode 100644 index 0000000..d809e62 --- /dev/null +++ b/docs/content/partials.md @@ -0,0 +1,59 @@ +# Partials + +Maud does not have a built-in concept of partials or sub-templates. Instead, you can compose your markup with any function that returns `Markup`. + +The following example uses a `header` and `footer` function that are used in the `page` function to return a final result. + +```rust +extern crate maud; + +use self::maud::{DOCTYPE, html, Markup}; + +/// A basic header with a dynamic `page_title`. +fn header(page_title: &str) -> Markup { + html! { + (DOCTYPE) + html { + meta charset="utf-8"; + title { (page_title) } + } + } +} + +/// A static footer. +fn footer() -> Markup { + html! { + footer { + span { + a href="rss.atom" { "RSS Feed" } + } + } + } +} + +/// The final Markup, including `header` and `footer`. +/// +/// Additionally takes a `greeting_box` that's `Markup`, not `&str`. +pub fn page(title: &str, greeting_box: Markup) -> Markup { + html! { + // Add the header markup to the page + (header(title)) + body { + h1 { "Hello World" } + (greeting_box) + } + // Add the footer markup to the page + (footer()) + } +} +``` + +Using the `page` function will return the markup for the whole page and looks like this: + +```rust +fn main() { + page("Hello!", html! { + div { "Greetings, Maud." } + }); +} +``` diff --git a/docs/content/traits.md b/docs/content/traits.md new file mode 100644 index 0000000..4c5983b --- /dev/null +++ b/docs/content/traits.md @@ -0,0 +1,83 @@ +# The `Render` trait + +By default, a `(splice)` is rendered using the [`std::fmt::Display`][Display] trait, with any HTML special characters escaped automatically. + +To change this behavior, implement the [`Render`][Render] trait for your type. Then, when a value of this type is used in a template, Maud will call your custom code instead. + +Below are some examples of using `Render`. Feel free to use these snippets in your own project! For more examples, take a look at the [`maud_extras`][maud_extras] crate. + +## Example: a shorthand for including CSS stylesheets + +When writing a web page, it can be annoying to write `link rel="stylesheet"` over and over again. This example provides a shorthand for linking to CSS stylesheets. + +```rust +use maud::{Markup, Render}; + +/// Links to a CSS stylesheet at the given path. +struct Css(&'static str); + +impl Render for Css { + fn render(&self) -> Markup { + html! { + link rel="stylesheet" type="text/css" href=(self.0); + } + } +} +``` + +## Example: a wrapper that calls `std::fmt::Debug` + +When debugging an application, it can be useful to see its internal state. But these internal data types often don't implement `Display`. This wrapper lets us use the [`Debug`][Debug] trait instead. + +To avoid extra allocation, we override the `.render_to()` method instead of `.render()`. This doesn't do any escaping by default, so we wrap the output in an `Escaper` as well. + +```rust +use maud::{Render, Escaper}; +use std::fmt; + +/// Renders the given value using its `Debug` implementation. +struct Debug<T: fmt::Debug>(T); + +impl<T: fmt::Debug> Render for Debug<T> { + fn render_to(&self, output: &mut String) { + let mut escaper = Escaper::new(output); + write!(escaper, "{:?}", self.0).unwrap(); + } +} +``` + +## Example: rendering Markdown using `pulldown-cmark` and `ammonia` + +[`pulldown-cmark`][pulldown-cmark] is a popular library for converting Markdown to HTML. + +We also use the [`ammonia`][ammonia] library, which sanitizes the resulting markup. + +```rust +extern crate ammonia; +extern crate pulldown_cmark; + +use maud::{PreEscaped, Render}; +use pulldown_cmark::{Parser, html}; + +/// Renders a block of Markdown using `pulldown-cmark`. +struct Markdown<T: AsRef<str>>(T); + +impl<T: AsRef<str>> Render for Markdown<T> { + fn render(self, buffer: &mut String) { + // Generate raw HTML + let mut unsafe_html = String::new(); + let parser = Parser::new(self.0.as_str()); + html::push_html(&mut unsafe_html, parser); + // Sanitize it with ammonia + let safe_html = ammonia::clean(&unsafe_html); + PreEscaped(safe_html) + } +} +``` + +[maud_extras]: https://github.com/lfairy/maud/tree/master/maud_extras +[Debug]: https://doc.rust-lang.org/std/fmt/trait.Debug.html +[Display]: https://doc.rust-lang.org/std/fmt/trait.Display.html +[Render]: https://docs.rs/maud/*/maud/trait.Render.html +[pulldown-cmark]: https://docs.rs/pulldown-cmark/0.0.8/pulldown_cmark/index.html +[ammonia]: https://github.com/notriddle/ammonia diff --git a/docs/content/web-frameworks.md b/docs/content/web-frameworks.md new file mode 100644 index 0000000..2a6a52d --- /dev/null +++ b/docs/content/web-frameworks.md @@ -0,0 +1,160 @@ +# Web framework integration + +Maud includes support for these web frameworks: [Actix], [Iron], [Rocket], and [Rouille]. + +[Actix]: https://actix.rs/ +[Iron]: http://ironframework.io +[Rocket]: https://rocket.rs/ +[Rouille]: https://github.com/tomaka/rouille + +# Actix + +Actix support is available with the "actix-web" feature: + +```toml +# ... +[dependencies] +maud = { version = "*", features = ["actix-web"] } +# ... +``` + +Actix request handlers can use a `Markup` that implements the `actix_web::Responder` trait. + +```rust +#![feature(proc_macro_non_items)] +#![feature(use_extern_macros)] + +extern crate actix; +extern crate actix_web; +extern crate maud; + +use maud::{html, Markup}; +use actix_web::{App, server, Path, http::Method}; + +fn index(params: Path<(String, u32)>) -> Markup { + html! { + h1 { "Hello " (params.0) " with id " (params.1) "!"} + } +} + +fn main() { + let sys = actix::System::new("maud-example"); + + server::new(move || { + App::new() + .resource("/user/{name}/{id}", |r| { + r.method(Method::GET).with(index) + }) + }).bind("127.0.0.1:8080") + .unwrap() + .start(); + + let _ = sys.run(); +} +``` + +# Iron + +Iron support is available with the "iron" feature: + +```toml +# ... +[dependencies] +maud = { version = "*", features = ["iron"] } +# ... +``` + +With this feature enabled, you can then build a `Response` from a `Markup` object directly. Here's an example application using Iron and Maud: + +```rust +#![feature(proc_macro_non_items)] +#![feature(use_extern_macros)] + +extern crate iron; +extern crate maud; + +use iron::prelude::*; +use iron::status; +use maud::html; + +fn main() { + Iron::new(|r: &mut Request| { + let markup = html! { + h1 { "Hello, world!" } + p { + "You are viewing the page at " (r.url) + } + }; + Ok(Response::with((status::Ok, markup))) + }).http("localhost:3000").unwrap(); +} +``` + +`Markup` will set the content type of the response automatically, so you don't need to add it yourself. + +# Rocket + +Rocket works in a similar way, except using the `rocket` feature: + +```toml +# ... +[dependencies] +maud = { version = "*", features = ["rocket"] } +# ... +``` + +This adds a `Responder` implementation for the `Markup` type, so you can return the result directly: + +```rust +#![feature(plugin)] +#![plugin(rocket_codegen)] +#![feature(proc_macro_non_items)] +#![feature(use_extern_macros)] + +extern crate maud; +extern crate rocket; + +use maud::{html, Markup}; +use std::borrow::Cow; + +#[get("/<name>")] +fn hello<'a>(name: Cow<'a, str>) -> Markup { + html! { + h1 { "Hello, " (name) "!" } + p { "Nice to meet you!" } + } +} + +fn main() { + rocket::ignite().mount("/", routes![hello]).launch(); +} +``` + +# Rouille + +Unlike with the other frameworks, Rouille doesn't need any extra features at all! Calling `Response::html` on the rendered `Markup` will Just Work®. + +```rust +#![feature(proc_macro_non_items)] +#![feature(use_extern_macros)] + +extern crate maud; +#[macro_use] extern crate rouille; + +use maud::html; +use rouille::Response; + +fn main() { + rouille::start_server("localhost:8000", move |request| { + router!(request, + (GET) (/{name: String}) => { + html! { + h1 { "Hello, " (name) "!" } + p { "Nice to meet you!" } + } + }, + _ => Response::empty_404() + ) + }); +} +``` diff --git a/docs/src/main.rs b/docs/src/main.rs index d88c526..bc72848 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -19,6 +19,13 @@ mod views; const BOOK_FILES: &[&str] = &[ "index", "getting-started", + "basic-syntax", + "dynamic-content", + "partials", + "control-structures", + "traits", + "web-frameworks", + "faq", ]; fn main() -> Result<(), Box<dyn Error>> { From 71709edc51636650f3c50335c7e31a55081c221f Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sun, 9 Dec 2018 10:33:29 +1300 Subject: [PATCH 04/17] Tweak CSS --- docs/styles.css | 14 +++++++++++++- docs/watch.sh | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/styles.css b/docs/styles.css index 4a2c76f..f6d7939 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -1,6 +1,7 @@ html { box-sizing: border-box; font: 16px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + border-top: 1rem solid #808; } *, *::before, *::after { @@ -9,7 +10,7 @@ html { body { margin: 0 auto; - padding: 2rem; + padding: 0 2rem 2rem; max-width: 40rem; } @@ -21,10 +22,21 @@ header { header h1 { margin: 0; + font-size: 16rem; + line-height: 0.5; +} + +header h1 a { + color: #808; } header p { + position: relative; + left: 25rem; + top: -2rem; margin: 0 0 0 1rem; + background: white; + padding: 0.5rem 0.75rem; } nav ul { diff --git a/docs/watch.sh b/docs/watch.sh index 0bf2ab6..b88b334 100755 --- a/docs/watch.sh +++ b/docs/watch.sh @@ -8,5 +8,5 @@ trap 'kill $server_pid' EXIT while true do - find . -name '*.rs' -o -name '*.md' | entr -d cargo run + find . -name '*.rs' -o -name '*.md' -o -name '*.css' | entr -d cargo run done From b21892b512ea6bc0d305c61e488a340ba19b142d Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 16 Mar 2019 19:13:04 +1300 Subject: [PATCH 05/17] Make title less ridiculous --- docs/styles.css | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/styles.css b/docs/styles.css index f6d7939..801f69e 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -1,7 +1,7 @@ html { box-sizing: border-box; font: 16px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; - border-top: 1rem solid #808; + border-top: 0.75rem solid #808; } *, *::before, *::after { @@ -21,9 +21,9 @@ header { } header h1 { - margin: 0; - font-size: 16rem; - line-height: 0.5; + margin: 0 0 0 -0.25rem; + font-size: 5rem; + line-height: 0.75; } header h1 a { @@ -31,12 +31,7 @@ header h1 a { } header p { - position: relative; - left: 25rem; - top: -2rem; - margin: 0 0 0 1rem; - background: white; - padding: 0.5rem 0.75rem; + margin: 0 0.75em; } nav ul { From a986c2252687f5687fedf9941afa14f830c3e1ce Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 16 Mar 2019 19:36:07 +1300 Subject: [PATCH 06/17] Remove tagline --- docs/content/index.md | 4 ++++ docs/src/views.rs | 1 - docs/styles.css | 19 +++---------------- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/docs/content/index.md b/docs/content/index.md index 9f26d61..d3b0545 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -1,3 +1,7 @@ +<!-- Comment that prevents the title from getting picked up --> + +# A macro for writing HTML + ```rust html! { h1 { "Hello, world!" } diff --git a/docs/src/views.rs b/docs/src/views.rs index 949299b..4e01222 100644 --- a/docs/src/views.rs +++ b/docs/src/views.rs @@ -63,7 +63,6 @@ crate fn main<'a>( "maud" } } - p { "a macro for writing html" } } nav { diff --git a/docs/styles.css b/docs/styles.css index 801f69e..4996242 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -14,12 +14,6 @@ body { max-width: 40rem; } -header { - display: flex; - flex-wrap: wrap; - align-items: baseline; -} - header h1 { margin: 0 0 0 -0.25rem; font-size: 5rem; @@ -30,10 +24,6 @@ header h1 a { color: #808; } -header p { - margin: 0 0.75em; -} - nav ul { padding: 0; list-style: none; @@ -57,12 +47,13 @@ a:focus, a:hover, a:active { body { max-width: 56rem; /* 15 + 1 + 40 */ display: grid; + grid-template-rows: auto minmax(0, 1fr); grid-template-columns: 15rem auto; grid-gap: 1rem; } header { - grid-column: 1 / 3; + grid-column: 1; grid-row: 1; } @@ -73,10 +64,6 @@ a:focus, a:hover, a:active { main { grid-column: 2; - grid-row: 2; - } - - main h2:first-child { - margin-top: .5rem; + grid-row: 1 / 3; } } From e3ee44cd4b84bb0ae8a85f41747872ae9f867cf7 Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 16 Mar 2019 19:40:46 +1300 Subject: [PATCH 07/17] Add meta tags --- docs/src/views.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/views.rs b/docs/src/views.rs index 4e01222..8fed8f0 100644 --- a/docs/src/views.rs +++ b/docs/src/views.rs @@ -56,6 +56,8 @@ crate fn main<'a>( "Maud, a macro for writing HTML" } link rel="stylesheet" href="styles.css"; + meta name="theme-color" content="#808"; + meta name="viewport" content="width=device-width"; header { h1 { From 9be2d31d72e2c6fbc25493117d22daac3790ad0f Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 16 Mar 2019 19:46:29 +1300 Subject: [PATCH 08/17] Update Comrak --- docs/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/main.rs b/docs/src/main.rs index bc72848..7137815 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -34,6 +34,7 @@ fn main() -> Result<(), Box<dyn Error>> { let arena = Arena::new(); let options = ComrakOptions { ext_header_ids: Some("".to_string()), + unsafe_: true, ..ComrakOptions::default() }; From 6c2b5a9aa90ed7690b90d646a8261673456deaed Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 16 Mar 2019 19:47:13 +1300 Subject: [PATCH 09/17] Check in Cargo.lock --- .gitignore | 2 +- docs/Cargo.lock | 749 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 750 insertions(+), 1 deletion(-) create mode 100644 docs/Cargo.lock diff --git a/.gitignore b/.gitignore index 9adeb4e..8cdc97e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ target -Cargo.lock +/Cargo.lock docs/site diff --git a/docs/Cargo.lock b/docs/Cargo.lock new file mode 100644 index 0000000..165d626 --- /dev/null +++ b/docs/Cargo.lock @@ -0,0 +1,749 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "aho-corasick" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arrayref" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bincode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block-buffer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "build_const" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byte-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "comrak" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "typed-arena 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "docs" +version = "0.0.0" +dependencies = [ + "comrak 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maud 0.20.0", + "syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "flate2" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "generic-array" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "humantime" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indexmap" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itoa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazycell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "literalext" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maplit" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maud" +version = "0.20.0" +dependencies = [ + "maud_htmlescape 0.17.0", + "maud_macros 0.20.0", +] + +[[package]] +name = "maud_htmlescape" +version = "0.17.0" + +[[package]] +name = "maud_macros" +version = "0.20.0" +dependencies = [ + "literalext 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "maud_htmlescape 0.17.0", +] + +[[package]] +name = "memchr" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "miniz-sys" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide_c_api" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "onig" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "onig_sys" +version = "69.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_generator" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_meta" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "plist" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "line-wrap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "safemem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha-1" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syntect" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "onig 4.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "plist 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typed-arena" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typenum" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ucd-trie" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ucd-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "2.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "xml-rs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" +"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3efe0b4c8eaeed8600549c29f538a6a11bf422858d0ed435b1d70ec4ab101190" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" +"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +"checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" +"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum comrak 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6c6a70fb894be1bf51ca939f9e9008d93679908541793a6bf752bd313640a1d8" +"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" +"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" +"checksum line-wrap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" +"checksum literalext 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2f42dd699527975a1e0d722e0707998671188a0125f2051d2d192fc201184a81" +"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0300eafb20369952951699b68243ab4334f4b10a88f411c221d444b36c40e649" +"checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e" +"checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab" +"checksum onig 4.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a646989adad8a19f49be2090374712931c3a59835cb5277b4530f48b417f26e7" +"checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" +"checksum pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "54f0c72a98d8ab3c99560bfd16df8059cc10e1f9a8e83e6e3b97718dd766e9c3" +"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +"checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646" +"checksum pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5a3492a4ed208ffc247adcdcc7ba2a95be3104f58877d0d02f0df39bf3efb5e" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum plist 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f4739851c08dd9a62a78beff2edf1a438517268b2c563c42fc6d9d3139e42d2a" +"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" +"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" +"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" +"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" +"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" +"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" +"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" +"checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" +"checksum syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e80b8831c5a543192ffc3727f01cf0e57579c6ac15558e3048bfb5708892167b" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +"checksum typed-arena 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6c06a92aef38bb4dc5b0df00d68496fc31307c5344c867bb61678c6e1671ec5" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "71a9c5b1fe77426cf144cc30e49e955270f5086e31a6441dfa8b32efc09b9d77" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5" +"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" From 07ca29aa555f9b15dfe6fa5f0618a12e99411356 Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 16 Mar 2019 19:53:24 +1300 Subject: [PATCH 10/17] Rust 2018 the web framework examples --- docs/content/web-frameworks.md | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/docs/content/web-frameworks.md b/docs/content/web-frameworks.md index 2a6a52d..0de7dc6 100644 --- a/docs/content/web-frameworks.md +++ b/docs/content/web-frameworks.md @@ -21,12 +21,7 @@ maud = { version = "*", features = ["actix-web"] } Actix request handlers can use a `Markup` that implements the `actix_web::Responder` trait. ```rust -#![feature(proc_macro_non_items)] -#![feature(use_extern_macros)] - -extern crate actix; -extern crate actix_web; -extern crate maud; +#![feature(proc_macro_hygiene)] use maud::{html, Markup}; use actix_web::{App, server, Path, http::Method}; @@ -67,11 +62,7 @@ maud = { version = "*", features = ["iron"] } With this feature enabled, you can then build a `Response` from a `Markup` object directly. Here's an example application using Iron and Maud: ```rust -#![feature(proc_macro_non_items)] -#![feature(use_extern_macros)] - -extern crate iron; -extern crate maud; +#![feature(proc_macro_hygiene)] use iron::prelude::*; use iron::status; @@ -106,15 +97,11 @@ maud = { version = "*", features = ["rocket"] } This adds a `Responder` implementation for the `Markup` type, so you can return the result directly: ```rust -#![feature(plugin)] -#![plugin(rocket_codegen)] -#![feature(proc_macro_non_items)] -#![feature(use_extern_macros)] - -extern crate maud; -extern crate rocket; +#![feature(decl_macro)] +#![feature(proc_macro_hygiene)] use maud::{html, Markup}; +use rocket::{get, routes}; use std::borrow::Cow; #[get("/<name>")] @@ -135,14 +122,10 @@ fn main() { Unlike with the other frameworks, Rouille doesn't need any extra features at all! Calling `Response::html` on the rendered `Markup` will Just Work®. ```rust -#![feature(proc_macro_non_items)] -#![feature(use_extern_macros)] - -extern crate maud; -#[macro_use] extern crate rouille; +#![feature(proc_macro_hygiene)] use maud::html; -use rouille::Response; +use rouille::{Response, router}; fn main() { rouille::start_server("localhost:8000", move |request| { From d23bbe66809f97e31eb5d49d468e2ddcd2e25d9c Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 23 Mar 2019 22:57:59 +1300 Subject: [PATCH 11/17] Add Makefile --- docs/Cargo.lock | 7 ------ docs/Cargo.toml | 1 - docs/Makefile | 18 ++++++++++++++ docs/src/main.rs | 62 +++++++++++++++++++++-------------------------- docs/src/views.rs | 13 +++++----- docs/watch.sh | 4 ++- 6 files changed, 55 insertions(+), 50 deletions(-) create mode 100644 docs/Makefile diff --git a/docs/Cargo.lock b/docs/Cargo.lock index 165d626..0d9c35b 100644 --- a/docs/Cargo.lock +++ b/docs/Cargo.lock @@ -157,7 +157,6 @@ name = "docs" version = "0.0.0" dependencies = [ "comrak 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "maud 0.20.0", "syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -204,11 +203,6 @@ dependencies = [ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "indexmap" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "itoa" version = "0.4.3" @@ -688,7 +682,6 @@ dependencies = [ "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" -"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 4771728..34b185b 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -12,6 +12,5 @@ edition = "2018" [dependencies] comrak = "*" -indexmap = "*" maud = { path = "../maud" } syntect = "*" diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..2cf4cdc --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,18 @@ +MARKDOWN_FILES := $(wildcard content/*.md) +HTML_FILES := $(patsubst content/%.md,site/%.html,$(MARKDOWN_FILES)) + +.PHONY: all +all: $(HTML_FILES) site/styles.css + +target/debug/docs: $(wildcard src/*) + cargo build + +site/%.html: content/%.md target/debug/docs + target/debug/docs build-page $< $@ + +site/styles.css: styles.css + cp $^ $@ + +.PHONY: clean +clean: + rm -fr site diff --git a/docs/src/main.rs b/docs/src/main.rs index 7137815..c9cb9c5 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -3,8 +3,8 @@ use comrak::{self, Arena, ComrakOptions}; use comrak::nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink, NodeValue}; -use indexmap::IndexMap; use std::error::Error; +use std::env; use std::fs; use std::io; use std::mem; @@ -16,20 +16,30 @@ use syntect::html::highlighted_html_for_string; mod views; -const BOOK_FILES: &[&str] = &[ - "index", - "getting-started", - "basic-syntax", - "dynamic-content", - "partials", - "control-structures", - "traits", - "web-frameworks", - "faq", -]; - fn main() -> Result<(), Box<dyn Error>> { - fs::create_dir_all("site")?; + let args = env::args().collect::<Vec<_>>(); + if args.len() == 4 && &args[1] == "build-page" { + build_page(&args[2], &args[3]) + } else { + Err("invalid arguments".into()) + } +} + +fn build_page(input_path: &str, output_path: &str) -> Result<(), Box<dyn Error>> { + // TODO make this list dynamically generated + const NAV: &[(&str, Option<&str>)] = &[ + ("index", None), + ("getting-started", Some("Getting started")), + ("basic-syntax", Some("Basic syntax")), + ("dynamic-content", Some("Dynamic content")), + ("partials", Some("Partials")), + ("control-structures", Some("Control structures")), + ("traits", Some("Traits")), + ("web-frameworks", Some("Web frameworks")), + ("faq", Some("FAQ")), + ]; + + fs::create_dir_all(Path::new(output_path).parent().unwrap())?; let arena = Arena::new(); let options = ComrakOptions { @@ -38,26 +48,10 @@ fn main() -> Result<(), Box<dyn Error>> { ..ComrakOptions::default() }; - let mut pages = IndexMap::<&str, _>::new(); + let page = load_page(&arena, &options, input_path)?; + let markup = views::main(&options, input_path, page, &NAV); - for path in BOOK_FILES { - let mut input_path = Path::new("content").join(path); - input_path.set_extension("md"); - - let page = load_page(&arena, &options, &input_path)?; - - pages.insert(path, page); - } - - for path in pages.keys() { - let mut output_path = Path::new("site").join(path); - output_path.set_extension("html"); - println!("{}", output_path.display()); - let markup = views::main(&options, path, &pages); - fs::write(output_path, markup.into_string())?; - } - - fs::copy("styles.css", "site/styles.css")?; + fs::write(output_path, markup.into_string())?; Ok(()) } @@ -70,7 +64,7 @@ struct Page<'a> { fn load_page<'a>( arena: &'a Arena<AstNode<'a>>, options: &ComrakOptions, - path: &Path, + path: impl AsRef<Path>, ) -> io::Result<Page<'a>> { let buffer = fs::read_to_string(path)?; let content = comrak::parse_document(arena, &buffer, options); diff --git a/docs/src/views.rs b/docs/src/views.rs index 8fed8f0..2c5a378 100644 --- a/docs/src/views.rs +++ b/docs/src/views.rs @@ -1,7 +1,6 @@ use comrak::{self, ComrakOptions}; use comrak::nodes::AstNode; use crate::Page; -use indexmap::IndexMap; use maud::{DOCTYPE, Markup, Render, html}; use std::io; use std::str; @@ -41,10 +40,10 @@ impl<'a> Render for ComrakText<'a> { crate fn main<'a>( options: &'a ComrakOptions, - path: &str, - pages: &IndexMap<&str, Page<'a>>, + _path: &str, // TODO add nav indicator + page: Page<'a>, + nav: &[(&str, Option<&str>)], ) -> Markup { - let page = &pages[path]; html! { (DOCTYPE) meta charset="utf-8"; @@ -69,11 +68,11 @@ crate fn main<'a>( nav { ul { - @for (other_path, other_page) in pages { - @if let Some(title) = other_page.title { + @for (other_path, other_title) in nav { + @if let Some(title) = other_title { li { a href={ (other_path) ".html" } { - (Comrak(title, options)) + (title) } } } diff --git a/docs/watch.sh b/docs/watch.sh index b88b334..b268871 100755 --- a/docs/watch.sh +++ b/docs/watch.sh @@ -6,7 +6,9 @@ python3 -m http.server -d site & server_pid=$! trap 'kill $server_pid' EXIT +nproc=$(nproc || echo 4) + while true do - find . -name '*.rs' -o -name '*.md' -o -name '*.css' | entr -d cargo run + find . -name '*.rs' -o -name '*.md' -o -name '*.css' | entr -d make -j$nproc done From 612208a3442ca58221b09a8b1016debba8ad473a Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sat, 23 Mar 2019 23:03:51 +1300 Subject: [PATCH 12/17] Don't link a page to itself --- docs/Makefile | 2 +- docs/src/main.rs | 8 ++++---- docs/src/views.rs | 14 ++++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 2cf4cdc..4031cee 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -8,7 +8,7 @@ target/debug/docs: $(wildcard src/*) cargo build site/%.html: content/%.md target/debug/docs - target/debug/docs build-page $< $@ + target/debug/docs build-page $< $@ $(*F) site/styles.css: styles.css cp $^ $@ diff --git a/docs/src/main.rs b/docs/src/main.rs index c9cb9c5..f120d62 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -18,14 +18,14 @@ mod views; fn main() -> Result<(), Box<dyn Error>> { let args = env::args().collect::<Vec<_>>(); - if args.len() == 4 && &args[1] == "build-page" { - build_page(&args[2], &args[3]) + if args.len() == 5 && &args[1] == "build-page" { + build_page(&args[2], &args[3], &args[4]) } else { Err("invalid arguments".into()) } } -fn build_page(input_path: &str, output_path: &str) -> Result<(), Box<dyn Error>> { +fn build_page(input_path: &str, output_path: &str, slug: &str) -> Result<(), Box<dyn Error>> { // TODO make this list dynamically generated const NAV: &[(&str, Option<&str>)] = &[ ("index", None), @@ -49,7 +49,7 @@ fn build_page(input_path: &str, output_path: &str) -> Result<(), Box<dyn Error>> }; let page = load_page(&arena, &options, input_path)?; - let markup = views::main(&options, input_path, page, &NAV); + let markup = views::main(&options, slug, page, &NAV); fs::write(output_path, markup.into_string())?; diff --git a/docs/src/views.rs b/docs/src/views.rs index 2c5a378..9942867 100644 --- a/docs/src/views.rs +++ b/docs/src/views.rs @@ -40,7 +40,7 @@ impl<'a> Render for ComrakText<'a> { crate fn main<'a>( options: &'a ComrakOptions, - _path: &str, // TODO add nav indicator + slug: &str, page: Page<'a>, nav: &[(&str, Option<&str>)], ) -> Markup { @@ -68,11 +68,17 @@ crate fn main<'a>( nav { ul { - @for (other_path, other_title) in nav { + @for &(other_slug, other_title) in nav { @if let Some(title) = other_title { li { - a href={ (other_path) ".html" } { - (title) + @if other_slug == slug { + b { + (title) + } + } @else { + a href={ (other_slug) ".html" } { + (title) + } } } } From 327480c442b0d72628aa595b43338081d9551a2e Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sun, 24 Mar 2019 14:25:05 +1300 Subject: [PATCH 13/17] Build table of contents in a separate step - Add pretty logging to Makefile --- docs/Cargo.lock | 1 + docs/Cargo.toml | 1 + docs/Makefile | 38 ++++++++--- docs/src/main.rs | 130 ++++++++++++++++++++++++++++---------- docs/src/string_writer.rs | 19 ++++++ docs/src/views.rs | 54 +++++++--------- 6 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 docs/src/string_writer.rs diff --git a/docs/Cargo.lock b/docs/Cargo.lock index 0d9c35b..bbbdcfb 100644 --- a/docs/Cargo.lock +++ b/docs/Cargo.lock @@ -158,6 +158,7 @@ version = "0.0.0" dependencies = [ "comrak 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "maud 0.20.0", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 34b185b..6438e4d 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -13,4 +13,5 @@ edition = "2018" [dependencies] comrak = "*" maud = { path = "../maud" } +serde_json = "*" syntect = "*" diff --git a/docs/Makefile b/docs/Makefile index 4031cee..2570a0b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,18 +1,38 @@ -MARKDOWN_FILES := $(wildcard content/*.md) -HTML_FILES := $(patsubst content/%.md,site/%.html,$(MARKDOWN_FILES)) +slugs := index getting-started basic-syntax dynamic-content partials control-structures traits web-frameworks faq + +slug_to_md = content/$(1).md +slug_to_html = site/$(1).html + +builder := target/debug/docs +nav_json := site/nav.json +md_files := $(foreach slug,$(slugs),$(call slug_to_md,$(slug))) +html_files := $(foreach slug,$(slugs),$(call slug_to_html,$(slug))) +slugs_and_md_files := $(foreach slug,$(slugs),$(slug):$(call slug_to_md,$(slug))) + +print_status = @ printf ' \e[1;35m♦ %s\e[0m\n' '$(1)' .PHONY: all -all: $(HTML_FILES) site/styles.css +all: $(html_files) site/styles.css -target/debug/docs: $(wildcard src/*) - cargo build +$(builder): $(wildcard src/*) + $(call print_status,Cargo) + @ cargo build -site/%.html: content/%.md target/debug/docs - target/debug/docs build-page $< $@ $(*F) +$(nav_json): $(md_files) $(builder) + $(call print_status,Table of contents) + @ $(builder) build-nav $@ $(slugs_and_md_files) + +site/%.html: content/%.md $(nav_json) $(builder) + $(call print_status,Render $(*F)) + @ $(builder) build-page $@ $(*F) $< $(nav_json) site/styles.css: styles.css - cp $^ $@ + $(call print_status,Copy stylesheet) + @ mkdir -p $(dir $@) + @ cp $^ $@ .PHONY: clean clean: - rm -fr site + $(call print_status,Clean) + @ cargo clean + @ rm -fr site diff --git a/docs/src/main.rs b/docs/src/main.rs index f120d62..ad33cd0 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -3,10 +3,11 @@ use comrak::{self, Arena, ComrakOptions}; use comrak::nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink, NodeValue}; +use serde_json; use std::error::Error; use std::env; -use std::fs; -use std::io; +use std::fs::{self, File}; +use std::io::{self, BufReader}; use std::mem; use std::path::Path; use std::string::FromUtf8Error; @@ -14,43 +15,72 @@ use syntect::parsing::SyntaxSet; use syntect::highlighting::{Color, ThemeSet}; use syntect::html::highlighted_html_for_string; +mod string_writer; mod views; fn main() -> Result<(), Box<dyn Error>> { let args = env::args().collect::<Vec<_>>(); - if args.len() == 5 && &args[1] == "build-page" { - build_page(&args[2], &args[3], &args[4]) + if args.len() >= 3 && &args[1] == "build-nav" && args[3..].iter().all(|arg| arg.contains(":")) { + let entries = args[3..].iter().map(|arg| { + let mut splits = arg.splitn(2, ":"); + let slug = splits.next().unwrap(); + let input_path = splits.next().unwrap(); + (slug, input_path) + }).collect::<Vec<_>>(); + build_nav(&entries, &args[2]) + } else if args.len() == 6 && &args[1] == "build-page" { + build_page(&args[2], &args[3], &args[4], &args[5]) } else { Err("invalid arguments".into()) } } -fn build_page(input_path: &str, output_path: &str, slug: &str) -> Result<(), Box<dyn Error>> { - // TODO make this list dynamically generated - const NAV: &[(&str, Option<&str>)] = &[ - ("index", None), - ("getting-started", Some("Getting started")), - ("basic-syntax", Some("Basic syntax")), - ("dynamic-content", Some("Dynamic content")), - ("partials", Some("Partials")), - ("control-structures", Some("Control structures")), - ("traits", Some("Traits")), - ("web-frameworks", Some("Web frameworks")), - ("faq", Some("FAQ")), - ]; - - fs::create_dir_all(Path::new(output_path).parent().unwrap())?; - +fn build_nav(entries: &[(&str, &str)], nav_path: &str) -> Result<(), Box<dyn Error>> { let arena = Arena::new(); - let options = ComrakOptions { - ext_header_ids: Some("".to_string()), - unsafe_: true, - ..ComrakOptions::default() + let options = comrak_options(); + + let nav = entries.iter().map(|&(slug, input_path)| { + let title = load_page_title(&arena, &options, input_path)?; + Ok((slug, title)) + }).collect::<io::Result<Vec<_>>>()?; + + // Only write if different to avoid spurious rebuilds + let old_string = fs::read_to_string(nav_path).unwrap_or(String::new()); + let new_string = serde_json::to_string_pretty(&nav)?; + if old_string != new_string { + fs::create_dir_all(Path::new(nav_path).parent().unwrap())?; + fs::write(nav_path, new_string)?; + } + + Ok(()) +} + +fn build_page( + output_path: &str, + slug: &str, + input_path: &str, + nav_path: &str, +) -> Result<(), Box<dyn Error>> { + let nav: Vec<(String, Option<String>)> = { + let file = File::open(nav_path)?; + let reader = BufReader::new(file); + serde_json::from_reader(reader)? }; - let page = load_page(&arena, &options, input_path)?; - let markup = views::main(&options, slug, page, &NAV); + let arena = Arena::new(); + let options = comrak_options(); + let nav = nav.iter().filter_map(|(slug, title)| { + title.as_ref().map(|title| { + let title = comrak::parse_document(&arena, title, &options); + (slug.as_str(), title) + }) + }).collect::<Vec<_>>(); + + let page = load_page(&arena, &options, input_path)?; + let markup = views::main(&options, slug, page, &nav); + + fs::create_dir_all(Path::new(output_path).parent().unwrap())?; fs::write(output_path, markup.into_string())?; Ok(()) @@ -65,6 +95,40 @@ fn load_page<'a>( arena: &'a Arena<AstNode<'a>>, options: &ComrakOptions, path: impl AsRef<Path>, +) -> io::Result<Page<'a>> { + let page = load_page_raw(arena, options, path)?; + + lower_headings(page.content); + rewrite_md_links(page.content) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + highlight_code(page.content) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + + Ok(page) +} + +fn load_page_title<'a>( + arena: &'a Arena<AstNode<'a>>, + options: &ComrakOptions, + path: impl AsRef<Path>, +) -> io::Result<Option<String>> { + let page = load_page_raw(arena, options, path)?; + let title = page.title.map(|title| { + let mut buffer = String::new(); + comrak::format_commonmark( + title, + options, + &mut string_writer::StringWriter(&mut buffer), + ).unwrap(); + buffer + }); + Ok(title) +} + +fn load_page_raw<'a>( + arena: &'a Arena<AstNode<'a>>, + options: &ComrakOptions, + path: impl AsRef<Path>, ) -> io::Result<Page<'a>> { let buffer = fs::read_to_string(path)?; let content = comrak::parse_document(arena, &buffer, options); @@ -82,12 +146,6 @@ fn load_page<'a>( } }); - lower_headings(content); - rewrite_md_links(content) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - highlight_code(content) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - Ok(Page { title, content }) } @@ -140,3 +198,11 @@ fn highlight_code<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> { } Ok(()) } + +fn comrak_options() -> ComrakOptions { + ComrakOptions { + ext_header_ids: Some("".to_string()), + unsafe_: true, + ..ComrakOptions::default() + } +} diff --git a/docs/src/string_writer.rs b/docs/src/string_writer.rs new file mode 100644 index 0000000..441430e --- /dev/null +++ b/docs/src/string_writer.rs @@ -0,0 +1,19 @@ +use std::io; +use std::str; + +pub struct StringWriter<'a>(pub &'a mut String); + +impl<'a> io::Write for StringWriter<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + str::from_utf8(buf) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) + .map(|s| { + self.0.push_str(s); + buf.len() + }) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/docs/src/views.rs b/docs/src/views.rs index 9942867..793e922 100644 --- a/docs/src/views.rs +++ b/docs/src/views.rs @@ -1,27 +1,10 @@ use comrak::{self, ComrakOptions}; use comrak::nodes::AstNode; use crate::Page; -use maud::{DOCTYPE, Markup, Render, html}; -use std::io; +use crate::string_writer::StringWriter; +use maud::{DOCTYPE, Markup, PreEscaped, Render, html}; use std::str; -struct StringWriter<'a>(&'a mut String); - -impl<'a> io::Write for StringWriter<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - str::from_utf8(buf) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) - .map(|s| { - self.0.push_str(s); - buf.len() - }) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - struct Comrak<'a>(&'a AstNode<'a>, &'a ComrakOptions); impl<'a> Render for Comrak<'a> { @@ -30,6 +13,19 @@ impl<'a> Render for Comrak<'a> { } } +/// Hack! Comrak wraps a single line of input in `<p>` tags, which is great in +/// general but not suitable for links in the navigation bar. +struct ComrakRemovePTags<'a>(&'a AstNode<'a>, &'a ComrakOptions); + +impl<'a> Render for ComrakRemovePTags<'a> { + fn render(&self) -> Markup { + let mut buffer = String::new(); + comrak::format_html(self.0, self.1, &mut StringWriter(&mut buffer)).unwrap(); + assert!(buffer.starts_with("<p>") && buffer.ends_with("</p>\n")); + PreEscaped(buffer.trim_start_matches("<p>").trim_end_matches("</p>\n").to_string()) + } +} + struct ComrakText<'a>(&'a AstNode<'a>, &'a ComrakOptions); impl<'a> Render for ComrakText<'a> { @@ -42,7 +38,7 @@ crate fn main<'a>( options: &'a ComrakOptions, slug: &str, page: Page<'a>, - nav: &[(&str, Option<&str>)], + nav: &[(&str, &'a AstNode<'a>)], ) -> Markup { html! { (DOCTYPE) @@ -69,16 +65,14 @@ crate fn main<'a>( nav { ul { @for &(other_slug, other_title) in nav { - @if let Some(title) = other_title { - li { - @if other_slug == slug { - b { - (title) - } - } @else { - a href={ (other_slug) ".html" } { - (title) - } + li { + @if other_slug == slug { + b { + (ComrakRemovePTags(other_title, options)) + } + } @else { + a href={ (other_slug) ".html" } { + (ComrakRemovePTags(other_title, options)) } } } From a16b26a64ee70fa0528051397c28c22aefe9acdb Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sun, 24 Mar 2019 14:39:20 +1300 Subject: [PATCH 14/17] Add publish script --- docs/deploy.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100755 docs/deploy.sh diff --git a/docs/deploy.sh b/docs/deploy.sh new file mode 100755 index 0000000..8516718 --- /dev/null +++ b/docs/deploy.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e + +nproc=$(nproc || echo 4) + +make clean +make + +cd site +git init +git add . +git commit -m 'Deploy' + +git remote add github git@github.com:lfairy/maud.git +git push -f github master:gh-pages From 3a038d8f95cadec146367e880a2d28f282c6c4c0 Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sun, 24 Mar 2019 14:51:41 +1300 Subject: [PATCH 15/17] Tweak colors --- docs/src/main.rs | 2 +- docs/styles.css | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/src/main.rs b/docs/src/main.rs index ad33cd0..7cd2409 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -177,7 +177,7 @@ fn highlight_code<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> { let ss = SyntaxSet::load_defaults_newlines(); let ts = ThemeSet::load_defaults(); let mut theme = ts.themes["InspiredGitHub"].clone(); - theme.settings.background = Some(Color { r: 0xee, g: 0xee, b: 0xee, a: 0xff }); + theme.settings.background = Some(Color { r: 0xff, g: 0xee, b: 0xff, a: 0xff }); for node in root.descendants() { let mut data = node.data.borrow_mut(); if let NodeValue::CodeBlock(NodeCodeBlock { info, literal, .. }) = &mut data.value { diff --git a/docs/styles.css b/docs/styles.css index 4996242..52c81ec 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -20,7 +20,7 @@ header h1 { line-height: 0.75; } -header h1 a { +header h1 a:link, header h1 a:visited { color: #808; } @@ -32,13 +32,17 @@ nav ul { pre { padding: 1rem; font: 15px Consolas, monospace; - background: #eee; + background: #fef; } a:link { text-decoration: none; } +a:link, a:visited { + color: #a0a; +} + a:focus, a:hover, a:active { background: #fdd; } From 563225064496c6a2842dc9cd66f07751e6c307b4 Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sun, 24 Mar 2019 14:56:44 +1300 Subject: [PATCH 16/17] In watch script, exclude built files from watch --- docs/watch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/watch.sh b/docs/watch.sh index b268871..a59d84a 100755 --- a/docs/watch.sh +++ b/docs/watch.sh @@ -10,5 +10,5 @@ nproc=$(nproc || echo 4) while true do - find . -name '*.rs' -o -name '*.md' -o -name '*.css' | entr -d make -j$nproc + find . -name '*.rs' -o -name '*.md' -o -name '*.css' -not -path './site/*' | entr -d make -j$nproc done From 5c2d5ca5e8666c63d14e266e387bd03ffc4b2e9f Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Sun, 24 Mar 2019 16:22:52 +1300 Subject: [PATCH 17/17] Update readme link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52d6fbb..10a2682 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Note that Maud depends on the unstable [procedural macro API][rustissue], and so For more info on Maud, see the [official book][book]. [book]: https://maud.lambda.xyz/ -[booksrc]: https://github.com/lfairy/maud-book +[booksrc]: https://github.com/lfairy/maud/tree/master/docs [apiref]: https://docs.rs/maud/ [changelog]: https://github.com/lfairy/maud/blob/master/CHANGELOG.md [rustissue]: https://github.com/rust-lang/rust/issues/38356