Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace PreEscaped and Markup with Html type #322

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
slugs := index getting-started text-escaping elements-attributes splices-toggles control-structures partials render-trait web-frameworks faq
slugs := index getting-started text-escaping elements-attributes splices-toggles control-structures partials web-frameworks faq

slug_to_md = content/$(1).md
slug_to_html = site/$(1).html
Expand Down
8 changes: 4 additions & 4 deletions docs/content/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ use maud::html;

fn main() {
let name = "Lyra";
let markup = html! {
let hello = html! {
p { "Hi, " (name) "!" }
};
println!("{}", markup.into_string());
println!("{}", hello.into_string());
}
```

`html!` takes a single argument:
a template using Maud's custom syntax.
This call expands to an expression of type [`Markup`][Markup],
This call expands to an expression of type [`Html`][Html],
which can then be converted to a `String` using `.into_string()`.

[Markup]: https://docs.rs/maud/*/maud/type.Markup.html
[Html]: https://docs.rs/maud/*/maud/struct.Html.html

Run this program with `cargo run`,
and you should get the following:
Expand Down
21 changes: 10 additions & 11 deletions docs/content/partials.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

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`.
you can compose your markup with any function that returns `Html`.

The following example defines a `header` and `footer` function.
These functions are combined to form the final `page`.

```rust
use maud::{DOCTYPE, html, Markup};
use maud::{DOCTYPE, html, Html};

/// A basic header with a dynamic `page_title`.
fn header(page_title: &str) -> Markup {
fn header(page_title: &str) -> Html {
html! {
(DOCTYPE)
meta charset="utf-8";
Expand All @@ -20,20 +20,19 @@ fn header(page_title: &str) -> Markup {
}

/// A static footer.
fn footer() -> Markup {
fn footer() -> Html {
html! {
footer {
a href="rss.atom" { "RSS Feed" }
}
}
}

/// The final Markup, including `header` and `footer`.
/// The final page, including `header` and `footer`.
///
/// Additionally takes a `greeting_box` that's `Markup`, not `&str`.
pub fn page(title: &str, greeting_box: Markup) -> Markup {
/// Additionally takes a `greeting_box` that's `Html`, not `&str`.
pub fn page(title: &str, greeting_box: Html) -> Html {
html! {
// Add the header markup to the page
(header(title))
h1 { (title) }
(greeting_box)
Expand All @@ -42,12 +41,12 @@ pub fn page(title: &str, greeting_box: Markup) -> Markup {
}
```

Using the `page` function will return the markup for the whole page.
Using the `page` function will return the HTML for the whole page.
Here's an example:

```rust
# use maud::{html, Markup};
# fn page(title: &str, greeting_box: Markup) -> Markup { greeting_box }
# use maud::{html, Html};
# fn page(title: &str, greeting_box: Html) -> Html { greeting_box }
page("Hello!", html! {
div { "Greetings, Maud." }
});
Expand Down
91 changes: 0 additions & 91 deletions docs/content/render-trait.md

This file was deleted.

44 changes: 29 additions & 15 deletions docs/content/splices-toggles.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,30 +94,44 @@ html! {

### What can be spliced?

You can splice any value that implements [`Render`][Render].
You can splice any value that implements [`ToHtml`][ToHtml].
Most primitive types (such as `str` and `i32`) implement this trait,
so they should work out of the box.

To get this behavior for a custom 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](render-trait.md) section for details.
you can implement the [`ToHtml`][ToHtml] trait by hand.

```rust
use maud::PreEscaped;
let post = "<p>Pre-escaped</p>";
# let _ = maud::
html! {
h1 { "My super duper blog post" }
(PreEscaped(post))
use maud::{Html, ToHtml, html};

struct Pony {
name: String,
cuteness: i32,
}
# ;

impl ToHtml for Pony {
fn to_html(&self) -> Html {
html! {
p {
"Pony " (self.name) " is " (self.cuteness) " cute!"
}
}
}
}

let sweetie_belle = Pony {
name: "Sweetie Belle".into(),
cuteness: 99,
};

let example = html! {
(sweetie_belle)
};

assert_eq!(example.into_string(), "<p>Pony Sweetie Belle is 99 cute!</p>");
```

[Render]: https://docs.rs/maud/*/maud/trait.Render.html
[PreEscaped]: https://docs.rs/maud/*/maud/struct.PreEscaped.html
[ToHtml]: https://docs.rs/maud/*/maud/trait.ToHtml.html

## Toggles: `[foo]`

Expand Down
81 changes: 70 additions & 11 deletions docs/content/text-escaping.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,33 @@ html! {

[raw strings]: https://doc.rust-lang.org/reference/tokens.html#raw-string-literals

## Escaping and `PreEscaped`
## Escaping

By default,
HTML special characters are escaped automatically.
Wrap the string in `(PreEscaped())` to disable this escaping.
(See the section on [splices](splices-toggles.md) to
learn more about how this works.)

```rust
use maud::PreEscaped;
# let _ = maud::
html! {
"<script>alert(\"XSS\")</script>" // &lt;script&gt;...
(PreEscaped("<script>alert(\"XSS\")</script>")) // <script>...
}
# ;
# use maud::html;
let markup = html! {
"<p>Pickle, barrel, kumquat.</p>"
};
assert_eq!(markup.into_string(), "&lt;p&gt;Pickle, barrel, kumquat.&lt;/p&gt;");
```

This escaping also applies within a [splice](splices-toggles.md),
which prevents [cross-site scripting][xss] attacks:

```rust
# use maud::html;
let unsafe_input = "<script>alert('Bwahahaha!')</script>";
let markup = html! {
(unsafe_input)
};
assert_eq!(markup.into_string(), "&lt;script&gt;alert('Bwahahaha!')&lt;/script&gt;");
```

[xss]: https://www.cloudflare.com/en-au/learning/security/threats/cross-site-scripting/

## The `DOCTYPE` constant

If you want to add a `<!DOCTYPE html>` declaration to your page,
Expand All @@ -71,3 +80,53 @@ html! {
}
# ;
```

## Inline `<script>` and `<style>`

In HTML,
`<script>` and `<style>` elements
have [special syntax rules].

[special syntax rules]: https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements

Maud does not yet implement these special rules,
so it's not recommended to write `script { ... }` or `style { ... }` directly.

Instead, either:

- Put the CSS or JavaScript in a separate file,
and link to it:

```rust
# let _ = maud::
html! {
script src="my-external-script.js" {}
}
# ;
```

- Wrap the whole thing in [`Html::from_const_unchecked`][from_const_unchecked],
to bypass Maud's escaping:

```rust
use maud::Html;
# let _ = maud::
html! {
(Html::from_const_unchecked("<script>doCoolStuff();</script>"))
}
# ;
```

[from_const_unchecked]: https://docs.rs/maud/*/maud/struct.Html.html#method.from_const_unchecked

When Maud implements [context-aware escaping],
these workarounds will no longer be needed.

[context-aware escaping]: https://github.com/lambda-fairy/maud/issues/181

## Custom escaping

If your use case isn't covered by these examples,
check out the [advanced API].

[advanced API]: https://docs.rs/maud/*/maud/struct.Html.html
Loading