Add package manager parsers (npm, pip, go, cargo)
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
124
src/depcheck/parsers/pip.py
Normal file
124
src/depcheck/parsers/pip.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
"""Pip requirements.txt and pyproject.toml parser."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import toml
|
||||||
|
|
||||||
|
from depcheck.models import Dependency, PackageManager
|
||||||
|
from depcheck.parsers import Parser
|
||||||
|
from depcheck.utils import parse_version_string
|
||||||
|
|
||||||
|
|
||||||
|
class PipParser(Parser):
|
||||||
|
"""Parser for pip requirements.txt and pyproject.toml files."""
|
||||||
|
|
||||||
|
package_manager = PackageManager.PIP
|
||||||
|
|
||||||
|
def supports_file(self, file_path: Path) -> bool:
|
||||||
|
return file_path.name in ("requirements.txt", "pyproject.toml")
|
||||||
|
|
||||||
|
def get_file_patterns(self) -> list[str]:
|
||||||
|
return ["requirements.txt", "pyproject.toml"]
|
||||||
|
|
||||||
|
def parse(self, file_path: Path) -> list[Dependency]:
|
||||||
|
if file_path.name == "requirements.txt":
|
||||||
|
return self._parse_requirements_txt(file_path)
|
||||||
|
elif file_path.name == "pyproject.toml":
|
||||||
|
return self._parse_pyproject_toml(file_path)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _parse_requirements_txt(self, file_path: Path) -> list[Dependency]:
|
||||||
|
dependencies: list[Dependency] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = file_path.read_text()
|
||||||
|
except OSError:
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
for line in content.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = self._parse_requirement_line(line)
|
||||||
|
if match:
|
||||||
|
name, version = match
|
||||||
|
dependencies.append(
|
||||||
|
Dependency(
|
||||||
|
name=name,
|
||||||
|
current_version=version,
|
||||||
|
package_manager=self.package_manager,
|
||||||
|
category="dependencies",
|
||||||
|
source_file=str(file_path),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
def _parse_requirement_line(self, line: str) -> tuple[str, str] | None:
|
||||||
|
line = line.split("#")[0].strip()
|
||||||
|
line = line.split(";")[0].strip()
|
||||||
|
|
||||||
|
patterns = [
|
||||||
|
r"^([a-zA-Z0-9_-]+)==([a-zA-Z0-9._-]+)$",
|
||||||
|
r"^([a-zA-Z0-9_-]+)>=([a-zA-Z0-9._-]+)$",
|
||||||
|
r"^([a-zA-Z0-9_-]+)<=([a-zA-Z0-9._-]+)$",
|
||||||
|
r"^([a-zA-Z0-9_-]+)~=([a-zA-Z0-9._-]+)$",
|
||||||
|
r"^([a-zA-Z0-9_-]+)!=([a-zA-Z0-9._-]+)$",
|
||||||
|
r"^([a-zA-Z0-9_-]+)$",
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
match = re.match(pattern, line)
|
||||||
|
if match:
|
||||||
|
name = match.group(1)
|
||||||
|
lastindex = match.lastindex
|
||||||
|
version = match.group(2) if lastindex is not None and lastindex >= 2 else "latest"
|
||||||
|
version = parse_version_string(version)
|
||||||
|
return (name, version) if version else (name, "unknown")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _parse_pyproject_toml(self, file_path: Path) -> list[Dependency]:
|
||||||
|
dependencies: list[Dependency] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = file_path.read_text()
|
||||||
|
data = toml.loads(content)
|
||||||
|
except (toml.TomlDecodeError, OSError):
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
project_section = data.get("project", {})
|
||||||
|
for key in ["dependencies", "optional-dependencies"]:
|
||||||
|
section = project_section.get(key, [])
|
||||||
|
if isinstance(section, list):
|
||||||
|
for item in section:
|
||||||
|
if isinstance(item, str):
|
||||||
|
match = self._parse_requirement_line(item)
|
||||||
|
if match:
|
||||||
|
name, version = match
|
||||||
|
dependencies.append(
|
||||||
|
Dependency(
|
||||||
|
name=name,
|
||||||
|
current_version=version,
|
||||||
|
package_manager=self.package_manager,
|
||||||
|
category="dependencies",
|
||||||
|
source_file=str(file_path),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
for name, version in item.items():
|
||||||
|
if isinstance(version, str):
|
||||||
|
parsed_version = parse_version_string(version)
|
||||||
|
dependencies.append(
|
||||||
|
Dependency(
|
||||||
|
name=name,
|
||||||
|
current_version=parsed_version or "unknown",
|
||||||
|
package_manager=self.package_manager,
|
||||||
|
category="dependencies",
|
||||||
|
source_file=str(file_path),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return dependencies
|
||||||
Reference in New Issue
Block a user