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![
40 "do you want".to_string(),
41 "would you like".to_string(),
42 "answer [y]es or [n]o.".to_string(),
43 ],
44 completed_with_warnings: vec!["installed with warnings".to_string()],
45 failed_with_error: vec![
46 "not installed due to errors".to_string(),
47 "installation aborted".to_string(),
48 ],
49 finished: vec![
50 "successfully installed".to_string(),
51 "process ended".to_string(),
52 ],
53 }
54 }
55}
56
57impl ParserConfig {
58 pub fn string_looks_like_question(&self, weidu_output: &str) -> bool {
59 let comparable_output = weidu_output.trim().to_ascii_lowercase();
60 for progress_word in self.in_progress_words.iter() {
62 if comparable_output.contains(progress_word) {
63 return false;
64 }
65 }
66
67 for question in self.choice_phrase.iter() {
68 if comparable_output.contains(question) {
69 return true;
70 }
71 }
72
73 for question in self.choice_words.iter() {
74 for word in comparable_output.split_whitespace() {
75 if word
76 .chars()
77 .filter(|c| c.is_alphabetic())
78 .collect::<String>()
79 == *question
80 {
81 return true;
82 }
83 }
84 }
85
86 false
87 }
88
89 pub fn detect_weidu_finished_state(&self, weidu_output: &str) -> State {
90 let comparable_output = weidu_output.trim().to_lowercase();
91 let failure = self.failed_with_error.iter().fold(false, |acc, fail_case| {
92 comparable_output.contains(fail_case) || acc
93 });
94 if failure {
95 return State::CompletedWithErrors {
96 error_details: comparable_output,
97 };
98 }
99 let warning = self
100 .completed_with_warnings
101 .iter()
102 .fold(false, |acc, warn_case| {
103 comparable_output.contains(warn_case) || acc
104 });
105 if warning {
106 return State::CompletedWithWarnings;
107 }
108 let finished = self.finished.iter().fold(false, |acc, success_case| {
109 comparable_output.contains(success_case) || acc
110 });
111 if finished {
112 return State::Completed;
113 }
114 State::InProgress
115 }
116}
117
118#[cfg(test)]
119mod tests {
120
121 use super::*;
122 use pretty_assertions::assert_eq;
123 use std::{error::Error, path::Path, result::Result};
124
125 #[test]
126 fn test_exit_warnings() -> Result<(), Box<dyn Error>> {
127 let config = ParserConfig::default();
128 let test = "INSTALLED WITH WARNINGS Additional equipment for Thieves and Bards";
129 assert_eq!(config.string_looks_like_question(test), false);
130 assert_eq!(
131 config.detect_weidu_finished_state(test),
132 State::CompletedWithWarnings
133 );
134 Ok(())
135 }
136
137 #[test]
138 fn test_exit_success() -> Result<(), Box<dyn Error>> {
139 let config = ParserConfig::default();
140 let test = "SUCCESSFULLY INSTALLED Jan's Extended Quest";
141 assert_eq!(config.string_looks_like_question(test), false);
142 assert_eq!(config.detect_weidu_finished_state(test), State::Completed);
143 Ok(())
144 }
145
146 #[test]
147 fn is_not_question() -> Result<(), Box<dyn Error>> {
148 let config = ParserConfig::default();
149 let test = "Creating epilogues. Too many epilogues... Why are there so many options here?";
150 assert_eq!(config.string_looks_like_question(test), false);
151 let test = "Including file(s) spellchoices_defensive/vanilla/ENCHANTER.TPH";
152 assert_eq!(config.string_looks_like_question(test), false);
153 Ok(())
154 }
155
156 #[test]
157 fn is_a_question() -> Result<(), Box<dyn Error>> {
158 let config = ParserConfig::default();
159 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.\
160Example: 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%)"];
161 for question in tests {
162 assert_eq!(
163 config.string_looks_like_question(question),
164 true,
165 "String {} doesn't look like a question",
166 question
167 );
168 }
169 Ok(())
170 }
171
172 #[test]
173 fn is_not_a_question() -> Result<(), Box<dyn Error>> {
174 let config = ParserConfig::default();
175 let tests = vec![
176 "FAILURE:",
177 "NOT INSTALLED DUE TO ERRORS The BG1 NPC Project: Required Modifications",
178 ];
179 for question in tests {
180 assert_eq!(
181 config.string_looks_like_question(question),
182 false,
183 "String {} does look like a question",
184 question
185 );
186 }
187 Ok(())
188 }
189
190 #[test]
191 fn load_config() -> Result<(), Box<dyn Error>> {
192 let root = std::env::current_dir()?;
193 let config_path = Path::join(&root, Path::new("example_config.toml"));
194 let config: ParserConfig = confy::load_path(config_path)?;
195 let expected = ParserConfig::default();
196 assert_eq!(expected, config);
197 Ok(())
198 }
199
200 #[test]
201 fn failure() -> Result<(), Box<dyn Error>> {
202 let config = ParserConfig::default();
203 let tests = vec![
204 "not installed due to errors the bg1 npc project: required modifications",
205 "installation aborted merge dlc into game -> merge all available dlcs",
206 ];
207 for input in tests {
208 assert_eq!(
209 config.detect_weidu_finished_state(input),
210 State::CompletedWithErrors {
211 error_details: input.to_string(),
212 },
213 "Input {} did not fail",
214 input
215 );
216 }
217 Ok(())
218 }
219}