Skip to content

Commit cb6bfea

Browse files
committed
Add resolve_venv utility supporting .venv file (PEP 832)
1 parent 1c28b88 commit cb6bfea

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

crates/pet-fs/src/path.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,42 @@ fn get_user_home() -> Option<PathBuf> {
334334
}
335335
}
336336

337+
/// Resolves the `.venv` entry in a directory to the virtual environment path. `.venv` may be either
338+
/// - A **directory**: The virtual environment itself (traditional convention)
339+
/// - A **file**: A text file containing the path (relative or absolute) to the virtual environment
340+
/// located somewhere else in disk (PEP 832 convention)
341+
///
342+
/// # Resolution order
343+
/// 1. If `<dir>/.venv` is a directory, return that path.
344+
/// 2. If `<dir>/.venv` is a file, read its contents, trim whitespaces, and resolve the path:
345+
/// - If the path is absolute, return it.
346+
/// - If the path is relative, resolve it against `<dir>` and return the absolute path.
347+
/// - If the resolved path does not exist or is not a directory, return `None`.
348+
/// 3. If `<dir>/.venv` does not exist, return `None`.
349+
/// See: <https://www.python.org/dev/peps/pep-0832/#specification>
350+
pub fn resolve_dot_venv(dir: &Path) -> Option<PathBuf> {
351+
let dot_venv = dir.join(".venv");
352+
let meta = std::fs::symlink_metadata(&dot_venv).ok()?;
353+
if meta.is_dir() {
354+
Some(dot_venv)
355+
} else if meta.is_file() {
356+
let content = std::fs::read_to_string(&dot_venv).ok()?.trim().to_string();
357+
let path = PathBuf::from(content);
358+
let resolved_path = if path.is_absolute() {
359+
path
360+
} else {
361+
dir.join(path)
362+
};
363+
if resolved_path.is_dir() {
364+
Some(resolved_path)
365+
} else {
366+
None
367+
}
368+
} else {
369+
None
370+
}
371+
}
372+
337373
#[cfg(test)]
338374
mod tests {
339375
use super::*;

0 commit comments

Comments
 (0)