mod pip_conf;
mod pyproject;
mod requirements;
pub(crate) use pip_conf::{analyze_pip_conf, pip_conf_capabilities, pip_conf_relations};
pub(crate) use pyproject::{
analyze_pyproject_toml, pyproject_expected_lockfiles, pyproject_toml_capabilities,
};
pub(crate) use requirements::{analyze_requirements_txt, requirements_txt_capabilities};
pub(super) const PYTHON_NETWORK_DEPS: &[&str] = &[
"requests",
"httpx",
"aiohttp",
"urllib3",
"paramiko",
"grpcio",
"websockets",
"tornado",
];
pub(super) const PYTHON_EXEC_DEPS: &[&str] = &["subprocess32", "pexpect", "fabric", "invoke"];
pub(super) const PYTHON_VCS_PREFIXES: &[&str] = &["git+", "hg+", "svn+", "bzr+"];
pub(super) fn parse_python_dep_name(line: &str) -> Option<String> {
if let Some((name, _)) = line.split_once(" @ ") {
let trimmed = name.trim();
if !trimmed.is_empty() {
return Some(canonical_python_name(trimmed));
}
}
if PYTHON_VCS_PREFIXES.iter().any(|p| line.starts_with(p)) {
for fragment in line.split('#').skip(1) {
for kv in fragment.split('&') {
if let Some(value) = kv.trim().strip_prefix("egg=") {
let name = value.split(['[', ';']).next().unwrap_or("").trim();
if !name.is_empty() {
return Some(canonical_python_name(name));
}
}
}
}
return None;
}
let name = line
.split(['=', '>', '<', '~', '!', '[', ';', ' '])
.next()
.unwrap_or("")
.trim();
if name.is_empty() {
return None;
}
Some(canonical_python_name(name))
}
fn canonical_python_name(name: &str) -> String {
name.to_ascii_lowercase()
}