md_designer/
mapping.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use pulldown_cmark::Tag;
5
6use crate::{
7    constant::AUTO_INCREMENT_KEY,
8    rule::Rule,
9    utils::{cmarktag_stringify, custom_prefix_to_key, get_custom_prefix_key},
10};
11
12#[derive(Debug, PartialEq)]
13pub struct Mapping {
14    blocks: Vec<Block>,
15}
16
17impl Mapping {
18    pub fn new(rule: &Rule) -> Result<Self> {
19        let mut blocks = vec![];
20        rule.doc.blocks.iter().for_each(|block| {
21            let mut mapping = HashMap::new();
22            let mut last_key = None;
23            block.columns.iter().enumerate().for_each(|(idx, column)| {
24                if column.auto_increment {
25                    mapping.insert(AUTO_INCREMENT_KEY.clone(), idx);
26                } else if let Some(prefix) = &column.custom_prefix {
27                    mapping.insert(get_custom_prefix_key(prefix), idx);
28                } else {
29                    mapping.insert(column.cmark_tag.clone(), idx);
30                }
31                if column.is_last {
32                    if let Some(prefix) = &column.custom_prefix {
33                        last_key = Some(get_custom_prefix_key(prefix));
34                    } else {
35                        last_key = Some(column.cmark_tag.clone());
36                    }
37                }
38            });
39            blocks.push(Block {
40                title: block.title.clone(),
41                mapping,
42                last_key,
43            });
44        });
45        Ok(Mapping { blocks })
46    }
47}
48
49impl Mapping {
50    pub fn get_idx(
51        &self,
52        block_idx: usize,
53        tag: Option<&Tag<'_>>,
54        text_with_custom_prefix: Option<&str>,
55    ) -> Option<&usize> {
56        if let Some(block) = self.blocks.get(block_idx) {
57            return block.get_idx(tag, text_with_custom_prefix);
58        }
59        None
60    }
61
62    pub fn get_auto_increment_idx(&self, block_idx: usize) -> Option<&usize> {
63        if let Some(block) = self.blocks.get(block_idx) {
64            return block.get_auto_increment_idx();
65        }
66        None
67    }
68
69    pub fn get_size(&self, block_idx: usize) -> Option<usize> {
70        if let Some(block) = self.blocks.get(block_idx) {
71            return block.get_size();
72        }
73        None
74    }
75
76    pub fn is_last_key(
77        &self,
78        block_idx: usize,
79        tag: Option<&Tag<'_>>,
80        text_with_custom_prefix: Option<&str>,
81    ) -> bool {
82        if let Some(block) = self.blocks.get(block_idx) {
83            return block.is_last_key(tag, text_with_custom_prefix);
84        }
85        false
86    }
87
88    pub fn get_title(&self, block_idx: usize) -> Option<String> {
89        if let Some(block) = self.blocks.get(block_idx) {
90            return Some(block.title.clone());
91        }
92        None
93    }
94}
95
96impl Default for Mapping {
97    fn default() -> Self {
98        Self { blocks: vec![] }
99    }
100}
101
102#[derive(Debug, PartialEq)]
103struct Block {
104    title: String,
105    mapping: HashMap<String, usize>,
106    last_key: Option<String>,
107}
108
109impl Block {
110    pub fn get_idx(
111        &self,
112        tag: Option<&Tag<'_>>,
113        text_with_custom_prefix: Option<&str>,
114    ) -> Option<&usize> {
115        if let Some(key) = custom_prefix_to_key(text_with_custom_prefix) {
116            return self.mapping.get(&key);
117        } else if let Some(t) = tag {
118            if let Some(tag_str) = cmarktag_stringify(t) {
119                return self.mapping.get(&tag_str);
120            }
121        }
122        None
123    }
124
125    pub fn get_auto_increment_idx(&self) -> Option<&usize> {
126        self.mapping.get(&AUTO_INCREMENT_KEY.clone())
127    }
128
129    pub fn get_size(&self) -> Option<usize> {
130        Some(self.mapping.len())
131    }
132
133    pub fn is_last_key(
134        &self,
135        tag: Option<&Tag<'_>>,
136        text_with_custom_prefix: Option<&str>,
137    ) -> bool {
138        let k = if let Some(key) = custom_prefix_to_key(text_with_custom_prefix) {
139            key
140        } else if let Some(t) = tag {
141            if let Some(tag_str) = cmarktag_stringify(t) {
142                tag_str
143            } else {
144                return false;
145            }
146        } else {
147            return false;
148        };
149        if let Some(last_key) = &self.last_key {
150            return &k == last_key;
151        }
152        false
153    }
154}
155
156impl Default for Block {
157    fn default() -> Self {
158        Self {
159            title: String::default(),
160            mapping: HashMap::new(),
161            last_key: None,
162        }
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use std::fs::read_to_string;
169
170    use super::*;
171
172    use crate::{
173        constant::AUTO_INCREMENT_KEY,
174        utils::{get_custom_prefix_as_normal_list, get_custom_prefix_key},
175    };
176
177    #[test]
178    fn test_auto_increment_idx_empty() {
179        let mapping = Mapping::default();
180        assert!(mapping.get_auto_increment_idx(0).is_none());
181    }
182
183    #[test]
184    fn test_get_size_empty() {
185        let mapping = Mapping::default();
186        assert!(mapping.get_size(0).is_none());
187    }
188
189    #[test]
190    fn test_get_title() {
191        let mut mapping = Mapping::default();
192        mapping.blocks.push(Block {
193            title: String::from("Block Title"),
194            ..Default::default()
195        });
196        assert_eq!(Some(String::from("Block Title")), mapping.get_title(0));
197        assert!(mapping.get_title(99).is_none());
198    }
199
200    #[test]
201    fn test_mapping() {
202        let rule =
203            Rule::marshal(&read_to_string("test_case/rule/default_rule.yml").unwrap()).unwrap();
204        let mapping = Mapping::new(&rule).unwrap();
205        let mut map = HashMap::new();
206        map.insert(AUTO_INCREMENT_KEY.clone(), 0);
207        map.insert("Heading2".to_string(), 1);
208        map.insert("Heading3".to_string(), 2);
209        map.insert("Heading4".to_string(), 3);
210        map.insert("Heading5".to_string(), 4);
211        map.insert("Heading6".to_string(), 5);
212        map.insert("Heading7".to_string(), 6);
213        map.insert("Heading8".to_string(), 7);
214        map.insert("List".to_string(), 8);
215        let expected = Mapping {
216            blocks: vec![Block {
217                title: String::from("Block Title"),
218                mapping: map,
219                last_key: Some(String::from("List")),
220            }],
221        };
222        assert_eq!(expected, mapping);
223        assert!(!mapping.is_last_key(0, Some(&Tag::Heading(8)), None));
224        assert!(mapping.is_last_key(0, Some(&Tag::List(None)), None));
225    }
226
227    #[test]
228    fn test_mapping_various_lists() {
229        let rule =
230            Rule::marshal(&read_to_string("test_case/rule/various_list.yml").unwrap()).unwrap();
231        let mapping = Mapping::new(&rule).unwrap();
232        let mut map = HashMap::new();
233        map.insert(AUTO_INCREMENT_KEY.clone(), 0);
234        map.insert("Heading2".to_string(), 1);
235        map.insert("Heading3".to_string(), 2);
236        map.insert("Heading4".to_string(), 3);
237        map.insert("Heading5".to_string(), 4);
238        map.insert("Heading6".to_string(), 5);
239        map.insert("Heading7".to_string(), 6);
240        map.insert("Heading8".to_string(), 7);
241        map.insert("List".to_string(), 8);
242        map.insert(get_custom_prefix_key("+"), 9);
243        map.insert(get_custom_prefix_key("$"), 10);
244        let mut expected = Mapping {
245            blocks: vec![Block {
246                title: String::from("Block Title 1"),
247                mapping: map,
248                last_key: Some(get_custom_prefix_key("$")),
249            }],
250        };
251        let mut map = HashMap::new();
252        map.insert(AUTO_INCREMENT_KEY.clone(), 0);
253        map.insert("Heading2".to_string(), 1);
254        map.insert(get_custom_prefix_key("$"), 2);
255        map.insert(get_custom_prefix_key("+"), 3);
256        expected.blocks.push(Block {
257            title: String::from("Block Title 2"),
258            mapping: map,
259            last_key: Some(get_custom_prefix_key("+")),
260        });
261        assert_eq!(expected, mapping);
262        assert!(!mapping.is_last_key(0, Some(&Tag::Heading(8)), None));
263        assert!(mapping.is_last_key(
264            0,
265            None,
266            Some(
267                &get_custom_prefix_as_normal_list("$")
268                    .strip_prefix("* ")
269                    .unwrap()
270            )
271        ));
272    }
273}