summaryrefslogtreecommitdiffstats
path: root/src/client/shell.rs
blob: 73843c8298e4294249b282fa7aecae2608590d23 (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
//! Parser for the terminal's command line.
//!
//! The core speaks `Command`; this module turns one typed line into either a
//! core command or an action only an interactive frontend can perform
//! (connecting, prompting for a password, listing hosts).

use crate::client::ui::parse_bet_arg;
use crate::client::view::Command;
use crate::history::{DEFAULT_HISTORY, MAX_HISTORY};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Shell {
    /// A command the core can run on its own.
    Run(Command),
    Login,
    Register,
    SetCred,
    Connect(Option<String>),
    Disconnect,
    Hosts,
    ForgetHost(Option<String>),
    ForgetLogin(Option<String>),
    Help,
    Quit,
    Empty,
    Usage(String),
}

/// Split off the first whitespace-delimited word.
///
/// The remainder keeps its internal spacing (so `send` can echo a message
/// verbatim) but loses the separator run before it.
fn split_word(line: &str) -> (&str, &str) {
    match line.find(char::is_whitespace) {
        Some(index) => (&line[..index], line[index..].trim_start()),
        None => (line, ""),
    }
}

pub fn parse(line: &str) -> Shell {
    let line = line.trim();
    if line.is_empty() {
        return Shell::Empty;
    }

    let (word, rest) = split_word(line);
    match word {
        "play" => {
            let (game, bet) = split_word(rest);
            if game.is_empty() {
                return Shell::Usage("Usage: play <game> [bet]".to_string());
            }
            let bet = if bet.is_empty() {
                None
            } else {
                match parse_bet_arg(bet) {
                    Ok(bet) => Some(bet),
                    Err(message) => return Shell::Usage(message),
                }
            };
            Shell::Run(Command::Play {
                game: game.to_string(),
                bet,
            })
        }
        "history" => {
            let (raw, _) = split_word(rest);
            let limit = if raw.is_empty() {
                DEFAULT_HISTORY
            } else {
                match raw.parse::<u32>() {
                    Ok(count) if (1..=MAX_HISTORY).contains(&count) => count,
                    _ => return Shell::Usage(format!("Usage: history [1-{MAX_HISTORY}]")),
                }
            };
            Shell::Run(Command::History { limit })
        }
        "games" => Shell::Run(Command::Games),
        "logout" => Shell::Run(Command::Logout),
        "ping" => Shell::Run(Command::Ping),
        "send" => {
            if rest.is_empty() {
                Shell::Usage("Usage: send <message>".to_string())
            } else {
                Shell::Run(Command::Echo(rest.to_string()))
            }
        }
        "info" => Shell::Run(Command::Info),
        "help" => Shell::Help,
        "connect" => Shell::Connect(optional_word(rest)),
        "login" => Shell::Login,
        "register" => Shell::Register,
        "setcred" => Shell::SetCred,
        "disconnect" => Shell::Disconnect,
        "hosts" => Shell::Hosts,
        "forget-host" => Shell::ForgetHost(optional_word(rest)),
        "forget-login" => Shell::ForgetLogin(optional_word(rest)),
        "quit" => Shell::Quit,
        _ => Shell::Help,
    }
}

fn optional_word(rest: &str) -> Option<String> {
    let (word, _) = split_word(rest);
    if word.is_empty() {
        None
    } else {
        Some(word.to_string())
    }
}