1
1
use std:: {
2
- ffi:: OsStr ,
2
+ collections:: HashMap ,
3
+ ffi:: { OsStr , OsString } ,
3
4
path:: { Path , PathBuf } ,
4
5
} ;
5
6
@@ -21,25 +22,24 @@ fn has_unghosted_plugin_file_extension(game_type: GameType, path: &Path) -> bool
21
22
}
22
23
}
23
24
24
- pub fn has_plugin_file_extension ( game_type : GameType , path : & Path ) -> bool {
25
+ pub fn has_ghosted_plugin_file_extension ( game_type : GameType , path : & Path ) -> bool {
25
26
match path. extension ( ) {
26
27
Some ( ext) if ext. eq_ignore_ascii_case ( GHOST_EXTENSION ) => path
27
28
. file_stem ( )
28
29
. map ( |s| has_unghosted_plugin_file_extension ( game_type, Path :: new ( s) ) )
29
30
. unwrap_or ( false ) ,
30
- Some ( ext) => is_unghosted_plugin_file_extension ( game_type, ext) ,
31
31
_ => false ,
32
32
}
33
33
}
34
34
35
- fn add_ghost_extension ( path : PathBuf ) -> PathBuf {
35
+ pub fn has_plugin_file_extension ( game_type : GameType , path : & Path ) -> bool {
36
36
match path. extension ( ) {
37
- Some ( e ) => {
38
- let mut new_extension = e . to_os_string ( ) ;
39
- new_extension . push ( GHOST_EXTENSION_WITH_PERIOD ) ;
40
- path . with_extension ( & new_extension )
41
- }
42
- None => path . with_extension ( GHOST_EXTENSION ) ,
37
+ Some ( ext ) if ext . eq_ignore_ascii_case ( GHOST_EXTENSION ) => path
38
+ . file_stem ( )
39
+ . map ( |s| has_unghosted_plugin_file_extension ( game_type , Path :: new ( s ) ) )
40
+ . unwrap_or ( false ) ,
41
+ Some ( ext ) => is_unghosted_plugin_file_extension ( game_type , ext ) ,
42
+ _ => false ,
43
43
}
44
44
}
45
45
@@ -61,6 +61,36 @@ pub fn normalise_file_name(game_type: GameType, name: &OsStr) -> &OsStr {
61
61
name
62
62
}
63
63
64
+ fn get_ghosted_filename ( path : & Path ) -> Option < OsString > {
65
+ let mut filename = path. file_name ( ) ?. to_os_string ( ) ;
66
+ filename. push ( GHOST_EXTENSION_WITH_PERIOD ) ;
67
+ Some ( filename)
68
+ }
69
+
70
+ fn add_ghost_extension (
71
+ path : & Path ,
72
+ ghosted_plugins : & HashMap < PathBuf , Vec < OsString > > ,
73
+ ) -> Option < PathBuf > {
74
+ // Can't just append a .ghost extension as the filesystem may be case-sensitive and the ghosted
75
+ // file may have a .GHOST extension (for example). Instead loop through the other files in the
76
+ // same parent directory and look for one that's unicode-case-insensitively-equal.
77
+ let expected_filename = get_ghosted_filename ( & path) ?;
78
+ let expected_filename = expected_filename. to_str ( ) ?;
79
+ let parent_path = path. parent ( ) ?;
80
+
81
+ let ghosted_plugins = ghosted_plugins. get ( & parent_path. to_path_buf ( ) ) ?;
82
+
83
+ for ghosted_plugin in ghosted_plugins {
84
+ let ghosted_plugin_str = ghosted_plugin. to_str ( ) ?;
85
+
86
+ if unicase:: eq ( expected_filename, ghosted_plugin_str) {
87
+ return Some ( parent_path. join ( ghosted_plugin) ) ;
88
+ }
89
+ }
90
+
91
+ None
92
+ }
93
+
64
94
pub fn resolve_path ( state : & State , path : & Path ) -> PathBuf {
65
95
// First check external data paths, as files there may override files in the main data path.
66
96
for data_path in & state. additional_data_paths {
@@ -71,7 +101,9 @@ pub fn resolve_path(state: &State, path: &Path) -> PathBuf {
71
101
}
72
102
73
103
if has_unghosted_plugin_file_extension ( state. game_type , & path) {
74
- path = add_ghost_extension ( path) ;
104
+ if let Some ( ghosted_path) = add_ghost_extension ( & path, & state. ghosted_plugins ) {
105
+ path = ghosted_path
106
+ }
75
107
}
76
108
77
109
if path. exists ( ) {
@@ -83,7 +115,7 @@ pub fn resolve_path(state: &State, path: &Path) -> PathBuf {
83
115
let path = state. data_path . join ( path) ;
84
116
85
117
if !path. exists ( ) && has_unghosted_plugin_file_extension ( state. game_type , & path) {
86
- add_ghost_extension ( path)
118
+ add_ghost_extension ( & path , & state . ghosted_plugins ) . unwrap_or ( path)
87
119
} else {
88
120
path
89
121
}
@@ -395,15 +427,52 @@ mod tests {
395
427
}
396
428
397
429
#[ test]
398
- fn add_ghost_extension_should_add_dot_ghost_to_an_existing_extension ( ) {
399
- let path = add_ghost_extension ( "plugin.esp" . into ( ) ) ;
400
- assert_eq ! ( PathBuf :: from( "plugin.esp.ghost" ) , path) ;
430
+ fn add_ghost_extension_should_return_none_if_the_given_parent_path_is_not_in_hashmap ( ) {
431
+ let path = Path :: new ( "subdir/plugin.esp" ) ;
432
+ let result = add_ghost_extension ( path, & HashMap :: new ( ) ) ;
433
+
434
+ assert ! ( result. is_none( ) ) ;
401
435
}
402
436
403
437
#[ test]
404
- fn add_ghost_extension_should_add_dot_ghost_to_an_a_path_with_no_extension ( ) {
405
- let path = add_ghost_extension ( "plugin" . into ( ) ) ;
406
- assert_eq ! ( PathBuf :: from( "plugin.ghost" ) , path) ;
438
+ fn add_ghost_extension_should_return_none_if_the_given_parent_path_has_no_ghosted_plugins ( ) {
439
+ let path = Path :: new ( "subdir/plugin.esp" ) ;
440
+ let mut map = HashMap :: new ( ) ;
441
+ map. insert ( PathBuf :: from ( "subdir" ) , Vec :: new ( ) ) ;
442
+
443
+ let result = add_ghost_extension ( path, & map) ;
444
+
445
+ assert ! ( result. is_none( ) ) ;
446
+ }
447
+
448
+ #[ test]
449
+ fn add_ghost_extension_should_return_none_if_the_given_parent_path_has_no_matching_ghosted_plugins (
450
+ ) {
451
+ let path = Path :: new ( "subdir/plugin.esp" ) ;
452
+ let mut map = HashMap :: new ( ) ;
453
+ map. insert (
454
+ PathBuf :: from ( "subdir" ) ,
455
+ vec ! [ OsString :: from( "plugin.esm.ghost" ) ] ,
456
+ ) ;
457
+ let result = add_ghost_extension ( path, & map) ;
458
+
459
+ assert ! ( result. is_none( ) ) ;
460
+ }
461
+
462
+ #[ test]
463
+ fn add_ghost_extension_should_return_some_if_the_given_parent_path_has_a_case_insensitively_equal_ghosted_plugin (
464
+ ) {
465
+ let path = Path :: new ( "subdir/plugin.esp" ) ;
466
+ let ghosted_plugin = "Plugin.ESp.GHoST" ;
467
+ let mut map = HashMap :: new ( ) ;
468
+ map. insert (
469
+ PathBuf :: from ( "subdir" ) ,
470
+ vec ! [ OsString :: from( ghosted_plugin) ] ,
471
+ ) ;
472
+ let result = add_ghost_extension ( path, & map) ;
473
+
474
+ assert ! ( result. is_some( ) ) ;
475
+ assert_eq ! ( Path :: new( "subdir" ) . join( ghosted_plugin) , result. unwrap( ) ) ;
407
476
}
408
477
409
478
#[ test]
@@ -436,7 +505,9 @@ mod tests {
436
505
fn resolve_path_should_return_the_given_data_relative_path_plus_a_ghost_extension_if_the_plugin_path_does_not_exist (
437
506
) {
438
507
let data_path = PathBuf :: from ( "." ) ;
439
- let state = State :: new ( GameType :: Skyrim , data_path. clone ( ) ) ;
508
+ let mut state = State :: new ( GameType :: Skyrim , data_path. clone ( ) ) ;
509
+ state. ghosted_plugins . insert ( data_path. clone ( ) , vec ! [ OsString :: from( "plugin.esp.ghost" ) ] ) ;
510
+
440
511
let input_path = Path :: new ( "plugin.esp" ) ;
441
512
let resolved_path = resolve_path ( & state, input_path) ;
442
513
0 commit comments