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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
//! Implementation of [`Command`]s with utilities that help to crate them.

use anyhow;
use thiserror;

/// Command handler.
///
/// It should return the status in case of correct execution. In case of
/// errors, all the errors will be handled by the REPL, except for
/// [`CriticalError`], which will be passed up from the REPL.
///
/// The handler should validate command arguments and can return [`ArgsError`]
/// to indicate that arguments were wrong.
pub type Handler<'a> = dyn 'a + FnMut(&[&str]) -> anyhow::Result<CommandStatus>;

/// Single command that can be called in the REPL.
///
/// Though it is possible to construct it by manually, it is not advised.
/// One should rather use the provided [`command!`] macro which will generate
/// appropriate arguments validation and args_info based on passed specification.
pub struct Command<'a> {
    /// Command desctiption that will be displayed in the help message
    pub description: String,
    /// Names and types of arguments to the command
    pub args_info: Vec<String>,
    /// Command handler which should validate arguments and perform command logic
    pub handler: Box<Handler<'a>>,
}

/// Return status of a command.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CommandStatus {
    /// Indicates that REPL should continue execution
    Done,
    /// Indicates that REPL should quit
    Quit,
}

/// Special error wrapper used to indicate that a critical error occured.
///
/// [`Handler`] can return [`CriticalError`] to indicate that this error
/// should not be handled by the REPL (which just prints error message
/// and continues for all other errors).
///
/// This is most conveniently used via the [`Critical`] extension trait.
#[derive(Debug, thiserror::Error)]
pub enum CriticalError {
    /// The contained error is critical and should be returned back from REPL.
    #[error(transparent)]
    Critical(#[from] anyhow::Error),
}

/// Extension trait to easily wrap errors in [`CriticalError`].
///
/// This is implemented for [`std::result::Result`] so can be used to coveniently
/// wrap errors that implement [`std::error::Error`] to indicate that they are
/// critical and should be returned by the REPL, for example:
/// ```rust
/// # use easy_repl::{CriticalError, Critical};
/// let result: Result<(), std::fmt::Error> = Err(std::fmt::Error);
/// let critical = result.into_critical();
/// assert!(matches!(critical, Err(CriticalError::Critical(_))));
/// ```
///
/// See `examples/errors.rs` for a concrete usage example.
pub trait Critical<T, E> {
    /// Wrap the contained [`Err`] in [`CriticalError`] or leave [`Ok`] untouched
    fn into_critical(self) -> Result<T, CriticalError>;
}

impl<T, E> Critical<T, E> for Result<T, E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    fn into_critical(self) -> Result<T, CriticalError> {
        self.map_err(|e| CriticalError::Critical(e.into()))
    }
}

/// Wrong command arguments.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
pub enum ArgsError {
    #[error("wrong number of arguments: got {got}, expected {expected}")]
    WrongNumberOfArguments { got: usize, expected: usize },
    #[error("failed to parse argument value '{argument}': {error}")]
    WrongArgumentValue {
        argument: String,
        #[source]
        error: anyhow::Error,
    },
}

impl<'a> Command<'a> {
    /// Validate the arguments and invoke the handler if arguments are correct.
    pub fn run(&mut self, args: &[&str]) -> anyhow::Result<CommandStatus> {
        (self.handler)(args)
    }

    /// Returns the string description of the argument types
    pub fn arg_types(&self) -> Vec<&str> {
        self.args_info
            .iter()
            .map(|info| info.split(":").collect::<Vec<_>>()[1])
            .collect()
    }
}

impl<'a> std::fmt::Debug for Command<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Command")
            .field("description", &self.description)
            .finish()
    }
}

