+205
src/cli.rs
+205
src/cli.rs
···
1
+
use std::{
2
+
error::Error,
3
+
fmt::Display,
4
+
net::{AddrParseError, SocketAddr},
5
+
path::PathBuf,
6
+
};
7
+
8
+
#[derive(Debug, PartialEq, Eq)]
9
+
pub enum Args {
10
+
Help,
11
+
Host { boats: PathBuf, addr: SocketAddr },
12
+
Join { boats: PathBuf, addr: SocketAddr },
13
+
}
14
+
15
+
#[derive(Debug, PartialEq, Eq)]
16
+
pub enum ArgsParseError {
17
+
NotEnoughArguments,
18
+
InvalidMode(String),
19
+
InvalidAddr(AddrParseError),
20
+
}
21
+
22
+
impl Error for ArgsParseError {
23
+
fn source(&self) -> Option<&(dyn Error + 'static)> {
24
+
match self {
25
+
ArgsParseError::NotEnoughArguments => None,
26
+
ArgsParseError::InvalidMode(_) => None,
27
+
ArgsParseError::InvalidAddr(e) => Some(e),
28
+
}
29
+
}
30
+
}
31
+
32
+
impl Display for ArgsParseError {
33
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34
+
match self {
35
+
ArgsParseError::NotEnoughArguments => write!(f, "not enough arguments"),
36
+
ArgsParseError::InvalidMode(mode) => write!(f, "invalid mode: '{mode}'"),
37
+
ArgsParseError::InvalidAddr(e) => write!(f, "error parsing address: {e}"),
38
+
}
39
+
}
40
+
}
41
+
42
+
impl Args {
43
+
pub fn parse<S: AsRef<str>>(iter: impl IntoIterator<Item = S>) -> Result<Self, ArgsParseError> {
44
+
let mut iter = iter.into_iter();
45
+
let Some(arg) = iter.next() else {
46
+
return Ok(Self::Help);
47
+
};
48
+
49
+
if matches!(arg.as_ref(), "-h" | "--help") {
50
+
return Ok(Self::Help);
51
+
}
52
+
53
+
let boats = PathBuf::from(arg.as_ref());
54
+
let arg = iter.next().ok_or(ArgsParseError::NotEnoughArguments)?;
55
+
56
+
let addr = iter
57
+
.next()
58
+
.ok_or(ArgsParseError::NotEnoughArguments)?
59
+
.as_ref()
60
+
.parse::<SocketAddr>()
61
+
.map_err(ArgsParseError::InvalidAddr)?;
62
+
63
+
match arg.as_ref() {
64
+
"host" => Ok(Self::Host { boats, addr }),
65
+
"join" => Ok(Self::Join { boats, addr }),
66
+
a => Err(ArgsParseError::InvalidMode(a.to_string())),
67
+
}
68
+
}
69
+
70
+
pub fn print_help(arg0: &str) {
71
+
println!("Battleship game!");
72
+
println!();
73
+
println!("Usage:");
74
+
println!(" {arg0} [-h|--help]");
75
+
println!(" \tPrint help information and exit.");
76
+
println!(" {arg0} <BOATS_FILE> host <ADDRESS>:<PORT>");
77
+
println!(" \tHost a game. Use 127.0.0.1 for a local");
78
+
println!(" \t game. Use 0.0.0.0 to make it joinable by");
79
+
println!(" \t other hosts. IPv6 is also supported!");
80
+
println!(" {arg0} <BOATS_FILE> join <ADDRESS>:<PORT>");
81
+
println!(" \tJoin a game. IPv4 and IPv6 are supported!");
82
+
}
83
+
}
84
+
85
+
#[cfg(test)]
86
+
mod tests {
87
+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
88
+
89
+
use super::*;
90
+
91
+
#[test]
92
+
fn parse_nothing() {
93
+
assert_eq!(Args::parse::<String>([]), Ok(Args::Help));
94
+
}
95
+
96
+
#[test]
97
+
fn parse_help() {
98
+
assert_eq!(Args::parse(["-h"]), Ok(Args::Help));
99
+
assert_eq!(Args::parse(["--help"]), Ok(Args::Help));
100
+
}
101
+
102
+
#[test]
103
+
fn parse_host() {
104
+
assert_eq!(
105
+
Args::parse(["./my_boats", "host", "127.0.0.1:8080"]),
106
+
Ok(Args::Host {
107
+
boats: "./my_boats".into(),
108
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
109
+
})
110
+
);
111
+
assert_eq!(
112
+
Args::parse(["./my_boats", "host", "0.0.0.0:8080"]),
113
+
Ok(Args::Host {
114
+
boats: "./my_boats".into(),
115
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080),
116
+
})
117
+
);
118
+
assert_eq!(
119
+
Args::parse(["./my_boats", "host", "[::1]:8080"]),
120
+
Ok(Args::Host {
121
+
boats: "./my_boats".into(),
122
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
123
+
})
124
+
);
125
+
assert_eq!(
126
+
Args::parse(["./my_boats", "host", "[::]:8080"]),
127
+
Ok(Args::Host {
128
+
boats: "./my_boats".into(),
129
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080),
130
+
})
131
+
);
132
+
}
133
+
134
+
#[test]
135
+
fn parse_join() {
136
+
assert_eq!(
137
+
Args::parse(["./my_boats", "join", "127.0.0.1:8080"]),
138
+
Ok(Args::Join {
139
+
boats: "./my_boats".into(),
140
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
141
+
})
142
+
);
143
+
assert_eq!(
144
+
Args::parse(["./my_boats", "join", "0.0.0.0:8080"]),
145
+
Ok(Args::Join {
146
+
boats: "./my_boats".into(),
147
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080),
148
+
})
149
+
);
150
+
assert_eq!(
151
+
Args::parse(["./my_boats", "join", "[::1]:8080"]),
152
+
Ok(Args::Join {
153
+
boats: "./my_boats".into(),
154
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
155
+
})
156
+
);
157
+
assert_eq!(
158
+
Args::parse(["./my_boats", "join", "[::]:8080"]),
159
+
Ok(Args::Join {
160
+
boats: "./my_boats".into(),
161
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080),
162
+
})
163
+
);
164
+
}
165
+
166
+
#[test]
167
+
fn parse_errors() {
168
+
assert_eq!(
169
+
Args::parse(["abcoabcuobacwa"]),
170
+
Err(ArgsParseError::NotEnoughArguments)
171
+
);
172
+
assert_eq!(
173
+
Args::parse(["./my_boats", "foobar"]),
174
+
Err(ArgsParseError::NotEnoughArguments)
175
+
);
176
+
assert_eq!(
177
+
Args::parse(["./my_boats", "foobar", "127.0.0.1:8080"]),
178
+
Err(ArgsParseError::InvalidMode("foobar".into()))
179
+
);
180
+
assert!(matches!(
181
+
Args::parse(["./my_boats", "host", "blablabla"]),
182
+
Err(ArgsParseError::InvalidAddr(_))
183
+
));
184
+
assert!(matches!(
185
+
Args::parse(["./my_boats", "host", "localhost"]),
186
+
Err(ArgsParseError::InvalidAddr(_))
187
+
));
188
+
assert!(matches!(
189
+
Args::parse(["./my_boats", "host", "0.0.0.0"]),
190
+
Err(ArgsParseError::InvalidAddr(_))
191
+
));
192
+
assert!(matches!(
193
+
Args::parse(["./my_boats", "join", "127.0.0.1"]),
194
+
Err(ArgsParseError::InvalidAddr(_))
195
+
));
196
+
assert!(matches!(
197
+
Args::parse(["./my_boats", "join", "localhost"]),
198
+
Err(ArgsParseError::InvalidAddr(_))
199
+
));
200
+
assert!(matches!(
201
+
Args::parse(["./my_boats", "join", "localhost:8080"]),
202
+
Err(ArgsParseError::InvalidAddr(_))
203
+
));
204
+
}
205
+
}
+24
-2
src/main.rs
+24
-2
src/main.rs
···
1
-
fn main() {
2
-
println!("Hello, world!");
1
+
use std::process::ExitCode;
2
+
3
+
mod cli;
4
+
5
+
fn main() -> ExitCode {
6
+
let mut args = std::env::args();
7
+
let arg0 = args.next();
8
+
let args = match cli::Args::parse(args) {
9
+
Err(e) => {
10
+
eprintln!("Invalid arguments: {e}");
11
+
return ExitCode::FAILURE;
12
+
}
13
+
Ok(args) => args,
14
+
};
15
+
16
+
eprintln!("Parsed arguments: {args:#?}");
17
+
18
+
match args {
19
+
cli::Args::Help => cli::Args::print_help(arg0.as_deref().unwrap_or("navy")),
20
+
cli::Args::Host { .. } => todo!(),
21
+
cli::Args::Join { .. } => todo!(),
22
+
}
23
+
24
+
ExitCode::SUCCESS
3
25
}