cli: sort-by flag for rad sync

Allow the caller of `rad sync` to `--sort-by` the NID, alias, or
status of the seeds.

This prevents random ordering, due to timestamps, in CLI testing.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2023-11-27 10:28:18 +00:00 committed by cloudhead
parent 9ae7fd702e
commit 72b00e3d41
No known key found for this signature in database
3 changed files with 73 additions and 28 deletions

View File

@ -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. 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 │ │ ● 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: 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 │ │ ● NID Alias Address Status At Timestamp │
├─────────────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────────────┤

View File

@ -1,11 +1,13 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ffi::OsString; use std::ffi::OsString;
use std::str::FromStr;
use std::time; use std::time;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use radicle::node; 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::node::{FetchResult, FetchResults, Handle as _, Node, SyncStatus};
use radicle::prelude::{Id, NodeId, Profile}; use radicle::prelude::{Id, NodeId, Profile};
use radicle::storage::{ReadRepository, ReadStorage}; use radicle::storage::{ReadRepository, ReadStorage};
@ -51,14 +53,15 @@ Commands
Options Options
--fetch, -f Turn on fetching (default: true) --sort-by <field> Sort the table by column (options: nid, alias, status)
--announce, -a Turn on ref announcing (default: true) -f, --fetch Turn on fetching (default: true)
--inventory, -i Turn on inventory announcing (default: false) -a, --announce Turn on ref announcing (default: true)
--timeout <secs> How many seconds to wait while syncing -i, --inventory Turn on inventory announcing (default: false)
--seed <nid> Sync with the given node (may be specified multiple times) --timeout <secs> How many seconds to wait while syncing
--replicas, -r <count> Sync with a specific number of seeds --seed <nid> Sync with the given node (may be specified multiple times)
--verbose, -v Verbose output -r, --replicas <count> Sync with a specific number of seeds
--help Print help -v, --verbose Verbose output
--help Print help
"#, "#,
}; };
@ -69,6 +72,27 @@ pub enum Operation {
Status, 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<Self, Self::Err> {
match s {
"nid" => Ok(Self::Nid),
"alias" => Ok(Self::Alias),
"status" => Ok(Self::Status),
_ => Err("invalid `--sort-by` field"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum SyncMode { pub enum SyncMode {
Repo { Repo {
@ -115,6 +139,7 @@ pub struct Options {
pub rid: Option<Id>, pub rid: Option<Id>,
pub verbose: bool, pub verbose: bool,
pub timeout: time::Duration, pub timeout: time::Duration,
pub sort_by: SortBy,
pub op: Operation, pub op: Operation,
} }
@ -131,6 +156,7 @@ impl Args for Options {
let mut inventory = false; let mut inventory = false;
let mut replicas = None; let mut replicas = None;
let mut seeds = Vec::new(); let mut seeds = Vec::new();
let mut sort_by = SortBy::default();
let mut op: Option<Operation> = None; let mut op: Option<Operation> = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
@ -162,6 +188,10 @@ impl Args for Options {
Long("inventory") | Short('i') => { Long("inventory") | Short('i') => {
inventory = true; inventory = true;
} }
Long("sort-by") if matches!(op, Some(Operation::Status)) => {
let value = parser.value()?;
sort_by = value.parse()?;
}
Long("timeout") | Short('t') => { Long("timeout") | Short('t') => {
let value = parser.value()?; let value = parser.value()?;
let secs = term::args::parse_value("timeout", value)?; let secs = term::args::parse_value("timeout", value)?;
@ -217,6 +247,7 @@ impl Args for Options {
rid, rid,
verbose, verbose,
timeout, timeout,
sort_by,
op: op.unwrap_or(Operation::Synchronize(sync)), op: op.unwrap_or(Operation::Synchronize(sync)),
}, },
vec![], vec![],
@ -294,22 +325,7 @@ fn sync_status(
]); ]);
table.divider(); table.divider();
// Always show our local node first. sort_seeds_by(local, &mut seeds, &aliases, &options.sort_by);
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))
}
});
for seed in seeds { for seed in seeds {
let (icon, status, head, time) = match seed.sync { let (icon, status, head, time) = match seed.sync {
@ -553,3 +569,32 @@ fn fetch_from(
} }
Ok(result) 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))
}
});
}

View File

@ -167,7 +167,7 @@ impl PartialOrd for SyncStatus {
} }
/// Node alias. /// 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); pub struct Alias(String);
impl Alias { impl Alias {