···
+
net::{AddrParseError, SocketAddr},
+
#[derive(Debug, PartialEq, Eq)]
+
Host { boats: PathBuf, addr: SocketAddr },
+
Join { boats: PathBuf, addr: SocketAddr },
+
#[derive(Debug, PartialEq, Eq)]
+
pub enum ArgsParseError {
+
InvalidAddr(AddrParseError),
+
impl Error for ArgsParseError {
+
fn source(&self) -> Option<&(dyn Error + 'static)> {
+
ArgsParseError::NotEnoughArguments => None,
+
ArgsParseError::InvalidMode(_) => None,
+
ArgsParseError::InvalidAddr(e) => Some(e),
+
impl Display for ArgsParseError {
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
ArgsParseError::NotEnoughArguments => write!(f, "not enough arguments"),
+
ArgsParseError::InvalidMode(mode) => write!(f, "invalid mode: '{mode}'"),
+
ArgsParseError::InvalidAddr(e) => write!(f, "error parsing address: {e}"),
+
pub fn parse<S: AsRef<str>>(iter: impl IntoIterator<Item = S>) -> Result<Self, ArgsParseError> {
+
let mut iter = iter.into_iter();
+
let Some(arg) = iter.next() else {
+
if matches!(arg.as_ref(), "-h" | "--help") {
+
let boats = PathBuf::from(arg.as_ref());
+
let arg = iter.next().ok_or(ArgsParseError::NotEnoughArguments)?;
+
.ok_or(ArgsParseError::NotEnoughArguments)?
+
.map_err(ArgsParseError::InvalidAddr)?;
+
"host" => Ok(Self::Host { boats, addr }),
+
"join" => Ok(Self::Join { boats, addr }),
+
a => Err(ArgsParseError::InvalidMode(a.to_string())),
+
pub fn print_help(arg0: &str) {
+
println!("Battleship game!");
+
println!(" {arg0} [-h|--help]");
+
println!(" \tPrint help information and exit.");
+
println!(" {arg0} <BOATS_FILE> host <ADDRESS>:<PORT>");
+
println!(" \tHost a game. Use 127.0.0.1 for a local");
+
println!(" \t game. Use 0.0.0.0 to make it joinable by");
+
println!(" \t other hosts. IPv6 is also supported!");
+
println!(" {arg0} <BOATS_FILE> join <ADDRESS>:<PORT>");
+
println!(" \tJoin a game. IPv4 and IPv6 are supported!");
+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+
assert_eq!(Args::parse::<String>([]), Ok(Args::Help));
+
assert_eq!(Args::parse(["-h"]), Ok(Args::Help));
+
assert_eq!(Args::parse(["--help"]), Ok(Args::Help));
+
Args::parse(["./my_boats", "host", "127.0.0.1:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
+
Args::parse(["./my_boats", "host", "0.0.0.0:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080),
+
Args::parse(["./my_boats", "host", "[::1]:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
+
Args::parse(["./my_boats", "host", "[::]:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080),
+
Args::parse(["./my_boats", "join", "127.0.0.1:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
+
Args::parse(["./my_boats", "join", "0.0.0.0:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080),
+
Args::parse(["./my_boats", "join", "[::1]:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
+
Args::parse(["./my_boats", "join", "[::]:8080"]),
+
boats: "./my_boats".into(),
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080),
+
Args::parse(["abcoabcuobacwa"]),
+
Err(ArgsParseError::NotEnoughArguments)
+
Args::parse(["./my_boats", "foobar"]),
+
Err(ArgsParseError::NotEnoughArguments)
+
Args::parse(["./my_boats", "foobar", "127.0.0.1:8080"]),
+
Err(ArgsParseError::InvalidMode("foobar".into()))
+
Args::parse(["./my_boats", "host", "blablabla"]),
+
Err(ArgsParseError::InvalidAddr(_))
+
Args::parse(["./my_boats", "host", "localhost"]),
+
Err(ArgsParseError::InvalidAddr(_))
+
Args::parse(["./my_boats", "host", "0.0.0.0"]),
+
Err(ArgsParseError::InvalidAddr(_))
+
Args::parse(["./my_boats", "join", "127.0.0.1"]),
+
Err(ArgsParseError::InvalidAddr(_))
+
Args::parse(["./my_boats", "join", "localhost"]),
+
Err(ArgsParseError::InvalidAddr(_))
+
Args::parse(["./my_boats", "join", "localhost:8080"]),
+
Err(ArgsParseError::InvalidAddr(_))