use clap::{Parser, ValueEnum};
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq)]
pub enum SparklineScale {
Linear,
Logarithmic,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq)]
pub enum Column {
Hop,
Host,
Loss,
Sent,
Last,
Avg,
Ema,
Jitter,
JitterAvg,
Best,
Worst,
Graph,
}
impl Column {
pub fn all() -> Vec<Column> {
vec![
Column::Hop,
Column::Host,
Column::Loss,
Column::Sent,
Column::Last,
Column::Avg,
Column::Ema,
Column::Jitter,
Column::JitterAvg,
Column::Best,
Column::Worst,
Column::Graph,
]
}
pub fn default_columns() -> Vec<Column> {
vec![
Column::Hop,
Column::Host,
Column::Loss,
Column::Sent,
Column::Last,
Column::Avg,
Column::Ema,
Column::Best,
Column::Worst,
Column::Graph,
]
}
pub fn header(&self) -> &'static str {
match self {
Column::Hop => "",
Column::Host => "Hostname",
Column::Loss => "Loss%",
Column::Sent => "Pkts",
Column::Last => "LastRTT",
Column::Avg => "AvgRTT",
Column::Ema => "EmaRTT",
Column::Jitter => "Jitter",
Column::JitterAvg => "JitAvg",
Column::Best => "BestRTT",
Column::Worst => "WorstRTT",
Column::Graph => "RTT History",
}
}
pub fn width(&self) -> usize {
match self {
Column::Hop => 3,
Column::Host => 21,
Column::Loss => 7,
Column::Sent => 4,
Column::Last => 8,
Column::Avg => 8,
Column::Ema => 8,
Column::Jitter => 8,
Column::JitterAvg => 8,
Column::Best => 8,
Column::Worst => 8,
Column::Graph => 20, }
}
}
#[derive(Parser, Debug, Clone)]
#[command(name = "mtr-ng")]
#[command(
about = "A modern implementation of mtr (My Traceroute) with unicode and terminal graphics"
)]
#[command(version = env!("CARGO_PKG_VERSION"))]
pub struct Args {
pub target: String,
#[arg(short, long)]
pub count: Option<usize>,
#[arg(short, long, default_value = "1000")]
pub interval: u64,
#[arg(short = 'M', long, default_value = "30")]
pub max_hops: u8,
#[arg(short, long)]
pub report: bool,
#[arg(short, long)]
pub numeric: bool,
#[arg(long, value_enum, default_value = "logarithmic")]
pub sparkline_scale: SparklineScale,
#[arg(long, default_value = "0.1")]
pub ema_alpha: f64,
#[arg(long, value_enum, value_delimiter = ',')]
pub fields: Option<Vec<Column>>,
#[arg(long, help = "Use Sixel graphics for enhanced sparklines")]
pub sixel: bool,
#[arg(long, help = "Display all available columns")]
pub show_all: bool,
}
impl Args {
pub fn get_columns(&self) -> Vec<Column> {
if self.show_all {
Column::all()
} else if let Some(ref fields) = self.fields {
fields.clone()
} else {
Column::default_columns()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_args_default_values() {
let args = Args::try_parse_from(["mtr-ng", "example.com"]).unwrap();
assert_eq!(args.target, "example.com");
assert_eq!(args.count, None); assert_eq!(args.interval, 1000);
assert_eq!(args.max_hops, 30);
assert!(!args.report);
assert!(!args.numeric);
assert_eq!(args.sparkline_scale, SparklineScale::Logarithmic);
assert_eq!(args.ema_alpha, 0.1);
assert!(args.fields.is_none());
assert!(!args.show_all);
}
#[test]
fn test_args_custom_values() {
let args = Args::try_parse_from([
"mtr-ng",
"--count",
"20",
"--interval",
"500",
"--max-hops",
"50",
"--report",
"--numeric",
"google.com",
])
.unwrap();
assert_eq!(args.target, "google.com");
assert_eq!(args.count, Some(20));
assert_eq!(args.interval, 500);
assert_eq!(args.max_hops, 50);
assert!(args.report);
assert!(args.numeric);
assert_eq!(args.sparkline_scale, SparklineScale::Logarithmic);
assert_eq!(args.ema_alpha, 0.1);
assert!(args.fields.is_none());
assert!(!args.show_all);
}
#[test]
fn test_args_short_flags() {
let args = Args::try_parse_from([
"mtr-ng",
"-c",
"15",
"-i",
"2000",
"-M",
"25",
"-r",
"-n",
"test.example.com",
])
.unwrap();
assert_eq!(args.target, "test.example.com");
assert_eq!(args.count, Some(15));
assert_eq!(args.interval, 2000);
assert_eq!(args.max_hops, 25);
assert!(args.report);
assert!(args.numeric);
assert_eq!(args.sparkline_scale, SparklineScale::Logarithmic);
assert_eq!(args.ema_alpha, 0.1);
assert!(args.fields.is_none());
assert!(!args.show_all);
}
}