33
44use pet_core:: os_environment:: Environment ;
55use std:: collections:: HashSet ;
6- use std:: path:: PathBuf ;
6+ use std:: path:: { Path , PathBuf } ;
77
88pub fn get_search_paths_from_env_variables ( environment : & dyn Environment ) -> Vec < PathBuf > {
9- // Exclude files from this folder, as they would have been discovered elsewhere (widows_store)
9+ let search_paths = environment
10+ . get_know_global_search_locations ( )
11+ . into_iter ( )
12+ . map ( normalize_search_path)
13+ . collect :: < HashSet < PathBuf > > ( ) ;
14+
15+ // Exclude files from this folder, as they would have been discovered elsewhere (windows_store)
1016 // Also the exe is merely a pointer to another file.
11- if let Some ( home) = environment. get_user_home ( ) {
17+ let user_home = environment. get_user_home ( ) ;
18+ search_paths
19+ . into_iter ( )
20+ . filter ( |search_path| !is_windows_apps_path ( search_path, user_home. as_ref ( ) ) )
21+ . collect ( )
22+ }
23+
24+ fn is_windows_apps_path ( search_path : & Path , user_home : Option < & PathBuf > ) -> bool {
25+ if let Some ( home) = user_home {
1226 let apps_path = home
1327 . join ( "AppData" )
1428 . join ( "Local" )
1529 . join ( "Microsoft" )
1630 . join ( "WindowsApps" ) ;
17-
18- environment
19- . get_know_global_search_locations ( )
20- . into_iter ( )
21- . map ( normalize_search_path)
22- . collect :: < HashSet < PathBuf > > ( )
23- . into_iter ( )
24- . filter ( |p| !p. starts_with ( apps_path. clone ( ) ) )
25- . collect ( )
26- } else {
27- Vec :: new ( )
31+ if search_path. starts_with ( apps_path) {
32+ return true ;
33+ }
2834 }
35+
36+ let components = search_path
37+ . components ( )
38+ . map ( |component| component. as_os_str ( ) . to_string_lossy ( ) )
39+ . collect :: < Vec < _ > > ( ) ;
40+
41+ components. windows ( 4 ) . any ( |components| {
42+ components[ 0 ] . eq_ignore_ascii_case ( "AppData" )
43+ && components[ 1 ] . eq_ignore_ascii_case ( "Local" )
44+ && components[ 2 ] . eq_ignore_ascii_case ( "Microsoft" )
45+ && components[ 3 ] . eq_ignore_ascii_case ( "WindowsApps" )
46+ } )
2947}
3048
3149/// Normalizes a search path for deduplication purposes.
@@ -52,3 +70,97 @@ fn normalize_search_path(path: PathBuf) -> PathBuf {
5270 pet_fs:: path:: norm_case ( & path)
5371 }
5472}
73+
74+ #[ cfg( test) ]
75+ mod tests {
76+ use super :: * ;
77+ use std:: {
78+ fs,
79+ time:: { SystemTime , UNIX_EPOCH } ,
80+ } ;
81+
82+ struct TestEnvironment {
83+ user_home : Option < PathBuf > ,
84+ global_search_locations : Vec < PathBuf > ,
85+ }
86+
87+ impl Environment for TestEnvironment {
88+ fn get_user_home ( & self ) -> Option < PathBuf > {
89+ self . user_home . clone ( )
90+ }
91+
92+ fn get_root ( & self ) -> Option < PathBuf > {
93+ None
94+ }
95+
96+ fn get_env_var ( & self , _key : String ) -> Option < String > {
97+ None
98+ }
99+
100+ fn get_know_global_search_locations ( & self ) -> Vec < PathBuf > {
101+ self . global_search_locations . clone ( )
102+ }
103+ }
104+
105+ fn create_test_dir ( name : & str ) -> PathBuf {
106+ let unique = SystemTime :: now ( )
107+ . duration_since ( UNIX_EPOCH )
108+ . unwrap ( )
109+ . as_nanos ( ) ;
110+ let directory = std:: env:: temp_dir ( ) . join ( format ! (
111+ "pet-env-var-path-{name}-{}-{unique}" ,
112+ std:: process:: id( )
113+ ) ) ;
114+ fs:: create_dir_all ( & directory) . unwrap ( ) ;
115+ directory
116+ }
117+
118+ #[ test]
119+ fn search_paths_are_deduplicated_and_windows_apps_paths_are_filtered ( ) {
120+ let home = create_test_dir ( "home" ) ;
121+ let regular_path = home. join ( "Python" ) ;
122+ let windows_apps_path = home
123+ . join ( "AppData" )
124+ . join ( "Local" )
125+ . join ( "Microsoft" )
126+ . join ( "WindowsApps" ) ;
127+ fs:: create_dir_all ( & regular_path) . unwrap ( ) ;
128+ fs:: create_dir_all ( & windows_apps_path) . unwrap ( ) ;
129+
130+ let environment = TestEnvironment {
131+ user_home : Some ( home. clone ( ) ) ,
132+ global_search_locations : vec ! [
133+ regular_path. clone( ) ,
134+ regular_path. clone( ) ,
135+ windows_apps_path,
136+ ] ,
137+ } ;
138+
139+ let mut search_paths = get_search_paths_from_env_variables ( & environment) ;
140+ search_paths. sort ( ) ;
141+
142+ assert_eq ! ( search_paths, vec![ normalize_search_path( regular_path) ] ) ;
143+
144+ fs:: remove_dir_all ( home) . unwrap ( ) ;
145+ }
146+
147+ #[ test]
148+ fn search_paths_are_preserved_when_home_is_unknown ( ) {
149+ let environment = TestEnvironment {
150+ user_home : None ,
151+ global_search_locations : vec ! [
152+ PathBuf :: from( "/usr/bin" ) ,
153+ PathBuf :: from( if cfg!( windows) {
154+ r"C:\Users\User\AppData\Local\Microsoft\WindowsApps"
155+ } else {
156+ "/Users/user/AppData/Local/Microsoft/WindowsApps"
157+ } ) ,
158+ ] ,
159+ } ;
160+
161+ assert_eq ! (
162+ get_search_paths_from_env_variables( & environment) ,
163+ vec![ normalize_search_path( PathBuf :: from( "/usr/bin" ) ) ]
164+ ) ;
165+ }
166+ }
0 commit comments