1use clap::Parser;
2
3fn parse_country_code(s: &str) -> Result<String, String> {
4 let upper = s.to_ascii_uppercase();
5 let valid = upper.chars().all(|c| c.is_ascii_alphabetic());
6 if !valid {
7 return Err("Country code must be alphabetic (A-Z)".into());
8 }
9 let len = upper.len();
10 if !(len == 2 || len == 3) {
11 return Err("Country code length must be 2 or 3".into());
12 }
13 Ok(upper)
14}
15
16#[derive(Parser, Debug)]
18#[command(
19 author,
20 version,
21 about = "This tool can be used to obtain IP addresses by country or by AS number."
22)]
23pub struct Cli {
24 #[arg(
25 short = 'c',
26 long = "country",
27 required_unless_present_any = ["as_numbers", "overlap"],
28 required = false,
29 num_args = 1..,
30 value_parser = parse_country_code,
31 help = "Specify the country codes.\nExample: jp br us"
32 )]
33 pub country_codes: Option<Vec<String>>,
34
35 #[arg(
36 short = 'a',
37 long = "as-number",
38 required_unless_present_any = ["country_codes", "overlap"],
39 required = false,
40 value_parser = clap::value_parser!(u32),
41 num_args = 1..,
42 help = "Specify AS numbers.\nExample: 0000 1234"
43 )]
44 pub as_numbers: Option<Vec<u32>>,
45
46 #[arg(
47 short = 'o',
48 long = "overlap",
49 help = "Write down the IP addresses of the overlapping country and AS numbers in a file of your choice.\nBoth the -c and -a arguments must be specified.",
50 required = false,
51 default_value = "false",
52 requires("country_codes"),
53 requires("as_numbers")
54 )]
55 pub overlap: bool,
56
57 #[arg(
58 short = 'f',
59 long = "format",
60 default_value = "txt",
61 required = false,
62 hide_default_value = true,
63 value_parser = ["txt", "nft"],
64 help = "Select output format: 'txt' or 'nft'.\ndefault: txt"
65 )]
66 pub output_format: String,
67
68 #[arg(
69 long = "max-retries",
70 help = "Maximum HTTP retry attempts for downloads.",
71 required = false,
72 default_value_t = 6u32,
73 value_parser = clap::value_parser!(u32)
74 )]
75 pub max_retries: u32,
76
77 #[arg(
78 long = "max-backoff-sec",
79 help = "Cap for exponential backoff seconds per retry.",
80 required = false,
81 default_value_t = 16u64,
82 value_parser = clap::value_parser!(u64)
83 )]
84 pub max_backoff_sec: u64,
85
86 #[arg(
87 long = "http-timeout-secs",
88 help = "HTTP request total timeout in seconds.",
89 required = false,
90 default_value_t = 20u64,
91 value_parser = clap::value_parser!(u64)
92 )]
93 pub http_timeout_secs: u64,
94
95 #[arg(
96 long = "connect-timeout-secs",
97 help = "HTTP connect timeout in seconds.",
98 required = false,
99 default_value_t = 10u64,
100 value_parser = clap::value_parser!(u64)
101 )]
102 pub connect_timeout_secs: u64,
103
104 #[arg(
105 long = "concurrency",
106 short = 'C',
107 help = "Max concurrent AS queries.",
108 required = false,
109 default_value_t = 5usize,
110 value_parser = clap::value_parser!(usize)
111 )]
112 pub concurrency: usize,
113
114 #[arg(
115 long = "continue-on-partial",
116 help = "Continue with successfully downloaded RIR files even if some downloads fail.",
117 required = false,
118 default_value_t = false
119 )]
120 pub continue_on_partial: bool,
121
122 #[arg(
123 long = "debug",
124 short = 'd',
125 help = "Enable verbose debug output to stderr.",
126 required = false,
127 default_value_t = false
128 )]
129 pub debug: bool,
130}