@@ -185,7 +185,45 @@ pub fn canonicalizes_path(os: &Os, path_as_str: &str) -> Result<String> {
185185 let context = |input : & str | Ok ( os. env . get ( input) . ok ( ) ) ;
186186 let home_dir = || os. env . home ( ) . map ( |p| p. to_string_lossy ( ) . to_string ( ) ) ;
187187
188- Ok ( shellexpand:: full_with_context ( path_as_str, home_dir, context) ?. to_string ( ) )
188+ let expanded = shellexpand:: full_with_context ( path_as_str, home_dir, context) ?;
189+ let path_buf = if !expanded. starts_with ( "/" ) {
190+ // Convert relative paths to absolute paths
191+ let current_dir = os. env . current_dir ( ) ?;
192+ current_dir. join ( expanded. as_ref ( ) as & str )
193+ } else {
194+ // Already absolute path
195+ PathBuf :: from ( expanded. as_ref ( ) as & str )
196+ } ;
197+
198+ // Try canonicalize first, fallback to manual normalization if it fails
199+ match path_buf. canonicalize ( ) {
200+ Ok ( normalized) => Ok ( normalized. as_path ( ) . to_string_lossy ( ) . to_string ( ) ) ,
201+ Err ( _) => {
202+ // If canonicalize fails (e.g., path doesn't exist), do manual normalization
203+ let normalized = normalize_path ( & path_buf) ;
204+ Ok ( normalized. to_string_lossy ( ) . to_string ( ) )
205+ } ,
206+ }
207+ }
208+
209+ /// Manually normalize a path by resolving . and .. components
210+ fn normalize_path ( path : & std:: path:: Path ) -> std:: path:: PathBuf {
211+ let mut components = Vec :: new ( ) ;
212+ for component in path. components ( ) {
213+ match component {
214+ std:: path:: Component :: CurDir => {
215+ // Skip current directory components
216+ } ,
217+ std:: path:: Component :: ParentDir => {
218+ // Pop the last component for parent directory
219+ components. pop ( ) ;
220+ } ,
221+ _ => {
222+ components. push ( component) ;
223+ } ,
224+ }
225+ }
226+ components. iter ( ) . collect ( )
189227}
190228
191229/// Given a globset builder and a path, build globs for both the file and directory patterns
@@ -445,28 +483,71 @@ mod tests {
445483
446484 // Test home directory expansion
447485 let result = canonicalizes_path ( & test_os, "~/test" ) . unwrap ( ) ;
486+ #[ cfg( windows) ]
487+ assert_eq ! ( result, "\\ home\\ testuser\\ test" ) ;
488+ #[ cfg( unix) ]
448489 assert_eq ! ( result, "/home/testuser/test" ) ;
449490
450491 // Test environment variable expansion
451492 let result = canonicalizes_path ( & test_os, "$TEST_VAR/path" ) . unwrap ( ) ;
452- assert_eq ! ( result, "test_value/path" ) ;
493+ #[ cfg( windows) ]
494+ assert_eq ! ( result, "\\ test_value\\ path" ) ;
495+ #[ cfg( unix) ]
496+ assert_eq ! ( result, "/test_value/path" ) ;
453497
454498 // Test combined expansion
455499 let result = canonicalizes_path ( & test_os, "~/$TEST_VAR" ) . unwrap ( ) ;
500+ #[ cfg( windows) ]
501+ assert_eq ! ( result, "\\ home\\ testuser\\ test_value" ) ;
502+ #[ cfg( unix) ]
456503 assert_eq ! ( result, "/home/testuser/test_value" ) ;
457504
505+ // Test ~, . and .. expansion
506+ let result = canonicalizes_path ( & test_os, "~/./.././testuser" ) . unwrap ( ) ;
507+ #[ cfg( windows) ]
508+ assert_eq ! ( result, "\\ home\\ testuser" ) ;
509+ #[ cfg( unix) ]
510+ assert_eq ! ( result, "/home/testuser" ) ;
511+
458512 // Test absolute path (no expansion needed)
459513 let result = canonicalizes_path ( & test_os, "/absolute/path" ) . unwrap ( ) ;
514+ #[ cfg( windows) ]
515+ assert_eq ! ( result, "\\ absolute\\ path" ) ;
516+ #[ cfg( unix) ]
460517 assert_eq ! ( result, "/absolute/path" ) ;
461518
462- // Test relative path (no expansion needed)
519+ // Test ~, . and .. expansion for a path that does not exist
520+ let result = canonicalizes_path ( & test_os, "~/./.././testuser/new/path/../../new" ) . unwrap ( ) ;
521+ #[ cfg( windows) ]
522+ assert_eq ! ( result, "\\ home\\ testuser\\ new" ) ;
523+ #[ cfg( unix) ]
524+ assert_eq ! ( result, "/home/testuser/new" ) ;
525+
526+ // Test path with . and ..
527+ let result = canonicalizes_path ( & test_os, "/absolute/./../path" ) . unwrap ( ) ;
528+ #[ cfg( windows) ]
529+ assert_eq ! ( result, "\\ path" ) ;
530+ #[ cfg( unix) ]
531+ assert_eq ! ( result, "/path" ) ;
532+
533+ // Test relative path (which should be expanded because now all inputs are converted to
534+ // absolute)
463535 let result = canonicalizes_path ( & test_os, "relative/path" ) . unwrap ( ) ;
464- assert_eq ! ( result, "relative/path" ) ;
536+ #[ cfg( windows) ]
537+ assert_eq ! ( result, "\\ relative\\ path" ) ;
538+ #[ cfg( unix) ]
539+ assert_eq ! ( result, "/relative/path" ) ;
465540
466541 // Test glob prefixed paths
467542 let result = canonicalizes_path ( & test_os, "**/path" ) . unwrap ( ) ;
468- assert_eq ! ( result, "**/path" ) ;
543+ #[ cfg( windows) ]
544+ assert_eq ! ( result, "\\ **\\ path" ) ;
545+ #[ cfg( unix) ]
546+ assert_eq ! ( result, "/**/path" ) ;
469547 let result = canonicalizes_path ( & test_os, "**/middle/**/path" ) . unwrap ( ) ;
470- assert_eq ! ( result, "**/middle/**/path" ) ;
548+ #[ cfg( windows) ]
549+ assert_eq ! ( result, "\\ **\\ middle\\ **\\ path" ) ;
550+ #[ cfg( unix) ]
551+ assert_eq ! ( result, "/**/middle/**/path" ) ;
471552 }
472553}
0 commit comments