rustronomy_core/universal_containers/
meta_only.rs

1/*
2  Copyright© 2022 Raúl Wolters(1)
3
4  This file is part of rustronomy-core.
5
6  rustronomy is free software: you can redistribute it and/or modify it under
7  the terms of the European Union Public License version 1.2 or later, as
8  published by the European Commission.
9
10  rustronomy is distributed in the hope that it will be useful, but WITHOUT ANY
11  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12  A PARTICULAR PURPOSE. See the European Union Public License for more details.
13
14  You should have received a copy of the EUPL in an/all official language(s) of
15  the European Union along with rustronomy.  If not, see
16  <https://ec.europa.eu/info/european-union-public-licence_en/>.
17
18  (1) Resident of the Kingdom of the Netherlands; agreement between licensor and
19  licensee subject to Dutch law as per article 15 of the EUPL.
20*/
21
22//! This module provides a metadata-only container called `MetaOnly`. It can be
23//! used by other libraries to construct additional containers, or to directly
24//! pass metadata.
25
26use std::{any::TypeId, collections::HashMap};
27
28use crate::meta::{MetaContainer, MetaTag};
29
30#[derive(Clone, Default)]
31/// Basic hashmap based metadata container, containing only metadata.
32///
33/// # Utility traits
34/// `MetaOnly` implements `Clone`, `Debug`, `Default`, `Display`, `Send` and `Sync`
35pub struct MetaOnly {
36  string_tags: HashMap<String, String>,
37  typed_tags: HashMap<TypeId, Box<dyn MetaTag>>,
38}
39
40impl MetaOnly {
41  /// Constructs empty `MetaOnly` container
42  pub fn new() -> Self {
43    Self::default()
44  }
45
46  /// Returns an `Iterator` over all strongly typed metadata tags in the container.
47  pub fn iter_typed_tags(&self) -> impl Iterator<Item = &dyn MetaTag> {
48    self.typed_tags.iter().map(|(_type_id, opaque)| opaque.as_ref())
49  }
50
51  /// Returns an `Iterator` over all string key-value metadata pairs in the container.
52  pub fn iter_string_kv(&self) -> impl Iterator<Item = (&str, &str)> {
53    self.string_tags.iter().map(|(k, v)| (k as &str, v as &str))
54  }
55
56  /// Returns an `Iterator` of nicely formatted string key-value metadata pairs in the container.
57  ///
58  /// The format looks like `[Key]: "Value"`
59  pub fn iter_string_fmt(&self) -> impl Iterator<Item = String> + '_ {
60    self.iter_string_kv().map(|(k, v)| format!("[{k}]: \"{v}\""))
61  }
62}
63
64impl PartialEq for MetaOnly {
65  fn eq(&self, other: &Self) -> bool {
66    self.iter_string_kv().all(|(key, _value)| other.contains_string_tag(key))
67      && self.iter_typed_tags().all(|tag| other.contains_type_id(&tag.type_id()))
68  }
69}
70
71impl std::fmt::Debug for MetaOnly {
72  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73    #[cfg_attr(rustfmt, rustfmt_skip)]
74    writeln!(f, "================================<TYPED METADATA>================================")?;
75    if self.has_typed_metadata() {
76      for strong in self.iter_typed_tags() {
77        writeln!(f, "{strong}")?;
78      }
79    } else {
80      #[cfg_attr(rustfmt, rustfmt_skip)]
81      writeln!(f, "                                  (not present)                                 ")?;
82    }
83    #[cfg_attr(rustfmt, rustfmt_skip)]
84    writeln!(f, "===============================<UNTYPED METADATA>===============================")?;
85    if self.has_string_metadata() {
86      for weak in self.iter_string_fmt() {
87        writeln!(f, "{weak}")?;
88      }
89    } else {
90      #[cfg_attr(rustfmt, rustfmt_skip)]
91      writeln!(f, "                                  (not present)                                 ")?;
92    }
93    Ok(())
94  }
95}
96
97impl MetaContainer for MetaOnly {
98  fn insert_tag<T: MetaTag + Clone>(&mut self, tag: &T) -> Option<T> {
99    self.typed_tags.insert(tag.type_id(), Box::new(tag.clone()))?.as_ref().downcast()
100  }
101
102  fn contains_tag<T: MetaTag + Clone>(&self) -> bool {
103    self.typed_tags.contains_key(&TypeId::of::<T>())
104  }
105
106  fn contains_type_id(&self, type_id: &TypeId) -> bool {
107    self.typed_tags.contains_key(type_id)
108  }
109
110  fn get_tag<T: MetaTag + Clone>(&self) -> Option<&T> {
111    self.typed_tags.get(&TypeId::of::<T>())?.downcast_ref()
112  }
113
114  fn get_tag_mut<T: MetaTag + Clone>(&mut self) -> Option<&mut T> {
115    self.typed_tags.get_mut(&TypeId::of::<T>())?.downcast_mut()
116  }
117
118  fn remove_tag<T: MetaTag + Clone>(&mut self) -> Option<T> {
119    self.typed_tags.remove(&TypeId::of::<T>())?.downcast()
120  }
121
122  fn has_typed_metadata(&self) -> bool {
123    !self.typed_tags.is_empty()
124  }
125
126  fn insert_string_tag(&mut self, key: &str, value: &str) -> Option<String> {
127    self.string_tags.insert(key.to_string(), value.to_string())
128  }
129
130  fn contains_string_tag(&self, key: &str) -> bool {
131    self.string_tags.contains_key(key)
132  }
133
134  fn get_string_tag(&self, key: &str) -> Option<&str> {
135    self.string_tags.get(key).and_then(|x| Some(x as &str))
136  }
137
138  fn get_string_tag_mut(&mut self, key: &str) -> Option<&mut String> {
139    self.string_tags.get_mut(key)
140  }
141
142  fn remove_string_tag(&mut self, key: &str) -> Option<String> {
143    self.string_tags.remove(key)
144  }
145
146  fn has_string_metadata(&self) -> bool {
147    !self.string_tags.is_empty()
148  }
149}
150
151#[test]
152fn test_string_meta_insert_remove() {
153  let mut meta = MetaOnly::new();
154  let (key, value) = ("test", "value");
155  meta.insert_string_tag(key, value);
156  assert_eq!(value, meta.remove_string_tag(key).unwrap())
157}
158
159#[test]
160fn test_partialeq_string_meta() {
161  let mut meta1 = MetaOnly::new();
162  let mut meta2 = MetaOnly::new();
163  assert_eq!(&meta1, &meta2);
164
165  let (key, value) = ("test", "value");
166  meta1.insert_string_tag(key, value);
167  assert_ne!(&meta1, &meta2);
168
169  meta2.insert_string_tag(key, value);
170  assert_eq!(&meta1, &meta2);
171
172  meta2.remove_string_tag(key);
173  assert_ne!(&meta1, &meta2);
174
175  meta1.remove_string_tag(key);
176  assert_eq!(&meta1, &meta2);
177}
178
179#[derive(Debug, Clone, PartialEq)]
180#[doc(hidden)]
181struct DummyTag(pub String);
182impl MetaTag for DummyTag {}
183impl std::fmt::Display for DummyTag {
184  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185    writeln!(f, "[DummyTag]: \"{}\"", self.0)
186  }
187}
188
189#[test]
190fn test_dummy_meta_insert_remove() {
191  let mut meta = MetaOnly::new();
192  let tag = DummyTag("🇦🇬".to_string());
193  meta.insert_tag(&tag);
194  assert_eq!(tag, meta.remove_tag().unwrap())
195}
196
197#[test]
198fn test_partialeq_dummy_meta() {
199  let mut meta1 = MetaOnly::new();
200  let mut meta2 = MetaOnly::new();
201  assert_eq!(&meta1, &meta2);
202
203  let tag = DummyTag("🇦🇬".to_string());
204  meta1.insert_tag(&tag);
205  assert_ne!(&meta1, &meta2);
206
207  meta2.insert_tag(&tag);
208  assert_eq!(&meta1, &meta2);
209
210  meta2.remove_tag::<DummyTag>();
211  assert_ne!(&meta1, &meta2);
212
213  meta1.remove_tag::<DummyTag>();
214  assert_eq!(&meta1, &meta2);
215}
216
217#[test]
218fn test_tag_display() {
219  use crate::meta::tags::*;
220  let mut meta = MetaOnly::new();
221  meta.insert_tag(&Author("test".to_string()));
222  meta.insert_tag(&ReferencePublication::new("test_title", "test_author"));
223  meta.insert_tag(&ExposureTime(12000));
224  println!("{meta:?}");
225}