1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
use std::{collections::HashSet, fmt::Debug, marker::PhantomData, rc::Rc};

use proc_macro2::TokenStream;
use syn::{parse::ParseStream, Result};

#[cfg(feature = "rawtext-stable-hack")]
use crate::rawtext_stable_hack::MacroPattern;
use crate::{
    atoms::{CloseTag, OpenTag},
    node::{CustomNode, NodeType},
    Infallible,
};

pub type TransformBlockFn = dyn Fn(ParseStream) -> Result<Option<TokenStream>>;
pub type ElementWildcardFn = dyn Fn(&OpenTag, &CloseTag) -> bool;

/// Configures the `Parser` behavior
pub struct ParserConfig<C = Infallible> {
    pub(crate) flat_tree: bool,
    pub(crate) number_of_top_level_nodes: Option<usize>,
    pub(crate) type_of_top_level_nodes: Option<NodeType>,
    pub(crate) transform_block: Option<Rc<TransformBlockFn>>,
    pub(crate) recover_block: bool,
    pub(crate) always_self_closed_elements: HashSet<&'static str>,
    pub(crate) raw_text_elements: HashSet<&'static str>,
    pub(crate) element_close_wildcard: Option<Rc<ElementWildcardFn>>,
    #[cfg(feature = "rawtext-stable-hack")]
    pub(crate) macro_pattern: MacroPattern,
    custom_node: PhantomData<C>,
}

impl<C> Clone for ParserConfig<C> {
    fn clone(&self) -> Self {
        Self {
            flat_tree: self.flat_tree.clone(),
            number_of_top_level_nodes: self.number_of_top_level_nodes,
            type_of_top_level_nodes: self.type_of_top_level_nodes.clone(),
            transform_block: self.transform_block.clone(),
            recover_block: self.recover_block.clone(),
            always_self_closed_elements: self.always_self_closed_elements.clone(),
            raw_text_elements: self.raw_text_elements.clone(),
            element_close_wildcard: self.element_close_wildcard.clone(),
            #[cfg(feature = "rawtext-stable-hack")]
            macro_pattern: self.macro_pattern.clone(),
            custom_node: self.custom_node.clone(),
        }
    }
}

impl Default for ParserConfig {
    fn default() -> Self {
        Self {
            flat_tree: Default::default(),
            number_of_top_level_nodes: Default::default(),
            type_of_top_level_nodes: Default::default(),
            transform_block: Default::default(),
            recover_block: Default::default(),
            always_self_closed_elements: Default::default(),
            raw_text_elements: Default::default(),
            element_close_wildcard: Default::default(),
            #[cfg(feature = "rawtext-stable-hack")]
            macro_pattern: Default::default(),
            custom_node: Default::default(),
        }
    }
}

impl<C> Debug for ParserConfig<C> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut s = f.debug_struct("ParserConfig");

        s.field("flat_tree", &self.flat_tree)
            .field("number_of_top_level_nodes", &self.number_of_top_level_nodes)
            .field("type_of_top_level_nodes", &self.type_of_top_level_nodes)
            .field("recover_block", &self.recover_block)
            .field(
                "always_self_closed_elements",
                &self.always_self_closed_elements,
            )
            .field("raw_text_elements", &self.raw_text_elements)
            .field(
                "element_close_wildcard",
                &self.element_close_wildcard.is_some(),
            );
        #[cfg(feature = "rawtext-stable-hack")]
        s.field("macro_pattern", &self.macro_pattern);
        s.finish()
    }
}

impl ParserConfig {
    /// Create new `ParserConfig` with default config
    pub fn new() -> ParserConfig {
        ParserConfig::default()
    }
}

impl<C> ParserConfig<C> {
    /// Return flat tree instead of nested tree
    pub fn flat_tree(mut self) -> Self {
        self.flat_tree = true;
        self
    }

    /// Exact number of required top level nodes
    pub fn number_of_top_level_nodes(mut self, number: usize) -> Self {
        self.number_of_top_level_nodes = Some(number);
        self
    }

    /// Enforce the `NodeType` of top level nodes
    pub fn type_of_top_level_nodes(mut self, node_type: NodeType) -> Self {
        self.type_of_top_level_nodes = Some(node_type);
        self
    }

    /// Try to parse invalid `syn::Block`.
    /// If set tot true, `NodeBlock` can return `Invalid` variant.
    ///
    /// If `NodeBlock` is failed to parse as `syn::Block`
    /// it still usefull to emit it as expression.
    /// It will enhance IDE compatibility, and provide completion in cases of
    /// invalid blocks, for example `{x.}` is invalid expression, because
    /// after dot token `}` is unexpected. But for ide it is a marker that
    /// quick completion should be provided.
    pub fn recover_block(mut self, recover_block: bool) -> Self {
        self.recover_block = recover_block;
        self
    }

    /// Set array of nodes that is known to be self closed,
    /// it also known as void element.
    /// Void elements has no child and must not have closing tag.
    /// Parser will not search for it closing tag,
    /// even if no slash at end of it open part was found.
    ///
    /// Because we work in proc-macro context, we expect it as 'static refs.
    ///
    /// Examples:
    /// <br> <link> <img>
    pub fn always_self_closed_elements(mut self, elements: HashSet<&'static str>) -> Self {
        self.always_self_closed_elements = elements;
        self
    }

