Skip to content

Commit

Permalink
Added the first cut
Browse files Browse the repository at this point in the history
- parse editables (svn+http...)
- parse file URIs
- parse "normal"
- parse extras
  • Loading branch information
davidfischer committed Nov 24, 2012
1 parent 819de98 commit d04253a
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 6 deletions.
8 changes: 3 additions & 5 deletions README.rst
Expand Up @@ -16,9 +16,7 @@ Reqfile Parser can parse a file-like object or a text string.
>>> import pprint
>>> with open('requirements.txt', 'rb') as f:
... pprint.pprint(reqfileparser.parse(f)))
[
('requests', '>=', '0.14.1'),
('requests-oath2', None, None),
('Django', '==', '1.4.2'),
]
[{'extras': None, 'name': 'requests', 'operator': '>=', 'version': '0.14.1'},
{'extras': None, 'name': 'requests-oath2', 'operator': None, 'version': None},
{'extras': None, 'name': 'Django', 'operator': '==', 'version': '1.4.2'}]

52 changes: 51 additions & 1 deletion reqfileparser/parser.py
@@ -1,5 +1,55 @@
import re
import warnings

# See pip/req.py:parse_requirements()
def parse(reqstr):
requirements = []

if not isinstance(reqstr, basestring):
reqstr = reqstr.read()

return []
for line in reqstr.splitlines():
line = line.strip()
if len(line[:1]) == 0:
continue
elif not line or line.startswith('#'):
continue
elif line.startswith('-r') or line.startswith('--requirement'):
warnings.warn('Recursive requirements are not supported. Skipping.')
continue
elif line.startswith('-f') or line.startswith('--find-links') or \
line.startswith('-i') or line.startswith('--index-url') or \
line.startswith('--extra-index-url') or \
line.startswith('--no-index'):
warnings.warn('Private repos are not supported. Skipping.')
continue
elif line.startswith('-Z') or line.startswith('--always-unzip'):
warnings.warn('Unused option --always-unzip. Skipping.')
continue
elif line.startswith('-e') or line.startswith('--editable'):
if line.startswith('-e'):
tmpstr = line[len('-e'):].strip()
else:
tmpstr = line[len('--editable'):].strip()
match = re.match(
r'^((?P<vcs>svn|git|bzr|hg)\+)?'
'(?P<uri>[^#&]+)'
'#egg=(?P<name>[^&]+)$', tmpstr, re.MULTILINE)
elif line.startswith('file:'):
match = re.match(
r'^(?P<path>[^#]+)'
'#egg=(?P<name>[^&]+)$', line, re.MULTILINE)
else:
match = re.match(
r'^(?P<name>[A-Za-z0-9\-_]+)'
'(\[(?P<extras>[A-Za-z0-9\-\_]+)\])?'
'(?P<operator>==|>=|>|<=|<=)?'
'(?P<version>[A-Za-z0-9\.]+)?$', line, re.MULTILINE)

if match:
requirements.append(match.groupdict())
else:
raise ValueError('Invalid requirement line "%s"' %line)

return requirements

33 changes: 33 additions & 0 deletions tests/test_parser.py
Expand Up @@ -7,4 +7,37 @@ class TestParser(unittest.TestCase):
def test_empty(self):
self.assertEqual(parse(''), [])
self.assertEqual(parse(StringIO('')), [])

def test_comment(self):
self.assertEqual(parse('#comment>>1.2.'), [])

def test_invalid(self):
# invalid operators
self.assertRaises(ValueError, parse, 'test>>1.2.0')
self.assertRaises(ValueError, parse, 'test=>1.2.0')

# invalid editables
self.assertRaises(ValueError, parse, 'novcs+http://example.com#egg=a')
self.assertRaises(ValueError, parse, 'svn+http://example.com#egg')

def test_editable(self):
out = parse('-e svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject')
self.assertEqual(out[0]['vcs'], 'svn')
self.assertEqual(out[0]['name'], 'MyProject')
self.assertEqual(out[0]['uri'], 'svn://svn.myproject.org/svn/MyProject')

def test_file(self):
out = parse('file:///path/to/your/lib/project#egg=MyProject')
self.assertEqual(out[0]['name'], 'MyProject')
self.assertEqual(out[0]['path'], 'file:///path/to/your/lib/project')

def test_normal(self):
out = parse('MyPackage')
self.assertEqual(out[0]['name'], 'MyPackage')

def test_extras(self):
out = parse('MyPackage[PDF]==3.0')
self.assertEqual(out[0]['name'], 'MyPackage')
self.assertEqual(out[0]['extras'], 'PDF')
self.assertEqual(out[0]['operator'], '==')
self.assertEqual(out[0]['version'], '3.0')

0 comments on commit d04253a

Please sign in to comment.