@@ -2404,7 +2404,20 @@ def _nonblank(str):
2404
2404
2405
2405
@functools .singledispatch
2406
2406
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
+ """
2408
2421
return itertools .chain .from_iterable (map (yield_lines , iterable ))
2409
2422
2410
2423
@@ -3079,26 +3092,61 @@ def issue_warning(*args, **kw):
3079
3092
warnings .warn (stacklevel = level + 1 , * args , ** kw )
3080
3093
3081
3094
3082
- def parse_requirements (strs ):
3083
- """Yield ``Requirement`` objects for each specification in `strs`
3095
+ def drop_comment (line ):
3096
+ """
3097
+ Drop comments.
3084
3098
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'
3086
3106
"""
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.
3089
3113
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 ('\\ ' ):
3097
3136
try :
3098
- line += next (lines )
3137
+ item = item [: - 2 ]. strip () + next (lines )
3099
3138
except StopIteration :
3100
3139
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 ))))
3102
3150
3103
3151
3104
3152
class RequirementParseError (packaging .requirements .InvalidRequirement ):
0 commit comments