99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
from dparse.parser import setuptools_parse_requirements_backport as _parse_requirements
|
|
from collections import namedtuple
|
|
import click
|
|
import sys
|
|
import json
|
|
import os
|
|
Package = namedtuple("Package", ["key", "version"])
|
|
RequirementFile = namedtuple("RequirementFile", ["path"])
|
|
|
|
|
|
def read_vulnerabilities(fh):
|
|
return json.load(fh)
|
|
|
|
|
|
def iter_lines(fh, lineno=0):
|
|
for line in fh.readlines()[lineno:]:
|
|
yield line
|
|
|
|
|
|
def parse_line(line):
|
|
if line.startswith('-e') or line.startswith('http://') or line.startswith('https://'):
|
|
if "#egg=" in line:
|
|
line = line.split("#egg=")[-1]
|
|
return _parse_requirements(line)
|
|
|
|
|
|
def read_requirements(fh, resolve=False):
|
|
"""
|
|
Reads requirements from a file like object and (optionally) from referenced files.
|
|
:param fh: file like object to read from
|
|
:param resolve: boolean. resolves referenced files.
|
|
:return: generator
|
|
"""
|
|
is_temp_file = not hasattr(fh, 'name')
|
|
for num, line in enumerate(iter_lines(fh)):
|
|
line = line.strip()
|
|
if not line:
|
|
# skip empty lines
|
|
continue
|
|
if line.startswith('#') or \
|
|
line.startswith('-i') or \
|
|
line.startswith('--index-url') or \
|
|
line.startswith('--extra-index-url') or \
|
|
line.startswith('-f') or line.startswith('--find-links') or \
|
|
line.startswith('--no-index') or line.startswith('--allow-external') or \
|
|
line.startswith('--allow-unverified') or line.startswith('-Z') or \
|
|
line.startswith('--always-unzip'):
|
|
# skip unsupported lines
|
|
continue
|
|
elif line.startswith('-r') or line.startswith('--requirement'):
|
|
# got a referenced file here, try to resolve the path
|
|
# if this is a tempfile, skip
|
|
if is_temp_file:
|
|
continue
|
|
filename = line.strip("-r ").strip("--requirement").strip()
|
|
# if there is a comment, remove it
|
|
if " #" in filename:
|
|
filename = filename.split(" #")[0].strip()
|
|
req_file_path = os.path.join(os.path.dirname(fh.name), filename)
|
|
if resolve:
|
|
# recursively yield the resolved requirements
|
|
if os.path.exists(req_file_path):
|
|
with open(req_file_path) as _fh:
|
|
for req in read_requirements(_fh, resolve=True):
|
|
yield req
|
|
else:
|
|
yield RequirementFile(path=req_file_path)
|
|
else:
|
|
try:
|
|
parseable_line = line
|
|
# multiline requirements are not parseable
|
|
if "\\" in line:
|
|
parseable_line = line.replace("\\", "")
|
|
for next_line in iter_lines(fh, num + 1):
|
|
parseable_line += next_line.strip().replace("\\", "")
|
|
line += "\n" + next_line
|
|
if "\\" in next_line:
|
|
continue
|
|
break
|
|
req, = parse_line(parseable_line)
|
|
if len(req.specifier._specs) == 1 and \
|
|
next(iter(req.specifier._specs))._spec[0] == "==":
|
|
yield Package(key=req.name, version=next(iter(req.specifier._specs))._spec[1])
|
|
else:
|
|
try:
|
|
fname = fh.name
|
|
except AttributeError:
|
|
fname = line
|
|
|
|
click.secho(
|
|
"Warning: unpinned requirement '{req}' found in {fname}, "
|
|
"unable to check.".format(req=req.name,
|
|
fname=fname),
|
|
fg="yellow",
|
|
file=sys.stderr
|
|
)
|
|
except ValueError:
|
|
continue
|