pub mod doctor;
mod metadata;
pub mod migrate;
mod version;
mod virtualenv;
pub use metadata::Metadata;
pub use version::VersionService;
pub use virtualenv::VirtualenvService;
pub const SCOOP_ACTIVE_ENV: &str = "SCOOP_ACTIVE";
pub fn parse_pyvenv_version(venv_path: &std::path::Path) -> Option<String> {
let cfg_path = venv_path.join("pyvenv.cfg");
let content = std::fs::read_to_string(&cfg_path).ok()?;
content.lines().find_map(pyvenv_version_from_line)
}
pub(crate) fn pyvenv_version_from_line(line: &str) -> Option<String> {
let (key, value) = line.split_once('=')?;
matches!(key.trim(), "version" | "version_info").then(|| normalize_pyvenv_version(value.trim()))
}
fn normalize_pyvenv_version(raw: &str) -> String {
let numeric: Vec<&str> = raw
.split('.')
.take_while(|seg| !seg.is_empty() && seg.bytes().all(|b| b.is_ascii_digit()))
.take(3)
.collect();
if numeric.is_empty() {
raw.to_string()
} else {
numeric.join(".")
}
}
pub fn get_active_env() -> Option<String> {
std::env::var(SCOOP_ACTIVE_ENV).ok()
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn venv_with_cfg(contents: &str) -> tempfile::TempDir {
let dir = tempfile::TempDir::new().unwrap();
let mut f = std::fs::File::create(dir.path().join("pyvenv.cfg")).unwrap();
f.write_all(contents.as_bytes()).unwrap();
dir
}
#[test]
fn parse_pyvenv_version_reads_stdlib_version_key() {
let dir = venv_with_cfg("home = /usr/bin\nversion = 3.12.12\n");
assert_eq!(
parse_pyvenv_version(dir.path()),
Some("3.12.12".to_string())
);
}
#[test]
fn parse_pyvenv_version_reads_uv_version_info_key() {
let dir =
venv_with_cfg("home = /x\nimplementation = CPython\nversion_info = 3.14.3.final.0\n");
assert_eq!(parse_pyvenv_version(dir.path()), Some("3.14.3".to_string()));
}
#[test]
fn parse_pyvenv_version_missing_returns_none() {
let dir = venv_with_cfg("home = /x\nimplementation = CPython\n");
assert_eq!(parse_pyvenv_version(dir.path()), None);
}
#[test]
fn normalize_pyvenv_version_truncates_suffix() {
assert_eq!(normalize_pyvenv_version("3.14.3.final.0"), "3.14.3");
assert_eq!(normalize_pyvenv_version("3.12.12"), "3.12.12");
assert_eq!(normalize_pyvenv_version("3.12"), "3.12");
assert_eq!(normalize_pyvenv_version("3"), "3");
}
#[test]
fn normalize_pyvenv_version_non_numeric_falls_back() {
assert_eq!(normalize_pyvenv_version("garbage"), "garbage");
assert_eq!(normalize_pyvenv_version(""), "");
assert_eq!(normalize_pyvenv_version("3..4"), "3");
assert_eq!(normalize_pyvenv_version("v3.12"), "v3.12");
}
}