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}