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: String,
14 pub(crate) failed_with_error: String,
15 pub(crate) finished: String,
16 pub(crate) eet_finished: String,
17}
18
19impl Default for ParserConfig {
20 fn default() -> Self {
21 Self {
22 in_progress_words: vec!["installing".to_string(), "creating".to_string()],
23 useful_status_words: vec![
24 "copied".to_string(),
25 "copying".to_string(),
26 "creating".to_string(),
27 "installed".to_string(),
28 "installing".to_string(),
29 "patched".to_string(),
30 "patching".to_string(),
31 "processed".to_string(),
32 "processing".to_string(),
33 ],
34 choice_words: vec![
35 "choice".to_string(),
36 "choose".to_string(),
37 "select".to_string(),
38 "enter".to_string(),
39 ],
40 choice_phrase: vec!["do you want".to_string(), "would you like".to_string()],
41 completed_with_warnings: "installed with warnings".to_string(),
42 failed_with_error: "not installed due to errors".to_string(),
43 finished: "successfully installed".to_string(),
44 eet_finished: "process ended".to_string(),
45 }
46 }
47}
48
49impl ParserConfig {
50 pub fn string_looks_like_question(&self, weidu_output: &str) -> bool {
51 let comparable_output = weidu_output.trim().to_ascii_lowercase();
52 for progress_word in self.in_progress_words.iter() {
54 if comparable_output.contains(progress_word) {
55 return false;
56 }
57 }
58
59 for question in self.choice_phrase.iter() {
60 if comparable_output.contains(question) {
61 return true;
62 }
63 }
64
65 for question in self.choice_words.iter() {
66 for word in comparable_output.split_whitespace() {
67 if word
68 .chars()
69 .filter(|c| c.is_alphabetic())
70 .collect::<String>()
71 == *question
72 {
73 return true;
74 }
75 }
76 }
77
78 false
79 }
80
81 pub fn detect_weidu_finished_state(&self, weidu_output: &str) -> Option<State> {
82 let comparable_output = weidu_output.trim().to_lowercase();
83 if comparable_output.contains(&self.failed_with_error) {
84 Some(State::CompletedWithErrors {
85 error_details: comparable_output,
86 })
87 } else if comparable_output.contains(&self.completed_with_warnings) {
88 Some(State::CompletedWithWarnings)
89 } else if comparable_output.contains(&self.finished)
90 || comparable_output.contains(&self.eet_finished)
91 {
92 Some(State::Completed)
93 } else {
94 None
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101
102 use super::*;
103 use pretty_assertions::assert_eq;
104 use std::{error::Error, path::Path, result::Result};
105
106 #[test]
107 fn test_exit_warnings() -> Result<(), Box<dyn Error>> {
108 let config = ParserConfig::default();
109 let test = "INSTALLED WITH WARNINGS Additional equipment for Thieves and Bards";
110 assert_eq!(config.string_looks_like_question(test), false);
111 assert_eq!(
112 config.detect_weidu_finished_state(test),
113 Some(State::CompletedWithWarnings)
114 );
115 Ok(())
116 }
117
118 #[test]
119 fn test_exit_success() -> Result<(), Box<dyn Error>> {
120 let config = ParserConfig::default();
121 let test = "SUCCESSFULLY INSTALLED Jan's Extended Quest";
122 assert_eq!(config.string_looks_like_question(test), false);
123 assert_eq!(
124 config.detect_weidu_finished_state(test),
125 Some(State::Completed)
126 );
127 Ok(())
128 }
129
130 #[test]
131 fn is_not_question() -> Result<(), Box<dyn Error>> {
132 let config = ParserConfig::default();
133 let test = "Creating epilogues. Too many epilogues... Why are there so many options here?";
134 assert_eq!(config.string_looks_like_question(test), false);
135 let test = "Including file(s) spellchoices_defensive/vanilla/ENCHANTER.TPH";
136 assert_eq!(config.string_looks_like_question(test), false);
137 Ok(())
138 }
139
140 #[test]
141 fn is_a_question() -> Result<(), Box<dyn Error>> {
142 let config = ParserConfig::default();
143 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.\
144Example: 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%)"];
145 for question in tests {
146 assert_eq!(
147 config.string_looks_like_question(question),
148 true,
149 "String {} doesn't look like a question",
150 question
151 );
152 }
153 Ok(())
154 }
155
156 #[test]
157 fn is_not_a_question() -> Result<(), Box<dyn Error>> {
158 let config = ParserConfig::default();
159 let tests = vec![
160 "FAILURE:",
161 "NOT INSTALLED DUE TO ERRORS The BG1 NPC Project: Required Modifications",
162 ];
163 for question in tests {
164 assert_eq!(
165 config.string_looks_like_question(question),
166 false,
167 "String {} does look like a question",
168 question
169 );
170 }
171 Ok(())
172 }
173
174 #[test]
175 fn load_config() -> Result<(), Box<dyn Error>> {
176 let root = std::env::current_dir()?;
177 let config_path = Path::join(&root, Path::new("example_config.toml"));
178 let config: ParserConfig = confy::load_path(config_path)?;
179 let expected = ParserConfig::default();
180 assert_eq!(expected, config);
181 Ok(())
182 }
183}