//! 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) }