cli: Improve `rad inbox` output
* Add change authors * Add object status * Add colors
This commit is contained in:
parent
b477864b83
commit
9767b485c2
|
|
@ -18,27 +18,27 @@ $ git push rad -o patch.message="Copyright fixes" HEAD:refs/patches
|
||||||
|
|
||||||
``` ~alice
|
``` ~alice
|
||||||
$ rad inbox --sort-by id
|
$ rad inbox --sort-by id
|
||||||
╭──────────────────────────────────────────────────────────────╮
|
╭──────────────────────────────────────────────────────────────────────╮
|
||||||
│ heartwood │
|
│ heartwood │
|
||||||
├──────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────┤
|
||||||
│ 1 ● issue No license file [ .. ] opened now │
|
│ 001 ● 58fff44 No license file issue open bob now │
|
||||||
│ 2 ● branch Change copyright bob/copy created now │
|
│ 002 ● bob/copy Change copyright branch created bob now │
|
||||||
╰──────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
``` ~alice
|
``` ~alice
|
||||||
$ rad inbox --all --sort-by id
|
$ rad inbox --all --sort-by id
|
||||||
╭──────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────────╮
|
||||||
│ radicle-git │
|
│ radicle-git │
|
||||||
├──────────────────────────────────────────────────────────┤
|
├────────────────────────────────────────────────────────────────┤
|
||||||
│ 3 ● patch Copyright fixes [ ... ] opened now │
|
│ 003 ● 4dd5843 Copyright fixes patch open bob now │
|
||||||
╰──────────────────────────────────────────────────────────╯
|
╰────────────────────────────────────────────────────────────────╯
|
||||||
╭──────────────────────────────────────────────────────────────╮
|
╭──────────────────────────────────────────────────────────────────────╮
|
||||||
│ heartwood │
|
│ heartwood │
|
||||||
├──────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────┤
|
||||||
│ 1 ● issue No license file [ .. ] opened now │
|
│ 001 ● 58fff44 No license file issue open bob now │
|
||||||
│ 2 ● branch Change copyright bob/copy created now │
|
│ 002 ● bob/copy Change copyright branch created bob now │
|
||||||
╰──────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
``` ~alice
|
``` ~alice
|
||||||
|
|
@ -64,12 +64,12 @@ Date: Mon Jan 1 14:39:16 2018 +0000
|
||||||
|
|
||||||
``` ~alice
|
``` ~alice
|
||||||
$ rad inbox list --sort-by id
|
$ rad inbox list --sort-by id
|
||||||
╭──────────────────────────────────────────────────────────────╮
|
╭──────────────────────────────────────────────────────────────────────╮
|
||||||
│ heartwood │
|
│ heartwood │
|
||||||
├──────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────┤
|
||||||
│ 1 ● issue No license file [ ... ] opened now │
|
│ 001 ● 58fff44 No license file issue open bob now │
|
||||||
│ 2 branch Change copyright bob/copy created now │
|
│ 002 bob/copy Change copyright branch created bob now │
|
||||||
╰──────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
``` ~alice
|
``` ~alice
|
||||||
|
|
@ -90,11 +90,11 @@ $ rad inbox clear
|
||||||
$ rad inbox
|
$ rad inbox
|
||||||
Your inbox is empty.
|
Your inbox is empty.
|
||||||
$ rad inbox --all
|
$ rad inbox --all
|
||||||
╭──────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────────╮
|
||||||
│ radicle-git │
|
│ radicle-git │
|
||||||
├──────────────────────────────────────────────────────────┤
|
├────────────────────────────────────────────────────────────────┤
|
||||||
│ 3 ● patch Copyright fixes [ ... ] opened now │
|
│ 003 ● 4dd5843 Copyright fixes patch open bob now │
|
||||||
╰──────────────────────────────────────────────────────────╯
|
╰────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
``` ~alice
|
``` ~alice
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@ The contributor's changes are now visible to us.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad inbox --sort-by id
|
$ rad inbox --sort-by id
|
||||||
╭──────────────────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ heartwood │
|
│ heartwood │
|
||||||
├──────────────────────────────────────────────────────────────────────┤
|
├────────────────────────────────────────────────────────────────────────────┤
|
||||||
│ 1 ● issue flux capacitor underpowered d060989 opened now │
|
│ 001 ● d060989 flux capacitor underpowered issue open bob now │
|
||||||
│ 2 ● patch Define power requirements a99d55e opened now │
|
│ 002 ● a99d55e Define power requirements patch open bob now │
|
||||||
╰──────────────────────────────────────────────────────────────────────╯
|
╰────────────────────────────────────────────────────────────────────────────╯
|
||||||
$ git branch -r
|
$ git branch -r
|
||||||
bob/patches/a99d55e5958a8c52ff7efbc8ff000d9bbdac79c7
|
bob/patches/a99d55e5958a8c52ff7efbc8ff000d9bbdac79c7
|
||||||
rad/master
|
rad/master
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ use radicle::node::notifications;
|
||||||
use radicle::node::notifications::*;
|
use radicle::node::notifications::*;
|
||||||
use radicle::patch::Patches;
|
use radicle::patch::Patches;
|
||||||
use radicle::prelude::{Profile, RepoId};
|
use radicle::prelude::{Profile, RepoId};
|
||||||
use radicle::storage::RefUpdate;
|
|
||||||
use radicle::storage::{ReadRepository, ReadStorage};
|
use radicle::storage::{ReadRepository, ReadStorage};
|
||||||
use radicle::{cob, Storage};
|
use radicle::{cob, Storage};
|
||||||
|
|
||||||
|
|
@ -157,7 +156,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let Options { op, mode, sort_by } = options;
|
let Options { op, mode, sort_by } = options;
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Operation::List => list(mode, sort_by, ¬ifs.read_only(), storage),
|
Operation::List => list(mode, sort_by, ¬ifs.read_only(), storage, &profile),
|
||||||
Operation::Clear => clear(mode, &mut notifs),
|
Operation::Clear => clear(mode, &mut notifs),
|
||||||
Operation::Show => show(mode, &mut notifs, storage, &profile),
|
Operation::Show => show(mode, &mut notifs, storage, &profile),
|
||||||
}
|
}
|
||||||
|
|
@ -168,21 +167,22 @@ fn list(
|
||||||
sort_by: SortBy,
|
sort_by: SortBy,
|
||||||
notifs: ¬ifications::StoreReader,
|
notifs: ¬ifications::StoreReader,
|
||||||
storage: &Storage,
|
storage: &Storage,
|
||||||
|
profile: &Profile,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let repos: Vec<term::VStack<'_>> = match mode {
|
let repos: Vec<term::VStack<'_>> = match mode {
|
||||||
Mode::Contextual => {
|
Mode::Contextual => {
|
||||||
if let Ok((_, rid)) = radicle::rad::cwd() {
|
if let Ok((_, rid)) = radicle::rad::cwd() {
|
||||||
list_repo(rid, sort_by, notifs, storage)?
|
list_repo(rid, sort_by, notifs, storage, profile)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
list_all(sort_by, notifs, storage)?
|
list_all(sort_by, notifs, storage, profile)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::ByRepo(rid) => list_repo(rid, sort_by, notifs, storage)?
|
Mode::ByRepo(rid) => list_repo(rid, sort_by, notifs, storage, profile)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
Mode::All => list_all(sort_by, notifs, storage)?,
|
Mode::All => list_all(sort_by, notifs, storage, profile)?,
|
||||||
Mode::ById(_) => anyhow::bail!("the `list` command does not take IDs"),
|
Mode::ById(_) => anyhow::bail!("the `list` command does not take IDs"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -200,13 +200,14 @@ fn list_all<'a>(
|
||||||
sort_by: SortBy,
|
sort_by: SortBy,
|
||||||
notifs: ¬ifications::StoreReader,
|
notifs: ¬ifications::StoreReader,
|
||||||
storage: &Storage,
|
storage: &Storage,
|
||||||
|
profile: &Profile,
|
||||||
) -> anyhow::Result<Vec<term::VStack<'a>>> {
|
) -> anyhow::Result<Vec<term::VStack<'a>>> {
|
||||||
let mut repos = storage.repositories()?;
|
let mut repos = storage.repositories()?;
|
||||||
repos.sort_by_key(|r| r.rid);
|
repos.sort_by_key(|r| r.rid);
|
||||||
|
|
||||||
let mut vstacks = Vec::new();
|
let mut vstacks = Vec::new();
|
||||||
for repo in repos {
|
for repo in repos {
|
||||||
let vstack = list_repo(repo.rid, sort_by, notifs, storage)?;
|
let vstack = list_repo(repo.rid, sort_by, notifs, storage, profile)?;
|
||||||
vstacks.extend(vstack.into_iter());
|
vstacks.extend(vstack.into_iter());
|
||||||
}
|
}
|
||||||
Ok(vstacks)
|
Ok(vstacks)
|
||||||
|
|
@ -217,6 +218,7 @@ fn list_repo<'a, R: ReadStorage>(
|
||||||
sort_by: SortBy,
|
sort_by: SortBy,
|
||||||
notifs: ¬ifications::StoreReader,
|
notifs: ¬ifications::StoreReader,
|
||||||
storage: &R,
|
storage: &R,
|
||||||
|
profile: &Profile,
|
||||||
) -> anyhow::Result<Option<term::VStack<'a>>>
|
) -> anyhow::Result<Option<term::VStack<'a>>>
|
||||||
where
|
where
|
||||||
<R as ReadStorage>::Repository: cob::Store,
|
<R as ReadStorage>::Repository: cob::Store,
|
||||||
|
|
@ -226,6 +228,7 @@ where
|
||||||
..term::TableOptions::default()
|
..term::TableOptions::default()
|
||||||
});
|
});
|
||||||
let repo = storage.repository(rid)?;
|
let repo = storage.repository(rid)?;
|
||||||
|
let (_, head) = repo.head()?;
|
||||||
let doc = repo.identity_doc()?;
|
let doc = repo.identity_doc()?;
|
||||||
let proj = doc.project()?;
|
let proj = doc.project()?;
|
||||||
let issues = Issues::open(&repo)?;
|
let issues = Issues::open(&repo)?;
|
||||||
|
|
@ -245,48 +248,82 @@ where
|
||||||
} else {
|
} else {
|
||||||
term::format::tertiary(String::from("●")).into()
|
term::format::tertiary(String::from("●")).into()
|
||||||
};
|
};
|
||||||
let (category, summary, status, name) = match n.kind {
|
let (category, summary, state, name) = match n.kind {
|
||||||
NotificationKind::Branch { name } => {
|
NotificationKind::Branch { name } => {
|
||||||
let commit = if let Some(head) = n.update.new() {
|
let commit = if let Some(head) = n.update.new() {
|
||||||
repo.commit(head)?.summary().unwrap_or_default().to_owned()
|
repo.commit(head)?.summary().unwrap_or_default().to_owned()
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
let status = match n.update {
|
|
||||||
RefUpdate::Updated { .. } => "updated",
|
let state = match n
|
||||||
RefUpdate::Created { .. } => "created",
|
.update
|
||||||
RefUpdate::Deleted { .. } => "deleted",
|
.new()
|
||||||
RefUpdate::Skipped { .. } => "skipped",
|
.map(|oid| repo.is_ancestor_of(oid, head))
|
||||||
};
|
.transpose()
|
||||||
("branch".to_string(), commit, status, name.to_string())
|
{
|
||||||
|
Ok(Some(true)) => {
|
||||||
|
term::Paint::<String>::from(term::format::secondary("merged"))
|
||||||
|
}
|
||||||
|
Ok(Some(false)) | Ok(None) => term::format::ref_update(n.update).into(),
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
(
|
||||||
|
"branch".to_string(),
|
||||||
|
commit,
|
||||||
|
state,
|
||||||
|
term::format::default(name.to_string()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
NotificationKind::Cob { type_name, id } => {
|
NotificationKind::Cob { type_name, id } => {
|
||||||
let (category, summary) = if type_name == *cob::issue::TYPENAME {
|
let (category, summary, state) = if type_name == *cob::issue::TYPENAME {
|
||||||
let issue = issues.get(&id)?.ok_or(anyhow!("missing"))?;
|
let Some(issue) = issues.get(&id)? else {
|
||||||
(String::from("issue"), issue.title().to_owned())
|
// Issue could have been deleted after notification was created.
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
(
|
||||||
|
String::from("issue"),
|
||||||
|
issue.title().to_owned(),
|
||||||
|
term::format::issue::state(issue.state()),
|
||||||
|
)
|
||||||
} else if type_name == *cob::patch::TYPENAME {
|
} else if type_name == *cob::patch::TYPENAME {
|
||||||
let patch = patches.get(&id)?.ok_or(anyhow!("missing"))?;
|
let Some(patch) = patches.get(&id)? else {
|
||||||
(String::from("patch"), patch.title().to_owned())
|
// Patch could have been deleted after notification was created.
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
(
|
||||||
|
String::from("patch"),
|
||||||
|
patch.title().to_owned(),
|
||||||
|
term::format::patch::state(patch.state()),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(type_name.to_string(), "".to_owned())
|
(
|
||||||
|
type_name.to_string(),
|
||||||
|
"".to_owned(),
|
||||||
|
term::format::default(String::new()),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let status = match n.update {
|
(category, summary, state, term::format::cob(&id))
|
||||||
RefUpdate::Updated { .. } => "updated",
|
|
||||||
RefUpdate::Created { .. } => "opened",
|
|
||||||
RefUpdate::Deleted { .. } => "deleted",
|
|
||||||
RefUpdate::Skipped { .. } => "skipped",
|
|
||||||
};
|
|
||||||
(category, summary, status, term::format::cob(&id))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let author = n
|
||||||
|
.remote
|
||||||
|
.map(|r| {
|
||||||
|
let (alias, _) = term::format::Author::new(&r, profile).labels();
|
||||||
|
alias
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
table.push([
|
table.push([
|
||||||
n.id.to_string().into(),
|
term::format::dim(format!("{:-03}", n.id)).into(),
|
||||||
seen,
|
seen,
|
||||||
category.into(),
|
term::format::tertiary(name).into(),
|
||||||
summary.into(),
|
summary.into(),
|
||||||
name.into(),
|
term::format::dim(category).into(),
|
||||||
status.into(),
|
state.into(),
|
||||||
term::format::timestamp(n.timestamp).into(),
|
author,
|
||||||
|
term::format::italic(term::format::timestamp(n.timestamp)).into(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,7 +333,7 @@ where
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
term::VStack::default()
|
term::VStack::default()
|
||||||
.border(Some(term::colors::FAINT))
|
.border(Some(term::colors::FAINT))
|
||||||
.child(term::label(proj.name()))
|
.child(term::label(term::format::bold(proj.name())))
|
||||||
.divider()
|
.divider()
|
||||||
.child(table),
|
.child(table),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,8 @@ impl Options {
|
||||||
Some(refname) => Ok(Qualified::from_refstr(refname)
|
Some(refname) => Ok(Qualified::from_refstr(refname)
|
||||||
.map_or_else(|| refname.clone(), |q| q.to_ref_string())),
|
.map_or_else(|| refname.clone(), |q| q.to_ref_string())),
|
||||||
// SAFETY: Patch IDs are valid refstrings.
|
// SAFETY: Patch IDs are valid refstrings.
|
||||||
None => Ok(
|
None => Ok(git::refname!("patch")
|
||||||
git::refname!("patch").join(RefString::try_from(term::format::cob(id)).unwrap())
|
.join(RefString::try_from(term::format::cob(id).item).unwrap())),
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use radicle::identity::Visibility;
|
||||||
use radicle::node::{Alias, AliasStore, NodeId};
|
use radicle::node::{Alias, AliasStore, NodeId};
|
||||||
use radicle::prelude::Did;
|
use radicle::prelude::Did;
|
||||||
use radicle::profile::Profile;
|
use radicle::profile::Profile;
|
||||||
|
use radicle::storage::RefUpdate;
|
||||||
use radicle_term::element::Line;
|
use radicle_term::element::Line;
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
|
@ -44,8 +45,8 @@ pub fn command<D: fmt::Display>(cmd: D) -> Paint<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a COB id.
|
/// Format a COB id.
|
||||||
pub fn cob(id: &ObjectId) -> String {
|
pub fn cob(id: &ObjectId) -> Paint<String> {
|
||||||
format!("{:.7}", id.to_string())
|
Paint::new(format!("{:.7}", id.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a DID.
|
/// Format a DID.
|
||||||
|
|
@ -72,6 +73,16 @@ pub fn timestamp(time: impl Into<LocalTime>) -> Paint<String> {
|
||||||
Paint::new(fmt.convert(duration.into()))
|
Paint::new(fmt.convert(duration.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format a ref update.
|
||||||
|
pub fn ref_update(update: RefUpdate) -> Paint<&'static str> {
|
||||||
|
match update {
|
||||||
|
RefUpdate::Updated { .. } => term::format::tertiary("updated"),
|
||||||
|
RefUpdate::Created { .. } => term::format::positive("created"),
|
||||||
|
RefUpdate::Deleted { .. } => term::format::negative("deleted"),
|
||||||
|
RefUpdate::Skipped { .. } => term::format::dim("skipped"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Identity formatter that takes a profile and displays it as
|
/// Identity formatter that takes a profile and displays it as
|
||||||
/// `<node-id> (<username>)` depending on the configuration.
|
/// `<node-id> (<username>)` depending on the configuration.
|
||||||
pub struct Identity<'a> {
|
pub struct Identity<'a> {
|
||||||
|
|
@ -222,6 +233,41 @@ pub mod html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Issue formatting
|
||||||
|
pub mod issue {
|
||||||
|
use super::*;
|
||||||
|
use radicle::issue::{CloseReason, State};
|
||||||
|
|
||||||
|
/// Format issue state.
|
||||||
|
pub fn state(s: &State) -> term::Paint<String> {
|
||||||
|
match s {
|
||||||
|
State::Open => term::format::positive(s.to_string()),
|
||||||
|
State::Closed {
|
||||||
|
reason: CloseReason::Other,
|
||||||
|
} => term::format::negative(s.to_string()),
|
||||||
|
State::Closed {
|
||||||
|
reason: CloseReason::Solved,
|
||||||
|
} => term::format::secondary(s.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Patch formatting
|
||||||
|
pub mod patch {
|
||||||
|
use super::*;
|
||||||
|
use radicle::patch::State;
|
||||||
|
|
||||||
|
/// Format patch state.
|
||||||
|
pub fn state(s: &State) -> term::Paint<String> {
|
||||||
|
match s {
|
||||||
|
State::Draft { .. } => term::format::dim(s.to_string()),
|
||||||
|
State::Open { .. } => term::format::positive(s.to_string()),
|
||||||
|
State::Archived => term::format::yellow(s.to_string()),
|
||||||
|
State::Merged { .. } => term::format::secondary(s.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue