blob: e246379bcc48523e3e1cca2b5510472fd80ddd72 [file] [log] [blame]
import re
from typing import Dict, Set, Tuple, Optional
def parse_extras(requirements_path: str) -> Dict[str, Set[str]]:
"""Parse over the requirements.txt file to find extras requested.
Args:
requirements_path: The filepath for the requirements.txt file to parse.
Returns:
A dictionary mapping the requirement name to a set of extras requested.
"""
extras_requested = {}
with open(requirements_path, "r") as requirements:
# Merge all backslash line continuations so we parse each requirement as a single line.
for line in requirements.read().replace("\\\n", "").split("\n"):
requirement, extras = _parse_requirement_for_extra(line)
if requirement and extras:
extras_requested[requirement] = extras
return extras_requested
def _parse_requirement_for_extra(
requirement: str,
) -> Tuple[Optional[str], Optional[Set[str]]]:
"""Given a requirement string, returns the requirement name and set of extras, if extras specified.
Else, returns (None, None)
"""
# https://www.python.org/dev/peps/pep-0508/#grammar
extras_pattern = re.compile(
r"^\s*([0-9A-Za-z][0-9A-Za-z_.\-]*)\s*\[\s*([0-9A-Za-z][0-9A-Za-z_.\-]*(?:\s*,\s*[0-9A-Za-z][0-9A-Za-z_.\-]*)*)\s*\]"
)
matches = extras_pattern.match(requirement)
if matches:
return (
matches.group(1),
{extra.strip() for extra in matches.group(2).split(",")},
)
return None, None