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
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Stdin, Stdout, Write};
use std::sync::Arc;
use std::vec::Vec;

use crate::CliHandler;

/// The string used to represent the manager is waiting for input.
pub const PROMPT: &str = "> ";
/// The command used to print all available commands.
pub const HELP: &str = "help";
/// The command used to stop the manager.
pub const EXIT: &str = "exit";
/// The message displayed when an invalid command is received by the manager.
pub const INVALID_COMMAND: &str = "Invalid command";

/// A manager responsible for handling command line input and output.
pub struct CliManager<R: BufRead, W: Write> {
    reader: R,
    writer: W,
    handlers: HashMap<String, Arc<dyn CliHandler>>,
}

impl Default for CliManager<BufReader<Stdin>, Stdout> {
    fn default() -> Self {
        CliManager {
            reader: BufReader::new(std::io::stdin()),
            writer: std::io::stdout(),
            handlers: HashMap::default(),
        }
    }
}

impl CliManager<BufReader<Stdin>, Stdout> {
    /// Constructs a new CliManager.
    pub fn new() -> Self {
        Self::default()
    }
}

impl<R: BufRead, W: 'static + Write> CliManager<R, W> {
    /// Constructs a new CliManager with the given read/write streams.
    pub fn with_reader_writer(reader: R, writer: W) -> CliManager<R, W> {
        CliManager {
            reader,
            writer,
            handlers: HashMap::new(),
        }
    }

    /// Starts the command line interface. Note that this is a blocking operation; once this function returns, the
    /// user has submitted a request to stop the application ([EXIT]).
    pub fn start(&mut self) {
        loop {
            write!(self.writer, "{}", PROMPT).expect("Failed to print prompt");
            self.writer.flush().expect("Failed to flush prompt");
            let mut input = String::new();
            self.reader.read_line(&mut input).expect("Failed to read line");
            let input = input.trim().to_owned();
            if input.is_empty() {
                continue;
            }
            let (command, args) = parse_input(input);
            if command.is_empty() {
                writeln!(self.writer, "{}", INVALID_COMMAND).expect("Failed to print `Invalid command`");
                continue;
            }

            if EXIT.eq_ignore_ascii_case(&command) {
                break;
            } else if HELP.eq_ignore_ascii_case(&command) {
                let mut cmds: Vec<String> = self.handlers.iter().map(|(cmd, _)| cmd.clone()).collect();
                cmds.sort();
                for cmd in cmds {
                    writeln!(self.writer, "{}", cmd).expect("Failed to print help output");
                }
            } else {
                let handler = self.handlers.get(&command);
                match handler {
                    Some(ref value) => {
                        if let Err(msg) = value.handle_command(&command, args, &mut self.writer) {
                            writeln!(self.writer, "{}", msg).expect("Failed to print error message");
                        }
                    }
                    None => writeln!(self.writer, "{}", INVALID_COMMAND).expect("Failed to print `Invalid command`"),
                };
            }
        }
    }

    /// Adds the given CliHandler. All commands returned by [CliHandler::get_commands()] will now be forwarded to
    /// this handler.
    ///
    /// # Arguments
    /// `handler` - A reference to the handler to add. This reference is cloned for retention by the manager.
    pub fn add_handler(&mut self, handler: Arc<dyn CliHandler>) {
        for cmd in handler.get_commands() {
            self.handlers.insert(cmd.to_string(), Arc::clone(&handler));
        }
    }
}

fn parse_input(input: String) -> (String, Vec<String>) {
    let quoted_vars: Vec<&str> = input.split('\"').collect();
    let mut command = String::new();
    let mut vars: Vec<String> = Vec::new();
    for element in quoted_vars.iter().enumerate() {
        if element.0 % 2 == 0 {
            let space_vars: Vec<&str> = element.1.split(' ').collect();
            for var in space_vars {
                let var = var.trim();
                if !var.is_empty() {
                    if command.is_empty() {
                        command.push_str(var);
                    } else {
                        vars.push(var.to_string());
                    }
                }
            }
        } else {
            vars.push(element.1.to_string());
        }
    }
    (command, vars)
}