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}