rstml/lib.rs
1//! Rust templating for XML-based formats (HTML, SVG, MathML) implemented on top
2//! of [`proc-macro::TokenStream`]s. Similar to JSX but for Rust (commonly named
3//! RSX). The parsed result is a nested [`Node`] structure, similar to the
4//! browser DOM, where node name and value are syn expressions to support
5//! building proc macros.
6//!
7//! ```rust
8//! # fn main() -> eyre::Result<()> {
9//! use std::convert::TryFrom;
10//!
11//! use eyre::bail;
12//! use quote::quote;
13//! use rstml::{
14//! node::{Node, NodeAttribute, NodeElement, NodeText},
15//! parse2,
16//! };
17//!
18//! // Create HTML `TokenStream`.
19//! let tokens = quote! { <hello world>"hi"</hello> };
20//!
21//! // Parse the tokens into a tree of `Node`s.
22//! let nodes = parse2(tokens)?;
23//!
24//! // Extract some specific nodes from the tree.
25//! let Node::Element(element) = &nodes[0] else {
26//! bail!("element")
27//! };
28//! let NodeAttribute::Attribute(attribute) = &element.attributes()[0] else {
29//! bail!("attribute")
30//! };
31//! let Node::Text(text) = &element.children[0] else {
32//! bail!("text")
33//! };
34//!
35//! // Work with the nodes.
36//! assert_eq!(element.name().to_string(), "hello");
37//! assert_eq!(attribute.key.to_string(), "world");
38//! assert_eq!(text.value_string(), "hi");
39//! # Ok(())
40//! # }
41//! ```
42//!
43//! You might want to check out the [html-to-string-macro example] as well.
44//!
45//! ## Features
46//!
47//! - **Not opinionated**
48//!
49//! Every tag or attribute name is valid
50//!
51//! ```rust
52//! # use quote::quote;
53//! # use rstml::parse2;
54//! # parse2(quote! {
55//! <hello world />
56//! # }).unwrap();
57//! ```
58//!
59//! - **Text nodes**
60//!
61//!
62//! ```rust
63//! # use quote::quote;
64//! # use rstml::parse2;
65//! # parse2(quote! {
66//! <div>"String literal"</div>
67//! # }).unwrap();
68//! ```
69//!
70//!
71//! - **Unquoted text nodes**
72//!
73//! Unquoted text is supported with few limitations:
74//! - Only valid Rust TokenStream can be unquoted text (no single quote text is
75//! supported, no unclosed braces, etc.)
76//! - Unquoted text not always can save spaces. It uses [`Span::source_text`]
77//! and [`Span::join`] to retrive info about spaces, and it is not always
78//! available.
79//! - Quoted text near unquoted treated as diferent Node, end library user
80//! should decide whenever to preserve quotation.
81//!
82//! ```rust
83//!
84//! # use quote::quote;
85//! # use rstml::parse2;
86//! # parse2(quote! {
87//! <div> Some string that is valid rust token stream </div>
88//! # }).unwrap();
89//! ```
90//!
91//! - **Node names separated by dash, colon or double colon**
92//!
93//! ```rust
94//! # use quote::quote;
95//! # use rstml::parse2;
96//! # parse2(quote! {
97//! <tag-name some:attribute-key="value" />
98//! <tag::name attribute::key="value" />
99//! # }).unwrap();
100//! ```
101//!
102//! - **Node names with reserved keywords**
103//!
104//! ```rust
105//! # use quote::quote;
106//! # use rstml::parse2;
107//! # parse2(quote! {
108//! <input type="submit" />
109//! # }).unwrap();
110//! ```
111//!
112//! - **Doctypes, Comments and Fragments**
113//!
114//! ```rust
115//! # use quote::quote;
116//! # use rstml::parse2;
117//! # parse2(quote! {
118//! <!DOCTYPE html>
119//! <!-- "comment" -->
120//! <></>
121//! # }).unwrap();
122//! ```
123//!
124//! - **Braced blocks are parsed as arbitrary Rust code**
125//!
126//! ```rust
127//! # use quote::quote;
128//! # use rstml::parse2;
129//! # parse2(quote! {
130//! <{ let block = "in node name position"; } />
131//! <div>{ let block = "in node position"; }</div>
132//! <div { let block = "in attribute position"; } />
133//! <div key={ let block = "in attribute value position"; } />
134//! # }).unwrap();
135//! ```
136//!
137//! - **Attribute values can be any valid syn expression without requiring
138//! braces**
139//!
140//! ```rust
141//! # use quote::quote;
142//! # use rstml::parse2;
143//! # parse2(quote! {
144//! <div key=some::value() />
145//! # }).unwrap();
146//! ```
147//!
148//! - **Helpful error reporting out of the box**
149//!
150//! ```no_build
151//! error: open tag has no corresponding close tag and is not self-closing
152//! --> examples/html-to-string-macro/tests/lib.rs:5:24
153//! |
154//! 5 | html_to_string! { <div> };
155//! | ^^^
156//! ```
157//!
158//! - **Possibility to get the span for a whole node**
159//!
160//! This can be used to improve error reporting, e.g.
161//!
162//! ```no_build
163//! error: Invalid element
164//! --> examples/src/main.rs:14:13
165//! |
166//! 14 | / <div>
167//! 15 | | "invalid node for some consumer specific reason"
168//! 16 | | </div>
169//! | |__________________^
170//! ```
171//!
172//! - **Recoverable parser**
173//!
174//! Can parse html with multiple mistakes.
175//! As result library user get array of errors that can be reported, and tree of
176//! nodes that was parsed.
177//!
178//! ```rust
179//! # use quote::quote;
180//! # use rstml::{Parser, ParserConfig};
181//! # Parser::new(ParserConfig::default()).parse_recoverable(quote! {
182//! <div hello={world.} /> <!-- dot after world is invalid syn expression -->
183//! <>
184//! <div>"1"</x> <!-- incorrect closed tag -->
185//! <div>"2"</div>
186//! <div>"3"</div>
187//! <div {"some-attribute-from-rust-block"}/>
188//! </>
189//! # });
190//! ```
191//!
192//! Using this feature one can write macro in IDE friendly way.
193//! This macro will work faster (because on invalid syntax it change output
194//! slightly, instead of removing it completely, so IDE can check diff quicly).
195//! And give completion (goto definition, and other semantic related feature)
196//! more often.
197//!
198//! - **Customization**
199//!
200//! A [`ParserConfig`] to customize parsing behavior is available, so if you
201//! have slightly different requirements for parsing and it's not yet
202//! customizable feel free to open an issue or pull request to extend the
203//! configuration.
204//!
205//! One highlight with regards to customization is the [`transform_block`]
206//! configuration, which takes a closure that receives raw block content as
207//! `ParseStream` and lets you optionally convert it to a `TokenStream`. That
208//! makes it possible to have custom syntax in blocks. More details in [#9].
209//!
210//! Additionally, [`CustomNode`] can be used to implement fully custom
211//! parsing.
212//!
213//! [`syn`]: /syn
214//! [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
215//! [`Node`]: enum.Node.html
216//! [`CustomNode`]: trait.CustomNode.html
217//! [`Span::join`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.join
218//! [`Span::source_text`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.source_text
219//! [`ParserConfig`]: struct.ParserConfig.html
220//! [mod style path]: https://docs.rs/syn/1.0.40/syn/struct.Path.html#method.parse_mod_style
221//! [unquoted text is planned]: https://github.com/stoically/syn-rsx/issues/2
222//! [`transform_block`]: struct.ParserConfig.html#method.transform_block
223//! [#9]: https://github.com/stoically/syn-rsx/issues/9
224//! [html-to-string-macro example]: https://github.com/stoically/syn-rsx/tree/main/examples/html-to-string-macro
225
226extern crate proc_macro;
227
228use syn::Result;
229
230mod config;
231mod error;
232pub mod node;
233mod parser;
234#[doc(hidden)] // Currently its api is not planned to be stable.
235#[cfg(feature = "rawtext-stable-hack-module")]
236pub mod rawtext_stable_hack;
237pub mod visitor;
238pub use config::ParserConfig;
239pub use error::Error;
240pub use node::{atoms, Infallible};
241use node::{CustomNode, Node};
242pub use parser::{recoverable, recoverable::ParsingResult, Parser};
243
244/// Parse the given [`proc-macro::TokenStream`] into a [`Node`] tree.
245///
246/// [`proc-macro::TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
247/// [`Node`]: struct.Node.html
248pub fn parse(tokens: proc_macro::TokenStream) -> Result<Vec<Node>> {
249 Parser::new(ParserConfig::default()).parse_simple(tokens)
250}
251
252/// Parse the given [`proc-macro::TokenStream`] into a [`Node`] tree with custom
253/// [`ParserConfig`].
254///
255/// [`proc-macro::TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
256/// [`Node`]: struct.Node.html
257/// [`ParserConfig`]: struct.ParserConfig.html
258#[deprecated(since = "0.10.2", note = "use rstml::Parser::parse_simple instead")]
259pub fn parse_with_config<C: CustomNode + std::fmt::Debug>(
260 tokens: proc_macro::TokenStream,
261 config: ParserConfig<C>,
262) -> Result<Vec<Node<C>>> {
263 Parser::new(config).parse_simple(tokens)
264}
265/// Parse the given [`proc-macro2::TokenStream`] into a [`Node`] tree.
266///
267/// [`proc-macro2::TokenStream`]: https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html
268/// [`Node`]: struct.Node.html
269pub fn parse2(tokens: proc_macro2::TokenStream) -> Result<Vec<Node>> {
270 Parser::new(ParserConfig::default()).parse_simple(tokens)
271}
272
273/// Parse the given [`proc-macro2::TokenStream`] into a [`Node`] tree with
274/// custom [`ParserConfig`].
275///
276/// [`proc-macro2::TokenStream`]: https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html
277/// [`Node`]: struct.Node.html
278/// [`ParserConfig`]: struct.ParserConfig.html
279#[deprecated(since = "0.10.2", note = "use rstml::Parser::parse_simple instead")]
280pub fn parse2_with_config<C: CustomNode + std::fmt::Debug>(
281 tokens: proc_macro2::TokenStream,
282 config: ParserConfig<C>,
283) -> Result<Vec<Node<C>>> {
284 Parser::new(config).parse_simple(tokens)
285}