vimwiki_core/lang/elements/blocks/
definitions.rs

1use 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/// Represents the newtype used for terms & definitions
20#[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    /// Represents the inner type that the definition list value wraps
41    InlineElementContainer<'a>,
42);
43
44impl<'a> DefinitionListValue<'a> {
45    /// Returns reference to underlying container
46    pub fn as_inner(&self) -> &InlineElementContainer<'a> {
47        &self.0
48    }
49
50    /// Converts into underlying container
51    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    /// Special conversion to support wrapping str as a [`Text`] element,
110    /// wrapped as an [`InlineElement`], wrapped as an [`InlineElementContainer`],
111    /// and finally wrapped as a [`DefinitionListValue`]
112    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    /// Performs strict_eq on inner container
122    #[inline]
123    fn strict_eq(&self, other: &Self) -> bool {
124        self.0.strict_eq(&other.0)
125    }
126}
127
128/// Represents the type alias used for a single term
129pub type Term<'a> = DefinitionListValue<'a>;
130
131/// Represents the type alias used for a single definition
132pub type Definition<'a> = DefinitionListValue<'a>;
133
134/// Represents a list of terms and definitions, where a term can have multiple
135/// definitions associated with it
136#[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    /// Represents the inner mapping of terms to definitions
149    #[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    /// Retrieves definitions for an specific term
194    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    /// Iterates through all terms and their associated definitions in the list
204    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    /// Iterates through all terms in the list
212    pub fn terms(&self) -> impl Iterator<Item = &Located<Term<'a>>> {
213        self.mapping.keys()
214    }
215
216    /// Iterates through all definitions in the list
217    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    /// Performs strict_eq on inner mapping
258    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}