• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loot / loot-condition-interpreter / 5983866926

26 Aug 2023 08:43AM UTC coverage: 89.809% (-0.007%) from 89.816%
5983866926

push

github

Ortham
Fix only lowercase plugin extensions being recognised

.esp, .esm, .esl and those plus .ghost suffixes are now matched
case-insensitively.

30 of 30 new or added lines in 2 files covered. (100.0%)

4001 of 4455 relevant lines covered (89.81%)

14.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.05
/src/function/path.rs
1
use std::{
2
    ffi::OsStr,
3
    path::{Path, PathBuf},
4
};
5

6
use crate::{GameType, State};
7

8
const GHOST_EXTENSION: &str = "ghost";
9
const GHOST_EXTENSION_WITH_PERIOD: &str = ".ghost";
10

11
fn is_unghosted_plugin_file_extension(game_type: GameType, extension: &OsStr) -> bool {
71✔
12
    extension.eq_ignore_ascii_case("esp")
71✔
13
        || extension.eq_ignore_ascii_case("esm")
55✔
14
        || (game_type.supports_light_plugins() && extension.eq_ignore_ascii_case("esl"))
37✔
15
}
71✔
16

17
fn has_unghosted_plugin_file_extension(game_type: GameType, path: &Path) -> bool {
32✔
18
    match path.extension() {
32✔
19
        Some(ext) => is_unghosted_plugin_file_extension(game_type, ext),
17✔
20
        _ => false,
15✔
21
    }
22
}
32✔
23

24
pub fn has_plugin_file_extension(game_type: GameType, path: &Path) -> bool {
25
    match path.extension() {
13✔
26
        Some(ext) if ext.eq_ignore_ascii_case(GHOST_EXTENSION) => path
3✔
27
            .file_stem()
3✔
28
            .map(|s| has_unghosted_plugin_file_extension(game_type, Path::new(s)))
3✔
29
            .unwrap_or(false),
3✔
30
        Some(ext) => is_unghosted_plugin_file_extension(game_type, ext),
9✔
31
        _ => false,
1✔
32
    }
33
}
13✔
34

35
fn add_ghost_extension(path: PathBuf) -> PathBuf {
6✔
36
    match path.extension() {
6✔
37
        Some(e) => {
5✔
38
            let mut new_extension = e.to_os_string();
5✔
39
            new_extension.push(GHOST_EXTENSION_WITH_PERIOD);
5✔
40
            path.with_extension(&new_extension)
5✔
41
        }
42
        None => path.with_extension(GHOST_EXTENSION),
1✔
43
    }
44
}
6✔
45

46
pub fn normalise_file_name(game_type: GameType, name: &OsStr) -> &OsStr {
59✔
47
    let path = Path::new(name);
59✔
48
    if path
59✔
49
        .extension()
59✔
50
        .map(|s| s.eq_ignore_ascii_case(GHOST_EXTENSION))
59✔
51
        .unwrap_or(false)
59✔
52
    {
53
        // name ends in .ghost, trim it and then check the file extension.
54
        if let Some(stem) = path.file_stem() {
3✔
55
            if has_unghosted_plugin_file_extension(game_type, Path::new(stem)) {
3✔
56
                return stem;
3✔
57
            }
×
58
        }
×
59
    }
56✔
60

61
    name
56✔
62
}
59✔
63

64
pub fn resolve_path(state: &State, path: &Path) -> PathBuf {
74✔
65
    // First check external data paths, as files there may override files in the main data path.
66
    for data_path in &state.additional_data_paths {
75✔
67
        let mut path = data_path.join(path);
2✔
68

2✔
69
        if path.exists() {
2✔
70
            return path;
1✔
71
        }
1✔
72

1✔
73
        if has_unghosted_plugin_file_extension(state.game_type, &path) {
1✔
74
            path = add_ghost_extension(path);
×
75
        }
1✔
76

77
        if path.exists() {
1✔
78
            return path;
×
79
        }
1✔
80
    }
81

82
    // Now check the main data path.
83
    let path = state.data_path.join(path);
73✔
84

73✔
85
    if !path.exists() && has_unghosted_plugin_file_extension(state.game_type, &path) {
73✔
86
        add_ghost_extension(path)
4✔
87
    } else {
88
        path
69✔
89
    }
90
}
74✔
91

