summaryrefslogtreecommitdiffstats
path: root/CLI/rust/src/game_of_life/opts.rs
blob: f32c4a81ee3c7c913652defba618f939c67cbd5d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use clap::{ArgMatches, Error};
use rand::{thread_rng, Rng};
use terminal_size::{terminal_size, Height, Width};

#[cfg(test)]
use mocktopus::macros::mockable;

pub(crate) mod validators;

#[derive(Debug, Clone)]
pub(crate) struct Opts {
    pub seed: usize,
    pub input: Option<String>,
    pub width: isize,
    pub height: isize,
    pub live_cell: char,
    pub dead_cell: char,
    pub delay: usize,
}

#[cfg_attr(test, mockable)]
impl Opts {
    pub fn from(args: ArgMatches) -> Result<Opts, Error> {
        // Set the seed to a random generated number as default if not provided
        let rnd = thread_rng().gen_range(0..1_000_000);
        let seed = args.value_of_t::<usize>("seed").unwrap_or(rnd);

        // Coerce the input to Options<String> from args
        let input = match args.value_of("input") {
            Some(val) => Some(String::from(val)),
            None => None,
        };

        // Set the width and height to the maximum terminal size possible when not provided
        let (Width(term_width), Height(term_height)) = terminal_size().unwrap();
        let max_width: isize = term_width as isize;
        let max_height: isize = term_height as isize - 2;
        let width = args.value_of_t::<isize>("width").unwrap_or(max_width);
        let height = args.value_of_t::<isize>("height").unwrap_or(max_height);

        // Unwrap the defaults of the other arguments and set them
        let live_cell = args.value_of_t::<char>("live-cell")?;
        let dead_cell = args.value_of_t::<char>("dead-cell")?;
        if live_cell == dead_cell {
            return Err(Error::with_description(
                String::from(
                    "The arguments '--live-cell', '--dead-cell' must have different values\n",
                ),
                clap::ErrorKind::InvalidValue,
            ));
        }

        // unwrap the delay
        let delay = args.value_of_t::<usize>("delay")?;

        Ok(Opts {
            seed,
            input,
            width,
            height,
            live_cell,
            dead_cell,
            delay,
        })
    }
}

#[cfg(test)]
mod from {
    use super::*;
    use crate::game_of_life;

    #[test]
    fn no_options() {
        let (Width(term_width), Height(term_height)) = terminal_size().unwrap();
        let matches = game_of_life::app().try_get_matches_from(vec![""]).unwrap();
        let opts = Opts::from(matches).unwrap();
        assert!(matches!(opts.seed, 0..=1_000_000));
        assert_eq!(opts.input, None);
        assert_eq!(opts.width, term_width as isize);
        assert_eq!(opts.height, term_height as isize - 2);
        assert_eq!(opts.live_cell, '█');
        assert_eq!(opts.dead_cell, ' ');
        assert_eq!(opts.delay, 50);
    }

    #[test]
    fn invalid_seed() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--seed=leet"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert!(matches!(opts.seed, 0..=1_000_000));
    }

    #[test]
    fn seed() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--seed=1337"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert_eq!(opts.seed, 1337);
    }

    #[test]
    fn input() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--input=file"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert_eq!(opts.input, Some(String::from("file")));
    }

    #[test]
    fn width() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--width=13"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert_eq!(opts.width, 13);
    }

    #[test]
    fn height() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--height=13"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert_eq!(opts.height, 13);
    }

    #[test]
    fn live_cell() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--live-cell=l"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert_eq!(opts.live_cell, 'l');
    }

    #[test]
    fn dead_cell() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--dead-cell=d"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert_eq!(opts.dead_cell, 'd');
    }

    #[test]
    fn same_live_and_dead_cells() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--live-cell=x", "--dead-cell=x"])
            .unwrap();
        let opts = Opts::from(matches);
        assert!(opts.is_err());
    }

    #[test]
    fn delay() {
        let matches = game_of_life::app()
            .try_get_matches_from(vec!["", "--delay=13"])
            .unwrap();
        let opts = Opts::from(matches).unwrap();
        assert_eq!(opts.delay, 13);
    }
}