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
//! CLI argument parsing (argh derive).
use argh::FromArgs;
/// Parse `--width`: 0 means "use the built-in default" (32 monsters / 24 items),
/// otherwise the value must lie in [24, 48].
fn parse_width(s: &str) -> Result<u32, String> {
let n: u32 = s
.parse()
.map_err(|_| format!("`{s}` is not a valid width"))?;
if n == 0 || (24..=48).contains(&n) {
Ok(n)
} else {
Err(format!("width must be 0 (default) or in 24..=48, got {n}"))
}
}
/// Monster Hunter bestiary in your terminal.
#[derive(FromArgs, Debug)]
pub struct Args {
/// monster or item name (fuzzy, typo-tolerant). multiple names render
/// back-to-back.
#[argh(positional)]
pub query: Vec<String>,
/// look up an item instead of a monster.
#[argh(switch)]
pub item: bool,
/// terminal column width for the icon. 0 = built-in default (32 monsters /
/// 24 items); otherwise must be in 24..=48.
#[argh(option, from_str_fn(parse_width), default = "0")]
pub width: u32,
/// show an info card next to the icon.
#[argh(switch)]
pub detail: bool,
/// pick a random monster (or item with --item).
#[argh(switch)]
pub random: bool,
/// list monsters belonging to a game (mhw, MHW, ...).
#[argh(option)]
pub list: Option<String>,
/// filter by game code or abbreviation (mhw, MHW, ...).
#[argh(option)]
pub game: Option<String>,
/// display language: en / ja / zh. "auto" uses config or defaults to en.
#[argh(option, default = "\"auto\".to_string()")]
pub lang: String,
/// suppress the name line on stderr.
#[argh(switch)]
pub hide_name: bool,
/// icon only, no info card (overrides --detail).
#[argh(switch)]
pub no_card: bool,
/// show every match instead of just the best one.
#[argh(switch, short = 'a')]
pub all: bool,
/// print version and exit.
#[argh(switch, short = 'V')]
pub version: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn width_zero_means_default() {
assert_eq!(parse_width("0").unwrap(), 0);
}
#[test]
fn width_accepts_inclusive_bounds() {
assert_eq!(parse_width("24").unwrap(), 24);
assert_eq!(parse_width("48").unwrap(), 48);
assert_eq!(parse_width("32").unwrap(), 32);
}
#[test]
fn width_rejects_out_of_range() {
// 1..=23 too small, 49+ too large.
assert!(parse_width("1").is_err());
assert!(parse_width("23").is_err());
assert!(parse_width("49").is_err());
assert!(parse_width("1000").is_err());
}
#[test]
fn width_rejects_non_numeric() {
assert!(parse_width("wide").is_err());
assert!(parse_width("-5").is_err());
}
}