use pep508_rs::marker::{MarkerEnvironment, MarkerEnvironmentBuilder};
use pep508_rs::{Requirement, VerbatimUrl};
use std::env;
use std::error::Error;
use std::str::FromStr;
pub fn build_marker_environment(python_version: &str) -> Result<MarkerEnvironment, Box<dyn Error>> {
let os_name = if cfg!(windows) { "nt" } else { "posix" };
let sys_platform = if cfg!(windows) {
"win32"
} else if cfg!(target_os = "macos") {
"darwin"
} else if cfg!(target_os = "linux") {
"linux"
} else {
env::consts::OS
};
let platform_machine = env::consts::ARCH;
let platform_system = if cfg!(windows) {
"Windows"
} else if cfg!(target_os = "macos") {
"Darwin"
} else if cfg!(target_os = "linux") {
"Linux"
} else {
env::consts::OS
};
let version_parts: Vec<&str> = python_version.split('.').collect();
let python_full_version = if version_parts.len() >= 3 {
python_version.to_string()
} else if version_parts.len() == 2 {
format!("{}.0", python_version)
} else {
format!("{}.0.0", python_version)
};
let python_version_short = if version_parts.len() >= 2 {
format!("{}.{}", version_parts[0], version_parts[1])
} else {
python_version.to_string()
};
let builder = MarkerEnvironmentBuilder {
implementation_name: "cpython",
implementation_version: &python_full_version,
os_name,
platform_machine,
platform_python_implementation: "CPython",
platform_release: "",
platform_system,
platform_version: "",
python_full_version: &python_full_version,
python_version: &python_version_short,
sys_platform,
};
let env = MarkerEnvironment::try_from(builder)?;
Ok(env)
}
pub fn should_include_requirement(requirement_str: &str, marker_env: &MarkerEnvironment) -> bool {
let requirement = match Requirement::<VerbatimUrl>::from_str(requirement_str) {
Ok(req) => req,
Err(e) => {
eprintln!("Warning: Failed to parse requirement '{}': {}", requirement_str, e);
return true;
}
};
requirement.evaluate_markers(marker_env, &[])
}
pub fn extract_package_name(requirement_str: &str) -> String {
if let Ok(req) = Requirement::<VerbatimUrl>::from_str(requirement_str) {
return req.name.to_string();
}
requirement_str
.split([';', '(', '<', '>', '=', '!', ' ', '['])
.next()
.unwrap_or(requirement_str)
.trim()
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_marker_environment() {
let env = build_marker_environment("3.12");
assert!(env.is_ok());
}
#[test]
fn test_extract_package_name_simple() {
assert_eq!(extract_package_name("requests"), "requests");
}
#[test]
fn test_extract_package_name_with_version() {
assert_eq!(extract_package_name("requests>=2.0.0"), "requests");
}
#[test]
fn test_extract_package_name_with_marker() {
assert_eq!(
extract_package_name("requests>=2.0.0; python_version>='3.8'"),
"requests"
);
}
#[test]
fn test_should_include_no_marker() {
let env = build_marker_environment("3.12").unwrap();
assert!(should_include_requirement("requests>=2.0.0", &env));
}
#[test]
#[cfg(windows)]
fn test_should_include_win32_on_windows() {
let env = build_marker_environment("3.12").unwrap();
assert!(should_include_requirement("pywin32; sys_platform=='win32'", &env));
}
#[test]
#[cfg(windows)]
fn test_should_exclude_linux_on_windows() {
let env = build_marker_environment("3.12").unwrap();
assert!(!should_include_requirement("uvloop; sys_platform=='linux'", &env));
}
#[test]
fn test_should_include_python_version_match() {
let env = build_marker_environment("3.12").unwrap();
assert!(should_include_requirement("package; python_version>='3.8'", &env));
}
#[test]
fn test_should_exclude_old_python_version() {
let env = build_marker_environment("3.12").unwrap();
assert!(!should_include_requirement("package; python_version<'3.10'", &env));
}
#[test]
fn test_should_handle_combined_markers() {
let env = build_marker_environment("3.12").unwrap();
let result = should_include_requirement("package; python_version>='3.8' and os_name=='nt'", &env);
if cfg!(windows) {
assert!(result);
} else {
assert!(!result);
}
}
}