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}