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
104
105
106
107
108
109
110
111
112
use self::super::util::ACTIVITY_TIMEOUT_DEFAULT;
use std::path::{PathBuf, Path};
use clap::{AppSettings, Arg};
use chrono::Duration;
use std::fs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Options {
pub database_file: (String, PathBuf),
pub leaderboard_settings_file: Option<(String, PathBuf)>,
pub activity_timeout: Duration,
}
impl Options {
pub fn parse() -> Options {
let matches = app_from_crate!("\n")
.setting(AppSettings::ColoredHelp)
.arg(Arg::from_usage("[DATABASE_FILE] 'File containing the main database. Default: ./sudoku-backend.db'")
.validator(|s| Options::file_validator("Database", s)))
.arg(Arg::from_usage("[LEADERBOARD_SETTINGS_FILE] 'Optional file containing the leaderboard settings. \
Default: ./leaderboard.toml, then hard defaults'")
.validator(|s| Options::file_validator("Leaderboard settings", s)))
.arg(Arg::from_usage("[ACTIVITY_TIMEOUT] 'Amount of time in milliseconds after last request a user is considered to have \"left the site\". \
Default: 600'000 (10 minutes)'")
.validator(Options::positive_integer_validator))
.get_matches();
Options {
database_file: matches.value_of("DATABASE_FILE")
.map(|s| if let Ok(f) = fs::canonicalize(s) {
(s.to_string(), f)
} else {
(s.to_string(), fs::canonicalize(Path::new(s).parent().unwrap_or_else(|| Path::new("."))).unwrap().join("sudoku-backend.db"))
})
.unwrap_or_else(|| ("./sudoku-backend.db".to_string(), fs::canonicalize(".").unwrap().join("sudoku-backend.db"))),
leaderboard_settings_file: matches.value_of("LEADERBOARD_SETTINGS_FILE")
.map(|s| if let Ok(f) = fs::canonicalize(s) {
(s.to_string(), f)
} else {
(s.to_string(), fs::canonicalize(Path::new(s).parent().unwrap_or_else(|| Path::new("."))).unwrap().join("leaderboard.toml"))
})
.or_else(|| {
fs::metadata("./leaderboard.toml").ok().and_then(|m| if m.is_file() {
fs::canonicalize("./leaderboard.toml").ok().map(|f| ("./leaderboard.toml".to_string(), f))
} else {
None
})
}),
activity_timeout: matches.value_of("ACTIVITY_TIMEOUT")
.map(|at| Duration::milliseconds(at.parse().unwrap()))
.unwrap_or_else(|| *ACTIVITY_TIMEOUT_DEFAULT),
}
}
fn file_validator(whom: &str, s: String) -> Result<(), String> {
let mut p = PathBuf::from(&s);
if let Ok(f) = fs::canonicalize(&p) {
if !f.is_file() {
return Err(format!("{} file \"{}\" not actually a file", whom, s));
}
}
p.pop();
if p == Path::new("") {
p = PathBuf::from(".");
}
fs::canonicalize(&p).map_err(|_| format!("{} parent directory \"{}\" nonexistant", whom, p.display())).and_then(|f| if !f.is_file() {
Ok(())
} else {
Err(format!("{} file \"{}\" actually a file", whom, p.display()))
})
}
fn positive_integer_validator(s: String) -> Result<(), String> {
match s.parse::<u64>().map_err(|e| format!("{} is not a valid integer: {}", s, e))? {
0 => Err("0 is not positive".to_string()),
_ => Ok(()),
}
}
}