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
mod args;
mod config;

use args::{ArgParseError, ArgVals};
use indexmap::{indexset, IndexSet};
use std::{error::Error, path::PathBuf};

use crate::{
    constants::CONFIG_FILE_PATH,
    convert::{InputConverterType, OutputConverterType},
};
use config::Config;

/// Conversion options
///
/// Contains input data and configuration
#[derive(Debug, PartialEq)]
pub struct Opts {
    // Input string to be converted
    pub input: String,
    // Vector of input converters that should be applied
    pub inconvs: IndexSet<InputConverterType>,
    // Vector of output converters that should be applied
    pub outconvs: IndexSet<OutputConverterType>,
}

impl Opts {
    /// Build Opts from args and config file
    pub fn build(args: &[String]) -> Result<Self, OptsBuildError> {
        // Read config from config file
        let file_path = dirs::home_dir().unwrap().join(CONFIG_FILE_PATH);
        let config = Config::from_file(&PathBuf::from(file_path))
            .map_err(|err| OptsBuildError::Config(err))?;

        // Parse args
        let argvals = ArgVals::from_args(args)?;

        Ok(opts_build_internal(config, argvals))
    }
}

/// Internal build opts from parsed args and config
fn opts_build_internal(config: Config, args: ArgVals) -> Opts {
    // Decide whether or not to use default output converters
    let outconvs = if let Some(outconvs) = args.outconvs {
        outconvs
    } else {
        config.default_outconvs.into_iter().collect()
    };

    // Decide whether or not to use default input converters
    let inconvs = if let Some(inconv) = args.inconv {
        indexset![inconv]
    } else {
        config.default_inconvs.into_iter().collect()
    };

    Opts {
        input: args.input,
        inconvs,
        outconvs,
    }
}

/// Error encountered while building of Opts struct
pub enum OptsBuildError<'a> {
    Args(ArgParseError<'a>),
    Config(Box<dyn Error>),
}

impl<'a> OptsBuildError<'a> {
    /// Perform graceful exit?
    pub fn graceful_exit(&self) -> bool {
        if let Self::Args(ArgParseError::GracefulExit) = self {
            true
        } else {
            false
        }
    }
}

impl<'a> From<ArgParseError<'a>> for OptsBuildError<'a> {
    fn from(value: ArgParseError<'a>) -> Self {
        Self::Args(value)
    }
}

#[cfg(test)]
mod tests {
    use args::CliOptions;

    use super::*;

    #[test]
    fn opts_build_internal_ok() {
        let tests = [
            (
                Config {
                    default_outconvs: vec![
                        OutputConverterType::HEX,
                        OutputConverterType::BIN,
                        OutputConverterType::DEC,
                    ],
                    default_inconvs: vec![
                        InputConverterType::HEX,
                        InputConverterType::BIN,
                        InputConverterType::DEC,
                    ],
                },
                ArgVals {
                    input: "test123".to_string(),
                    inconv: None,
                    outconvs: None,
                    opts: CliOptions { help: false },
                },
                Opts {
                    input: "test123".to_string(),
                    inconvs: indexset![
                        InputConverterType::HEX,
                        InputConverterType::BIN,
                        InputConverterType::DEC,
                    ],
                    outconvs: indexset![
                        OutputConverterType::HEX,
                        OutputConverterType::BIN,
                        OutputConverterType::DEC,
                    ],
                },
            ),
            (
                Config {
                    default_outconvs: vec![
                        OutputConverterType::HEX,
                        OutputConverterType::BIN,
                        OutputConverterType::DEC,
                    ],
                    default_inconvs: vec![
                        InputConverterType::HEX,
                        InputConverterType::BIN,
                        InputConverterType::DEC,
                    ],
                },
                ArgVals {
                    input: "test123".to_string(),
                    inconv: Some(InputConverterType::BIN),
                    outconvs: Some(indexset![
                        OutputConverterType::HEX,
                        OutputConverterType::BIN
                    ]),
                    opts: CliOptions { help: false },
                },
                Opts {
                    input: "test123".to_string(),
                    inconvs: indexset![InputConverterType::BIN],
                    outconvs: indexset![OutputConverterType::HEX, OutputConverterType::BIN],
                },
            ),
        ];

        for (config, args, exp) in tests {
            assert_eq!(opts_build_internal(config, args), exp);
        }
    }
}