diff --git a/radicle-cli/examples/rad-sync.md b/radicle-cli/examples/rad-sync.md index 8e2f4888..94dec8e3 100644 --- a/radicle-cli/examples/rad-sync.md +++ b/radicle-cli/examples/rad-sync.md @@ -12,7 +12,7 @@ Our own node is also out of sync, since we used `--no-announce`. It isn't aware of the updates to the repo. ``` -$ rad sync status +$ rad sync status --sort-by alias ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │ ● NID Alias Address Status At Timestamp │ ├──────────────────────────────────────────────────────────────────────────────────────────┤ @@ -76,7 +76,7 @@ $ rad sync --fetch --replicas 1 We can check the sync status again to make sure everything's in sync: ``` -$ rad sync status +$ rad sync status --sort-by alias ╭─────────────────────────────────────────────────────────────────────────────────────╮ │ ● NID Alias Address Status At Timestamp │ ├─────────────────────────────────────────────────────────────────────────────────────┤ diff --git a/radicle-cli/src/commands/sync.rs b/radicle-cli/src/commands/sync.rs index 4ebc1f9b..55b7e143 100644 --- a/radicle-cli/src/commands/sync.rs +++ b/radicle-cli/src/commands/sync.rs @@ -1,11 +1,13 @@ use std::cmp::Ordering; use std::ffi::OsString; +use std::str::FromStr; use std::time; use anyhow::{anyhow, Context as _}; use radicle::node; -use radicle::node::AliasStore as _; +use radicle::node::AliasStore; +use radicle::node::Seed; use radicle::node::{FetchResult, FetchResults, Handle as _, Node, SyncStatus}; use radicle::prelude::{Id, NodeId, Profile}; use radicle::storage::{ReadRepository, ReadStorage}; @@ -51,14 +53,15 @@ Commands Options - --fetch, -f Turn on fetching (default: true) - --announce, -a Turn on ref announcing (default: true) - --inventory, -i Turn on inventory announcing (default: false) - --timeout How many seconds to wait while syncing - --seed Sync with the given node (may be specified multiple times) - --replicas, -r Sync with a specific number of seeds - --verbose, -v Verbose output - --help Print help + --sort-by Sort the table by column (options: nid, alias, status) + -f, --fetch Turn on fetching (default: true) + -a, --announce Turn on ref announcing (default: true) + -i, --inventory Turn on inventory announcing (default: false) + --timeout How many seconds to wait while syncing + --seed Sync with the given node (may be specified multiple times) + -r, --replicas Sync with a specific number of seeds + -v, --verbose Verbose output + --help Print help "#, }; @@ -69,6 +72,27 @@ pub enum Operation { Status, } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum SortBy { + Nid, + Alias, + #[default] + Status, +} + +impl FromStr for SortBy { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "nid" => Ok(Self::Nid), + "alias" => Ok(Self::Alias), + "status" => Ok(Self::Status), + _ => Err("invalid `--sort-by` field"), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyncMode { Repo { @@ -115,6 +139,7 @@ pub struct Options { pub rid: Option, pub verbose: bool, pub timeout: time::Duration, + pub sort_by: SortBy, pub op: Operation, } @@ -131,6 +156,7 @@ impl Args for Options { let mut inventory = false; let mut replicas = None; let mut seeds = Vec::new(); + let mut sort_by = SortBy::default(); let mut op: Option = None; while let Some(arg) = parser.next()? { @@ -162,6 +188,10 @@ impl Args for Options { Long("inventory") | Short('i') => { inventory = true; } + Long("sort-by") if matches!(op, Some(Operation::Status)) => { + let value = parser.value()?; + sort_by = value.parse()?; + } Long("timeout") | Short('t') => { let value = parser.value()?; let secs = term::args::parse_value("timeout", value)?; @@ -217,6 +247,7 @@ impl Args for Options { rid, verbose, timeout, + sort_by, op: op.unwrap_or(Operation::Synchronize(sync)), }, vec![], @@ -294,22 +325,7 @@ fn sync_status( ]); table.divider(); - // Always show our local node first. - seeds.sort_by(|a, b| { - if a.nid == local { - Ordering::Less - } else if b.nid == local { - Ordering::Greater - } else { - match (&a.sync, &b.sync) { - (Some(_), None) => Ordering::Less, - (None, Some(_)) => Ordering::Greater, - (Some(a), Some(b)) => a.cmp(b).reverse(), - (None, None) => Ordering::Equal, - } - .then(a.nid.cmp(&b.nid)) - } - }); + sort_seeds_by(local, &mut seeds, &aliases, &options.sort_by); for seed in seeds { let (icon, status, head, time) = match seed.sync { @@ -553,3 +569,32 @@ fn fetch_from( } Ok(result) } + +fn sort_seeds_by(local: NodeId, seeds: &mut [Seed], aliases: &impl AliasStore, sort_by: &SortBy) { + let compare = |a: &Seed, b: &Seed| match sort_by { + SortBy::Nid => a.nid.cmp(&b.nid), + SortBy::Alias => { + let a = aliases.alias(&a.nid); + let b = aliases.alias(&b.nid); + a.cmp(&b) + } + SortBy::Status => a.sync.cmp(&b.sync), + }; + + // Always show our local node first. + seeds.sort_by(|a, b| { + if a.nid == local { + Ordering::Less + } else if b.nid == local { + Ordering::Greater + } else { + match (&a.sync, &b.sync) { + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (Some(a), Some(b)) => a.cmp(b).reverse(), + (None, None) => Ordering::Equal, + } + .then(compare(a, b)) + } + }); +} diff --git a/radicle/src/node.rs b/radicle/src/node.rs index b1657dd9..b4f7378e 100644 --- a/radicle/src/node.rs +++ b/radicle/src/node.rs @@ -167,7 +167,7 @@ impl PartialOrd for SyncStatus { } /// Node alias. -#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, serde::Serialize, serde::Deserialize)] pub struct Alias(String); impl Alias {