vimwiki_core/lang/elements/blocks/
definitions.rs1use crate::{
2 lang::elements::{
3 InlineBlockElement, InlineElement, InlineElementContainer,
4 IntoChildren, Located, Text,
5 },
6 StrictEq,
7};
8use derive_more::{
9 AsRef, Constructor, Deref, DerefMut, Display, Index, IndexMut, Into,
10 IntoIterator,
11};
12use serde::{Deserialize, Serialize};
13use std::{
14 collections::HashMap,
15 hash::{Hash, Hasher},
16 iter::FromIterator,
17};
18
19#[derive(
21 AsRef,
22 Constructor,
23 Clone,
24 Debug,
25 Deref,
26 DerefMut,
27 Display,
28 Index,
29 IndexMut,
30 IntoIterator,
31 Into,
32 Serialize,
33 Deserialize,
34)]
35#[as_ref(forward)]
36#[display(fmt = "{}", _0)]
37#[into_iterator(owned, ref, ref_mut)]
38#[serde(transparent)]
39pub struct DefinitionListValue<'a>(
40 InlineElementContainer<'a>,
42);
43
44impl<'a> DefinitionListValue<'a> {
45 pub fn as_inner(&self) -> &InlineElementContainer<'a> {
47 &self.0
48 }
49
50 pub fn into_inner(self) -> InlineElementContainer<'a> {
52 self.0
53 }
54}
55
56impl DefinitionListValue<'_> {
57 pub fn to_borrowed(&self) -> DefinitionListValue {
58 DefinitionListValue(self.0.to_borrowed())
59 }
60
61 pub fn into_owned(self) -> DefinitionListValue<'static> {
62 DefinitionListValue(self.0.into_owned())
63 }
64}
65
66impl<'a> IntoChildren for DefinitionListValue<'a> {
67 type Child = Located<InlineElement<'a>>;
68
69 fn into_children(self) -> Vec<Self::Child> {
70 self.0.into_children()
71 }
72}
73
74impl<'a> Hash for DefinitionListValue<'a> {
75 fn hash<H: Hasher>(&self, state: &mut H) {
76 self.0.to_string().hash(state);
77 }
78}
79
80impl<'a> Eq for DefinitionListValue<'a> {}
81
82impl<'a> PartialEq for DefinitionListValue<'a> {
83 #[allow(clippy::cmp_owned)]
84 fn eq(&self, other: &Self) -> bool {
85 self.to_string() == other.to_string()
86 }
87}
88
89impl<'a, 'b> PartialEq<InlineElementContainer<'b>> for DefinitionListValue<'a> {
90 #[allow(clippy::cmp_owned)]
91 fn eq(&self, other: &InlineElementContainer<'b>) -> bool {
92 self.to_string() == other.to_string()
93 }
94}
95
96impl<'a> PartialEq<String> for DefinitionListValue<'a> {
97 fn eq(&self, other: &String) -> bool {
98 &self.to_string() == other
99 }
100}
101
102impl<'a, 'b> PartialEq<&'b str> for DefinitionListValue<'a> {
103 fn eq(&self, other: &&'b str) -> bool {
104 &self.to_string() == other
105 }
106}
107
108impl<'a> From<&'a str> for DefinitionListValue<'a> {
109 fn from(s: &'a str) -> Self {
113 let element = InlineElement::Text(Text::from(s));
114 let container =
115 InlineElementContainer::new(vec![Located::from(element)]);
116 Self(container)
117 }
118}
119
120impl<'a> StrictEq for DefinitionListValue<'a> {
121 #[inline]
123 fn strict_eq(&self, other: &Self) -> bool {
124 self.0.strict_eq(&other.0)
125 }
126}
127
128pub type Term<'a> = DefinitionListValue<'a>;
130
131pub type Definition<'a> = DefinitionListValue<'a>;
133
134#[derive(
137 Constructor,
138 Clone,
139 Debug,
140 Default,
141 Eq,
142 PartialEq,
143 Serialize,
144 Deserialize,
145 IntoIterator,
146)]
147pub struct DefinitionList<'a> {
148 #[into_iterator(owned, ref, ref_mut)]
150 #[serde(with = "serde_with::rust::map_as_tuple_list")]
151 pub mapping: HashMap<Located<Term<'a>>, Vec<Located<Definition<'a>>>>,
152}
153
154impl DefinitionList<'_> {
155 pub fn to_borrowed(&self) -> DefinitionList {
156 let mapping = self
157 .iter()
158 .map(|(key, value)| {
159 (
160 key.as_ref().map(DefinitionListValue::to_borrowed),
161 value
162 .iter()
163 .map(|x| {
164 x.as_ref().map(DefinitionListValue::to_borrowed)
165 })
166 .collect(),
167 )
168 })
169 .collect();
170
171 DefinitionList { mapping }
172 }
173
174 pub fn into_owned(self) -> DefinitionList<'static> {
175 let mapping = self
176 .into_iter()
177 .map(|(key, value)| {
178 (
179 key.map(DefinitionListValue::into_owned),
180 value
181 .into_iter()
182 .map(|x| x.map(DefinitionListValue::into_owned))
183 .collect(),
184 )
185 })
186 .collect();
187
188 DefinitionList { mapping }
189 }
190}
191
192impl<'a> DefinitionList<'a> {
193 pub fn get(
195 &'a self,
196 term: impl Into<Term<'a>>,
197 ) -> Option<&[Located<Definition<'a>>]> {
198 self.mapping
199 .get(&Located::from(term.into()))
200 .map(AsRef::as_ref)
201 }
202
203 pub fn iter(
205 &self,
206 ) -> impl Iterator<Item = (&Located<Term<'a>>, &[Located<Definition<'a>>])>
207 {
208 self.mapping.iter().map(|(k, v)| (k, v.as_slice()))
209 }
210
211 pub fn terms(&self) -> impl Iterator<Item = &Located<Term<'a>>> {
213 self.mapping.keys()
214 }
215
216 pub fn definitions(
218 &self,
219 ) -> impl Iterator<Item = &Located<Definition<'a>>> {
220 self.mapping.values().flatten()
221 }
222}
223
224impl<'a> IntoChildren for DefinitionList<'a> {
225 type Child = Located<InlineBlockElement<'a>>;
226
227 fn into_children(self) -> Vec<Self::Child> {
228 self.mapping
229 .into_iter()
230 .flat_map(|(term, defs)| {
231 std::iter::once(term.map(InlineBlockElement::Term)).chain(
232 defs.into_iter()
233 .map(|x| x.map(InlineBlockElement::Definition)),
234 )
235 })
236 .collect()
237 }
238}
239
240impl<'a, T: IntoIterator<Item = Located<Definition<'a>>>>
241 FromIterator<(Located<Term<'a>>, T)> for DefinitionList<'a>
242{
243 fn from_iter<I: IntoIterator<Item = (Located<Term<'a>>, T)>>(
244 iter: I,
245 ) -> Self {
246 let mut dl = Self::default();
247
248 for (term, definitions) in iter.into_iter() {
249 dl.mapping.insert(term, definitions.into_iter().collect());
250 }
251
252 dl
253 }
254}
255
256impl<'a> StrictEq for DefinitionList<'a> {
257 fn strict_eq(&self, other: &Self) -> bool {
259 self.mapping.len() == other.mapping.len()
260 && self.mapping.iter().all(|(key, value)| {
261 other.mapping.get_key_value(key).map_or(false, |(k, v)| {
262 key.strict_eq(k) && value.strict_eq(v)
263 })
264 })
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::{InlineElement, Located};
272
273 #[test]
274 fn term_should_equal_other_instance_if_string_representations_are_same() {
275 let t1 = Term::from("term");
276 let t2 = Term::new(InlineElementContainer::new(vec![
277 Located::from(InlineElement::Text("t".into())),
278 Located::from(InlineElement::Text("e".into())),
279 Located::from(InlineElement::Text("r".into())),
280 Located::from(InlineElement::Text("m".into())),
281 ]));
282 assert_eq!(t1, t2);
283 }
284
285 #[test]
286 fn term_should_equal_inline_element_container_if_string_representations_are_same(
287 ) {
288 let term = Term::from("term");
289 let other = InlineElementContainer::new(vec![Located::from(
290 InlineElement::Text("term".into()),
291 )]);
292 assert_eq!(term, other);
293 }
294
295 #[test]
296 fn term_should_equal_string_if_string_representations_are_same() {
297 let term = Term::from("term");
298 let other = String::from("term");
299 assert_eq!(term, other);
300 }
301
302 #[test]
303 fn term_should_equal_str_slice_if_string_representations_are_same() {
304 let term = Term::from("term");
305 let other = "term";
306 assert_eq!(term, other);
307 }
308
309 #[test]
310 fn term_should_hash_using_its_string_representation() {
311 let t1 = Term::from("term");
312 let t2 = Term::new(InlineElementContainer::new(vec![
313 Located::from(InlineElement::Text("t".into())),
314 Located::from(InlineElement::Text("e".into())),
315 Located::from(InlineElement::Text("r".into())),
316 Located::from(InlineElement::Text("m".into())),
317 ]));
318
319 let mut hs = HashMap::new();
320 hs.insert(t1, vec![Definition::from("definition")]);
321 assert_eq!(hs.len(), 1);
322 assert!(hs.get(&t2).is_some());
323 }
324
325 #[test]
326 fn definition_list_should_be_able_to_iterate_through_terms() {
327 let dl: DefinitionList = vec![
328 (Located::from(Term::from("term1")), vec![]),
329 (Located::from(Term::from("term2")), vec![]),
330 ]
331 .into_iter()
332 .collect();
333
334 let term_names =
335 dl.terms().map(|t| t.to_string()).collect::<Vec<String>>();
336 assert_eq!(term_names.len(), 2);
337 assert!(term_names.contains(&"term1".to_string()));
338 assert!(term_names.contains(&"term2".to_string()));
339 }
340
341 #[test]
342 fn definition_list_should_be_able_to_iterate_through_definitions_for_term()
343 {
344 let dl: DefinitionList = vec![
345 (
346 Located::from(Term::from("term1")),
347 vec![Located::from(Definition::from("definition"))],
348 ),
349 (Located::from(Term::from("term2")), vec![]),
350 ]
351 .into_iter()
352 .collect();
353
354 let defs = dl
355 .get("term1")
356 .expect("Failed to find term")
357 .iter()
358 .map(|d| d.to_string())
359 .collect::<Vec<String>>();
360 assert_eq!(defs.len(), 1);
361 assert!(defs.contains(&"definition".to_string()));
362
363 let defs = dl
364 .get("term2")
365 .expect("Failed to find term")
366 .iter()
367 .map(|d| d.to_string())
368 .collect::<Vec<String>>();
369 assert!(defs.is_empty());
370
371 assert!(dl.get("term-unknown").is_none());
372 }
373
374 #[test]
375 fn definition_list_should_support_lookup_with_terms_containing_other_inline_elements(
376 ) {
377 let dl: DefinitionList = vec![
378 (
379 Located::from(Term::from("term1")),
380 vec![
381 Located::from(Definition::from("def1")),
382 Located::from(Definition::from("def2")),
383 ],
384 ),
385 (Located::from(Term::from("term2")), vec![]),
386 ]
387 .into_iter()
388 .collect();
389 assert!(dl.get("term1").is_some());
390 }
391}