nmd_core/compilation/compilation_rule/
replacement_rule.rs

1pub mod replacement_rule_part;
2
3
4use std::fmt::Debug;
5use std::sync::Arc;
6use getset::{Getters, Setters};
7use log;
8use regex::Regex;
9use replacement_rule_part::ReplacementRuleReplacerPart;
10use crate::compilable_text::CompilableText;
11use crate::compilation::compilation_configuration::compilation_configuration_overlay::CompilationConfigurationOverLay;
12use crate::compilation::compilation_configuration::CompilationConfiguration;
13use crate::output_format::OutputFormat;
14use super::CompilationRule;
15use crate::compilation::compilation_error::CompilationError;
16
17
18pub type ReplacementRuleParts = Vec<Arc<dyn ReplacementRuleReplacerPart>>;
19
20
21/// Rule to replace a NMD text based on a specific pattern matching rule
22#[derive(Debug, Clone, Getters, Setters)]
23pub struct ReplacementRule {
24
25    #[getset(set)]
26    search_pattern: String,
27
28    #[getset(set)]
29    search_pattern_regex: Regex,
30
31    #[getset(get = "pub", set = "pub")]
32    replacer_parts: ReplacementRuleParts,
33}
34
35impl ReplacementRule {
36    
37    /// Returns a new instance having a search pattern and a replication pattern
38    pub fn new(searching_pattern: String, replacers: ReplacementRuleParts) -> Self {
39
40        log::debug!("created new compilation rule with search_pattern: '{}'", searching_pattern);
41
42        Self {
43            search_pattern_regex: Regex::new(&searching_pattern).unwrap(),
44            search_pattern: searching_pattern,
45            replacer_parts: replacers,
46        }
47    }
48
49}
50
51impl CompilationRule for ReplacementRule {
52
53    /// Compile the content using internal search and replacement pattern
54    fn standard_compile(&self, compilable: &CompilableText, format: &OutputFormat, compilation_configuration: &CompilationConfiguration, compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<CompilableText, CompilationError> {
55
56        log::debug!("compile:\n{:#?}\nusing '{}'->'{:?}'", compilable, self.search_pattern(), self.replacer_parts);
57
58        let mut compiled_parts = Vec::new();
59
60        let compilable_content = compilable.compilable_content();
61
62        let captures_matches = self.search_pattern_regex.captures_iter(&compilable_content);
63
64        for captures in captures_matches {
65
66            for replacer_part in &self.replacer_parts {
67
68                compiled_parts.append(&mut replacer_part.compile(&captures, compilable, format, compilation_configuration, compilation_configuration_overlay.clone())?.into())
69            }   
70        }
71
72        Ok(CompilableText::new(compiled_parts))
73    }
74    
75    fn search_pattern(&self) -> &String {
76        &self.search_pattern
77    }
78    
79    fn search_pattern_regex(&self) -> &Regex {
80        &self.search_pattern_regex
81    }
82}
83
84
85
86#[cfg(test)]
87mod test {
88
89    use std::sync::Arc;
90
91    use crate::{codex::modifier::{standard_text_modifier::StandardTextModifier, ModifiersBucket}, compilable_text::{compilable_text_part::{CompilableTextPart, CompilableTextPartType}, CompilableText}, compilation::{compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_rule::{constants::ESCAPE_HTML, replacement_rule::{replacement_rule_part::{closure_replacement_rule_part::ClosureReplacementRuleReplacerPart, fixed_replacement_rule_part::FixedReplacementRuleReplacerPart, single_capture_group_replacement_rule_part::SingleCaptureGroupReplacementRuleReplacerPart, ReplacementRuleReplacerPart}, ReplacementRule}, CompilationRule}}, output_format::OutputFormat};
92
93
94    #[test]
95    fn bold_compiling() {
96
97        // valid pattern with a valid text modifier
98        let replacement_rule = ReplacementRule::new(StandardTextModifier::BoldStarVersion.modifier_pattern(), vec![
99            Arc::new(FixedReplacementRuleReplacerPart::new(String::from("<strong>"))) as Arc<dyn ReplacementRuleReplacerPart>,
100            Arc::new(ClosureReplacementRuleReplacerPart::new(Arc::new(|captures, compilable, _, _, _| {
101                
102                let capture1 = captures.get(1).unwrap();
103                
104                let slice = compilable.parts_slice(capture1.start(), capture1.end())?;
105
106                Ok(CompilableText::new(slice))
107            }))),
108            Arc::new(FixedReplacementRuleReplacerPart::new(String::from("</strong>"))),
109        ]);
110
111        let text_to_compile = r"A piece of **bold text** and **bold text2**";
112        let compilation_configuration = CompilationConfiguration::default();
113
114        let compilable = CompilableText::new(
115            vec![
116                CompilableTextPart::new(
117                    text_to_compile.to_string(),
118                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
119                )
120        ]);
121        
122        let outcome = replacement_rule.compile(&compilable, &OutputFormat::Html, &compilation_configuration, CompilationConfigurationOverLay::default()).unwrap();
123
124        assert_eq!(outcome.content(), r"<strong>bold text</strong><strong>bold text2</strong>");
125
126        // without text modifier
127        let text_to_compile = r"A piece of text without bold text";
128
129        let compilable = CompilableText::new(
130            vec![
131                CompilableTextPart::new(
132                    text_to_compile.to_string(),
133                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
134                )
135        ]);
136
137        let outcome = replacement_rule.compile(&compilable, &OutputFormat::Html, &compilation_configuration, CompilationConfigurationOverLay::default()).unwrap();
138
139        assert_eq!(outcome.content(), r"");
140
141
142    }
143
144    #[test]
145    fn input_with_fixed_parts() {
146        let replacement_rule = ReplacementRule::new(StandardTextModifier::ItalicStarVersion.modifier_pattern(), vec![
147            Arc::new(FixedReplacementRuleReplacerPart::new(String::from("<em>"))) as Arc<dyn ReplacementRuleReplacerPart>,
148            Arc::new(SingleCaptureGroupReplacementRuleReplacerPart::new(1, ESCAPE_HTML.clone(), ModifiersBucket::None)),
149            Arc::new(FixedReplacementRuleReplacerPart::new(String::from("</em>"))),
150        ]);
151
152        let compilation_configuration = CompilationConfiguration::default();
153
154        // ==== case 1 ====
155        let compilable = CompilableText::new(
156            vec![
157                CompilableTextPart::new(
158                    String::from("*start "),
159                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
160                ),
161                CompilableTextPart::new_fixed(String::from("<strong>")),
162                CompilableTextPart::new_compilable(String::from("fixed"), ModifiersBucket::None),
163                CompilableTextPart::new_fixed(String::from("</strong>")),
164                CompilableTextPart::new(
165                    String::from(" end*"),
166                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
167                ),
168        ]);
169        
170        let outcome = replacement_rule.compile(&compilable, &OutputFormat::Html, &compilation_configuration, CompilationConfigurationOverLay::default()).unwrap();
171
172        assert_eq!(outcome.content(), r"<em>start <strong>fixed</strong> end</em>");
173
174
175        // ==== case 2 ====
176        let compilable = CompilableText::new(
177            vec![
178                CompilableTextPart::new(
179                    String::from("*start "),
180                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
181                ),
182                CompilableTextPart::new_fixed(String::from("<strong>")),
183                CompilableTextPart::new_compilable(String::from("fixed"), ModifiersBucket::None),
184                CompilableTextPart::new_fixed(String::from("</strong>")),
185                CompilableTextPart::new(
186                    String::from("*"),
187                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
188                ),
189        ]);
190        
191        let outcome = replacement_rule.compile(&compilable, &OutputFormat::Html, &compilation_configuration, CompilationConfigurationOverLay::default()).unwrap();
192
193        assert_eq!(outcome.content(), r"<em>start <strong>fixed</strong></em>");
194
195
196        // ==== case 3 ====
197        let compilable = CompilableText::new(
198            vec![
199                CompilableTextPart::new(
200                    String::from("*"),
201                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
202                ),
203                CompilableTextPart::new_fixed(String::from("<strong>")),
204                CompilableTextPart::new_compilable(String::from("fixed"), ModifiersBucket::None),
205                CompilableTextPart::new_fixed(String::from("</strong>")),
206                CompilableTextPart::new(
207                    String::from(" end*"),
208                    CompilableTextPartType::Compilable { incompatible_modifiers: ModifiersBucket::None }
209                ),
210        ]);
211        
212        let outcome = replacement_rule.compile(&compilable, &OutputFormat::Html, &compilation_configuration, CompilationConfigurationOverLay::default()).unwrap();
213
214        assert_eq!(outcome.content(), r"<em><strong>fixed</strong> end</em>");
215
216    }
217
218    // #[test]
219    // fn heading_parsing() {
220
221    //     let codex = Codex::of_html(CodexConfiguration::default());
222
223    //     let compilation_configuration = CompilationConfiguration::default();
224
225    //     let parsing_rule = ReplacementRule::new(StandardHeading::HeadingGeneralExtendedVersion(6).modifier_pattern().clone(), vec![
226    //         ReplacementRuleReplacerPart::new_fixed(String::from("<h6>")),
227    //         ReplacementRuleReplacerPart::new_mutable(String::from("$1")),
228    //         ReplacementRuleReplacerPart::new_fixed(String::from("</h6>")),
229    //     ]);
230
231    //     let text_to_parse = r"###### title 6";
232
233    //     let compilable: Box<dyn Compilable> = Box::new(GenericCompilable::from(text_to_parse.to_string()));
234
235    //     let parsed_text = parsing_rule.compile(&compilable, &OutputFormat::Html, &codex, &compilation_configuration, Arc::new(RwLock::new(CompilationConfigurationOverLay::default()))).unwrap();
236
237    //     assert_eq!(parsed_text.content(), r"<h6>title 6</h6>");
238    // }
239
240    // #[test]
241    // fn code_block() {
242
243    //     let codex = Codex::of_html(CodexConfiguration::default());
244
245    //     let compilation_configuration = CompilationConfiguration::default();
246
247    //     let parsing_rule = ReplacementRule::new(StandardParagraphModifier::CodeBlock.modifier_pattern_with_paragraph_separator().clone(), vec![
248    //         ReplacementRuleReplacerPart::new_fixed(String::from(r#"<pre><code class="language-$1 codeblock">"#)),
249    //         ReplacementRuleReplacerPart::new_mutable(String::from("$2")),
250    //         ReplacementRuleReplacerPart::new_fixed(String::from("</code></pre>")),
251    //     ]);
252
253    //     let text_to_parse = concat!(
254    //         "\n\n",
255    //         "```python\n\n",
256    //         r#"print("hello world")"#,
257    //         "\n\n```\n\n"
258    //     );
259        
260    //     let compilable: Box<dyn Compilable> = Box::new(GenericCompilable::from(text_to_parse.to_string()));
261
262    //     let parsed_text = parsing_rule.compile(&compilable, &OutputFormat::Html, &codex, &compilation_configuration, Arc::new(RwLock::new(CompilationConfigurationOverLay::default()))).unwrap();
263
264    //     assert_eq!(parsed_text.content(), "<pre><code class=\"language-python codeblock\">print(\"hello world\")</code></pre>");
265    // }
266
267    
268}