summaryrefslogtreecommitdiffstats
path: root/src/client/shell.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/client/shell.rs110
1 files changed, 110 insertions, 0 deletions
diff --git a/src/client/shell.rs b/src/client/shell.rs
new file mode 100644
index 0000000..73843c8
--- /dev/null
+++ b/src/client/shell.rs
@@ -0,0 +1,110 @@
1//! Parser for the terminal's command line.
2//!
3//! The core speaks `Command`; this module turns one typed line into either a
4//! core command or an action only an interactive frontend can perform
5//! (connecting, prompting for a password, listing hosts).
6
7use crate::client::ui::parse_bet_arg;
8use crate::client::view::Command;
9use crate::history::{DEFAULT_HISTORY, MAX_HISTORY};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Shell {
13 /// A command the core can run on its own.
14 Run(Command),
15 Login,
16 Register,
17 SetCred,
18 Connect(Option<String>),
19 Disconnect,
20 Hosts,
21 ForgetHost(Option<String>),
22 ForgetLogin(Option<String>),
23 Help,
24 Quit,
25 Empty,
26 Usage(String),
27}
28
29/// Split off the first whitespace-delimited word.
30///
31/// The remainder keeps its internal spacing (so `send` can echo a message
32/// verbatim) but loses the separator run before it.
33fn split_word(line: &str) -> (&str, &str) {
34 match line.find(char::is_whitespace) {
35 Some(index) => (&line[..index], line[index..].trim_start()),
36 None => (line, ""),
37 }
38}
39
40pub fn parse(line: &str) -> Shell {
41 let line = line.trim();
42 if line.is_empty() {
43 return Shell::Empty;
44 }
45
46 let (word, rest) = split_word(line);
47 match word {
48 "play" => {
49 let (game, bet) = split_word(rest);
50 if game.is_empty() {
51 return Shell::Usage("Usage: play <game> [bet]".to_string());
52 }
53 let bet = if bet.is_empty() {
54 None
55 } else {
56 match parse_bet_arg(bet) {
57 Ok(bet) => Some(bet),
58 Err(message) => return Shell::Usage(message),
59 }
60 };
61 Shell::Run(Command::Play {
62 game: game.to_string(),
63 bet,
64 })
65 }
66 "history" => {
67 let (raw, _) = split_word(rest);
68 let limit = if raw.is_empty() {
69 DEFAULT_HISTORY
70 } else {
71 match raw.parse::<u32>() {
72 Ok(count) if (1..=MAX_HISTORY).contains(&count) => count,
73 _ => return Shell::Usage(format!("Usage: history [1-{MAX_HISTORY}]")),
74 }
75 };
76 Shell::Run(Command::History { limit })
77 }
78 "games" => Shell::Run(Command::Games),
79 "logout" => Shell::Run(Command::Logout),
80 "ping" => Shell::Run(Command::Ping),
81 "send" => {
82 if rest.is_empty() {
83 Shell::Usage("Usage: send <message>".to_string())
84 } else {
85 Shell::Run(Command::Echo(rest.to_string()))
86 }
87 }
88 "info" => Shell::Run(Command::Info),
89 "help" => Shell::Help,
90 "connect" => Shell::Connect(optional_word(rest)),
91 "login" => Shell::Login,
92 "register" => Shell::Register,
93 "setcred" => Shell::SetCred,
94 "disconnect" => Shell::Disconnect,
95 "hosts" => Shell::Hosts,
96 "forget-host" => Shell::ForgetHost(optional_word(rest)),
97 "forget-login" => Shell::ForgetLogin(optional_word(rest)),
98 "quit" => Shell::Quit,
99 _ => Shell::Help,
100 }
101}
102
103fn optional_word(rest: &str) -> Option<String> {
104 let (word, _) = split_word(rest);
105 if word.is_empty() {
106 None
107 } else {
108 Some(word.to_string())
109 }
110}