@@ -283,7 +283,6 @@ impl VirtualEnvironment {
283283 Arc :: new ( pyvenv_cfg_path) ,
284284 Some ( range) ,
285285 ) ) ;
286-
287286 Some ( PythonVersionWithSource { version, source } )
288287 } ) ;
289288
@@ -382,6 +381,7 @@ System site-packages will not be used for module resolution.",
382381/// See also: <https://snarky.ca/how-virtual-environments-work/>
383382#[ derive( Debug ) ]
384383struct PyvenvCfgParser < ' s > {
384+ source : & ' s str ,
385385 cursor : Cursor < ' s > ,
386386 line_number : NonZeroUsize ,
387387 data : RawPyvenvCfg < ' s > ,
@@ -390,6 +390,7 @@ struct PyvenvCfgParser<'s> {
390390impl < ' s > PyvenvCfgParser < ' s > {
391391 fn new ( source : & ' s str ) -> Self {
392392 Self {
393+ source,
393394 cursor : Cursor :: new ( source) ,
394395 line_number : NonZeroUsize :: new ( 1 ) . unwrap ( ) ,
395396 data : RawPyvenvCfg :: default ( ) ,
@@ -409,6 +410,7 @@ impl<'s> PyvenvCfgParser<'s> {
409410 /// to the beginning of the next line.
410411 fn parse_line ( & mut self ) -> Result < ( ) , PyvenvCfgParseErrorKind > {
411412 let PyvenvCfgParser {
413+ source,
412414 cursor,
413415 line_number,
414416 data,
@@ -418,35 +420,24 @@ impl<'s> PyvenvCfgParser<'s> {
418420
419421 cursor. eat_while ( |c| c. is_whitespace ( ) && c != '\n' ) ;
420422
421- let remaining_file = cursor. chars ( ) . as_str ( ) ;
422-
423- let next_newline_position = remaining_file
424- . find ( '\n' )
425- . unwrap_or ( cursor. text_len ( ) . to_usize ( ) ) ;
423+ let key_start = cursor. offset ( ) ;
424+ cursor. eat_while ( |c| !matches ! ( c, '\n' | '=' ) ) ;
425+ let key_end = cursor. offset ( ) ;
426426
427- // The Python standard-library's `site` module parses these files by splitting each line on
428- // '=' characters, so that's what we should do as well.
429- let Some ( eq_position) = remaining_file[ ..next_newline_position] . find ( '=' ) else {
427+ if !cursor. eat_char ( '=' ) {
430428 // Skip over any lines that do not contain '=' characters, same as the CPython stdlib
431429 // <https://github.com/python/cpython/blob/e64395e8eb8d3a9e35e3e534e87d427ff27ab0a5/Lib/site.py#L625-L632>
432- cursor. skip_bytes ( next_newline_position) ;
433-
434- debug_assert ! (
435- matches!( cursor. first( ) , '\n' | ruff_python_trivia:: EOF_CHAR , ) ,
436- "{}" ,
437- cursor. first( )
438- ) ;
439-
440430 cursor. eat_char ( '\n' ) ;
441431 return Ok ( ( ) ) ;
442- } ;
432+ }
443433
444- let key = remaining_file [ ..eq_position ] . trim ( ) ;
434+ let key = source [ TextRange :: new ( key_start , key_end ) ] . trim ( ) ;
445435
446- cursor. skip_bytes ( eq_position + 1 ) ;
447436 cursor. eat_while ( |c| c. is_whitespace ( ) && c != '\n' ) ;
448-
449- let value = remaining_file[ eq_position + 1 ..next_newline_position] . trim ( ) ;
437+ let value_start = cursor. offset ( ) ;
438+ cursor. eat_while ( |c| c != '\n' ) ;
439+ let value = source[ TextRange :: new ( value_start, cursor. offset ( ) ) ] . trim ( ) ;
440+ cursor. eat_char ( '\n' ) ;
450441
451442 if value. is_empty ( ) {
452443 return Err ( PyvenvCfgParseErrorKind :: MalformedKeyValuePair { line_number } ) ;
@@ -460,7 +451,7 @@ impl<'s> PyvenvCfgParser<'s> {
460451 // `virtualenv` and `uv` call this key `version_info`,
461452 // but the stdlib venv module calls it `version`
462453 "version" | "version_info" => {
463- let version_range = TextRange :: at ( cursor . offset ( ) , value. text_len ( ) ) ;
454+ let version_range = TextRange :: at ( value_start , value. text_len ( ) ) ;
464455 data. version = Some ( ( value, version_range) ) ;
465456 }
466457 "implementation" => {
@@ -479,8 +470,6 @@ impl<'s> PyvenvCfgParser<'s> {
479470 _ => { }
480471 }
481472
482- cursor. eat_while ( |c| c != '\n' ) ;
483- cursor. eat_char ( '\n' ) ;
484473 Ok ( ( ) )
485474 }
486475}
@@ -1581,4 +1570,18 @@ mod tests {
15811570 assert_eq ! ( & pyvenv_cfg[ version. 1 ] , version. 0 ) ;
15821571 assert_eq ! ( parsed. implementation, PythonImplementation :: PyPy ) ;
15831572 }
1573+
1574+ #[ test]
1575+ fn pyvenv_cfg_with_strange_whitespace_parses ( ) {
1576+ let pyvenv_cfg = " home= /a path with whitespace/python\t \t \n version_info = 3.13 \n \n \n \n implementation =PyPy" ;
1577+ let parsed = PyvenvCfgParser :: new ( pyvenv_cfg) . parse ( ) . unwrap ( ) ;
1578+ assert_eq ! (
1579+ parsed. base_executable_home_path,
1580+ Some ( "/a path with whitespace/python" )
1581+ ) ;
1582+ let version = parsed. version . unwrap ( ) ;
1583+ assert_eq ! ( version. 0 , "3.13" ) ;
1584+ assert_eq ! ( & pyvenv_cfg[ version. 1 ] , version. 0 ) ;
1585+ assert_eq ! ( parsed. implementation, PythonImplementation :: PyPy ) ;
1586+ }
15841587}
0 commit comments