@@ -23,6 +23,7 @@ pub struct PyVenvCfg {
2323 pub version_major : u64 ,
2424 pub version_minor : u64 ,
2525 pub prompt : Option < String > ,
26+ pub uv_version : Option < String > ,
2627}
2728
2829impl PyVenvCfg {
@@ -31,14 +32,19 @@ impl PyVenvCfg {
3132 version_major : u64 ,
3233 version_minor : u64 ,
3334 prompt : Option < String > ,
35+ uv_version : Option < String > ,
3436 ) -> Self {
3537 Self {
3638 version,
3739 version_major,
3840 version_minor,
3941 prompt,
42+ uv_version,
4043 }
4144 }
45+ pub fn is_uv ( & self ) -> bool {
46+ self . uv_version . is_some ( )
47+ }
4248 pub fn find ( path : & Path ) -> Option < Self > {
4349 if let Some ( ref file) = find ( path) {
4450 parse ( file)
@@ -99,6 +105,7 @@ fn parse(file: &Path) -> Option<PyVenvCfg> {
99105 let mut version_major: Option < u64 > = None ;
100106 let mut version_minor: Option < u64 > = None ;
101107 let mut prompt: Option < String > = None ;
108+ let mut uv_version: Option < String > = None ;
102109
103110 for line in contents. lines ( ) {
104111 if version. is_none ( ) {
@@ -120,13 +127,18 @@ fn parse(file: &Path) -> Option<PyVenvCfg> {
120127 prompt = Some ( p) ;
121128 }
122129 }
123- if version. is_some ( ) && prompt. is_some ( ) {
130+ if uv_version. is_none ( ) {
131+ if let Some ( uv_ver) = parse_uv_version ( line) {
132+ uv_version = Some ( uv_ver) ;
133+ }
134+ }
135+ if version. is_some ( ) && prompt. is_some ( ) && uv_version. is_some ( ) {
124136 break ;
125137 }
126138 }
127139
128140 match ( version, version_major, version_minor) {
129- ( Some ( ver) , Some ( major) , Some ( minor) ) => Some ( PyVenvCfg :: new ( ver, major, minor, prompt) ) ,
141+ ( Some ( ver) , Some ( major) , Some ( minor) ) => Some ( PyVenvCfg :: new ( ver, major, minor, prompt, uv_version ) ) ,
130142 _ => None ,
131143 }
132144}
@@ -177,3 +189,72 @@ fn parse_prompt(line: &str) -> Option<String> {
177189 }
178190 None
179191}
192+
193+ fn parse_uv_version ( line : & str ) -> Option < String > {
194+ let trimmed = line. trim ( ) ;
195+ if trimmed. starts_with ( "uv" ) {
196+ if let Some ( eq_idx) = trimmed. find ( '=' ) {
197+ let mut version = trimmed[ eq_idx + 1 ..] . trim ( ) . to_string ( ) ;
198+ // Strip any leading or trailing single or double quotes
199+ if version. starts_with ( '"' ) {
200+ version = version. trim_start_matches ( '"' ) . to_string ( ) ;
201+ }
202+ if version. ends_with ( '"' ) {
203+ version = version. trim_end_matches ( '"' ) . to_string ( ) ;
204+ }
205+ if version. starts_with ( '\'' ) {
206+ version = version. trim_start_matches ( '\'' ) . to_string ( ) ;
207+ }
208+ if version. ends_with ( '\'' ) {
209+ version = version. trim_end_matches ( '\'' ) . to_string ( ) ;
210+ }
211+ if !version. is_empty ( ) {
212+ return Some ( version) ;
213+ }
214+ }
215+ }
216+ None
217+ }
218+
219+ #[ cfg( test) ]
220+ mod tests {
221+ use super :: * ;
222+ use std:: { path:: PathBuf , fs} ;
223+
224+ #[ test]
225+ fn test_parse_uv_version ( ) {
226+ assert_eq ! ( parse_uv_version( "uv = 0.8.14" ) , Some ( "0.8.14" . to_string( ) ) ) ;
227+ assert_eq ! ( parse_uv_version( "uv=0.8.14" ) , Some ( "0.8.14" . to_string( ) ) ) ;
228+ assert_eq ! ( parse_uv_version( "uv = \" 0.8.14\" " ) , Some ( "0.8.14" . to_string( ) ) ) ;
229+ assert_eq ! ( parse_uv_version( "uv = '0.8.14'" ) , Some ( "0.8.14" . to_string( ) ) ) ;
230+ assert_eq ! ( parse_uv_version( "version = 3.12.11" ) , None ) ;
231+ assert_eq ! ( parse_uv_version( "prompt = test-env" ) , None ) ;
232+ }
233+
234+ #[ test]
235+ fn test_pyvenv_cfg_detects_uv ( ) {
236+ let temp_file = "/tmp/test_pyvenv_uv.cfg" ;
237+ let contents = "home = /usr/bin/python3.12\n implementation = CPython\n uv = 0.8.14\n version_info = 3.12.11\n include-system-site-packages = false\n prompt = test-uv-env\n " ;
238+ fs:: write ( temp_file, contents) . unwrap ( ) ;
239+
240+ let cfg = parse ( & PathBuf :: from ( temp_file) ) . unwrap ( ) ;
241+ assert ! ( cfg. is_uv( ) ) ;
242+ assert_eq ! ( cfg. uv_version, Some ( "0.8.14" . to_string( ) ) ) ;
243+ assert_eq ! ( cfg. prompt, Some ( "test-uv-env" . to_string( ) ) ) ;
244+
245+ fs:: remove_file ( temp_file) . ok ( ) ;
246+ }
247+
248+ #[ test]
249+ fn test_pyvenv_cfg_regular_venv ( ) {
250+ let temp_file = "/tmp/test_pyvenv_regular.cfg" ;
251+ let contents = "home = /usr/bin/python3.12\n include-system-site-packages = false\n version = 3.13.5\n executable = /usr/bin/python3.12\n command = python -m venv /path/to/env\n " ;
252+ fs:: write ( temp_file, contents) . unwrap ( ) ;
253+
254+ let cfg = parse ( & PathBuf :: from ( temp_file) ) . unwrap ( ) ;
255+ assert ! ( !cfg. is_uv( ) ) ;
256+ assert_eq ! ( cfg. uv_version, None ) ;
257+
258+ fs:: remove_file ( temp_file) . ok ( ) ;
259+ }
260+ }
0 commit comments