rustronomy_core/universal_containers/
meta_only.rs1use std::{any::TypeId, collections::HashMap};
27
28use crate::meta::{MetaContainer, MetaTag};
29
30#[derive(Clone, Default)]
31pub struct MetaOnly {
36 string_tags: HashMap<String, String>,
37 typed_tags: HashMap<TypeId, Box<dyn MetaTag>>,
38}
39
40impl MetaOnly {
41 pub fn new() -> Self {
43 Self::default()
44 }
45
46 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 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 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}