1use crate::{dict_get, dict_has};
6
7use crate::haystack::val::*;
8use std::borrow::Cow;
9use std::collections::BTreeMap;
10use std::fmt::{Debug, Display, Formatter};
11use std::hash::Hash;
12use std::iter::{FromIterator, Iterator};
13use std::ops::{Deref, DerefMut};
14
15pub type DictType = BTreeMap<String, Value>;
17
18#[derive(Eq, PartialEq, Hash, Clone, Debug, Default)]
43pub struct Dict {
44 value: DictType,
45}
46
47pub trait HaystackDict {
50 fn id(&self) -> Option<&Ref>;
52
53 fn safe_id(&self) -> Ref;
55
56 fn ts(&self) -> Option<&DateTime>;
60
61 fn has(&self, key: &str) -> bool;
63
64 fn missing(&self, key: &str) -> bool;
66
67 fn has_marker(&self, key: &str) -> bool;
69
70 fn has_na(&self, key: &str) -> bool;
72
73 fn has_remove(&self, key: &str) -> bool;
75
76 fn get_bool<'a>(&'a self, key: &str) -> Option<&'a Bool>;
78
79 fn get_num<'a>(&'a self, key: &str) -> Option<&'a Number>;
81
82 fn get_ref<'a>(&'a self, key: &str) -> Option<&'a Ref>;
84
85 fn get_str<'a>(&'a self, key: &str) -> Option<&'a Str>;
87
88 fn get_xstr<'a>(&'a self, key: &str) -> Option<&'a XStr>;
90
91 fn get_uri<'a>(&'a self, key: &str) -> Option<&'a Uri>;
93
94 fn get_symbol<'a>(&'a self, key: &str) -> Option<&'a Symbol>;
96
97 fn get_date<'a>(&'a self, key: &str) -> Option<&'a Date>;
99
100 fn get_time<'a>(&'a self, key: &str) -> Option<&'a Time>;
102
103 fn get_date_time<'a>(&'a self, key: &str) -> Option<&'a DateTime>;
105
106 fn get_coord<'a>(&'a self, key: &str) -> Option<&'a Coord>;
108
109 fn get_dict<'a>(&'a self, key: &str) -> Option<&'a Dict>;
111
112 fn get_list<'a>(&'a self, key: &str) -> Option<&'a List>;
114
115 fn get_grid<'a>(&'a self, key: &str) -> Option<&'a Grid>;
117
118 fn dis(&self) -> Cow<'_, str>;
120}
121
122impl Dict {
123 pub fn new() -> Dict {
125 Dict {
126 value: DictType::new(),
127 }
128 }
129}
130
131impl FromIterator<(String, Value)> for Dict {
135 fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
136 Dict {
137 value: DictType::from_iter(iter),
138 }
139 }
140}
141
142impl Deref for Dict {
144 type Target = DictType;
145 #[inline]
146 fn deref(&self) -> &Self::Target {
147 &self.value
148 }
149}
150
151impl DerefMut for Dict {
153 #[inline]
154 fn deref_mut(&mut self) -> &mut DictType {
155 &mut self.value
156 }
157}
158
159#[allow(clippy::non_canonical_partial_ord_impl)]
160impl PartialOrd for Dict {
161 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
162 self.value.partial_cmp(&other.value)
163 }
164}
165
166impl Ord for Dict {
167 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
168 if self.is_empty() && other.is_empty() {
169 std::cmp::Ordering::Equal
170 } else {
171 let keys_cmp = self.value.keys().cmp(other.value.keys());
172 if keys_cmp == std::cmp::Ordering::Equal {
173 self.value.values().cmp(other.value.values())
174 } else {
175 keys_cmp
176 }
177 }
178 }
179}
180
181impl HaystackDict for Dict {
182 fn id(&self) -> Option<&Ref> {
183 self.get_ref("id")
184 }
185
186 fn safe_id(&self) -> Ref {
187 self.get_ref("id").map_or(Ref::default(), |id| id.clone())
188 }
189
190 fn ts(&self) -> Option<&DateTime> {
191 self.get_date_time("mod")
192 }
193
194 fn has(&self, key: &str) -> bool {
195 self.contains_key(key)
196 }
197
198 fn missing(&self, key: &str) -> bool {
199 !self.has(key)
200 }
201
202 fn has_marker(&self, key: &str) -> bool {
203 dict_has! {self, key, Marker}
204 }
205
206 fn has_na(&self, key: &str) -> bool {
207 dict_has! {self, key, Na}
208 }
209
210 fn has_remove(&self, key: &str) -> bool {
211 dict_has! {self, key, Remove}
212 }
213
214 fn get_bool<'a>(&'a self, key: &str) -> Option<&'a Bool> {
215 dict_get! {self, key, Bool}
216 }
217
218 fn get_num<'a>(&'a self, key: &str) -> Option<&'a Number> {
219 dict_get! {self, key, Number}
220 }
221
222 fn get_str<'a>(&'a self, key: &str) -> Option<&'a Str> {
223 dict_get! {self, key, Str}
224 }
225
226 fn get_xstr<'a>(&'a self, key: &str) -> Option<&'a XStr> {
227 dict_get! {self, key, XStr}
228 }
229
230 fn get_ref<'a>(&'a self, key: &str) -> Option<&'a Ref> {
231 dict_get! {self, key, Ref}
232 }
233
234 fn get_uri<'a>(&'a self, key: &str) -> Option<&'a Uri> {
235 dict_get! {self, key, Uri}
236 }
237
238 fn get_symbol<'a>(&'a self, key: &str) -> Option<&'a Symbol> {
239 dict_get! {self, key, Symbol}
240 }
241
242 fn get_date<'a>(&'a self, key: &str) -> Option<&'a Date> {
243 dict_get! {self, key, Date}
244 }
245
246 fn get_time<'a>(&'a self, key: &str) -> Option<&'a Time> {
247 dict_get! {self, key, Time}
248 }
249
250 fn get_date_time<'a>(&'a self, key: &str) -> Option<&'a DateTime> {
251 dict_get! {self, key, DateTime}
252 }
253
254 fn get_coord<'a>(&'a self, key: &str) -> Option<&'a Coord> {
255 dict_get! {self, key, Coord}
256 }
257
258 fn get_dict<'a>(&'a self, key: &str) -> Option<&'a Dict> {
259 dict_get! {self, key, Dict}
260 }
261
262 fn get_list<'a>(&'a self, key: &str) -> Option<&'a List> {
263 dict_get! {self, key, List}
264 }
265
266 fn get_grid<'a>(&'a self, key: &str) -> Option<&'a Grid> {
267 dict_get! {self, key, Grid}
268 }
269
270 fn dis(&self) -> Cow<'_, str> {
271 dict_to_dis(self, &|_| None, None)
272 }
273}
274
275impl From<DictType> for Dict {
277 fn from(from: DictType) -> Self {
278 Dict { value: from }
279 }
280}
281
282impl From<DictType> for Value {
284 fn from(from: DictType) -> Self {
285 Value::from(Dict { value: from })
286 }
287}
288
289impl From<Dict> for Value {
291 fn from(value: Dict) -> Self {
292 Value::Dict(value)
293 }
294}
295
296impl TryFrom<&Value> for Dict {
298 type Error = &'static str;
299 fn try_from(value: &Value) -> Result<Self, Self::Error> {
300 match value {
301 Value::Dict(v) => Ok(v.clone()),
302 _ => Err("Value is not an `Dict`"),
303 }
304 }
305}
306
307impl Display for Dict {
309 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
310 Debug::fmt(&self.value, f)
311 }
312}
313
314#[macro_export]
327macro_rules! dict(
328 { $($key:expr => $value:expr),* $(,)? } => {
329 {
330 let mut map = ::std::collections::BTreeMap::new();
331 $(
332 map.insert(String::from($key), $value);
333 )+
334 Dict::from(map)
335 }
336 };
337);
338
339#[macro_export]
345macro_rules! dict_get(
346 { $self:ident, $key:expr, $type:ident } => {
347 {
348 if let Some(value) = $self.get($key) {
349 match value {
350 Value::$type(val) => Some(&val),
351 _ => None,
352 }
353 } else {
354 None
355 }
356 }
357 };
358);
359
360#[macro_export]
365macro_rules! dict_has(
366 { $self:ident, $key:expr, $type:ident } => {
367 {
368 let entry = $self.get($key);
369 matches!(entry, Some(Value::$type))
370 }
371 };
372);
373
374pub fn dict_to_dis<'a, GetLocalizedFunc>(
376 dict: &'a Dict,
377 get_localized: &'a GetLocalizedFunc,
378 def: Option<Cow<'a, str>>,
379) -> Cow<'a, str>
380where
381 GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
382{
383 if let Some(val) = dict.get("dis") {
384 return decode_str_from_value(val);
385 }
386
387 if let Some(val) = dict.get("disMacro") {
388 return if let Value::Str(val) = val {
389 dis_macro(
390 &val.value,
391 |val| dict.get(val).map(Cow::Borrowed),
392 get_localized,
393 )
394 } else {
395 decode_str_from_value(val)
396 };
397 }
398
399 if let Some(val) = dict.get("disKey") {
400 if let Value::Str(val_str) = val {
401 if let Some(val_str) = get_localized(&val_str.value) {
402 return val_str;
403 }
404 }
405 return decode_str_from_value(val);
406 }
407
408 if let Some(val) = dict.get("name") {
409 return decode_str_from_value(val);
410 }
411
412 if let Some(val) = dict.get("def") {
413 return decode_str_from_value(val);
414 }
415
416 if let Some(val) = dict.get("tag") {
417 return decode_str_from_value(val);
418 }
419
420 if let Some(val) = dict.get("navName") {
421 return decode_str_from_value(val);
422 }
423
424 if let Some(val) = dict.get("id") {
425 return if let Value::Ref(val) = val {
426 Cow::Borrowed(val.dis.as_ref().unwrap_or(&val.value))
427 } else {
428 decode_str_from_value(val)
429 };
430 }
431
432 def.unwrap_or(Cow::Borrowed(""))
433}
434
435fn decode_str_from_value(val: &'_ Value) -> Cow<'_, str> {
436 match val {
437 Value::Str(val) => Cow::Borrowed(&val.value),
438 _ => Cow::Owned(val.to_string()),
439 }
440}
441
442#[cfg(test)]
443mod test {
444 use std::borrow::Cow;
445
446 use crate::val::{dict_to_dis, Dict, HaystackDict, Value};
447
448 fn get_localized<'a>(key: &str) -> Option<Cow<'a, str>> {
449 match key {
450 "key" => Some(Cow::Borrowed("translated")),
451 _ => None,
452 }
453 }
454
455 #[test]
456 fn dict_to_dis_returns_dis() {
457 let dict = dict!["dis" => Value::make_str("display")];
458 assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
459 }
460
461 #[test]
462 fn dict_to_dis_returns_dis_not_str() {
463 let dict = dict!["dis" => Value::make_ref("display")];
464 assert_eq!(dict_to_dis(&dict, &|_| None, None), "@display");
465 }
466
467 #[test]
468 fn dict_to_dis_returns_dis_macro() {
469 let dict = dict!["foo" => Value::make_str("bar"), "disMacro" => Value::make_str("hello $foo world!")];
470 assert_eq!(dict_to_dis(&dict, &|_| None, None), "hello bar world!");
471 }
472
473 #[test]
474 fn dict_to_dis_returns_dis_key_translated() {
475 let dict = dict!["foo" => Value::make_str("bar"), "disKey" => Value::make_str("key")];
476 assert_eq!(dict_to_dis(&dict, &get_localized, None), "translated");
477 }
478
479 #[test]
480 fn dict_to_dis_returns_dis_key_not_translated() {
481 let dict =
482 dict!["foo" => Value::make_str("bar"), "disKey" => Value::make_str("notTranslated")];
483 assert_eq!(dict_to_dis(&dict, &get_localized, None), "notTranslated");
484 }
485
486 #[test]
487 fn dict_to_dis_returns_name() {
488 let dict = dict!["name" => Value::make_str("display")];
489 assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
490 }
491
492 #[test]
493 fn dict_to_dis_returns_def() {
494 let dict = dict!["def" => Value::make_str("display")];
495 assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
496 }
497
498 #[test]
499 fn dict_to_dis_returns_tag() {
500 let dict = dict!["tag" => Value::make_str("display")];
501 assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
502 }
503
504 #[test]
505 fn dict_to_dis_returns_nav_name() {
506 let dict = dict!["navName" => Value::make_str("navName")];
507 assert_eq!(dict_to_dis(&dict, &|_| None, None), "navName");
508 }
509
510 #[test]
511 fn dict_to_dis_returns_id() {
512 let dict = dict!["id" => Value::make_ref("id")];
513 assert_eq!(dict_to_dis(&dict, &|_| None, None), "id");
514 }
515
516 #[test]
517 fn dict_to_dis_returns_id_dis() {
518 let dict = dict!["id" => Value::make_ref_with_dis("id", "dis")];
519 assert_eq!(dict_to_dis(&dict, &|_| None, None), "dis");
520 }
521
522 #[test]
523 fn dict_returns_dis() {
524 let dict = dict!["dis" => Value::make_str("display")];
525 assert_eq!(dict.dis(), "display");
526 }
527
528 #[test]
529 fn dict_returns_default_value_if_none_found() {
530 let dict = dict!["something" => Value::make_str("display")];
531 assert_eq!(
532 dict_to_dis(&dict, &|_| None, Some("default".into())),
533 "default"
534 );
535 }
536}