awsl_syn/lib.rs
1//! [`syn`]-powered parser for JSX-like [`TokenStream`]s, aka RSX. The parsed result is a
2//! nested [`Node`] structure, similar to the browser DOM, where node name and
3//! value are syn expressions to support building proc macros.
4//!
5//! ```rust
6//! use quote::quote;
7//! use syn_rsx::parse2;
8//!
9//! let tokens = quote! { <hello world>"hi"</hello> };
10//!
11//! let nodes = parse2(tokens).unwrap();
12//! assert_eq!(nodes[0].name_as_string().unwrap(), "hello");
13//! assert_eq!(nodes[0].attributes[0].name_as_string().unwrap(), "world");
14//! assert_eq!(nodes[0].children[0].value_as_string().unwrap(), "hi");
15//! ```
16//!
17//! ## Features
18//!
19//!
20//! - **Not opinionated**
21//!
22//! Every tag or attribute name is valid
23//!
24//! ```rust
25//! # use quote::quote;
26//! # use syn_rsx::parse2;
27//! # parse2(quote! {
28//! <hello world />
29//! # }).unwrap();
30//! ```
31//!
32//! - **Text nodes**
33//!
34//! ```rust
35//! # use quote::quote;
36//! # use syn_rsx::parse2;
37//! # parse2(quote! {
38//! <div>"String literal"</div>
39//! # }).unwrap();
40//! ```
41//!
42//! Support for [unquoted text is planned]
43//!
44//! - **Node names separated by dash, colon or double colon**
45//!
46//! ```rust
47//! # use quote::quote;
48//! # use syn_rsx::parse2;
49//! # parse2(quote! {
50//! <tag-name attribute-key="value" />
51//! <tag:name attribute:key="value" />
52//! <tag::name attribute::key="value" />
53//! # }).unwrap();
54//! ```
55//!
56//! - **Node names with reserved keywords**
57//!
58//! ```rust
59//! # use quote::quote;
60//! # use syn_rsx::parse2;
61//! # parse2(quote! {
62//! <input type="submit" />
63//! # }).unwrap();
64//! ```
65//!
66//! - **Attribute values can be any valid syn expression without requiring braces**
67//!
68//! ```rust
69//! # use quote::quote;
70//! # use syn_rsx::parse2;
71//! # parse2(quote! {
72//! <div key=some::value() />
73//! # }).unwrap();
74//! ```
75//!
76//! - **Doctypes, Comments and Fragments**
77//!
78//! ```rust
79//! # use quote::quote;
80//! # use syn_rsx::parse2;
81//! # parse2(quote! {
82//! <!DOCTYPE html>
83//! <!-- "comment" -->
84//! <></>
85//! # }).unwrap();
86//! ```
87//!
88//! - **Braced blocks are parsed as arbitrary Rust code**
89//!
90//! ```rust
91//! # use quote::quote;
92//! # use syn_rsx::parse2;
93//! # parse2(quote! {
94//! <{ let block = "in node name position"; } />
95//! <div>{ let block = "in node position"; }</div>
96//! <div { let block = "in attribute position"; } />
97//! <div key={ let block = "in attribute value position"; } />
98//! # }).unwrap();
99//! ```
100//!
101//! - **Helpful error reporting out of the box**
102//!
103//! ```ignore
104//! error: open tag has no corresponding close tag and is not self-closing
105//! --> examples/html-to-string-macro/tests/lib.rs:5:24
106//! |
107//! 5 | html_to_string! { <div> };
108//! | ^^^
109//! ```
110//!
111//! - **Customization**
112//!
113//! A [`ParserConfig`] to customize parsing behavior is available, so if you have
114//! slightly different requirements for parsing and it's not yet customizable
115//! feel free to open an issue or pull request to extend the configuration.
116//!
117//! One highlight with regards to customization is the [`transform_block`]
118//! configuration, which takes a closure that receives raw block content as
119//! `ParseStream` and lets you optionally convert it to a `TokenStream`. That makes it
120//! possible to have custom syntax in blocks. More details in [#9]
121//!
122//!
123//! [`syn`]: /syn
124//! [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
125//! [`Node`]: struct.Node.html
126//! [`ParserConfig`]: struct.ParserConfig.html
127//! [mod style path]: https://docs.rs/syn/1.0.40/syn/struct.Path.html#method.parse_mod_style
128//! [unquoted text is planned]: https://github.com/stoically/syn-rsx/issues/2
129//! [`transform_block`]: struct.ParserConfig.html#method.transform_block
130//! [#9]: https://github.com/stoically/syn-rsx/issues/9
131
132extern crate proc_macro;
133
134use syn::{
135 parse::{ParseStream, Parser as _},
136 Result,
137};
138
139mod node;
140mod parser;
141
142pub mod punctuation {
143 //! Custom syn punctuations
144 use syn::custom_punctuation;
145
146 custom_punctuation!(Dash, -);
147}
148
149pub use node::{Node, NodeName, NodeType};
150pub use parser::{Parser, ParserConfig};
151
152/// Parse the given [`proc-macro::TokenStream`] into a [`Node`] tree
153///
154/// [`proc-macro::TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
155/// [`Node`]: struct.Node.html
156pub fn parse(tokens: proc_macro::TokenStream) -> Result<Vec<Node>> {
157 let parser = move |input: ParseStream| Parser::new(ParserConfig::default()).parse(input);
158
159 parser.parse(tokens)
160}
161
162/// Parse the given [`proc-macro::TokenStream`] into a [`Node`] tree with custom [`ParserConfig`]
163///
164/// [`proc-macro::TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html
165/// [`Node`]: struct.Node.html
166/// [`ParserConfig`]: struct.ParserConfig.html
167pub fn parse_with_config(
168 tokens: proc_macro::TokenStream,
169 config: ParserConfig,
170) -> Result<Vec<Node>> {
171 let parser = move |input: ParseStream| Parser::new(config).parse(input);
172
173 parser.parse(tokens)
174}
175
176/// Parse the given [`proc-macro2::TokenStream`] into a [`Node`] tree
177///
178/// [`proc-macro2::TokenStream`]: https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html
179/// [`Node`]: struct.Node.html
180pub fn parse2(tokens: proc_macro2::TokenStream) -> Result<Vec<Node>> {
181 let parser = move |input: ParseStream| Parser::new(ParserConfig::default()).parse(input);
182
183 parser.parse2(tokens)
184}
185
186/// Parse the given [`proc-macro2::TokenStream`] into a [`Node`] tree with custom [`ParserConfig`]
187///
188/// [`proc-macro2::TokenStream`]: https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html
189/// [`Node`]: struct.Node.html
190/// [`ParserConfig`]: struct.ParserConfig.html
191pub fn parse2_with_config(
192 tokens: proc_macro2::TokenStream,
193 config: ParserConfig,
194) -> Result<Vec<Node>> {
195 let parser = move |input: ParseStream| Parser::new(config).parse(input);
196
197 parser.parse2(tokens)
198}