Skip to content

Commit

Permalink
feat: better docs
Browse files Browse the repository at this point in the history
  • Loading branch information
vidhanio committed Jan 12, 2024
1 parent ef10480 commit 491c023
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 24 deletions.
63 changes: 45 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
# `hypertext`

```rs
hypertext::maud! {
"see" a href="https://docs.rs/hypertext" { "docs.rs" } "for documentation."
}
```
A blazing fast type-checked HTML macro crate.

## Features

### Speed

The macros generate code that is as fast as writing HTML to a string by hand. The macro automatically combines what would be multiple `push_str` calls into one if there is no dynamic content between them.

The crate gives extreme importance to lazy rendering and minimizing allocation, so it will only render the HTML to a string when you finally call the render function at the end.

### Type-Checking

All macros are validated at compile time, so you can't ever misspell an element/attribute or use invalid attributes. All of this validation has absolutely no runtime cost however, and it is just used for developer experience.

## Multiple Syntaxes

The crate provides a macro for writing rsx-style code, and another macro for writing [maud](https://maud.lambda.xyz)-style code, and lets you decide whichever one you like more.
- Type checking for element names/attributes
- Completely extensible for use with non-standard elements/attributes
- `#![no_std]` support
- Automatic escaping
- Lazy rendering by default to avoid multiple allocations
- Results in outstanding performance in cases of nested documents, which other libraries may falter in

## Example

```rust
use hypertext::{html_elements, GlobalAttributes, RenderIterator};

let shopping_list = &vec!["milk", "eggs", "bread"];

let shopping_list_maud = hypertext::maud! {
div {
h1 { "Shopping List" }
ul {
@for (&item, i) in shopping_list.iter().zip(1..) {
li.item {
input #{ "item-" (i) } type="checkbox";
label for={ "item-" (i) } { (item) }
}
}
}
}
};

// or, alternatively:

let shopping_list_rsx = hypertext::rsx! {
<div>
<h1>Shopping List</h1>
<ul>
{ shopping_list.iter().zip(1..).map(|(&item, i)| hypertext::rsx! {
<li class="item">
<input id=format!("item-{i}") type="checkbox">
<label for=format!("item-{i}")>{ item }</label>
</li>
}).render_all() }
</ul>
</div>
};
```
24 changes: 20 additions & 4 deletions hypertext-macros/src/rstml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::HashSet;

use proc_macro2::TokenStream;
use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
use quote::ToTokens;
use rstml::{
node::{
AttributeValueExpr, KeyedAttribute, KeyedAttributeValue, Node, NodeAttribute, NodeBlock,
Expand All @@ -11,7 +12,8 @@ use rstml::{
Parser, ParserConfig,
};
use syn::{
punctuated::Pair, spanned::Spanned, Expr, ExprBlock, ExprLit, ExprPath, Ident, Lit, LitStr,
parse_quote, punctuated::Pair, spanned::Spanned, Expr, ExprBlock, ExprLit, ExprPath, Ident,
Lit, LitStr,
};

use crate::generate::{Generate, Generator};
Expand Down Expand Up @@ -245,7 +247,15 @@ fn node_name_ident(node_name: &NodeName) -> Ident {
match node_name {
NodeName::Path(ExprPath { path, .. }) => path.segments.last().map_or_else(
|| Ident::new("_", path.span()),
|segment| Ident::new(&segment.ident.to_string(), segment.ident.span()),
|segment| {
syn::parse2::<Ident>(segment.ident.to_token_stream()).map_or_else(
|_| Ident::new_raw(&segment.ident.to_string(), segment.ident.span()),
|mut ident| {
ident.set_span(segment.ident.span());
ident
},
)
},
),
NodeName::Punctuated(punctuated) => {
let string = punctuated.pairs().map(Pair::into_tuple).fold(
Expand All @@ -259,7 +269,13 @@ fn node_name_ident(node_name: &NodeName) -> Ident {
},
);

Ident::new(&string, punctuated.span())
syn::parse_str::<Ident>(&string).map_or_else(
|_| Ident::new_raw(&string, punctuated.span()),
|mut ident| {
ident.set_span(punctuated.span());
ident
},
)
}
NodeName::Block(_) => Ident::new("_", node_name.span()),
}
Expand Down Expand Up @@ -293,7 +309,7 @@ impl Generate for NodeBlock {
fn generate(&self, gen: &mut Generator) {
if let Self::ValidBlock(block) = self {
gen.push_rendered_expr(&Expr::Block(ExprBlock {
attrs: Vec::new(),
attrs: vec![parse_quote!(#[allow(unused_braces)])],
label: None,
block: block.clone(),
}));
Expand Down
4 changes: 2 additions & 2 deletions hypertext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
//! ## Speed
//!
//! The macros generate code that is as fast as writing HTML to a string by
//! hand. The macro automatically combines what would be multiple `push_str`
//! calls into one if there is no dynamic content between them.
//! hand, and intelligently combines what would be multiple `push_str` calls
//! into one if there is no dynamic content between them.
//!
//! The entire crate is `#![no_std]` compatible, and allocation is completely
//! optional if you don't use any dynamic content. Disabling the `alloc` feature
Expand Down
38 changes: 38 additions & 0 deletions hypertext/tests/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#[test]
fn readme() {
use hypertext::{html_elements, GlobalAttributes, RenderIterator};

let shopping_list = &vec!["milk", "eggs", "bread"];

let shopping_list_maud = hypertext::maud! {
div {
h1 { "Shopping List" }
ul {
@for (&item, i) in shopping_list.iter().zip(1..) {
li.item {
input #{ "item-" (i) } type="checkbox";
label for={ "item-" (i) } { (item) }
}
}
}
}
};

// or, alternatively:

let shopping_list_rsx = hypertext::rsx! {
<div>
<h1>Shopping List</h1>
<ul>
{ shopping_list.iter().zip(1..).map(|(&item, i)| hypertext::rsx! {
<li class="item">
<input id=format!("item-{i}") type="checkbox">
<label for=format!("item-{i}")>{ item }</label>
</li>
}).render_all() }
</ul>
</div>
};

assert_eq!(shopping_list_maud.render(), shopping_list_rsx.render());
}

0 comments on commit 491c023

Please sign in to comment.