92
#[cfg(test)]
93
mod tests {
94
    use super::*;
95

96
    #[test]
1✔
97
    fn is_unghosted_plugin_file_extension_should_be_true_for_esp_for_all_game_types() {
1✔
98
        let extension = OsStr::new("Esp");
1✔
99

1✔
100
        assert!(is_unghosted_plugin_file_extension(
1✔
101
            GameType::Morrowind,
1✔
102
            extension
1✔
103
        ));
1✔
104
        assert!(is_unghosted_plugin_file_extension(
1✔
105
            GameType::Oblivion,
1✔
106
            extension
1✔
107
        ));
1✔
108
        assert!(is_unghosted_plugin_file_extension(
1✔
109
            GameType::Skyrim,
1✔
110
            extension
1✔
111
        ));
1✔
112
        assert!(is_unghosted_plugin_file_extension(
1✔
113
            GameType::SkyrimSE,
1✔
114
            extension
1✔
115
        ));
1✔
116
        assert!(is_unghosted_plugin_file_extension(
1✔
117
            GameType::SkyrimVR,
1✔
118
            extension
1✔
119
        ));
1✔
120
        assert!(is_unghosted_plugin_file_extension(
1✔
121
            GameType::Fallout3,
1✔
122
            extension
1✔
123
        ));
1✔
124
        assert!(is_unghosted_plugin_file_extension(
1✔
125
            GameType::FalloutNV,
1✔
126
            extension
1✔
127
        ));
1✔
128
        assert!(is_unghosted_plugin_file_extension(
1✔
129
            GameType::Fallout4,
1✔
130
            extension
1✔
131
        ));
1✔
132
        assert!(is_unghosted_plugin_file_extension(
1✔
133
            GameType::Fallout4VR,
1✔
134
            extension
1✔
135
        ));
1✔
136
    }
1✔
137

138
    #[test]
1✔
139
    fn is_unghosted_plugin_file_extension_should_be_true_for_esm_for_all_game_types() {
1✔
140
        let extension = OsStr::new("Esm");
1✔
141

1✔
142
        assert!(is_unghosted_plugin_file_extension(
1✔
143
            GameType::Morrowind,
1✔
144
            extension
1✔
145
        ));
1✔
146
        assert!(is_unghosted_plugin_file_extension(
1✔
147
            GameType::Oblivion,
1✔
148
            extension
1✔
149
        ));
1✔
150
        assert!(is_unghosted_plugin_file_extension(
1✔
151
            GameType::Skyrim,
1✔
152
            extension
1✔
153
        ));
1✔
154
        assert!(is_unghosted_plugin_file_extension(
1✔
155
            GameType::SkyrimSE,
1✔
156
            extension
1✔
157
        ));
1✔
158
        assert!(is_unghosted_plugin_file_extension(
1✔
159
            GameType::SkyrimVR,
1✔
160
            extension
1✔
161
        ));
1✔
162
        assert!(is_unghosted_plugin_file_extension(
1✔
163
            GameType::Fallout3,
1✔
164
            extension
1✔
165
        ));
1✔
166
        assert!(is_unghosted_plugin_file_extension(
1✔
167
            GameType::FalloutNV,
1✔
168
            extension
1✔
169
        ));
1✔
170
        assert!(is_unghosted_plugin_file_extension(
1✔
171
            GameType::Fallout4,
1✔
172
            extension
1✔
173
        ));
1✔
174
        assert!(is_unghosted_plugin_file_extension(
1✔
175
            GameType::Fallout4VR,
1✔
176
            extension
1✔
177
        ));
1✔
178
    }
1✔
179

180
    #[test]
1✔
181
    fn is_unghosted_plugin_file_extension_should_be_true_for_esl_for_tes5se_tes5vr_fo4_and_fo4vr() {
1✔
182
        let extension = OsStr::new("Esl");
1✔
183

1✔
184
        assert!(is_unghosted_plugin_file_extension(
1✔
185
            GameType::SkyrimSE,
1✔
186
            extension
1✔
187
        ));
1✔
188
        assert!(is_unghosted_plugin_file_extension(
1✔
189
            GameType::SkyrimVR,
1✔
190
            extension
1✔
191
        ));
1✔
192
        assert!(is_unghosted_plugin_file_extension(
1✔
193
            GameType::Fallout4,
1✔
194
            extension
1✔
195
        ));
1✔
196
        assert!(is_unghosted_plugin_file_extension(
1✔
197
            GameType::Fallout4VR,
1✔
198
            extension
1✔
199
        ));
1✔
200
    }
1✔
201

202
    #[test]
1✔
203
    fn is_unghosted_plugin_file_extension_should_be_false_for_esl_for_tes3_to_5_fo3_and_fonv() {
1✔
204
        let extension = OsStr::new("Esl");
1✔
205

1✔
206
        assert!(!is_unghosted_plugin_file_extension(
1✔
207
            GameType::Morrowind,
1✔
208
            extension
1✔
209
        ));
1✔
210
        assert!(!is_unghosted_plugin_file_extension(
1✔
211
            GameType::Oblivion,
1✔
212
            extension
1✔
213
        ));
1✔
214
        assert!(!is_unghosted_plugin_file_extension(
1✔
215
            GameType::Skyrim,
1✔
216
            extension
1✔
217
        ));
1✔
218
        assert!(!is_unghosted_plugin_file_extension(
1✔
219
            GameType::Fallout3,
1✔
220
            extension
1✔
221
        ));
1✔
222
        assert!(!is_unghosted_plugin_file_extension(
1✔
223
            GameType::FalloutNV,
1✔
224
            extension
1✔
225
        ));
1✔
226
    }
1✔
227

228
    #[test]
1✔
229
    fn is_unghosted_plugin_file_extension_should_be_false_for_ghost_for_all_game_types() {
1✔
230
        let extension = OsStr::new("Ghost");
1✔
231

1✔
232
        assert!(!is_unghosted_plugin_file_extension(
1✔
233
            GameType::Morrowind,
1✔
234
            extension
1✔
235
        ));
1✔
236
        assert!(!is_unghosted_plugin_file_extension(
1✔
237
            GameType::Oblivion,
1✔
238
            extension
1✔
239
        ));
1✔
240
        assert!(!is_unghosted_plugin_file_extension(
1✔
241
            GameType::Skyrim,
1✔
242
            extension
1✔
243
        ));
1✔
244
        assert!(!is_unghosted_plugin_file_extension(
1✔
245
            GameType::SkyrimSE,
1✔
246
            extension
1✔
247
        ));
1✔
248
        assert!(!is_unghosted_plugin_file_extension(
1✔
249
            GameType::SkyrimVR,
1✔
250
            extension
1✔
251
        ));
1✔
252
        assert!(!is_unghosted_plugin_file_extension(
1✔
253
            GameType::Fallout3,
1✔
254
            extension
1✔
255
        ));
1✔
256
        assert!(!is_unghosted_plugin_file_extension(
1✔
257
            GameType::FalloutNV,
1✔
258
            extension
1✔
259
        ));
1✔
260
        assert!(!is_unghosted_plugin_file_extension(
1✔
261
            GameType::Fallout4,
1✔
262
            extension
1✔
263
        ));
1✔
264
        assert!(!is_unghosted_plugin_file_extension(
1✔
265
            GameType::Fallout4VR,
1✔
266
            extension
1✔
267
        ));
1✔
268
    }
1✔
269

270
    #[test]
1✔
271
    fn is_unghosted_plugin_file_extension_should_be_false_for_non_esp_esm_esl_for_all_game_types() {
1✔
272
        let extension = OsStr::new("txt");
1✔
273

1✔
274
        assert!(!is_unghosted_plugin_file_extension(
1✔
275
            GameType::Morrowind,
1✔
276
            extension
1✔
277
        ));
1✔
278
        assert!(!is_unghosted_plugin_file_extension(
1✔
279
            GameType::Oblivion,
1✔
280
            extension
1✔
281
        ));
1✔
282
        assert!(!is_unghosted_plugin_file_extension(
1✔
283
            GameType::Skyrim,
1✔
284
            extension
1✔
285
        ));
1✔
286
        assert!(!is_unghosted_plugin_file_extension(
1✔
287
            GameType::SkyrimSE,
1✔
288
            extension
1✔
289
        ));
1✔
290
        assert!(!is_unghosted_plugin_file_extension(
1✔
291
            GameType::SkyrimVR,
1✔
292
            extension
1✔
293
        ));
1✔
294
        assert!(!is_unghosted_plugin_file_extension(
1✔
295
            GameType::Fallout3,
1✔
296
            extension
1✔
297
        ));
1✔
298
        assert!(!is_unghosted_plugin_file_extension(
1✔
299
            GameType::FalloutNV,
1✔
300
            extension
1✔
301
        ));
1✔
302
        assert!(!is_unghosted_plugin_file_extension(
1✔
303
            GameType::Fallout4,
1✔
304
            extension
1✔
305
        ));
1✔
306
        assert!(!is_unghosted_plugin_file_extension(
1✔
307
            GameType::Fallout4VR,
1✔
308
            extension
1✔
309
        ));
1✔
310
    }
1✔
311

312
    #[test]
1✔
313
    fn has_unghosted_plugin_file_extension_should_return_false_if_the_path_has_no_extension() {
1✔
314
        assert!(!has_unghosted_plugin_file_extension(
1✔
315
            GameType::Skyrim,
1✔
316
            Path::new("file")
1✔
317
        ));
1✔
318
    }
1✔
319

320
    #[test]
1✔
321
    fn has_unghosted_plugin_file_extension_should_return_false_if_the_path_has_a_non_plugin_extension(
1✔
322
    ) {
1✔
323
        assert!(!has_unghosted_plugin_file_extension(
1✔
324
            GameType::Skyrim,
1✔
325
            Path::new("plugin.bsa")
1✔
326
        ));
1✔
327
    }
1✔
328

329
    #[test]
1✔
330
    fn has_unghosted_plugin_file_extension_should_return_false_if_the_path_has_a_ghosted_plugin_extension(
1✔
331
    ) {
1✔
332
        assert!(!has_unghosted_plugin_file_extension(
1✔
333
            GameType::Skyrim,
1✔
334
            Path::new("plugin.esp.ghost")
1✔
335
        ));
1✔
336
    }
1✔
337

338
    #[test]
1✔
339
    fn has_unghosted_plugin_file_extension_should_return_true_if_the_path_has_an_unghosted_plugin_extension(
1✔
340
    ) {
1✔
341
        assert!(has_unghosted_plugin_file_extension(
1✔
342
            GameType::Skyrim,
1✔
343
            Path::new("plugin.esp")
1✔
344
        ));
1✔
345
    }
1✔
346

347
    #[test]
1✔
348
    fn has_plugin_file_extension_should_return_true_if_the_path_has_an_unghosted_plugin_extension()
1✔
349
    {
1✔
350
        assert!(has_plugin_file_extension(
1✔
351
            GameType::Skyrim,
1✔
352
            Path::new("plugin.esp")
1✔
353
        ));
1✔
354
    }
1✔
355

356
    #[test]
1✔
357
    fn has_plugin_file_extension_should_return_true_if_the_path_has_a_ghosted_plugin_extension() {
1✔
358
        assert!(has_plugin_file_extension(
1✔
359
            GameType::Skyrim,
1✔
360
            Path::new("plugin.esp.Ghost")
1✔
361
        ));
1✔
362
    }
1✔
363

364
    #[test]
1✔
365
    fn has_plugin_file_extension_should_return_false_if_the_path_has_a_non_plugin_extension() {
1✔
366
        assert!(!has_plugin_file_extension(
1✔
367
            GameType::Skyrim,
1✔
368
            Path::new("plugin.bsa")
1✔
369
        ));
1✔
370
    }
1✔
371

372
    #[test]
1✔
373
    fn has_plugin_file_extension_should_return_false_if_the_path_has_a_ghosted_non_plugin_extension(
1✔
374
    ) {
1✔
375
        assert!(!has_plugin_file_extension(
1✔
376
            GameType::Skyrim,
1✔
377
            Path::new("plugin.bsa.Ghost")
1✔
378
        ));
1✔
379
    }
1✔
380

381
    #[test]
1✔
382
    fn has_plugin_file_extension_should_return_false_if_the_path_has_only_ghost_extension() {
1✔
383
        assert!(!has_plugin_file_extension(
1✔
384
            GameType::Skyrim,
1✔
385
            Path::new("plugin.Ghost")
1✔
386
        ));
1✔
387
    }
1✔
388

389
    #[test]
1✔
390
    fn has_plugin_file_extension_should_return_false_if_the_path_has_no_extension() {
1✔
391
        assert!(!has_plugin_file_extension(
1✔
392
            GameType::Skyrim,
1✔
393
            Path::new("plugin")
1✔
394
        ));
1✔
395
    }
1✔
396

397
    #[test]
1✔
398
    fn add_ghost_extension_should_add_dot_ghost_to_an_existing_extension() {
1✔
399
        let path = add_ghost_extension("plugin.esp".into());
1✔
400
        assert_eq!(PathBuf::from("plugin.esp.ghost"), path);
1✔
401
    }
1✔
402

403
    #[test]
1✔
404
    fn add_ghost_extension_should_add_dot_ghost_to_an_a_path_with_no_extension() {
1✔
405
        let path = add_ghost_extension("plugin".into());
1✔
406
        assert_eq!(PathBuf::from("plugin.ghost"), path);
1✔
407
    }
1✔
408

409
    #[test]
1✔
410
    fn resolve_path_should_return_the_data_path_prefixed_path_if_it_exists() {
1✔
411
        let data_path = PathBuf::from(".");
1✔
412
        let state = State::new(GameType::Skyrim, data_path.clone());
1✔
413
        let input_path = Path::new("README.md");
1✔
414
        let resolved_path = resolve_path(&state, input_path);
1✔
415

1✔
416
        assert_eq!(data_path.join(input_path), resolved_path);
1✔
417
    }
1✔
418

419
    #[test]
1✔
420
    fn resolve_path_should_return_the_data_path_prefixed_path_if_it_does_not_exist_and_is_not_an_unghosted_plugin_filename(
1✔
421
    ) {
1✔
422
        let data_path = PathBuf::from(".");
1✔
423
        let state = State::new(GameType::Skyrim, data_path.clone());
1✔
424
        let input_path = Path::new("plugin.esp.ghost");
1✔
425
        let resolved_path = resolve_path(&state, input_path);
1✔
426

1✔
427
        assert_eq!(data_path.join(input_path), resolved_path);
1✔
428

429
        let input_path = Path::new("file.txt");
1✔
430
        let resolved_path = resolve_path(&state, input_path);
1✔
431

1✔
432
        assert_eq!(data_path.join(input_path), resolved_path);
1✔
433
    }
1✔
434

435
    #[test]
1✔
436
    fn resolve_path_should_return_the_given_data_relative_path_plus_a_ghost_extension_if_the_plugin_path_does_not_exist(
1✔
437
    ) {
1✔
438
        let data_path = PathBuf::from(".");
1✔
439
        let state = State::new(GameType::Skyrim, data_path.clone());
1✔
440
        let input_path = Path::new("plugin.esp");
1✔
441
        let resolved_path = resolve_path(&state, input_path);
1✔
442

1✔
443
        assert_eq!(
1✔
444
            data_path.join(input_path.with_extension("esp.ghost")),
1✔
445
            resolved_path
1✔
446
        );
1✔
447
    }
1✔
448

449
    #[test]
1✔
450
    fn resolve_path_should_check_external_data_paths_in_order_before_data_path() {
1✔
451
        use std::fs::copy;
1✔
452
        use std::fs::create_dir;
1✔
453

1✔
454
        let tmp_dir = tempfile::tempdir().unwrap();
1✔
455
        let external_data_path_1 = tmp_dir.path().join("Data1");
1✔
456
        let external_data_path_2 = tmp_dir.path().join("Data2");
1✔
457
        let data_path = tmp_dir.path().join("Data3");
1✔
458

1✔
459
        create_dir(&external_data_path_1).unwrap();
1✔
460
        create_dir(&external_data_path_2).unwrap();
1✔
461
        create_dir(&data_path).unwrap();
1✔
462
        copy(
1✔
463
            Path::new("Cargo.toml"),
1✔
464
            external_data_path_2.join("Cargo.toml"),
1✔
465
        )
1✔
466
        .unwrap();
1✔
467
        copy(Path::new("Cargo.toml"), data_path.join("Cargo.toml")).unwrap();
1✔
468

1✔
469
        let mut state = State::new(GameType::Skyrim, data_path);
1✔
470
        state.set_additional_data_paths(vec![external_data_path_1, external_data_path_2.clone()]);
1✔
471

1✔
472
        let input_path = Path::new("Cargo.toml");
1✔
473
        let resolved_path = resolve_path(&state, input_path);
1✔
474

1✔
475
        assert_eq!(external_data_path_2.join(input_path), resolved_path);
1✔
476
    }
1✔
477
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc