1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
#![cfg_attr(nightly_yew, feature(proc_macro_span))]
//! This crate provides handy extensions to [Yew](https://yew.rs)'s
//! [HTML macros](https://docs.rs/yew/latest/yew/macro.html.html).
//! It provides [`html!`] and [`html_nested!`] macros that are fully backwards-compatible with the
//! original ones defined in Yew, meaning all one has to do to start using this crate is
//! just change the uses/imports of `yew::html{_nested}` to `yew_html_ext::html{_nested}`.
//! # New syntax
//! ## `for` loops
//! The syntax is the same as of Rust's `for` loops, the body of the loop can contain 0 or more
//! nodes.
//! ```rust
//! use yew::{Properties, function_component, html::Html};
//! use yew_html_ext::html;
//!
//! #[derive(PartialEq, Properties)]
//! struct CountdownProps {
//! n: usize,
//! }
//!
//! #[function_component]
//! fn Countdown(props: &CountdownProps) -> Html {
//! html! {
//! <div>
//! for i in (0 .. props.n).rev() {
//! <h2>{ i }</h2>
//! <br />
//! }
//! </div>
//! }
//! }
//! ```
//! However, `break`ing or `continue`ing the loop is forbidden.
//! ```rust,compile_fail
//! # use yew::{Properties, function_component, html::Html};
//! # use yew_html_ext::html;
//! #
//! # #[derive(PartialEq, Properties)]
//! # struct CountdownProps {
//! # n: usize,
//! # }
//! #
//! # #[function_component]
//! # fn Countdown(props: &CountdownProps) -> Html {
//! html! {
//! <div>
//! for i in (0 .. props.n).rev() {
//! if i % 2 == 0 {
//! { continue } // nuh-uh
//! } else {
//! <h2>{ i }</h2>
//! }
//! <br />
//! }
//! </div>
//! }
//! # }
//! ```
//! In a list of nodes all nodes must have unique keys or have no key, which is why using a
//! constant to specify a key of a node in a loop is dangerous: if the loop iterates more than
//! once, the generated list will have repeated keys; as a best-effort attempt to prevent such
//! cases, the macro disallows specifying literals or constants as keys
//! ```rust,compile_fail
//! # use yew::{Properties, function_component, html::Html};
//! # use yew_html_ext::html;
//! #
//! # #[derive(PartialEq, Properties)]
//! # struct CountdownProps {
//! # n: usize,
//! # }
//! #
//! # #[function_component]
//! # fn Countdown(props: &CountdownProps) -> Html {
//! html! {
//! <div>
//! for i in (0 .. props.n).rev() {
//! <h2 key="number" /* nuh-uh */>{ i }</h2>
//! <br />
//! }
//! </div>
//! }
//! # }
//! ```
//! ## `match` nodes
//! The syntax is the same as of Rust's `match` expressions; the body of a match arm must have
//! exactly 1 node.
//! ```rust
//! use yew::{Properties, function_component, html::Html};
//! use yew_html_ext::html;
//! use std::cmp::Ordering;
//!
//! #[derive(PartialEq, Properties)]
//! struct ComparisonProps {
//! int1: usize,
//! int2: usize,
//! }
//!
//! #[function_component]
//! fn Comparison(props: &ComparisonProps) -> Html {
//! html! {
//! match props.int1.cmp(&props.int2) {
//! Ordering::Less => { '<' },
//! Ordering::Equal => { '=' },
//! Ordering::Greater => { '>' },
//! }
//! }
//! }
//! ```
mod html_tree;
mod props;
mod stringify;
use html_tree::{HtmlRoot, HtmlRootVNode};
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::buffer::Cursor;
use syn::parse_macro_input;
trait Peek<'a, T> {
fn peek(cursor: Cursor<'a>) -> Option<(T, Cursor<'a>)>;
}
trait PeekValue<T> {
fn peek(cursor: Cursor) -> Option<T>;
}
fn non_capitalized_ascii(string: &str) -> bool {
if !string.is_ascii() {
false
} else if let Some(c) = string.bytes().next() {
c.is_ascii_lowercase()
} else {
false
}
}
/// Combine multiple `syn` errors into a single one.
/// Returns `Result::Ok` if the given iterator is empty
fn join_errors(mut it: impl Iterator<Item = syn::Error>) -> syn::Result<()> {
it.next().map_or(Ok(()), |mut err| {
for other in it {
err.combine(other);
}
Err(err)
})
}
fn is_ide_completion() -> bool {
match std::env::var_os("RUST_IDE_PROC_MACRO_COMPLETION_DUMMY_IDENTIFIER") {
None => false,
Some(dummy_identifier) => !dummy_identifier.is_empty(),
}
}
#[proc_macro_error::proc_macro_error]
#[proc_macro]
pub fn html_nested(input: TokenStream) -> TokenStream {
let root = parse_macro_input!(input as HtmlRoot);
TokenStream::from(root.into_token_stream())
}
#[proc_macro_error::proc_macro_error]
#[proc_macro]
pub fn html(input: TokenStream) -> TokenStream {
let root = parse_macro_input!(input as HtmlRootVNode);
TokenStream::from(root.into_token_stream())
}