-
-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Allow custom TUI Columns feat: Allow custom TUI Column feat: allow customizing columns rebased (WIP) feat: Allow Custom Columns TUI
- Loading branch information
Showing
12 changed files
with
506 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
use anyhow::anyhow; | ||
use itertools::Itertools; | ||
use std::collections::HashSet; | ||
use std::fmt::{Display, Formatter}; | ||
|
||
/// The columns to display in the hops table of the TUI. | ||
#[derive(Debug, Clone, Eq, PartialEq)] | ||
pub struct TuiColumns(pub Vec<TuiColumn>); | ||
|
||
impl TryFrom<&str> for TuiColumns { | ||
type Error = anyhow::Error; | ||
|
||
fn try_from(value: &str) -> Result<Self, Self::Error> { | ||
Ok(Self( | ||
value | ||
.chars() | ||
.map(TuiColumn::try_from) | ||
.collect::<Result<Vec<_>, Self::Error>>()?, | ||
)) | ||
} | ||
} | ||
|
||
impl Default for TuiColumns { | ||
fn default() -> Self { | ||
Self::try_from(super::constants::DEFAULT_CUSTOM_COLUMNS).expect("custom columns") | ||
} | ||
} | ||
|
||
impl TuiColumns { | ||
/// Validate the columns. | ||
/// | ||
/// Returns any duplicate columns. | ||
pub fn find_duplicates(&self) -> Vec<String> { | ||
let (_, duplicates) = self.0.iter().fold( | ||
(HashSet::<TuiColumn>::new(), Vec::new()), | ||
|(mut all, mut dups), column| { | ||
if all.iter().contains(column) { | ||
dups.push(column.to_string()); | ||
} else { | ||
all.insert(*column); | ||
} | ||
(all, dups) | ||
}, | ||
); | ||
duplicates | ||
} | ||
} | ||
|
||
/// A TUI hops table column. | ||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] | ||
pub enum TuiColumn { | ||
/// The ttl for a hop. | ||
Ttl, | ||
/// The hostname for a hostname. | ||
Host, | ||
/// The packet loss % for a hop. | ||
LossPct, | ||
/// The number of probes sent for a hop. | ||
Sent, | ||
/// The number of responses received for a hop. | ||
Received, | ||
/// The last RTT for a hop. | ||
Last, | ||
/// The rolling average RTT for a hop. | ||
Average, | ||
/// The best RTT for a hop. | ||
Best, | ||
/// The worst RTT for a hop. | ||
Worst, | ||
/// The stddev of RTT for a hop. | ||
StdDev, | ||
/// The status of a hop. | ||
Status, | ||
} | ||
|
||
impl TryFrom<char> for TuiColumn { | ||
type Error = anyhow::Error; | ||
|
||
fn try_from(value: char) -> Result<Self, Self::Error> { | ||
match value.to_ascii_lowercase() { | ||
'h' => Ok(Self::Ttl), | ||
'o' => Ok(Self::Host), | ||
'l' => Ok(Self::LossPct), | ||
's' => Ok(Self::Sent), | ||
'r' => Ok(Self::Received), | ||
'a' => Ok(Self::Last), | ||
'v' => Ok(Self::Average), | ||
'b' => Ok(Self::Best), | ||
'w' => Ok(Self::Worst), | ||
'd' => Ok(Self::StdDev), | ||
't' => Ok(Self::Status), | ||
c => Err(anyhow!(format!("unknown column code: {c}"))), | ||
} | ||
} | ||
} | ||
|
||
impl Display for TuiColumn { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Self::Ttl => write!(f, "h"), | ||
Self::Host => write!(f, "o"), | ||
Self::LossPct => write!(f, "l"), | ||
Self::Sent => write!(f, "s"), | ||
Self::Received => write!(f, "r"), | ||
Self::Last => write!(f, "a"), | ||
Self::Average => write!(f, "v"), | ||
Self::Best => write!(f, "b"), | ||
Self::Worst => write!(f, "w"), | ||
Self::StdDev => write!(f, "d"), | ||
Self::Status => write!(f, "t"), | ||
} | ||
} | ||
} | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_try_from_char_for_tui_column() { | ||
assert_eq!(TuiColumn::try_from('h').unwrap(), TuiColumn::Ttl); | ||
assert_eq!(TuiColumn::try_from('o').unwrap(), TuiColumn::Host); | ||
assert_eq!(TuiColumn::try_from('l').unwrap(), TuiColumn::LossPct); | ||
assert_eq!(TuiColumn::try_from('s').unwrap(), TuiColumn::Sent); | ||
assert_eq!(TuiColumn::try_from('r').unwrap(), TuiColumn::Received); | ||
assert_eq!(TuiColumn::try_from('a').unwrap(), TuiColumn::Last); | ||
assert_eq!(TuiColumn::try_from('v').unwrap(), TuiColumn::Average); | ||
assert_eq!(TuiColumn::try_from('b').unwrap(), TuiColumn::Best); | ||
assert_eq!(TuiColumn::try_from('w').unwrap(), TuiColumn::Worst); | ||
assert_eq!(TuiColumn::try_from('d').unwrap(), TuiColumn::StdDev); | ||
assert_eq!(TuiColumn::try_from('t').unwrap(), TuiColumn::Status); | ||
|
||
// Negative test for an unknown character | ||
assert!(TuiColumn::try_from('x').is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_display_formatting_for_tui_column() { | ||
assert_eq!(format!("{}", TuiColumn::Ttl), "h"); | ||
assert_eq!(format!("{}", TuiColumn::Host), "o"); | ||
assert_eq!(format!("{}", TuiColumn::LossPct), "l"); | ||
assert_eq!(format!("{}", TuiColumn::Sent), "s"); | ||
assert_eq!(format!("{}", TuiColumn::Received), "r"); | ||
assert_eq!(format!("{}", TuiColumn::Last), "a"); | ||
assert_eq!(format!("{}", TuiColumn::Average), "v"); | ||
assert_eq!(format!("{}", TuiColumn::Best), "b"); | ||
assert_eq!(format!("{}", TuiColumn::Worst), "w"); | ||
assert_eq!(format!("{}", TuiColumn::StdDev), "d"); | ||
assert_eq!(format!("{}", TuiColumn::Status), "t"); | ||
} | ||
|
||
#[test] | ||
fn test_try_from_str_for_tui_columns() { | ||
let valid_input = "hol"; | ||
let tui_columns = TuiColumns::try_from(valid_input).unwrap(); | ||
assert_eq!( | ||
tui_columns, | ||
TuiColumns(vec![TuiColumn::Ttl, TuiColumn::Host, TuiColumn::LossPct]) | ||
); | ||
|
||
// Test for invalid characters in the input | ||
let invalid_input = "xyz"; | ||
assert!(TuiColumns::try_from(invalid_input).is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_default_for_tui_columns() { | ||
let default_columns = TuiColumns::default(); | ||
assert_eq!( | ||
default_columns, | ||
TuiColumns(vec![ | ||
TuiColumn::Ttl, | ||
TuiColumn::Host, | ||
TuiColumn::LossPct, | ||
TuiColumn::Sent, | ||
TuiColumn::Received, | ||
TuiColumn::Last, | ||
TuiColumn::Average, | ||
TuiColumn::Best, | ||
TuiColumn::Worst, | ||
TuiColumn::StdDev, | ||
TuiColumn::Status | ||
]) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_find_duplicates_for_tui_columns() { | ||
let columns_with_duplicates = TuiColumns(vec![ | ||
TuiColumn::Ttl, | ||
TuiColumn::Host, | ||
TuiColumn::LossPct, | ||
TuiColumn::Host, // Duplicate | ||
]); | ||
|
||
let duplicates = columns_with_duplicates.find_duplicates(); | ||
assert_eq!(duplicates, vec!["o".to_string()]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.