1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
//! 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 <dir>] [--unsafe]
-d, --data-dir <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<PathBuf>,
pub help: bool,
}
/// Parse arguments, without the program name.
pub fn parse_args(args: impl IntoIterator<Item = String>) -> Result<ClientArgs, String> {
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)
}
|