Shivam311's picture
feat: CodeAtlas Enterprise - IBM Bob Engineering Intelligence Platform
3a7842d
Raw
History Blame Contribute Delete
5.3 kB
"""Parse dependency files from common package managers."""
from __future__ import annotations
import json
import os
import re
import xml.etree.ElementTree as ET
import toml
import yaml
def parse_dependencies(repo_path: str) -> dict:
deps = {"npm": [], "pip": [], "maven": [], "gem": [], "go": [], "cargo": [], "composer": []}
deps["npm"] = _parse_package_json(os.path.join(repo_path, "package.json"))
deps["pip"] = _parse_requirements(os.path.join(repo_path, "requirements.txt"))
deps["pip"].extend(_parse_pyproject(os.path.join(repo_path, "pyproject.toml")))
deps["maven"] = _parse_pom(os.path.join(repo_path, "pom.xml"))
deps["gem"] = _parse_gemfile(os.path.join(repo_path, "Gemfile"))
deps["go"] = _parse_go_mod(os.path.join(repo_path, "go.mod"))
deps["cargo"] = _parse_cargo(os.path.join(repo_path, "Cargo.toml"))
deps["composer"] = _parse_composer(os.path.join(repo_path, "composer.json"))
return {key: sorted(set(value)) for key, value in deps.items()}
def _parse_package_json(path: str) -> list[str]:
if not os.path.exists(path):
return []
try:
with open(path, "r", encoding="utf-8") as file:
pkg = json.load(file)
merged = {}
for key in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
merged.update(pkg.get(key, {}))
return list(merged.keys())
except (OSError, json.JSONDecodeError):
return []
def _parse_requirements(path: str) -> list[str]:
if not os.path.exists(path):
return []
deps = []
try:
with open(path, "r", encoding="utf-8", errors="ignore") as file:
for raw_line in file:
line = raw_line.strip()
if not line or line.startswith("#") or line.startswith(("-r", "--")):
continue
package = re.split(r"[<>=~!\[]", line, maxsplit=1)[0].strip()
if package:
deps.append(package)
except OSError:
return []
return deps
def _parse_pyproject(path: str) -> list[str]:
if not os.path.exists(path):
return []
try:
data = toml.load(path)
except Exception:
return []
deps = []
project = data.get("project", {})
for dep in project.get("dependencies", []):
package = re.split(r"[<>=~!\[]", dep, maxsplit=1)[0].strip()
if package:
deps.append(package)
poetry_deps = data.get("tool", {}).get("poetry", {}).get("dependencies", {})
deps.extend(key for key in poetry_deps.keys() if key.lower() != "python")
return deps
def _parse_pom(path: str) -> list[str]:
if not os.path.exists(path):
return []
try:
tree = ET.parse(path)
root = tree.getroot()
deps = []
for dependency in root.findall(".//{*}dependency"):
group = dependency.find("{*}groupId")
artifact = dependency.find("{*}artifactId")
if artifact is not None:
deps.append(f"{group.text if group is not None else ''}:{artifact.text}".strip(":"))
return deps
except Exception:
return []
def _parse_gemfile(path: str) -> list[str]:
if not os.path.exists(path):
return []
deps = []
try:
with open(path, "r", encoding="utf-8", errors="ignore") as file:
for line in file:
match = re.match(r"\s*gem\s+['\"]([^'\"]+)['\"]", line)
if match:
deps.append(match.group(1))
except OSError:
return []
return deps
def _parse_go_mod(path: str) -> list[str]:
if not os.path.exists(path):
return []
deps = []
try:
with open(path, "r", encoding="utf-8", errors="ignore") as file:
for line in file:
stripped = line.strip()
if not stripped or stripped.startswith(("module ", "go ", "//", "require (", ")")):
continue
if stripped.startswith("require "):
parts = stripped.split()
if len(parts) >= 2:
deps.append(parts[1])
elif "/" in stripped:
deps.append(stripped.split()[0])
except OSError:
return []
return deps
def _parse_cargo(path: str) -> list[str]:
if not os.path.exists(path):
return []
try:
data = toml.load(path)
return list(data.get("dependencies", {}).keys()) + list(data.get("dev-dependencies", {}).keys())
except Exception:
return []
def _parse_composer(path: str) -> list[str]:
if not os.path.exists(path):
return []
try:
with open(path, "r", encoding="utf-8") as file:
data = json.load(file)
merged = {}
merged.update(data.get("require", {}))
merged.update(data.get("require-dev", {}))
return [key for key in merged.keys() if key != "php"]
except (OSError, json.JSONDecodeError):
return []
def parse_yaml_file(path: str) -> dict:
if not os.path.exists(path):
return {}
try:
with open(path, "r", encoding="utf-8", errors="ignore") as file:
return yaml.safe_load(file) or {}
except Exception:
return {}