Skip to content

Commit 401bb33

Browse files
committed
Honor case-insensitivity of metadata keys using FoldedCase.
1 parent 96c0fec commit 401bb33

File tree

1 file changed

+115
-13
lines changed

1 file changed

+115
-13
lines changed

importlib_metadata/_adapters.py

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import textwrap
33
import email.message
44

5+
from ._functools import method_cache
6+
57

68
class Message(email.message.Message):
79
def __new__(cls, orig: email.message.Message):
@@ -34,18 +36,22 @@ def json(self):
3436
Convert PackageMetadata to a JSON-compatible format
3537
per PEP 0566.
3638
"""
37-
# TODO: Need to match case-insensitive
38-
multiple_use = {
39-
'Classifier',
40-
'Obsoletes-Dist',
41-
'Platform',
42-
'Project-URL',
43-
'Provides-Dist',
44-
'Provides-Extra',
45-
'Requires-Dist',
46-
'Requires-External',
47-
'Supported-Platform',
48-
}
39+
multiple_use = set(
40+
map(
41+
FoldedCase,
42+
[
43+
'Classifier',
44+
'Obsoletes-Dist',
45+
'Platform',
46+
'Project-URL',
47+
'Provides-Dist',
48+
'Provides-Extra',
49+
'Requires-Dist',
50+
'Requires-External',
51+
'Supported-Platform',
52+
],
53+
)
54+
)
4955

5056
def transform(key):
5157
value = self.get_all(key) if key in multiple_use else self[key]
@@ -54,4 +60,100 @@ def transform(key):
5460
tk = key.lower().replace('-', '_')
5561
return tk, value
5662

57-
return dict(map(transform, self))
63+
return dict(map(transform, map(FoldedCase, self)))
64+
65+
66+
# from jaraco.text 3.5
67+
class FoldedCase(str):
68+
"""
69+
A case insensitive string class; behaves just like str
70+
except compares equal when the only variation is case.
71+
72+
>>> s = FoldedCase('hello world')
73+
74+
>>> s == 'Hello World'
75+
True
76+
77+
>>> 'Hello World' == s
78+
True
79+
80+
>>> s != 'Hello World'
81+
False
82+
83+
>>> s.index('O')
84+
4
85+
86+
>>> s.split('O')
87+
['hell', ' w', 'rld']
88+
89+
>>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
90+
['alpha', 'Beta', 'GAMMA']
91+
92+
Sequence membership is straightforward.
93+
94+
>>> "Hello World" in [s]
95+
True
96+
>>> s in ["Hello World"]
97+
True
98+
99+
You may test for set inclusion, but candidate and elements
100+
must both be folded.
101+
102+
>>> FoldedCase("Hello World") in {s}
103+
True
104+
>>> s in {FoldedCase("Hello World")}
105+
True
106+
107+
String inclusion works as long as the FoldedCase object
108+
is on the right.
109+
110+
>>> "hello" in FoldedCase("Hello World")
111+
True
112+
113+
But not if the FoldedCase object is on the left:
114+
115+
>>> FoldedCase('hello') in 'Hello World'
116+
False
117+
118+
In that case, use in_:
119+
120+
>>> FoldedCase('hello').in_('Hello World')
121+
True
122+
123+
>>> FoldedCase('hello') > FoldedCase('Hello')
124+
False
125+
"""
126+
127+
def __lt__(self, other):
128+
return self.lower() < other.lower()
129+
130+
def __gt__(self, other):
131+
return self.lower() > other.lower()
132+
133+
def __eq__(self, other):
134+
return self.lower() == other.lower()
135+
136+
def __ne__(self, other):
137+
return self.lower() != other.lower()
138+
139+
def __hash__(self):
140+
return hash(self.lower())
141+
142+
def __contains__(self, other):
143+
return super(FoldedCase, self).lower().__contains__(other.lower())
144+
145+
def in_(self, other):
146+
"Does self appear in other?"
147+
return self in FoldedCase(other)
148+
149+
# cache lower since it's likely to be called frequently.
150+
@method_cache
151+
def lower(self):
152+
return super(FoldedCase, self).lower()
153+
154+
def index(self, sub):
155+
return self.lower().index(sub.lower())
156+
157+
def split(self, splitter=' ', maxsplit=0):
158+
pattern = re.compile(re.escape(splitter), re.I)
159+
return pattern.split(self, maxsplit)

0 commit comments

Comments
 (0)