1use clap::{Parser, ValueEnum};
2
3#[derive(ValueEnum, Debug, Clone, Copy, PartialEq)]
4pub enum SparklineScale {
5 Linear,
6 Logarithmic,
7}
8
9#[derive(ValueEnum, Debug, Clone, Copy, PartialEq)]
10pub enum Column {
11 Hop,
13 Host,
15 Loss,
17 Sent,
19 Last,
21 Avg,
23 Ema,
25 Jitter,
27 JitterAvg,
29 Best,
31 Worst,
33 Graph,
35}
36
37impl Column {
38 pub fn all() -> Vec<Column> {
40 vec![
41 Column::Hop,
42 Column::Host,
43 Column::Loss,
44 Column::Sent,
45 Column::Last,
46 Column::Avg,
47 Column::Ema,
48 Column::Jitter,
49 Column::JitterAvg,
50 Column::Best,
51 Column::Worst,
52 Column::Graph,
53 ]
54 }
55
56 pub fn default_columns() -> Vec<Column> {
58 vec![
59 Column::Hop,
60 Column::Host,
61 Column::Loss,
62 Column::Sent,
63 Column::Last,
64 Column::Avg,
65 Column::Ema,
66 Column::Best,
67 Column::Worst,
68 Column::Graph,
69 ]
70 }
71
72 pub fn header(&self) -> &'static str {
74 match self {
75 Column::Hop => "",
76 Column::Host => "Hostname",
77 Column::Loss => "Loss%",
78 Column::Sent => "Pkts",
79 Column::Last => "LastRTT",
80 Column::Avg => "AvgRTT",
81 Column::Ema => "EmaRTT",
82 Column::Jitter => "Jitter",
83 Column::JitterAvg => "JitAvg",
84 Column::Best => "BestRTT",
85 Column::Worst => "WorstRTT",
86 Column::Graph => "RTT History",
87 }
88 }
89
90 pub fn width(&self) -> usize {
92 match self {
93 Column::Hop => 3,
94 Column::Host => 21,
95 Column::Loss => 7,
96 Column::Sent => 4,
97 Column::Last => 8,
98 Column::Avg => 8,
99 Column::Ema => 8,
100 Column::Jitter => 8,
101 Column::JitterAvg => 8,
102 Column::Best => 8,
103 Column::Worst => 8,
104 Column::Graph => 20, }
106 }
107}
108
109#[derive(Parser, Debug, Clone)]
110#[command(name = "mtr-ng")]
111#[command(
112 about = "A modern implementation of mtr (My Traceroute) with unicode and terminal graphics"
113)]
114#[command(version = env!("CARGO_PKG_VERSION"))]
115pub struct Args {
116 pub target: String,
118
119 #[arg(short, long)]
121 pub count: Option<usize>,
122
123 #[arg(short, long, default_value = "1000")]
125 pub interval: u64,
126
127 #[arg(short = 'M', long, default_value = "30")]
129 pub max_hops: u8,
130
131 #[arg(short, long)]
133 pub report: bool,
134
135 #[arg(short, long)]
137 pub numeric: bool,
138
139 #[arg(long, value_enum, default_value = "logarithmic")]
141 pub sparkline_scale: SparklineScale,
142
143 #[arg(long, default_value = "0.1")]
145 pub ema_alpha: f64,
146
147 #[arg(long, value_enum, value_delimiter = ',')]
149 pub fields: Option<Vec<Column>>,
150
151 #[arg(long, help = "Use Sixel graphics for enhanced sparklines")]
153 pub sixel: bool,
154
155 #[arg(long, help = "Display all available columns")]
157 pub show_all: bool,
158}
159
160impl Args {
161 pub fn get_columns(&self) -> Vec<Column> {
163 if self.show_all {
164 Column::all()
165 } else if let Some(ref fields) = self.fields {
166 fields.clone()
167 } else {
168 Column::default_columns()
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_args_default_values() {
179 let args = Args::try_parse_from(["mtr-ng", "example.com"]).unwrap();
180 assert_eq!(args.target, "example.com");
181 assert_eq!(args.count, None); assert_eq!(args.interval, 1000);
183 assert_eq!(args.max_hops, 30);
184 assert!(!args.report);
185 assert!(!args.numeric);
186 assert_eq!(args.sparkline_scale, SparklineScale::Logarithmic);
187 assert_eq!(args.ema_alpha, 0.1);
188 assert!(args.fields.is_none());
189 assert!(!args.show_all);
190 }
191
192 #[test]
193 fn test_args_custom_values() {
194 let args = Args::try_parse_from([
195 "mtr-ng",
196 "--count",
197 "20",
198 "--interval",
199 "500",
200 "--max-hops",
201 "50",
202 "--report",
203 "--numeric",
204 "google.com",
205 ])
206 .unwrap();
207
208 assert_eq!(args.target, "google.com");
209 assert_eq!(args.count, Some(20));
210 assert_eq!(args.interval, 500);
211 assert_eq!(args.max_hops, 50);
212 assert!(args.report);
213 assert!(args.numeric);
214 assert_eq!(args.sparkline_scale, SparklineScale::Logarithmic);
215 assert_eq!(args.ema_alpha, 0.1);
216 assert!(args.fields.is_none());
217 assert!(!args.show_all);
218 }
219
220 #[test]
221 fn test_args_short_flags() {
222 let args = Args::try_parse_from([
223 "mtr-ng",
224 "-c",
225 "15",
226 "-i",
227 "2000",
228 "-M",
229 "25",
230 "-r",
231 "-n",
232 "test.example.com",
233 ])
234 .unwrap();
235
236 assert_eq!(args.target, "test.example.com");
237 assert_eq!(args.count, Some(15));
238 assert_eq!(args.interval, 2000);
239 assert_eq!(args.max_hops, 25);
240 assert!(args.report);
241 assert!(args.numeric);
242 assert_eq!(args.sparkline_scale, SparklineScale::Logarithmic);
243 assert_eq!(args.ema_alpha, 0.1);
244 assert!(args.fields.is_none());
245 assert!(!args.show_all);
246 }
247}