/// Generate argument validator based on a list of types (used by [`command!`]).
///
/// This macro can be used to generate a closure that takes arguments as `&[&str]`
/// and makes sure that the nubmer of arguments is correct and all can be parsed
/// to appropriate types. This macro should generally not be used. Prefer to use
/// [`command!`] which will use this macro appropriately.
///
/// Example usage:
/// ```rust
/// # use easy_repl::validator;
/// let validator = validator!(i32, f32, String);
/// assert!(validator(&["10", "3.14", "hello"]).is_ok());
/// ```
///
/// # Note
///
/// For string arguments use [`String`] instead of [`&str`].
#[macro_export]
macro_rules! validator {
    ($($type:ty),*) => {
        |args: &[&str]| -> std::result::Result<(), $crate::command::ArgsError> {
            // check the number of arguments
            let n_args: usize = <[()]>::len(&[ $( $crate::validator!(@replace $type ()) ),* ]);
            if args.len() != n_args {
                return Err($crate::command::ArgsError::WrongNumberOfArguments {
                    got: args.len(),
                    expected: n_args,
            });
            }
            #[allow(unused_variables, unused_mut)]
            let mut i = 0;
            #[allow(unused_assignments)]
            {
                $(
                    if let Err(err) = args[i].parse::<$type>() {
                        return Err($crate::command::ArgsError::WrongArgumentValue {
                            argument: args[i].into(),
                            error: err.into()
                    });
                    }
                    i += 1;
                )*
            }

            Ok(())
        }
    };
    // Helper that allows to replace one expression with another (possibly "noop" one)
    (@replace $_old:tt $new:expr) => { $new };
}

