@@ -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) ]
338374mod tests {
339375 use super :: * ;
0 commit comments