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