loadorder/load_order/
readable.rs

1/*
2 * This file is part of libloadorder
3 *
4 * Copyright (C) 2017 Oliver Hamlet
5 *
6 * libloadorder is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * libloadorder is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with libloadorder. If not, see <http://www.gnu.org/licenses/>.
18 */
19use crate::game_settings::GameSettings;
20use crate::plugin::Plugin;
21
22pub trait ReadableLoadOrderBase {
23    fn plugins(&self) -> &[Plugin];
24
25    fn game_settings_base(&self) -> &GameSettings;
26
27    fn find_plugin(&self, plugin_name: &str) -> Option<&Plugin> {
28        self.plugins().iter().find(|p| p.name_matches(plugin_name))
29    }
30
31    fn find_plugin_and_index(&self, plugin_name: &str) -> Option<(usize, &Plugin)> {
32        self.plugins()
33            .iter()
34            .enumerate()
35            .find(|(_, p)| p.name_matches(plugin_name))
36    }
37}
38
39pub trait ReadableLoadOrder {
40    fn game_settings(&self) -> &GameSettings;
41
42    fn plugin_names(&self) -> Vec<&str>;
43
44    fn index_of(&self, plugin_name: &str) -> Option<usize>;
45
46    fn plugin_at(&self, index: usize) -> Option<&str>;
47
48    fn active_plugin_names(&self) -> Vec<&str>;
49
50    fn is_active(&self, plugin_name: &str) -> bool;
51}
52
53impl<T: ReadableLoadOrderBase> ReadableLoadOrder for T {
54    fn game_settings(&self) -> &GameSettings {
55        self.game_settings_base()
56    }
57
58    fn plugin_names(&self) -> Vec<&str> {
59        self.plugins().iter().map(Plugin::name).collect()
60    }
61
62    fn index_of(&self, plugin_name: &str) -> Option<usize> {
63        self.plugins()
64            .iter()
65            .position(|p| p.name_matches(plugin_name))
66    }
67
68    fn plugin_at(&self, index: usize) -> Option<&str> {
69        self.plugins().get(index).map(Plugin::name)
70    }
71
72    fn active_plugin_names(&self) -> Vec<&str> {
73        self.plugins()
74            .iter()
75            .filter(|p| p.is_active())
76            .map(Plugin::name)
77            .collect()
78    }
79
80    fn is_active(&self, plugin_name: &str) -> bool {
81        self.find_plugin(plugin_name).is_some_and(Plugin::is_active)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    use std::path::Path;
90
91    use tempfile::tempdir;
92
93    use crate::enums::GameId;
94    use crate::load_order::tests::{game_settings_for_test, mock_game_files};
95    use crate::tests::copy_to_test_dir;
96
97    struct TestLoadOrder {
98        game_settings: GameSettings,
99        plugins: Vec<Plugin>,
100    }
101
102    impl ReadableLoadOrderBase for TestLoadOrder {
103        fn game_settings_base(&self) -> &GameSettings {
104            &self.game_settings
105        }
106
107        fn plugins(&self) -> &[Plugin] {
108            &self.plugins
109        }
110    }
111
112    fn prepare(game_dir: &Path) -> TestLoadOrder {
113        let mut game_settings = game_settings_for_test(GameId::Oblivion, game_dir);
114        mock_game_files(&mut game_settings);
115
116        let plugins = vec![
117            Plugin::with_active("Blank.esp", &game_settings, true).unwrap(),
118            Plugin::new("Blank - Different.esp", &game_settings).unwrap(),
119        ];
120
121        TestLoadOrder {
122            game_settings,
123            plugins,
124        }
125    }
126
127    fn prepare_with_ghosted_plugin(game_dir: &Path) -> TestLoadOrder {
128        let mut load_order = prepare(game_dir);
129
130        copy_to_test_dir(
131            "Blank - Different.esm",
132            "Blank - Different.esm.ghost",
133            &load_order.game_settings,
134        );
135        load_order.plugins.insert(
136            0,
137            Plugin::new("Blank - Different.esm.ghost", &load_order.game_settings).unwrap(),
138        );
139
140        load_order
141    }
142
143    #[test]
144    fn plugin_names_should_return_filenames_for_plugins_in_load_order() {
145        let tmp_dir = tempdir().unwrap();
146        let load_order = prepare(tmp_dir.path());
147
148        let expected_plugin_names = vec!["Blank.esp", "Blank - Different.esp"];
149        assert_eq!(expected_plugin_names, load_order.plugin_names());
150    }
151
152    #[test]
153    fn plugin_names_should_return_unghosted_filenames() {
154        let tmp_dir = tempdir().unwrap();
155        let load_order = prepare_with_ghosted_plugin(tmp_dir.path());
156
157        let expected_plugin_names = vec![
158            "Blank - Different.esm",
159            "Blank.esp",
160            "Blank - Different.esp",
161        ];
162        assert_eq!(expected_plugin_names, load_order.plugin_names());
163    }
164
165    #[test]
166    fn index_of_should_return_none_if_the_plugin_is_not_in_the_load_order() {
167        let tmp_dir = tempdir().unwrap();
168        let load_order = prepare(tmp_dir.path());
169
170        assert!(load_order.index_of("Blank.esm").is_none());
171    }
172
173    #[test]
174    fn index_of_should_return_some_index_if_the_plugin_is_in_the_load_order() {
175        let tmp_dir = tempdir().unwrap();
176        let load_order = prepare(tmp_dir.path());
177
178        assert_eq!(0, load_order.index_of("Blank.esp").unwrap());
179    }
180
181    #[test]
182    fn index_of_should_be_case_insensitive() {
183        let tmp_dir = tempdir().unwrap();
184        let load_order = prepare(tmp_dir.path());
185
186        assert_eq!(0, load_order.index_of("blank.esp").unwrap());
187    }
188
189    #[test]
190    fn plugin_at_should_return_none_if_given_an_out_of_bounds_index() {
191        let tmp_dir = tempdir().unwrap();
192        let load_order = prepare(tmp_dir.path());
193
194        assert!(load_order.plugin_at(3).is_none());
195    }
196
197    #[test]
198    fn plugin_at_should_return_some_filename_if_given_an_in_bounds_index() {
199        let tmp_dir = tempdir().unwrap();
200        let load_order = prepare(tmp_dir.path());
201
202        assert_eq!("Blank.esp", load_order.plugin_at(0).unwrap());
203    }
204
205    #[test]
206    fn plugin_at_should_return_some_unghosted_filename() {
207        let tmp_dir = tempdir().unwrap();
208        let load_order = prepare_with_ghosted_plugin(tmp_dir.path());
209
210        assert_eq!("Blank - Different.esm", load_order.plugin_at(0).unwrap());
211    }
212
213    #[test]
214    fn active_plugin_names_should_return_filenames_for_active_plugins_in_load_order() {
215        let tmp_dir = tempdir().unwrap();
216        let load_order = prepare(tmp_dir.path());
217
218        let expected_plugin_names = vec!["Blank.esp"];
219        assert_eq!(expected_plugin_names, load_order.active_plugin_names());
220    }
221
222    #[test]
223    fn is_active_should_return_false_for_an_inactive_plugin() {
224        let tmp_dir = tempdir().unwrap();
225        let load_order = prepare(tmp_dir.path());
226
227        assert!(!load_order.is_active("Blank - Different.esp"));
228    }
229
230    #[test]
231    fn is_active_should_return_false_a_plugin_not_in_the_load_order() {
232        let tmp_dir = tempdir().unwrap();
233        let load_order = prepare(tmp_dir.path());
234
235        assert!(!load_order.is_active("missing.esp"));
236    }
237
238    #[test]
239    fn is_active_should_return_true_for_an_active_plugin() {
240        let tmp_dir = tempdir().unwrap();
241        let load_order = prepare(tmp_dir.path());
242
243        assert!(load_order.is_active("Blank.esp"));
244    }
245
246    #[test]
247    fn is_active_should_be_case_insensitive() {
248        let tmp_dir = tempdir().unwrap();
249        let load_order = prepare(tmp_dir.path());
250
251        assert!(load_order.is_active("blank.esp"));
252    }
253}