mod_installer/config/
parser_config.rs1use crate::state::State;
2
3use serde_derive::{Deserialize, Serialize};
4
5pub(crate) const PARSER_CONFIG_LOCATION: &str = "parser";
6
7#[derive(Debug, PartialEq, Serialize, Deserialize)]
8pub(crate) struct ParserConfig {
9 pub(crate) in_progress_words: Vec<String>,
10 pub(crate) useful_status_words: Vec<String>,
11 pub(crate) choice_words: Vec<String>,
12 pub(crate) choice_phrase: Vec<String>,
13 pub(crate) completed_with_warnings: Vec<String>,
14 pub(crate) failed_with_error: Vec<String>,
15 pub(crate) finished: Vec<String>,
16}
17
18impl Default for ParserConfig {
19 fn default() -> Self {
20 Self {
21 in_progress_words: vec!["installing".to_string(), "creating".to_string()],
22 useful_status_words: vec![
23 "copied".to_string(),
24 "copying".to_string(),
25 "creating".to_string(),
26 "installed".to_string(),
27 "installing".to_string(),
28 "patched".to_string(),
29 "patching".to_string(),
30 "processed".to_string(),
31 "processing".to_string(),
32 ],
33 choice_words: vec![
34 "choice".to_string(),
35 "choose".to_string(),
36 "select".to_string(),
37 "enter".to_string(),
38 ],
39 choice_phrase: vec!["do you want".to_string(), "would you like".to_string()],
40 completed_with_warnings: vec!["installed with warnings".to_string()],
41 failed_with_error: vec![
42 "not installed due to errors".to_string(),
43 "installation aborted".to_string(),
44 ],
45 finished: vec![
46 "successfully installed".to_string(),
47 "process ended".to_string(),
48 ],
49 }
50 }
51}
52
53impl ParserConfig {
54 pub fn string_looks_like_question(&self, weidu_output: &str) -> bool {
55 let comparable_output = weidu_output.trim().to_ascii_lowercase();
56 for progress_word in self.in_progress_words.iter() {
58 if comparable_output.contains(progress_word) {
59 return false;
60 }
61 }
62
63 for question in self.choice_phrase.iter() {
64 if comparable_output.contains(question) {
65 return true;
66 }
67 }
68
69 for question in self.choice_words.iter() {
70 for word in comparable_output.split_whitespace() {
71 if word
72 .chars()
73 .filter(|c| c.is_alphabetic())
74 .collect::<String>()
75 == *question
76 {
77 return true;
78 }
79 }
80 }
81
82 false
83 }
84
85 pub fn detect_weidu_finished_state(&self, weidu_output: &str) -> State {
86 let comparable_output = weidu_output.trim().to_lowercase();
87 let failure = self.failed_with_error.iter().fold(false, |acc, fail_case| {
88 comparable_output.contains(fail_case) || acc
89 });
90 if failure {
91 return State::CompletedWithErrors {
92 error_details: comparable_output,
93 };
94 }
95 let warning = self
96 .completed_with_warnings
97 .iter()
98 .fold(false, |acc, warn_case| {
99 comparable_output.contains(warn_case) || acc
100 });
101 if warning {
102 return State::CompletedWithWarnings;
103 }
104 let finished = self.finished.iter().fold(false, |acc, success_case| {
105 comparable_output.contains(success_case) || acc
106 });
107 if finished {
108 return State::Completed;
109 }
110 State::InProgress
111 }
112}
113
114#[cfg(test)]
115mod tests {
116
117 use super::*;
118 use pretty_assertions::assert_eq;
119 use std::{error::Error, path::Path, result::Result};
120
121 #[test]
122 fn test_exit_warnings() -> Result<(), Box<dyn Error>> {
123 let config = ParserConfig::default();
124 let test = "INSTALLED WITH WARNINGS Additional equipment for Thieves and Bards";
125 assert_eq!(config.string_looks_like_question(test), false);
126 assert_eq!(
127 config.detect_weidu_finished_state(test),
128 State::CompletedWithWarnings
129 );
130 Ok(())
131 }
132
133 #[test]
134 fn test_exit_success() -> Result<(), Box<dyn Error>> {
135 let config = ParserConfig::default();
136 let test = "SUCCESSFULLY INSTALLED Jan's Extended Quest";
137 assert_eq!(config.string_looks_like_question(test), false);
138 assert_eq!(config.detect_weidu_finished_state(test), State::Completed);
139 Ok(())
140 }
141
142 #[test]
143 fn is_not_question() -> Result<(), Box<dyn Error>> {
144 let config = ParserConfig::default();
145 let test = "Creating epilogues. Too many epilogues... Why are there so many options here?";
146 assert_eq!(config.string_looks_like_question(test), false);
147 let test = "Including file(s) spellchoices_defensive/vanilla/ENCHANTER.TPH";
148 assert_eq!(config.string_looks_like_question(test), false);
149 Ok(())
150 }
151
152 #[test]
153 fn is_a_question() -> Result<(), Box<dyn Error>> {
154 let config = ParserConfig::default();
155 let tests = vec!["Enter the full path to your Baldur's Gate installation then press Enter.", "Enter the full path to your BG:EE+SoD installation then press Enter.\
156Example: C:\\Program Files (x86)\\BeamDog\\Games\\00806", "[N]o, [Q]uit or choose one:", "Please enter the chance for items to randomly not be randomised as a integet number (e.g. 10 for 10%)"];
157 for question in tests {
158 assert_eq!(
159 config.string_looks_like_question(question),
160 true,
161 "String {} doesn't look like a question",
162 question
163 );
164 }
165 Ok(())
166 }
167
168 #[test]
169 fn is_not_a_question() -> Result<(), Box<dyn Error>> {
170 let config = ParserConfig::default();
171 let tests = vec![
172 "FAILURE:",
173 "NOT INSTALLED DUE TO ERRORS The BG1 NPC Project: Required Modifications",
174 ];
175 for question in tests {
176 assert_eq!(
177 config.string_looks_like_question(question),
178 false,
179 "String {} does look like a question",
180 question
181 );
182 }
183 Ok(())
184 }
185
186 #[test]
187 fn load_config() -> Result<(), Box<dyn Error>> {
188 let root = std::env::current_dir()?;
189 let config_path = Path::join(&root, Path::new("example_config.toml"));
190 let config: ParserConfig = confy::load_path(config_path)?;
191 let expected = ParserConfig::default();
192 assert_eq!(expected, config);
193 Ok(())
194 }
195
196 #[test]
197 fn failure() -> Result<(), Box<dyn Error>> {
198 let config = ParserConfig::default();
199 let tests = vec![
200 "not installed due to errors the bg1 npc project: required modifications",
201 "installation aborted merge dlc into game -> merge all available dlcs",
202 ];
203 for input in tests {
204 assert_eq!(
205 config.detect_weidu_finished_state(input),
206 State::CompletedWithErrors {
207 error_details: input.to_string(),
208 },
209 "Input {} did not fail",
210 input
211 );
212 }
213 Ok(())
214 }
215}