1use crate::kinds::{HRef, Kind};
4use std::collections::HashMap;
5use std::fmt;
6
7#[derive(Debug, Clone, Default)]
12pub struct HDict {
13 tags: HashMap<String, Kind>,
14}
15
16impl HDict {
17 pub fn new() -> Self {
19 Self::default()
20 }
21
22 pub fn from_tags(tags: HashMap<String, Kind>) -> Self {
24 Self { tags }
25 }
26
27 pub fn has(&self, name: &str) -> bool {
29 self.tags.contains_key(name)
30 }
31
32 pub fn get(&self, name: &str) -> Option<&Kind> {
34 self.tags.get(name)
35 }
36
37 pub fn missing(&self, name: &str) -> bool {
39 !self.tags.contains_key(name)
40 }
41
42 pub fn id(&self) -> Option<&HRef> {
44 match self.tags.get("id") {
45 Some(Kind::Ref(r)) => Some(r),
46 _ => None,
47 }
48 }
49
50 pub fn dis(&self) -> Option<&str> {
55 if let Some(Kind::Str(s)) = self.tags.get("dis") {
56 return Some(s.as_str());
57 }
58 if let Some(r) = self.id() {
59 return r.dis.as_deref();
60 }
61 None
62 }
63
64 pub fn is_empty(&self) -> bool {
66 self.tags.is_empty()
67 }
68
69 pub fn len(&self) -> usize {
71 self.tags.len()
72 }
73
74 pub fn set(&mut self, name: impl Into<String>, val: Kind) {
76 self.tags.insert(name.into(), val);
77 }
78
79 pub fn remove_tag(&mut self, name: &str) -> Option<Kind> {
81 self.tags.remove(name)
82 }
83
84 pub fn merge(&mut self, other: &HDict) {
89 for (k, v) in &other.tags {
90 match v {
91 Kind::Remove => {
92 self.tags.remove(k);
93 }
94 _ => {
95 self.tags.insert(k.clone(), v.clone());
96 }
97 }
98 }
99 }
100
101 pub fn tags(&self) -> &HashMap<String, Kind> {
103 &self.tags
104 }
105
106 pub fn iter(&self) -> impl Iterator<Item = (&str, &Kind)> {
108 self.tags.iter().map(|(k, v)| (k.as_str(), v))
109 }
110
111 pub fn sorted_iter(&self) -> Vec<(&str, &Kind)> {
113 let mut pairs: Vec<_> = self.tags.iter().map(|(k, v)| (k.as_str(), v)).collect();
114 pairs.sort_unstable_by_key(|(k, _)| *k);
115 pairs
116 }
117
118 pub fn tag_names(&self) -> impl Iterator<Item = &str> {
120 self.tags.keys().map(|k| k.as_str())
121 }
122
123 pub fn tag_name_set(&self) -> std::collections::HashSet<&str> {
125 self.tags.keys().map(|k| k.as_str()).collect()
126 }
127}
128
129impl PartialEq for HDict {
130 fn eq(&self, other: &Self) -> bool {
131 self.tags == other.tags
132 }
133}
134
135impl fmt::Display for HDict {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 write!(f, "HDict({{")?;
138 let mut first = true;
139 let mut keys: Vec<&String> = self.tags.keys().collect();
141 keys.sort();
142 for k in keys {
143 let v = &self.tags[k];
144 if !first {
145 write!(f, ", ")?;
146 }
147 write!(f, "{k}: {v}")?;
148 first = false;
149 }
150 write!(f, "}})")
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::kinds::Number;
158
159 #[test]
160 fn empty_dict() {
161 let d = HDict::new();
162 assert!(d.is_empty());
163 assert_eq!(d.len(), 0);
164 assert!(d.missing("anything"));
165 assert!(!d.has("anything"));
166 assert_eq!(d.get("anything"), None);
167 }
168
169 #[test]
170 fn set_get_has_missing() {
171 let mut d = HDict::new();
172 d.set("site", Kind::Marker);
173 d.set("area", Kind::Number(Number::unitless(4500.0)));
174 d.set("dis", Kind::Str("Main Site".into()));
175
176 assert!(d.has("site"));
177 assert!(!d.missing("site"));
178 assert_eq!(d.get("site"), Some(&Kind::Marker));
179
180 assert!(d.has("area"));
181 assert_eq!(d.get("area"), Some(&Kind::Number(Number::unitless(4500.0))));
182
183 assert!(d.has("dis"));
184 assert_eq!(d.get("dis"), Some(&Kind::Str("Main Site".into())));
185
186 assert!(d.missing("nonexistent"));
187 assert_eq!(d.get("nonexistent"), None);
188
189 assert_eq!(d.len(), 3);
190 assert!(!d.is_empty());
191 }
192
193 #[test]
194 fn id_with_ref() {
195 let mut d = HDict::new();
196 let r = HRef::new("site-1", Some("Main Site".into()));
197 d.set("id", Kind::Ref(r));
198
199 let id = d.id().unwrap();
200 assert_eq!(id.val, "site-1");
201 assert_eq!(id.dis, Some("Main Site".into()));
202 }
203
204 #[test]
205 fn id_with_non_ref() {
206 let mut d = HDict::new();
207 d.set("id", Kind::Str("not-a-ref".into()));
208 assert!(d.id().is_none());
209 }
210
211 #[test]
212 fn id_missing() {
213 let d = HDict::new();
214 assert!(d.id().is_none());
215 }
216
217 #[test]
218 fn dis_from_dis_tag() {
219 let mut d = HDict::new();
220 d.set("dis", Kind::Str("My Building".into()));
221 d.set("id", Kind::Ref(HRef::new("b-1", Some("Ref Dis".into()))));
222
223 assert_eq!(d.dis(), Some("My Building"));
225 }
226
227 #[test]
228 fn dis_from_ref_fallback() {
229 let mut d = HDict::new();
230 d.set(
231 "id",
232 Kind::Ref(HRef::new("b-1", Some("Ref Display".into()))),
233 );
234
235 assert_eq!(d.dis(), Some("Ref Display"));
237 }
238
239 #[test]
240 fn dis_from_ref_without_dis() {
241 let mut d = HDict::new();
242 d.set("id", Kind::Ref(HRef::from_val("b-1")));
243
244 assert_eq!(d.dis(), None);
246 }
247
248 #[test]
249 fn dis_missing_entirely() {
250 let d = HDict::new();
251 assert_eq!(d.dis(), None);
252 }
253
254 #[test]
255 fn dis_non_str_dis_tag() {
256 let mut d = HDict::new();
257 d.set("dis", Kind::Number(Number::unitless(42.0)));
259 assert_eq!(d.dis(), None);
260 }
261
262 #[test]
263 fn merge_updates_and_adds() {
264 let mut base = HDict::new();
265 base.set("site", Kind::Marker);
266 base.set("area", Kind::Number(Number::unitless(1000.0)));
267
268 let mut update = HDict::new();
269 update.set("area", Kind::Number(Number::unitless(2000.0)));
270 update.set("geoCity", Kind::Str("Richmond".into()));
271
272 base.merge(&update);
273
274 assert_eq!(base.get("site"), Some(&Kind::Marker));
275 assert_eq!(
276 base.get("area"),
277 Some(&Kind::Number(Number::unitless(2000.0)))
278 );
279 assert_eq!(base.get("geoCity"), Some(&Kind::Str("Richmond".into())));
280 assert_eq!(base.len(), 3);
281 }
282
283 #[test]
284 fn merge_with_remove() {
285 let mut base = HDict::new();
286 base.set("site", Kind::Marker);
287 base.set("area", Kind::Number(Number::unitless(1000.0)));
288 base.set("dis", Kind::Str("Old Name".into()));
289
290 let mut update = HDict::new();
291 update.set("area", Kind::Remove); update.set("dis", Kind::Str("New Name".into())); base.merge(&update);
295
296 assert!(base.has("site"));
297 assert!(base.missing("area")); assert_eq!(base.get("dis"), Some(&Kind::Str("New Name".into())));
299 assert_eq!(base.len(), 2);
300 }
301
302 #[test]
303 fn remove_tag() {
304 let mut d = HDict::new();
305 d.set("a", Kind::Marker);
306 d.set("b", Kind::Str("hello".into()));
307
308 let removed = d.remove_tag("a");
309 assert_eq!(removed, Some(Kind::Marker));
310 assert!(d.missing("a"));
311 assert_eq!(d.len(), 1);
312
313 let not_found = d.remove_tag("nonexistent");
314 assert_eq!(not_found, None);
315 }
316
317 #[test]
318 fn from_tags() {
319 let mut map = HashMap::new();
320 map.insert("site".to_string(), Kind::Marker);
321 map.insert("dis".to_string(), Kind::Str("Test".into()));
322
323 let d = HDict::from_tags(map);
324 assert_eq!(d.len(), 2);
325 assert!(d.has("site"));
326 assert!(d.has("dis"));
327 }
328
329 #[test]
330 fn tag_iteration() {
331 let mut d = HDict::new();
332 d.set("a", Kind::Marker);
333 d.set("b", Kind::Str("hello".into()));
334 d.set("c", Kind::Number(Number::unitless(3.0)));
335
336 let pairs: Vec<(&str, &Kind)> = d.iter().collect();
337 assert_eq!(pairs.len(), 3);
338
339 let names: std::collections::HashSet<&str> = d.tag_names().collect();
341 assert!(names.contains("a"));
342 assert!(names.contains("b"));
343 assert!(names.contains("c"));
344 assert_eq!(names.len(), 3);
345 }
346
347 #[test]
348 fn tag_name_set() {
349 let mut d = HDict::new();
350 d.set("alpha", Kind::Marker);
351 d.set("beta", Kind::Marker);
352
353 let set = d.tag_name_set();
354 assert_eq!(set.len(), 2);
355 assert!(set.contains("alpha"));
356 assert!(set.contains("beta"));
357 }
358
359 #[test]
360 fn equality() {
361 let mut a = HDict::new();
362 a.set("x", Kind::Number(Number::unitless(1.0)));
363 a.set("y", Kind::Str("hi".into()));
364
365 let mut b = HDict::new();
366 b.set("x", Kind::Number(Number::unitless(1.0)));
367 b.set("y", Kind::Str("hi".into()));
368
369 assert_eq!(a, b);
370 }
371
372 #[test]
373 fn inequality_different_values() {
374 let mut a = HDict::new();
375 a.set("x", Kind::Number(Number::unitless(1.0)));
376
377 let mut b = HDict::new();
378 b.set("x", Kind::Number(Number::unitless(2.0)));
379
380 assert_ne!(a, b);
381 }
382
383 #[test]
384 fn inequality_different_keys() {
385 let mut a = HDict::new();
386 a.set("x", Kind::Marker);
387
388 let mut b = HDict::new();
389 b.set("y", Kind::Marker);
390
391 assert_ne!(a, b);
392 }
393
394 #[test]
395 fn display_empty() {
396 let d = HDict::new();
397 assert_eq!(d.to_string(), "HDict({})");
398 }
399
400 #[test]
401 fn display_with_tags() {
402 let mut d = HDict::new();
403 d.set("site", Kind::Marker);
404
405 let s = d.to_string();
406 assert!(s.starts_with("HDict({"));
407 assert!(s.ends_with("})"));
408 assert!(s.contains("site"));
409 }
410
411 #[test]
412 fn overwrite_existing_tag() {
413 let mut d = HDict::new();
414 d.set("val", Kind::Number(Number::unitless(1.0)));
415 d.set("val", Kind::Number(Number::unitless(2.0)));
416
417 assert_eq!(d.len(), 1);
418 assert_eq!(d.get("val"), Some(&Kind::Number(Number::unitless(2.0))));
419 }
420
421 #[test]
422 fn default_is_empty() {
423 let d = HDict::default();
424 assert!(d.is_empty());
425 assert_eq!(d.len(), 0);
426 }
427
428 #[test]
429 fn tags_returns_inner_map() {
430 let mut d = HDict::new();
431 d.set("a", Kind::Marker);
432 let tags = d.tags();
433 assert_eq!(tags.len(), 1);
434 assert_eq!(tags.get("a"), Some(&Kind::Marker));
435 }
436}