1from __future__ import annotations
2
3from icalendar.parser_tools import to_unicode
4
5from collections import OrderedDict
6
7from typing import Any, Optional, Iterable, Mapping, TypeVar
8
9try:
10 from typing import Self
11except ImportError:
12 from typing_extensions import Self
13
14KT = TypeVar("KT")
15VT = TypeVar("VT")
16
17def canonsort_keys(keys: Iterable[KT], canonical_order: Optional[Iterable[KT]] = None) -> list[KT]:
18 """Sorts leading keys according to canonical_order. Keys not specified in
19 canonical_order will appear alphabetically at the end.
20 """
21 canonical_map = {k: i for i, k in enumerate(canonical_order or [])}
22 head = [k for k in keys if k in canonical_map]
23 tail = [k for k in keys if k not in canonical_map]
24 return sorted(head, key=lambda k: canonical_map[k]) + sorted(tail)
25
26
27def canonsort_items(dict1: Mapping[KT, VT], canonical_order: Optional[Iterable[KT]] = None) -> list[tuple[KT, VT]]:
28 """Returns a list of items from dict1, sorted by canonical_order."""
29 return [(k, dict1[k]) for k in canonsort_keys(dict1.keys(), canonical_order)]
30
31
32class CaselessDict(OrderedDict):
33 """A dictionary that isn't case sensitive, and only uses strings as keys.
34 Values retain their case.
35 """
36
37 def __init__(self, *args: Any, **kwargs: Any) -> None:
38 """Set keys to upper for initial dict."""
39 super().__init__(*args, **kwargs)
40 for key, value in self.items():
41 key_upper = to_unicode(key).upper()
42 if key != key_upper:
43 super().__delitem__(key)
44 self[key_upper] = value
45
46 def __getitem__(self, key: Any) -> Any:
47 key = to_unicode(key)
48 return super().__getitem__(key.upper())
49
50 def __setitem__(self, key: Any, value: Any) -> None:
51 key = to_unicode(key)
52 super().__setitem__(key.upper(), value)
53
54 def __delitem__(self, key: Any) -> None:
55 key = to_unicode(key)
56 super().__delitem__(key.upper())
57
58 def __contains__(self, key: Any) -> bool:
59 key = to_unicode(key)
60 return super().__contains__(key.upper())
61
62 def get(self, key: Any, default: Any = None) -> Any:
63 key = to_unicode(key)
64 return super().get(key.upper(), default)
65
66 def setdefault(self, key: Any, value: Any = None) -> Any:
67 key = to_unicode(key)
68 return super().setdefault(key.upper(), value)
69
70 def pop(self, key: Any, default: Any = None) -> Any:
71 key = to_unicode(key)
72 return super().pop(key.upper(), default)
73
74 def popitem(self) -> tuple[Any, Any]:
75 return super().popitem()
76
77 def has_key(self, key: Any) -> bool:
78 key = to_unicode(key)
79 return super().__contains__(key.upper())
80
81 def update(self, *args: Any, **kwargs: Any) -> None:
82 # Multiple keys where key1.upper() == key2.upper() will be lost.
83 mappings = list(args) + [kwargs]
84 for mapping in mappings:
85 if hasattr(mapping, "items"):
86 mapping = iter(mapping.items())
87 for key, value in mapping:
88 self[key] = value
89
90 def copy(self) -> Self:
91 return type(self)(super().copy())
92
93 def __repr__(self) -> str:
94 return f"{type(self).__name__}({dict(self)})"
95
96 def __eq__(self, other: object) -> bool:
97 if not isinstance(other, dict):
98 return NotImplemented
99 return self is other or dict(self.items()) == dict(other.items())
100
101 def __ne__(self, other: object) -> bool:
102 return not self == other
103
104 # A list of keys that must appear first in sorted_keys and sorted_items;
105 # must be uppercase.
106 canonical_order = None
107
108 def sorted_keys(self) -> list[str]:
109 """Sorts keys according to the canonical_order for the derived class.
110 Keys not specified in canonical_order will appear at the end.
111 """
112 return canonsort_keys(self.keys(), self.canonical_order)
113
114 def sorted_items(self) -> list[tuple[Any, Any]]:
115 """Sorts items according to the canonical_order for the derived class.
116 Items not specified in canonical_order will appear at the end.
117 """
118 return canonsort_items(self, self.canonical_order)
119
120
121__all__ = ["canonsort_keys", "canonsort_items", "CaselessDict"]