// TODO: avoid parsing arguments 2 times by generating validation logic in the function
/// Generate [`Command`] based on desctiption, list of arg types and a closure used in handler.
///
/// This macro should be used when creating [`Command`]s. It takes a string description,
/// a list of argument types with optional names (in the form `name: type`) and a closure.
/// The closure should have the same number of arguments as provided in the argument list.
/// The generated command handler will parse all the arguments and call the closure.
/// The closure used for handler is `move`.
///
/// The following command description:
/// ```rust
/// # use easy_repl::{CommandStatus, command};
/// let cmd = command! {
///     "Example command",
///     (arg1: i32, arg2: String) => |arg1, arg2| {
///         Ok(CommandStatus::Done)
///     }
/// };
/// ```
///
/// will roughly be translated into something like (code here is slightly simplified):
/// ```rust
/// # use anyhow;
/// # use easy_repl::{Command, CommandStatus, command, validator};
/// let cmd = Command {
///     description: "Example command".into(),
///     args_info: vec!["arg1:i32".into(), "arg2:String".into()],
///     handler: Box::new(move |args| -> anyhow::Result<CommandStatus> {
///         let validator = validator!(i32, String);
///         validator(args)?;
///         let mut handler = |arg1, arg2| {
///             Ok(CommandStatus::Done)
///         };
///         handler(args[0].parse::<i32>().unwrap(), args[1].parse::<String>().unwrap())
///     }),
/// };
/// ```
#[macro_export]
macro_rules! command {
    ($description:expr, ( $($( $name:ident )? : $type:ty),* ) => $handler:expr $(,)?) => {
        $crate::command::Command {
            description: $description.into(),
            args_info: vec![ $(
                concat!($(stringify!($name), )? ":", stringify!($type)).into()
            ),* ], // TODO
            handler: command!(@handler $($type)*, $handler),
        }
    };
    (@handler $($type:ty)*, $handler:expr) => {
        Box::new( move |#[allow(unused_variables)] args| -> $crate::anyhow::Result<CommandStatus> {
            let validator = $crate::validator!($($type),*);
            validator(args)?;
            #[allow(unused_mut)]
            let mut handler = $handler;
            command!(@handler_call handler; args; $($type;)*)
        })
    };
    // transform element of $args into parsed function argument by calling .parse::<$type>().unwrap()
    // on each, this starts a recursive muncher that constructs following argument getters args[i]
    (@handler_call $handler:ident; $args:ident; $($types:ty;)*) => {
        command!(@handler_call $handler, $args, 0; $($types;)* =>)
    };
    // $num is used to index $args; pop $type from beginning of list, add new parsed at the endo of $parsed
    (@handler_call $handler:ident, $args:ident, $num:expr; $type:ty; $($types:ty;)* => $($parsed:expr;)*) => {
        command!(@handler_call $handler, $args, $num + 1;
            $($types;)* =>
            $($parsed;)* $args[$num].parse::<$type>().unwrap();
        )
    };
    // finally when there are no more types emit code that calls the handler with all arguments parsed
    (@handler_call $handler:ident, $args:ident, $num:expr; => $($parsed:expr;)*) => {
        $handler( $($parsed),* )
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn manual_command() {
        let mut cmd = Command {
            description: "Test command".into(),
            args_info: vec![],
            handler: Box::new(|_args| Ok(CommandStatus::Done)),
        };
        match (cmd.handler)(&[]) {
            Ok(CommandStatus::Done) => {}
            _ => panic!("Wrong variant"),
        };
    }

    #[test]
    fn validator_no_args() {
        let validator = validator!();
        assert!(validator(&[]).is_ok());
        assert!(validator(&["hello"]).is_err());
    }

    #[test]
    fn validator_one_arg() {
        let validator = validator!(i32);
        assert!(validator(&[]).is_err());
        assert!(validator(&["hello"]).is_err());
        assert!(validator(&["13"]).is_ok());
    }

    #[test]
    fn validator_multiple_args() {
        let validator = validator!(i32, f32, String);
        assert!(validator(&[]).is_err());
        assert!(validator(&["1", "2.1", "hello"]).is_ok());
        assert!(validator(&["1.2", "2.1", "hello"]).is_err());
        assert!(validator(&["1", "a", "hello"]).is_err());
        assert!(validator(&["1", "2.1", "hello", "world"]).is_err());
    }

    #[test]
    fn command_auto_no_args() {
        let mut cmd = command! {
            "Example cmd",
            () => || {
                Ok(CommandStatus::Done)
            }
        };
        match cmd.run(&[]) {
            Ok(CommandStatus::Done) => {}
            Ok(v) => panic!("Wrong variant: {:?}", v),
            Err(e) => panic!("Error: {:?}", e),
        };
    }

    #[test]
    fn command_auto_with_args() {
        let mut cmd = command! {
            "Example cmd",
            (:i32, :f32) => |_x, _y| {
                Ok(CommandStatus::Done)
            }
        };
        match cmd.run(&["13", "1.1"]) {
            Ok(CommandStatus::Done) => {}
            Ok(v) => panic!("Wrong variant: {:?}", v),
            Err(e) => panic!("Error: {:?}", e),
        };
    }

    #[test]
    fn command_auto_with_critical() {
        let mut cmd = command! {
            "Example cmd",
            (:i32, :f32) => |_x, _y| {
                let err = std::io::Error::new(std::io::ErrorKind::InvalidData, "example error");
                Err(CriticalError::Critical(err.into()).into())
            }
        };
        match cmd.run(&["13", "1.1"]) {
            Ok(v) => panic!("Wrong variant: {:?}", v),
            Err(e) => {
                if e.downcast_ref::<CriticalError>().is_none() {
                    panic!("Wrong error: {:?}", e)
                }
            }
        };
    }

    #[test]
    fn command_auto_args_info() {
        let cmd = command!("Example cmd", (:i32, :String, :f32) => |_x, _s, _y| { Ok(CommandStatus::Done) });
        assert_eq!(cmd.args_info, &[":i32", ":String", ":f32"]);
        let cmd = command!("Example cmd", (:i32, :f32) => |_x, _y| { Ok(CommandStatus::Done) });
        assert_eq!(cmd.args_info, &[":i32", ":f32"]);
        let cmd = command!("Example cmd", (:f32) => |_x| { Ok(CommandStatus::Done) });
        assert_eq!(cmd.args_info, &[":f32"]);
        let cmd = command!("Example cmd", () => || { Ok(CommandStatus::Done) });
        let res: &[&str] = &[];
        assert_eq!(cmd.args_info, res);
    }

    #[test]
    fn command_auto_args_info_with_names() {
        let cmd = command! {
            "Example cmd",
            (number:i32, name : String, :f32) => |_x, _s, _y| Ok(CommandStatus::Done)
        };
        assert_eq!(cmd.args_info, &["number:i32", "name:String", ":f32"]);
    }
}