Skip to content

Commit 19c1dc0

Browse files
authored
Merge pull request #3045 from pypa/feature/refactor-parse-requirements
Feature/refactor parse requirements
2 parents aa3d9b9 + 3eca992 commit 19c1dc0

File tree

1 file changed

+63
-15
lines changed

1 file changed

+63
-15
lines changed

pkg_resources/__init__.py

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,7 +2404,20 @@ def _nonblank(str):
24042404

24052405
@functools.singledispatch
24062406
def yield_lines(iterable):
2407-
"""Yield valid lines of a string or iterable"""
2407+
r"""
2408+
Yield valid lines of a string or iterable.
2409+
2410+
>>> list(yield_lines(''))
2411+
[]
2412+
>>> list(yield_lines(['foo', 'bar']))
2413+
['foo', 'bar']
2414+
>>> list(yield_lines('foo\nbar'))
2415+
['foo', 'bar']
2416+
>>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))
2417+
['foo', 'baz #comment']
2418+
>>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))
2419+
['foo', 'bar', 'baz', 'bing']
2420+
"""
24082421
return itertools.chain.from_iterable(map(yield_lines, iterable))
24092422

24102423

@@ -3079,26 +3092,61 @@ def issue_warning(*args, **kw):
30793092
warnings.warn(stacklevel=level + 1, *args, **kw)
30803093

30813094

3082-
def parse_requirements(strs):
3083-
"""Yield ``Requirement`` objects for each specification in `strs`
3095+
def drop_comment(line):
3096+
"""
3097+
Drop comments.
30843098
3085-
`strs` must be a string, or a (possibly-nested) iterable thereof.
3099+
>>> drop_comment('foo # bar')
3100+
'foo'
3101+
3102+
A hash without a space may be in a URL.
3103+
3104+
>>> drop_comment('http://example.com/foo#bar')
3105+
'http://example.com/foo#bar'
30863106
"""
3087-
# create a steppable iterator, so we can handle \-continuations
3088-
lines = iter(yield_lines(strs))
3107+
return line.partition(' #')[0]
3108+
3109+
3110+
def join_continuation(lines):
3111+
r"""
3112+
Join lines continued by a trailing backslash.
30893113
3090-
for line in lines:
3091-
# Drop comments -- a hash without a space may be in a URL.
3092-
if ' #' in line:
3093-
line = line[:line.find(' #')]
3094-
# If there is a line continuation, drop it, and append the next line.
3095-
if line.endswith('\\'):
3096-
line = line[:-2].strip()
3114+
>>> list(join_continuation(['foo \\', 'bar', 'baz']))
3115+
['foobar', 'baz']
3116+
>>> list(join_continuation(['foo \\', 'bar', 'baz']))
3117+
['foobar', 'baz']
3118+
>>> list(join_continuation(['foo \\', 'bar \\', 'baz']))
3119+
['foobarbaz']
3120+
3121+
Not sure why, but...
3122+
The character preceeding the backslash is also elided.
3123+
3124+
>>> list(join_continuation(['goo\\', 'dly']))
3125+
['godly']
3126+
3127+
A terrible idea, but...
3128+
If no line is available to continue, suppress the lines.
3129+
3130+
>>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
3131+
['foo']
3132+
"""
3133+
lines = iter(lines)
3134+
for item in lines:
3135+
while item.endswith('\\'):
30973136
try:
3098-
line += next(lines)
3137+
item = item[:-2].strip() + next(lines)
30993138
except StopIteration:
31003139
return
3101-
yield Requirement(line)
3140+
yield item
3141+
3142+
3143+
def parse_requirements(strs):
3144+
"""
3145+
Yield ``Requirement`` objects for each specification in `strs`.
3146+
3147+
`strs` must be a string, or a (possibly-nested) iterable thereof.
3148+
"""
3149+
return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs))))
31023150

31033151

31043152
class RequirementParseError(packaging.requirements.InvalidRequirement):

0 commit comments

Comments
 (0)