//! Client data location and command-line arguments.
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
pub const APP_DIR: &str = "zagan-bet";
pub const DATABASE_FILE: &str = "client.db";
pub const USAGE: &str = "\
usage: client [-d
] [--unsafe]
-d, --data-dir where to keep client.db (default: XDG data dir)
--unsafe skip certificate verification
-h, --help show this message
The database lives in $XDG_DATA_HOME/zagan-bet/client.db, falling back to
~/.local/share/zagan-bet/client.db (or %APPDATA%\\zagan-bet on Windows).";
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ClientArgs {
pub unsafe_mode: bool,
pub data_dir: Option,
pub help: bool,
}
/// Parse arguments, without the program name.
pub fn parse_args(args: impl IntoIterator- ) -> Result {
let mut parsed = ClientArgs::default();
let mut args = args.into_iter();
while let Some(argument) = args.next() {
match argument.as_str() {
"-d" | "--data-dir" => {
let Some(value) = args.next() else {
return Err(format!("{argument} needs a directory\n{USAGE}"));
};
if value.is_empty() {
return Err(format!("{argument} needs a directory\n{USAGE}"));
}
parsed.data_dir = Some(PathBuf::from(value));
}
"--unsafe" => parsed.unsafe_mode = true,
"-h" | "--help" => parsed.help = true,
_ => {
if let Some(value) = argument.strip_prefix("--data-dir=") {
if value.is_empty() {
return Err(format!("--data-dir needs a directory\n{USAGE}"));
}
parsed.data_dir = Some(PathBuf::from(value));
} else {
return Err(format!("unknown argument: {argument}\n{USAGE}"));
}
}
}
}
Ok(parsed)
}
/// `$XDG_DATA_HOME/zagan-bet`, `%APPDATA%\zagan-bet`, or
/// `$HOME/.local/share/zagan-bet`, in that order.
pub fn data_dir_from(
xdg_data_home: Option<&OsStr>,
home: Option<&OsStr>,
appdata: Option<&OsStr>,
) -> PathBuf {
if let Some(xdg) = xdg_data_home {
let path = Path::new(xdg);
// The XDG spec says a relative XDG_DATA_HOME is invalid.
if !xdg.is_empty() && path.is_absolute() {
return path.join(APP_DIR);
}
}
if let Some(appdata) = appdata {
let path = Path::new(appdata);
if !appdata.is_empty() && path.is_absolute() {
return path.join(APP_DIR);
}
}
if let Some(home) = home {
let path = Path::new(home);
if !home.is_empty() && path.is_absolute() {
return path.join(".local").join("share").join(APP_DIR);
}
}
// No usable home directory: keep the old behaviour and stay local.
PathBuf::from(APP_DIR)
}
pub fn default_data_dir() -> PathBuf {
data_dir_from(
std::env::var_os("XDG_DATA_HOME").as_deref(),
std::env::var_os("HOME").as_deref(),
std::env::var_os("APPDATA").as_deref(),
)
}
pub fn database_path(data_dir: &Path) -> PathBuf {
data_dir.join(DATABASE_FILE)
}