    /// Set array of nodes that is known to be parsed in two-phases,
    /// Parser will skip parsing of children nodes.
    /// and provide one child with RawText instead.
    ///
    /// This is usefull when parsing `<script>` or `<style>` tags elements.
    ///
    /// If you need fragment to be used in this context, empty string("") should
    /// be inserted.
    ///
    /// Raw texts has few limitations, check out `RawText` documentation.
    pub fn raw_text_elements(mut self, elements: HashSet<&'static str>) -> Self {
        self.raw_text_elements = elements;
        self
    }

    /// Transforms the `value` of all `NodeType::Block`s with the given closure
    /// callback. The provided `ParseStream` is the content of the block.
    ///
    /// When `Some(TokenStream)` is returned, the `TokenStream` is parsed as
    /// Rust block content. The `ParseStream` must be completely consumed in
    /// this case, meaning no tokens can be left in the stream.
    ///
    /// If `None` is returned, parsing happens with the original `ParseStream`,
    /// since the tokens that are passend into the transform callback are a
    /// fork, which gets only advanced if `Some` is returned.
    ///
    /// An example usage might be a custom syntax inside blocks which isn't
    /// valid Rust. The given example simply translates the `%` character into
    /// the string `percent`
    ///
    /// ```rust
    /// use quote::quote;
    /// use syn::Token;
    /// use rstml::{parse2_with_config, ParserConfig};
    ///
    /// let tokens = quote! {
    ///     <div>{%}</div>
    /// };
    ///
    /// let config = ParserConfig::new().transform_block(|input| {
    ///     input.parse::<Token![%]>()?;
    ///     Ok(Some(quote! { "percent" }))
    /// });
    ///
    /// parse2_with_config(tokens, config).unwrap();
    /// ```
    pub fn transform_block<F>(mut self, callback: F) -> Self
    where
        F: Fn(ParseStream) -> Result<Option<TokenStream>> + 'static,
    {
        self.transform_block = Some(Rc::new(callback));
        self
    }

    /// Allows unmatched tag pairs where close tag matches a specified wildcard.
    ///
    /// For example:
    ///
    /// ```rust
    /// use quote::quote;
    /// use rstml::{
    ///     node::{Node, NodeElement},
    ///     Parser, ParserConfig,
    /// };
    /// use syn::{Expr, ExprRange, RangeLimits, Stmt};
    ///
    /// let config = ParserConfig::new()
    ///     .element_close_wildcard(|_open_tag, close_tag| close_tag.name.is_wildcard());
    ///
    /// let tokens = quote! {
    ///     <{"OpenTag"}>{"Content"}</_>
    /// };
    ///
    /// Parser::new(config).parse_simple(tokens).unwrap();
    /// ```
    pub fn element_close_wildcard<F>(mut self, predicate: F) -> Self
    where
        F: Fn(&OpenTag, &CloseTag) -> bool + 'static,
    {
        self.element_close_wildcard = Some(Rc::new(predicate));
        self
    }
    /// Allows unmatched tag pairs where close tag matches a specified wildcard.
    ///
    /// Uses default wildcard ident (`_`), and optionally check whenewer
    /// open_tag name is block.
    pub fn element_close_use_default_wildcard_ident(self, open_tag_should_be_block: bool) -> Self {
        self.element_close_wildcard(move |open_tag, close_tag| {
            close_tag.name.is_wildcard() && (!open_tag_should_be_block || open_tag.name.is_block())
        })
    }

    ///
    /// Provide pattern of macro call.
    ///
    /// It is used with feature = "rawtext-stable-hack" to retrive
    /// space information in `RawText`.
    /// Checkout https://github.com/rs-tml/rstml/issues/5 for details.
    ///
    ///
    /// This method receive macro pattern as `TokenStream`, uses tokens `%%`
    /// as marker for input `rstml::parse`.
    /// Support only one marker in pattern.
    /// Currently, don't check other tokens for equality with pattern.
    ///
    /// Example:
    ///
    /// Imagine one have macro, that used like this:
    /// ```no_compile
    /// html! {some_context, provided, [ can use groups, etc], {<div>}, [other context]};
    /// ```
    ///
    /// Then one can set `macro_call_pattern` with following arguments:
    /// ```no_compile
    /// config.macro_call_pattern(quote!(html! // macro name currently is not checked
    ///     {ident, ident, // can use real idents, or any other
    ///         [/* can ignore context of auxilary groups */],
    ///         {%%}, // important part
    ///         []
    ///     }))
    /// ```
    ///
    /// And rstml will do the rest for you.
    ///
    /// Panics if no `%%` token was found.
    ///
    /// If macro_call_patern is set rstml will parse input two times in order to
    /// recover spaces in `RawText`. Rstml will panic if macro source text
    /// is not possible to recover.
    #[cfg(feature = "rawtext-stable-hack")]
    pub fn macro_call_pattern(mut self, pattern: TokenStream) -> Self {
        self.macro_pattern =
            MacroPattern::from_token_stream(pattern).expect("No %% token found in pattern.");
        self
    }

    /// Enables parsing for [`Node::Custom`] using a type implementing
    /// [`CustomNode`].
    pub fn custom_node<CN: CustomNode>(self) -> ParserConfig<CN> {
        ParserConfig {
            flat_tree: self.flat_tree,
            number_of_top_level_nodes: self.number_of_top_level_nodes,
            type_of_top_level_nodes: self.type_of_top_level_nodes,
            transform_block: self.transform_block,
            recover_block: self.recover_block,
            always_self_closed_elements: self.always_self_closed_elements,
            raw_text_elements: self.raw_text_elements,
            element_close_wildcard: self.element_close_wildcard,
            #[cfg(feature = "rawtext-stable-hack")]
            macro_pattern: self.macro_pattern,
            custom_node: Default::default(),
        }
    }
}