loadorder/load_order/
readable.rs1use 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::plugin::ActiveState;
96 use crate::tests::copy_to_test_dir;
97
98 struct TestLoadOrder {
99 game_settings: GameSettings,
100 plugins: Vec<Plugin>,
101 }
102
103 impl ReadableLoadOrderBase for TestLoadOrder {
104 fn game_settings_base(&self) -> &GameSettings {
105 &self.game_settings
106 }
107
108 fn plugins(&self) -> &[Plugin] {
109 &self.plugins
110 }
111 }
112
113 fn prepare(game_dir: &Path) -> TestLoadOrder {
114 let mut game_settings = game_settings_for_test(GameId::Oblivion, game_dir);
115 mock_game_files(&mut game_settings);
116
117 let plugins = vec![
118 Plugin::with_active("Blank.esp", &game_settings, ActiveState::ExplicitlyActive)
119 .unwrap(),
120 Plugin::new("Blank - Different.esp", &game_settings).unwrap(),
121 ];
122
123 TestLoadOrder {
124 game_settings,
125 plugins,
126 }
127 }
128
129 fn prepare_with_ghosted_plugin(game_dir: &Path) -> TestLoadOrder {
130 let mut load_order = prepare(game_dir);
131
132 copy_to_test_dir(
133 "Blank - Different.esm",
134 "Blank - Different.esm.ghost",
135 &load_order.game_settings,
136 );
137 load_order.plugins.insert(
138 0,
139 Plugin::new("Blank - Different.esm.ghost", &load_order.game_settings).unwrap(),
140 );
141
142 load_order
143 }
144
145 #[test]
146 fn plugin_names_should_return_filenames_for_plugins_in_load_order() {
147 let tmp_dir = tempdir().unwrap();
148 let load_order = prepare(tmp_dir.path());
149
150 let expected_plugin_names = vec!["Blank.esp", "Blank - Different.esp"];
151 assert_eq!(expected_plugin_names, load_order.plugin_names());
152 }
153
154 #[test]
155 fn plugin_names_should_return_unghosted_filenames() {
156 let tmp_dir = tempdir().unwrap();
157 let load_order = prepare_with_ghosted_plugin(tmp_dir.path());
158
159 let expected_plugin_names = vec![
160 "Blank - Different.esm",
161 "Blank.esp",
162 "Blank - Different.esp",
163 ];
164 assert_eq!(expected_plugin_names, load_order.plugin_names());
165 }
166
167 #[test]
168 fn index_of_should_return_none_if_the_plugin_is_not_in_the_load_order() {
169 let tmp_dir = tempdir().unwrap();
170 let load_order = prepare(tmp_dir.path());
171
172 assert!(load_order.index_of("Blank.esm").is_none());
173 }
174
175 #[test]
176 fn index_of_should_return_some_index_if_the_plugin_is_in_the_load_order() {
177 let tmp_dir = tempdir().unwrap();
178 let load_order = prepare(tmp_dir.path());
179
180 assert_eq!(0, load_order.index_of("Blank.esp").unwrap());
181 }
182
183 #[test]
184 fn index_of_should_be_case_insensitive() {
185 let tmp_dir = tempdir().unwrap();
186 let load_order = prepare(tmp_dir.path());
187
188 assert_eq!(0, load_order.index_of("blank.esp").unwrap());
189 }
190
191 #[test]
192 fn plugin_at_should_return_none_if_given_an_out_of_bounds_index() {
193 let tmp_dir = tempdir().unwrap();
194 let load_order = prepare(tmp_dir.path());
195
196 assert!(load_order.plugin_at(3).is_none());
197 }
198
199 #[test]
200 fn plugin_at_should_return_some_filename_if_given_an_in_bounds_index() {
201 let tmp_dir = tempdir().unwrap();
202 let load_order = prepare(tmp_dir.path());
203
204 assert_eq!("Blank.esp", load_order.plugin_at(0).unwrap());
205 }
206
207 #[test]
208 fn plugin_at_should_return_some_unghosted_filename() {
209 let tmp_dir = tempdir().unwrap();
210 let load_order = prepare_with_ghosted_plugin(tmp_dir.path());
211
212 assert_eq!("Blank - Different.esm", load_order.plugin_at(0).unwrap());
213 }
214
215 #[test]
216 fn active_plugin_names_should_return_filenames_for_active_plugins_in_load_order() {
217 let tmp_dir = tempdir().unwrap();
218 let load_order = prepare(tmp_dir.path());
219
220 let expected_plugin_names = vec!["Blank.esp"];
221 assert_eq!(expected_plugin_names, load_order.active_plugin_names());
222 }
223
224 #[test]
225 fn is_active_should_return_false_for_an_inactive_plugin() {
226 let tmp_dir = tempdir().unwrap();
227 let load_order = prepare(tmp_dir.path());
228
229 assert!(!load_order.is_active("Blank - Different.esp"));
230 }
231
232 #[test]
233 fn is_active_should_return_false_a_plugin_not_in_the_load_order() {
234 let tmp_dir = tempdir().unwrap();
235 let load_order = prepare(tmp_dir.path());
236
237 assert!(!load_order.is_active("missing.esp"));
238 }
239
240 #[test]
241 fn is_active_should_return_true_for_an_active_plugin() {
242 let tmp_dir = tempdir().unwrap();
243 let load_order = prepare(tmp_dir.path());
244
245 assert!(load_order.is_active("Blank.esp"));
246 }
247
248 #[test]
249 fn is_active_should_be_case_insensitive() {
250 let tmp_dir = tempdir().unwrap();
251 let load_order = prepare(tmp_dir.path());
252
253 assert!(load_order.is_active("blank.esp"));
254 }
255}