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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//! # types
//!
//! Defines the various types.
//!

#[cfg(test)]
#[path = "./types_test.rs"]
mod types_test;

use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::fmt;
use std::fmt::Display;

#[derive(Debug)]
/// Holds the error information
pub enum ParserError {
    /// Error Info Type
    InvalidCommandLine(String),
    /// Error Info Type
    InvalidCliSpec(String),
    /// Error Info Type
    CommandDoesNotMatchSpec(String),
    /// Error Info Type
    InternalError(String),
}

impl Display for ParserError {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self {
            Self::InvalidCommandLine(ref message) => write!(formatter, "{}", message),
            Self::InvalidCliSpec(ref message) => write!(formatter, "{}", message),
            Self::CommandDoesNotMatchSpec(ref message) => write!(formatter, "{}", message),
            Self::InternalError(ref message) => write!(formatter, "{}", message),
        }
    }
}

impl Error for ParserError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::InvalidCommandLine(_) => None,
            Self::InvalidCliSpec(_) => None,
            Self::CommandDoesNotMatchSpec(_) => None,
            Self::InternalError(_) => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
/// The command (not params) string/s
pub enum Command {
    /// Single command (not sub command) such as: "ls".
    /// Any path prefix will be ignored while parsing.
    Command(String),
    /// Sub command value such as: vec!["cargo".to_string(), "myplugin".to_string()].
    /// Any path prefix will be ignored while parsing for the first element only.
    SubCommand(Vec<String>),
}

#[derive(Debug, Clone, PartialEq, Copy)]
/// The argument occurrence type (see values for more info)
pub enum ArgumentOccurrence {
    /// The argument can appear only once
    Single,
    /// The argument can appear multiple times. The value of each occurrence will be
    /// picked up so even args with single value constraint can have multiple values if
    /// they support multiple occurrences.
    Multiple,
}

#[derive(Debug, Clone, PartialEq, Copy)]
/// The argument value type (see values for more info)
pub enum ArgumentValueType {
    /// The argument does not accept any value
    None,
    /// Only single value is allowed
    Single,
    /// Allows multiple values (minimum one)
    Multiple,
}

#[derive(Debug, Clone, PartialEq)]
/// The argument help text
pub enum ArgumentHelp {
    /// Text value
    Text(String),
    /// Text and variable name
    TextAndParam(String, String),
}

#[derive(Debug, Clone, PartialEq)]
/// Holds the command line argument spec
pub struct Argument {
    /// Unique name for the argument later used to pull the parsed information
    pub name: String,
    /// All possible argument keys in the command line (for example: vec!["--env".to_string(), "-e".to_string()])
    pub key: Vec<String>,
    /// The argument occurrence (see enum)
    pub argument_occurrence: ArgumentOccurrence,
    /// The possible value type for this specific argument
    pub value_type: ArgumentValueType,
    /// Default value if not found
    pub default_value: Option<String>,
    /// Help text
    pub help: Option<ArgumentHelp>,
}

#[derive(Debug, Clone, PartialEq)]
/// Holds the positional argument spec
pub struct PositionalArgument {
    /// Unique name for the argument later used to pull the parsed information
    pub name: String,
    /// Help text
    pub help: Option<ArgumentHelp>,
}

#[derive(Debug, Clone, PartialEq, Default)]
/// Holds the command line spec meta information used to generate version and help messages
pub struct CliSpecMetaInfo {
    /// Author name
    pub author: Option<String>,
    /// Version string
    pub version: Option<String>,
    /// Description string
    pub description: Option<String>,
    /// Project name
    pub project: Option<String>,
    /// Post help text
    pub help_post_text: Option<String>,
}

impl CliSpecMetaInfo {
    /// Returns new instance
    pub fn new() -> CliSpecMetaInfo {
        Default::default()
    }
}

#[derive(Debug, Clone, PartialEq, Default)]
/// Holds the command line spec (command/parameters/...)
pub struct CliSpec {
    /// A list of all possible commands and sub commands.
    pub command: Vec<Command>,
    /// A list of all possible command line arguments.
    pub arguments: Vec<Argument>,
    /// The name of the argument that will hold all arguments after the last known key based
    /// argument. If not defined, such positional arguments are not allowed.
    pub positional_argument: Option<PositionalArgument>,
    /// Meta information used for generating version and help messages
    pub meta_info: Option<CliSpecMetaInfo>,
}

impl CliSpec {
    /// Returns new instance
    pub fn new() -> CliSpec {
        Default::default()
    }

    /// Sets the spec meta info
    pub fn set_meta_info(mut self, meta_info: Option<CliSpecMetaInfo>) -> Self {
        self.meta_info = meta_info;
        self
    }

    /// Adds a command to the spec
    pub fn add_command(mut self, command: &str) -> Self {
        self.command.push(Command::Command(command.to_string()));
        self
    }

    /// Adds a sub command to the spec
    pub fn add_subcommand(mut self, sub_command: Vec<&str>) -> Self {
        let string_vec = sub_command.iter().map(|value| value.to_string()).collect();
        self.command.push(Command::SubCommand(string_vec));
        self
    }

    /// Sets the PositionalArgument
    pub fn set_positional_argument(mut self, argument: Option<PositionalArgument>) -> Self {
        self.positional_argument = argument;
        self
    }

    /// Adds a Argument
    pub fn add_argument(mut self, argument: Argument) -> Self {
        self.arguments.push(argument);
        self
    }
}

#[derive(Debug, Clone, PartialEq, Default)]
/// Holds the command line parse result
pub struct CliParsed {
    /// A collection of all arguments found (list of names not keys).
    /// Arguments that were not found by defaulted to a given value will not be listed here.
    pub arguments: HashSet<String>,
    /// A map of all values for arguments found.
    /// The map will exclude arguments that do not accept value but include arguments not provided
    /// on the command line but were defaulted to a given value.
    /// The map keys are the argument names (not keys) and the value is the list of all values
    /// found for all occurrences.
    pub argument_values: HashMap<String, Vec<String>>,
}

impl CliParsed {
    /// Returns new instance
    pub fn new() -> CliParsed {
        Default::default()
    }

    /// returns the first value (if exists)
    pub fn get_first_value(&self, key: &str) -> Option<String> {
        match self.argument_values.get(key) {
            Some(ref values) => {
                if values.len() == 0 {
                    None
                } else {
                    let first_value = values.first().clone().unwrap();
                    Some(first_value.to_string())
                }
            }
            None => None,
        }
    }
}