rust_sadari_cli/helper/
tools.rs

1use rand::rngs::ThreadRng;
2use std::{
3    collections::HashMap,
4    fmt,
5    fmt::{Debug, Display},
6    fs::File,
7    io,
8    io::prelude::*,
9    process,
10};
11
12const MAX_NUMBER_OF_BLOCKS: i32 = 12;
13const MIN_NUMBER_OF_BLOCKS: i32 = 2;
14const NUMBER_OF_LINES_TO_READ: i32 = 2;
15
16#[derive(Debug)]
17pub struct SadariEnvironment {
18    pub number_of_blocks: u8,
19    pub number_of_max_bridges: u8,
20    pub y_coordinate: u16,
21    rng: ThreadRng,
22    pub name_vec: Vec<String>,
23    pub result_vec: Vec<String>,
24    pub tick_rate: u64,
25}
26
27impl SadariEnvironment {
28    fn default() -> SadariEnvironment {
29        SadariEnvironment {
30            number_of_blocks: 0,
31            number_of_max_bridges: 6,
32            y_coordinate: 10,
33            rng: rand::thread_rng(),
34            name_vec: Vec::new(),
35            result_vec: Vec::new(),
36            tick_rate: 250,
37        }
38    }
39
40    fn number_of_blocks(mut self, number_of_blocks: u8) -> Self {
41        self.number_of_blocks = number_of_blocks;
42
43        self
44    }
45
46    fn name_vec(mut self, name_vec: Vec<String>) -> Self {
47        self.name_vec = name_vec;
48
49        self
50    }
51
52    fn result_vec(mut self, result_vec: Vec<String>) -> Self {
53        self.result_vec = result_vec;
54
55        self
56    }
57}
58
59impl Display for SadariEnvironment {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(
62            f,
63            "
64        sadari env, block : {}, \
65        max_bridges : {}, \
66        y_coordinate : {}, \
67        \nname_vec : {:?}, \
68        \nresult_vec : {:?}",
69            self.number_of_blocks,
70            self.number_of_max_bridges,
71            self.y_coordinate,
72            self.name_vec,
73            self.result_vec
74        )
75    }
76}
77
78mod interaction {
79    use super::{SadariEnvironment, MAX_NUMBER_OF_BLOCKS, MIN_NUMBER_OF_BLOCKS};
80    use std::io;
81    use std::io::prelude::*;
82
83    pub enum State {
84        Idle,
85        NameInput,
86        ResultInput,
87        BeforeDone,
88        Done,
89        Quit,
90    }
91
92    impl State {
93        fn next_state(self) -> State {
94            match self {
95                State::Idle => State::NameInput,
96                State::NameInput => State::ResultInput,
97                State::ResultInput => State::BeforeDone,
98                State::BeforeDone => State::Done,
99                _ => self,
100            }
101        }
102
103        fn previous_state(self) -> State {
104            match self {
105                State::NameInput => State::Idle,
106                State::ResultInput => State::NameInput,
107                State::BeforeDone => State::ResultInput,
108                _ => self,
109            }
110        }
111    }
112
113    pub fn help_guide() {
114        let text = r#"
115        There are TWO modes to run sadari application.
116        1. Using file path as input
117        2. Interacting with user by asking sevearl Questions.
118
119        1 -> For file as input mode example : cargo run ./text.txt
120        2 -> For interaction mode example : cargo run
121
122        Enjoy!
123        "#;
124
125        println!("{}", text);
126    }
127
128    fn idle_guide() {
129        println!("\tType list of names separated by comma! ex) name1, name2, name3 ...\n");
130        println!("\tQ,q) Quit\n");
131        print!("type: ");
132
133        io::stdout().flush().unwrap();
134    }
135
136    fn name_input_guide(v: &Vec<String>) {
137        println!("\tIs that right? \n\tnames: {:?}, len: {}\n", v, v.len());
138        println!("\tY, y) yes");
139        println!("\tN, n) no");
140        println!("\tQ,q) Quit\n");
141        print!("type: ");
142        io::stdout().flush().unwrap();
143    }
144
145    fn result_input_guide() {
146        println!("\tType list of results separated by comma! ex) result1, result2, result3 ...");
147        println!("\tR, r) If you want auto generated results");
148        println!("\tQ,q) Quit\n");
149        print!("type: ");
150        io::stdout().flush().unwrap();
151    }
152
153    fn before_done_guide(sadari_env: &SadariEnvironment) {
154        println!(
155            "\tIs that right? \n\tname: {:?}, len: {}",
156            sadari_env.name_vec,
157            sadari_env.name_vec.len()
158        );
159        println!(
160            "\tresult: {:?}, len: {}\n",
161            sadari_env.result_vec,
162            sadari_env.result_vec.len()
163        );
164        println!("\tY, y) yes");
165        println!("\tN, n) no");
166        println!("\tQ,q) Quit\n");
167        print!("type: ");
168        io::stdout().flush().unwrap();
169    }
170
171    pub fn handle_state_guide(state: &State, sadari_env: &SadariEnvironment) {
172        match state {
173            State::Idle => idle_guide(),
174            State::NameInput => name_input_guide(&sadari_env.name_vec),
175            State::ResultInput => result_input_guide(),
176            State::BeforeDone => before_done_guide(&sadari_env),
177            _ => {}
178        };
179    }
180
181    pub fn handle_user_input(
182        action: String,
183        state: State,
184        sadari_env: SadariEnvironment,
185    ) -> (SadariEnvironment, State) {
186        let action = action.trim();
187
188        let mut next_sadari_env = sadari_env;
189
190        match action {
191            "Q" | "q" => (next_sadari_env, State::Quit),
192            "Y" | "y" => {
193                let (is_valid, message) = validate_input(&state, &next_sadari_env);
194
195                match message {
196                    Some(m) => println!("{}", m),
197                    None => {}
198                };
199
200                let next_state = match state {
201                    State::NameInput => {
202                        if is_valid {
203                            state.next_state()
204                        } else {
205                            state
206                        }
207                    }
208                    State::BeforeDone => {
209                        if is_valid {
210                            state.next_state()
211                        } else {
212                            state.previous_state()
213                        }
214                    }
215                    _ => state,
216                };
217
218                (next_sadari_env, next_state)
219            }
220            "N" | "n" => {
221                let next_state = match state {
222                    State::NameInput => {
223                        next_sadari_env = next_sadari_env.name_vec(Vec::new()).number_of_blocks(0);
224
225                        state.previous_state()
226                    }
227                    State::BeforeDone => {
228                        next_sadari_env = next_sadari_env.result_vec(Vec::new());
229
230                        state.previous_state()
231                    }
232                    _ => state,
233                };
234
235                (next_sadari_env, next_state)
236            }
237            "R" | "r" => {
238                let next_state = match state {
239                    State::ResultInput => {
240                        let vec: Vec<String> = (0..next_sadari_env.number_of_blocks as u8)
241                            .into_iter()
242                            .map(|x| x.to_string())
243                            .collect();
244
245                        next_sadari_env = next_sadari_env.result_vec(vec);
246
247                        state.next_state()
248                    }
249                    _ => state,
250                };
251
252                (next_sadari_env, next_state)
253            }
254            _ => {
255                let vec: Vec<String> = action
256                    .split(",")
257                    .map(move |x| String::from(x.trim()))
258                    .collect();
259
260                let next_state = match state {
261                    State::Idle => {
262                        next_sadari_env = next_sadari_env
263                            .number_of_blocks(vec.len() as u8)
264                            .name_vec(vec);
265
266                        state.next_state()
267                    }
268                    State::ResultInput => {
269                        next_sadari_env = next_sadari_env.result_vec(vec);
270
271                        let (is_valid, message) = validate_input(&state, &next_sadari_env);
272
273                        match message {
274                            Some(m) => println!("{}", m),
275                            None => {}
276                        };
277
278                        if is_valid {
279                            state.next_state()
280                        } else {
281                            next_sadari_env = next_sadari_env.result_vec(Vec::new());
282
283                            state
284                        }
285                    }
286                    _ => state,
287                };
288
289                (next_sadari_env, next_state)
290            }
291        }
292    }
293
294    fn validate_input(state: &State, sadari_env: &SadariEnvironment) -> (bool, Option<String>) {
295        match state {
296            State::NameInput => {
297                let len = sadari_env.name_vec.len();
298
299                if len >= MIN_NUMBER_OF_BLOCKS as usize && len <= MAX_NUMBER_OF_BLOCKS as usize {
300                    (true, None)
301                } else {
302                    (
303                        false,
304                        Some(format!(
305                            "\n\tInput length should be {} <= input_length <= {}\n",
306                            MIN_NUMBER_OF_BLOCKS, MAX_NUMBER_OF_BLOCKS
307                        )),
308                    )
309                }
310            }
311            State::BeforeDone | State::ResultInput => {
312                let name_vec_len = sadari_env.name_vec.len();
313                let result_vec_len = sadari_env.result_vec.len();
314
315                if name_vec_len != result_vec_len {
316                    return (
317                        false,
318                        Some(format!(
319                            "\n\tLengths are different! name: {}, result: {}\n",
320                            name_vec_len, result_vec_len
321                        )),
322                    );
323                }
324
325                if result_vec_len >= MIN_NUMBER_OF_BLOCKS as usize
326                    && result_vec_len <= MAX_NUMBER_OF_BLOCKS as usize
327                {
328                    (true, None)
329                } else {
330                    (
331                        false,
332                        Some(format!(
333                            "\n\tInput length should be {} <= input_length <= {}\n",
334                            MIN_NUMBER_OF_BLOCKS, MAX_NUMBER_OF_BLOCKS
335                        )),
336                    )
337                }
338            }
339            _ => (false, None),
340        }
341    }
342}
343
344fn read_args_from_stdin() -> SadariEnvironment {
345    let mut sadari_env = SadariEnvironment::default();
346    let mut state = interaction::State::Idle;
347
348    loop {
349        interaction::handle_state_guide(&state, &sadari_env);
350
351        let mut action = String::new();
352        io::stdin().read_line(&mut action).expect("read error");
353
354        // println!("\naction is {}\n", action);
355
356        let (next_sadari_env, next_state) =
357            interaction::handle_user_input(action, state, sadari_env);
358        state = next_state;
359        sadari_env = next_sadari_env;
360
361        match state {
362            interaction::State::Quit => {
363                process::exit(0);
364            }
365            interaction::State::Done => {
366                break;
367            }
368            _ => {}
369        }
370    }
371
372    sadari_env
373}
374
375fn get_input_from_file(filename: &String) -> Result<Vec<Vec<String>>, io::Error> {
376    let file = File::open(filename)?;
377    let reader = std::io::BufReader::new(&file);
378
379    let mut vec: Vec<Vec<String>> = Vec::new();
380    let mut line_iter = reader.lines();
381
382    (0..NUMBER_OF_LINES_TO_READ).into_iter().for_each(|_| {
383        let line = line_iter.next();
384
385        match line {
386            Some(l) => {
387                let s: String = l.unwrap();
388                let v: Vec<String> = s.split(",").map(move |x| String::from(x.trim())).collect();
389                vec.push(v);
390            }
391            None => {}
392        };
393    });
394
395    Ok(vec)
396}
397
398fn read_args_from_file(args: Vec<String>) -> SadariEnvironment {
399    let filename = &args[1];
400    let vec_read_file = get_input_from_file(filename).unwrap_or_else(|err| {
401        panic!("\n\tget_input_from_file error : {}", err);
402    });
403
404    if vec_read_file.len() < 1 {
405        panic!("\n\ttest input file has few lines, provide 2 lines!");
406    }
407
408    let name_vec: &Vec<String> = vec_read_file
409        .get(0)
410        .ok_or_else(|| "no input for names")
411        .unwrap_or_else(|err| {
412            panic!("\n\tname_vec, test error : {}", err);
413        });
414
415    let number_of_bloks = name_vec.len();
416    if number_of_bloks > MAX_NUMBER_OF_BLOCKS as usize {
417        panic!(
418            "\n\tname_vec length is larger than limit, length: {}, limit {}",
419            number_of_bloks, MAX_NUMBER_OF_BLOCKS
420        );
421    }
422    if number_of_bloks < MIN_NUMBER_OF_BLOCKS as usize {
423        panic!(
424            "\n\tname_vec length is smaller than limit, length: {}, limit {}",
425            number_of_bloks, MIN_NUMBER_OF_BLOCKS
426        );
427    }
428
429    let name_vec = name_vec.clone();
430    let result_vec: Vec<String> = if vec_read_file.len() == 1 {
431        eprintln!("because i got one line, result will be automatically set as number, 0..n");
432        let vec: Vec<String> = (0..number_of_bloks as u8)
433            .into_iter()
434            .map(|x| x.to_string())
435            .collect();
436
437        vec
438    } else {
439        vec_read_file.get(1).unwrap().clone()
440    };
441
442    if name_vec.len() != result_vec.len() {
443        panic!(
444            "\n\tname and result length are different name: {}, result: {}",
445            name_vec.len(),
446            result_vec.len()
447        );
448    }
449
450    SadariEnvironment::default()
451        .number_of_blocks(number_of_bloks as u8)
452        .name_vec(name_vec)
453        .result_vec(result_vec)
454}
455
456pub fn read_args<T>(args: T) -> SadariEnvironment
457where
458    T: Iterator<Item = String>,
459{
460    let args: Vec<String> = args.collect();
461
462    if args.len() >= 2 && ["help", "--help"].contains(&args[1].as_str()) {
463        interaction::help_guide();
464        process::exit(0);
465    }
466
467    if args.len() < 2 {
468        read_args_from_stdin()
469    } else {
470        read_args_from_file(args)
471    }
472}
473
474fn _print_hashmap<K, V>(name: String, hashmap: &HashMap<K, V>)
475where
476    K: Debug + Display,
477    V: Debug,
478{
479    eprintln!("\n{} --------------", &name);
480    for (key, value) in hashmap {
481        eprintln!("key : {}, value : {:?}", key, value);
482    }
483    eprintln!("{} --------------\n", &name);
484}