Skip to content

Commit 33ea676

Browse files
authored
Merge pull request #299 from python/feature/unify-parser
Unify section parser implementations.
2 parents 50a1549 + 244fc48 commit 33ea676

File tree

3 files changed

+51
-53
lines changed

3 files changed

+51
-53
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v3.10.0
2+
=======
3+
4+
* #295: Internal refactoring to unify section parsing logic.
5+
16
v3.9.1
27
======
38

importlib_metadata/__init__.py

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import contextlib
1616
import collections
1717

18-
from ._collections import FreezableDefaultDict
18+
from ._collections import FreezableDefaultDict, Pair
1919
from ._compat import (
2020
NullFinder,
2121
Protocol,
@@ -64,17 +64,27 @@ class Sectioned:
6464
"""
6565
A simple entry point config parser for performance
6666
67-
>>> res = Sectioned.get_sections(Sectioned._sample)
68-
>>> sec, values = next(res)
69-
>>> sec
67+
>>> for item in Sectioned.read(Sectioned._sample):
68+
... print(item)
69+
Pair(name='sec1', value='# comments ignored')
70+
Pair(name='sec1', value='a = 1')
71+
Pair(name='sec1', value='b = 2')
72+
Pair(name='sec2', value='a = 2')
73+
74+
>>> res = Sectioned.section_pairs(Sectioned._sample)
75+
>>> item = next(res)
76+
>>> item.name
7077
'sec1'
71-
>>> [(key, value) for key, value in values]
72-
[('a', '1'), ('b', '2')]
73-
>>> sec, values = next(res)
74-
>>> sec
78+
>>> item.value
79+
Pair(name='a', value='1')
80+
>>> item = next(res)
81+
>>> item.value
82+
Pair(name='b', value='2')
83+
>>> item = next(res)
84+
>>> item.name
7585
'sec2'
76-
>>> [(key, value) for key, value in values]
77-
[('a', '2')]
86+
>>> item.value
87+
Pair(name='a', value='2')
7888
>>> list(res)
7989
[]
8090
"""
@@ -91,32 +101,28 @@ class Sectioned:
91101
"""
92102
).lstrip()
93103

94-
def __init__(self):
95-
self.section = None
96-
97-
def __call__(self, line):
98-
if line.startswith('[') and line.endswith(']'):
99-
# new section
100-
self.section = line.strip('[]')
101-
return
102-
return self.section
103-
104104
@classmethod
105-
def get_sections(cls, text):
106-
lines = filter(cls.valid, map(str.strip, text.splitlines()))
105+
def section_pairs(cls, text):
107106
return (
108-
(section, map(cls.parse_value, values))
109-
for section, values in itertools.groupby(lines, cls())
110-
if section is not None
107+
section._replace(value=Pair.parse(section.value))
108+
for section in cls.read(text, filter_=cls.valid)
109+
if section.name is not None
111110
)
112111

113112
@staticmethod
114-
def valid(line):
115-
return line and not line.startswith('#')
113+
def read(text, filter_=None):
114+
lines = filter(filter_, map(str.strip, text.splitlines()))
115+
name = None
116+
for value in lines:
117+
section_match = value.startswith('[') and value.endswith(']')
118+
if section_match:
119+
name = value.strip('[]')
120+
continue
121+
yield Pair(name, value)
116122

117123
@staticmethod
118-
def parse_value(line):
119-
return map(str.strip, line.split("=", 1))
124+
def valid(line):
125+
return line and not line.startswith('#')
120126

121127

122128
class EntryPoint(
@@ -255,9 +261,8 @@ def _from_text(cls, text):
255261
@staticmethod
256262
def _parse_groups(text):
257263
return (
258-
(name, value, section)
259-
for section, values in Sectioned.get_sections(text)
260-
for name, value in values
264+
(item.value.name, item.value.value, item.name)
265+
for item in Sectioned.section_pairs(text)
261266
)
262267

263268

@@ -573,24 +578,7 @@ def _read_egg_info_reqs(self):
573578

574579
@classmethod
575580
def _deps_from_requires_text(cls, source):
576-
section_pairs = cls._read_sections(source.splitlines())
577-
sections = {
578-
section: list(map(operator.itemgetter('line'), results))
579-
for section, results in itertools.groupby(
580-
section_pairs, operator.itemgetter('section')
581-
)
582-
}
583-
return cls._convert_egg_info_reqs_to_simple_reqs(sections)
584-
585-
@staticmethod
586-
def _read_sections(lines):
587-
section = None
588-
for line in filter(None, lines):
589-
section_match = re.match(r'\[(.*)\]$', line)
590-
if section_match:
591-
section = section_match.group(1)
592-
continue
593-
yield locals()
581+
return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source))
594582

595583
@staticmethod
596584
def _convert_egg_info_reqs_to_simple_reqs(sections):
@@ -615,9 +603,8 @@ def parse_condition(section):
615603
conditions = list(filter(None, [markers, make_condition(extra)]))
616604
return '; ' + ' and '.join(conditions) if conditions else ''
617605

618-
for section, deps in sections.items():
619-
for dep in deps:
620-
yield dep + parse_condition(section)
606+
for section in sections:
607+
yield section.value + parse_condition(section.name)
621608

622609

623610
class DistributionFinder(MetaPathFinder):

importlib_metadata/_collections.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,9 @@ def __missing__(self, key):
2222

2323
def freeze(self):
2424
self._frozen = lambda key: self.default_factory()
25+
26+
27+
class Pair(collections.namedtuple('Pair', 'name value')):
28+
@classmethod
29+
def parse(cls, text):
30+
return cls(*map(str.strip, text.split("=", 1)))

0 commit comments

Comments
 (0)