rstml/
config.rs

1use std::{collections::HashSet, fmt::Debug, marker::PhantomData, rc::Rc};
2
3use proc_macro2::TokenStream;
4use syn::{parse::ParseStream, Result};
5
6#[cfg(feature = "rawtext-stable-hack")]
7use crate::rawtext_stable_hack::MacroPattern;
8use crate::{
9    atoms::{CloseTag, OpenTag},
10    node::{CustomNode, NodeType},
11    Infallible,
12};
13
14pub type TransformBlockFn = dyn Fn(ParseStream) -> Result<Option<TokenStream>>;
15pub type ElementWildcardFn = dyn Fn(&OpenTag, &CloseTag) -> bool;
16
17/// Configures the `Parser` behavior
18pub struct ParserConfig<C = Infallible> {
19    pub(crate) flat_tree: bool,
20    pub(crate) number_of_top_level_nodes: Option<usize>,
21    pub(crate) type_of_top_level_nodes: Option<NodeType>,
22    pub(crate) transform_block: Option<Rc<TransformBlockFn>>,
23    pub(crate) recover_block: bool,
24    pub(crate) always_self_closed_elements: HashSet<&'static str>,
25    pub(crate) raw_text_elements: HashSet<&'static str>,
26    pub(crate) element_close_wildcard: Option<Rc<ElementWildcardFn>>,
27    #[cfg(feature = "rawtext-stable-hack")]
28    pub(crate) macro_pattern: MacroPattern,
29    custom_node: PhantomData<C>,
30}
31
32impl<C> Clone for ParserConfig<C> {
33    fn clone(&self) -> Self {
34        Self {
35            flat_tree: self.flat_tree.clone(),
36            number_of_top_level_nodes: self.number_of_top_level_nodes,
37            type_of_top_level_nodes: self.type_of_top_level_nodes.clone(),
38            transform_block: self.transform_block.clone(),
39            recover_block: self.recover_block.clone(),
40            always_self_closed_elements: self.always_self_closed_elements.clone(),
41            raw_text_elements: self.raw_text_elements.clone(),
42            element_close_wildcard: self.element_close_wildcard.clone(),
43            #[cfg(feature = "rawtext-stable-hack")]
44            macro_pattern: self.macro_pattern.clone(),
45            custom_node: self.custom_node.clone(),
46        }
47    }
48}
49
50impl Default for ParserConfig {
51    fn default() -> Self {
52        Self {
53            flat_tree: Default::default(),
54            number_of_top_level_nodes: Default::default(),
55            type_of_top_level_nodes: Default::default(),
56            transform_block: Default::default(),
57            recover_block: Default::default(),
58            always_self_closed_elements: Default::default(),
59            raw_text_elements: Default::default(),
60            element_close_wildcard: Default::default(),
61            #[cfg(feature = "rawtext-stable-hack")]
62            macro_pattern: Default::default(),
63            custom_node: Default::default(),
64        }
65    }
66}
67
68impl<C> Debug for ParserConfig<C> {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        let mut s = f.debug_struct("ParserConfig");
71
72        s.field("flat_tree", &self.flat_tree)
73            .field("number_of_top_level_nodes", &self.number_of_top_level_nodes)
74            .field("type_of_top_level_nodes", &self.type_of_top_level_nodes)
75            .field("recover_block", &self.recover_block)
76            .field(
77                "always_self_closed_elements",
78                &self.always_self_closed_elements,
79            )
80            .field("raw_text_elements", &self.raw_text_elements)
81            .field(
82                "element_close_wildcard",
83                &self.element_close_wildcard.is_some(),
84            );
85        #[cfg(feature = "rawtext-stable-hack")]
86        s.field("macro_pattern", &self.macro_pattern);
87        s.finish()
88    }
89}
90
91impl ParserConfig {
92    /// Create new `ParserConfig` with default config
93    pub fn new() -> ParserConfig {
94        ParserConfig::default()
95    }
96}
97
98impl<C> ParserConfig<C> {
99    /// Return flat tree instead of nested tree
100    pub fn flat_tree(mut self) -> Self {
101        self.flat_tree = true;
102        self
103    }
104
105    /// Exact number of required top level nodes
106    pub fn number_of_top_level_nodes(mut self, number: usize) -> Self {
107        self.number_of_top_level_nodes = Some(number);
108        self
109    }
110
111    /// Enforce the `NodeType` of top level nodes
112    pub fn type_of_top_level_nodes(mut self, node_type: NodeType) -> Self {
113        self.type_of_top_level_nodes = Some(node_type);
114        self
115    }
116
117    /// Try to parse invalid `syn::Block`.
118    /// If set tot true, `NodeBlock` can return `Invalid` variant.
119    ///
120    /// If `NodeBlock` is failed to parse as `syn::Block`
121    /// it still usefull to emit it as expression.
122    /// It will enhance IDE compatibility, and provide completion in cases of
123    /// invalid blocks, for example `{x.}` is invalid expression, because
124    /// after dot token `}` is unexpected. But for ide it is a marker that
125    /// quick completion should be provided.
126    pub fn recover_block(mut self, recover_block: bool) -> Self {
127        self.recover_block = recover_block;
128        self
129    }
130
131    /// Set array of nodes that is known to be self closed,
132    /// it also known as void element.
133    /// Void elements has no child and must not have closing tag.
134    /// Parser will not search for it closing tag,
135    /// even if no slash at end of it open part was found.
136    ///
137    /// Because we work in proc-macro context, we expect it as 'static refs.
138    ///
139    /// Examples:
140    /// <br> <link> <img>
141    pub fn always_self_closed_elements(mut self, elements: HashSet<&'static str>) -> Self {
142        self.always_self_closed_elements = elements;
143        self
144    }
145
146    /// Set array of nodes that is known to be parsed in two-phases,
147    /// Parser will skip parsing of children nodes.
148    /// and provide one child with RawText instead.
149    ///
150    /// This is usefull when parsing `<script>` or `<style>` tags elements.
151    ///
152    /// If you need fragment to be used in this context, empty string("") should
153    /// be inserted.
154    ///
155    /// Raw texts has few limitations, check out `RawText` documentation.
156    pub fn raw_text_elements(mut self, elements: HashSet<&'static str>) -> Self {
157        self.raw_text_elements = elements;
158        self
159    }
160
161    /// Transforms the `value` of all `NodeType::Block`s with the given closure
162    /// callback. The provided `ParseStream` is the content of the block.
163    ///
164    /// When `Some(TokenStream)` is returned, the `TokenStream` is parsed as
165    /// Rust block content. The `ParseStream` must be completely consumed in
166    /// this case, meaning no tokens can be left in the stream.
167    ///
168    /// If `None` is returned, parsing happens with the original `ParseStream`,
169    /// since the tokens that are passend into the transform callback are a
170    /// fork, which gets only advanced if `Some` is returned.
171    ///
172    /// An example usage might be a custom syntax inside blocks which isn't
173    /// valid Rust. The given example simply translates the `%` character into
174    /// the string `percent`
175    ///
176    /// ```rust
177    /// use quote::quote;
178    /// use syn::Token;
179    /// use rstml::{parse2_with_config, ParserConfig};
180    ///
181    /// let tokens = quote! {
182    ///     <div>{%}</div>
183    /// };
184    ///
185    /// let config = ParserConfig::new().transform_block(|input| {
186    ///     input.parse::<Token![%]>()?;
187    ///     Ok(Some(quote! { "percent" }))
188    /// });
189    ///
190    /// parse2_with_config(tokens, config).unwrap();
191    /// ```
192    pub fn transform_block<F>(mut self, callback: F) -> Self
193    where
194        F: Fn(ParseStream) -> Result<Option<TokenStream>> + 'static,
195    {
196        self.transform_block = Some(Rc::new(callback));
197        self
198    }
199
200    /// Allows unmatched tag pairs where close tag matches a specified wildcard.
201    ///
202    /// For example:
203    ///
204    /// ```rust
205    /// use quote::quote;
206    /// use rstml::{
207    ///     node::{Node, NodeElement},
208    ///     Parser, ParserConfig,
209    /// };
210    /// use syn::{Expr, ExprRange, RangeLimits, Stmt};
211    ///
212    /// let config = ParserConfig::new()
213    ///     .element_close_wildcard(|_open_tag, close_tag| close_tag.name.is_wildcard());
214    ///
215    /// let tokens = quote! {
216    ///     <{"OpenTag"}>{"Content"}</_>
217    /// };
218    ///
219    /// Parser::new(config).parse_simple(tokens).unwrap();
220    /// ```
221    pub fn element_close_wildcard<F>(mut self, predicate: F) -> Self
222    where
223        F: Fn(&OpenTag, &CloseTag) -> bool + 'static,
224    {
225        self.element_close_wildcard = Some(Rc::new(predicate));
226        self
227    }
228    /// Allows unmatched tag pairs where close tag matches a specified wildcard.
229    ///
230    /// Uses default wildcard ident (`_`), and optionally check whenewer
231    /// open_tag name is block.
232    pub fn element_close_use_default_wildcard_ident(self, open_tag_should_be_block: bool) -> Self {
233        self.element_close_wildcard(move |open_tag, close_tag| {
234            close_tag.name.is_wildcard() && (!open_tag_should_be_block || open_tag.name.is_block())
235        })
236    }
237
238    ///
239    /// Provide pattern of macro call.
240    ///
241    /// It is used with feature = "rawtext-stable-hack" to retrive
242    /// space information in `RawText`.
243    /// Checkout https://github.com/rs-tml/rstml/issues/5 for details.
244    ///
245    ///
246    /// This method receive macro pattern as `TokenStream`, uses tokens `%%`
247    /// as marker for input `rstml::parse`.
248    /// Support only one marker in pattern.
249    /// Currently, don't check other tokens for equality with pattern.
250    ///
251    /// Example:
252    ///
253    /// Imagine one have macro, that used like this:
254    /// ```no_compile
255    /// html! {some_context, provided, [ can use groups, etc], {<div>}, [other context]};
256    /// ```
257    ///
258    /// Then one can set `macro_call_pattern` with following arguments:
259    /// ```no_compile
260    /// config.macro_call_pattern(quote!(html! // macro name currently is not checked
261    ///     {ident, ident, // can use real idents, or any other
262    ///         [/* can ignore context of auxilary groups */],
263    ///         {%%}, // important part
264    ///         []
265    ///     }))
266    /// ```
267    ///
268    /// And rstml will do the rest for you.
269    ///
270    /// Panics if no `%%` token was found.
271    ///
272    /// If macro_call_patern is set rstml will parse input two times in order to
273    /// recover spaces in `RawText`. Rstml will panic if macro source text
274    /// is not possible to recover.
275    #[cfg(feature = "rawtext-stable-hack")]
276    pub fn macro_call_pattern(mut self, pattern: TokenStream) -> Self {
277        self.macro_pattern =
278            MacroPattern::from_token_stream(pattern).expect("No %% token found in pattern.");
279        self
280    }
281
282    /// Enables parsing for [`Node::Custom`] using a type implementing
283    /// [`CustomNode`].
284    pub fn custom_node<CN: CustomNode>(self) -> ParserConfig<CN> {
285        ParserConfig {
286            flat_tree: self.flat_tree,
287            number_of_top_level_nodes: self.number_of_top_level_nodes,
288            type_of_top_level_nodes: self.type_of_top_level_nodes,
289            transform_block: self.transform_block,
290            recover_block: self.recover_block,
291            always_self_closed_elements: self.always_self_closed_elements,
292            raw_text_elements: self.raw_text_elements,
293            element_close_wildcard: self.element_close_wildcard,
294            #[cfg(feature = "rawtext-stable-hack")]
295            macro_pattern: self.macro_pattern,
296            custom_node: Default::default(),
297        }
298    }
299}