diff --git a/radicle-cli/examples/rad-issue.md b/radicle-cli/examples/rad-issue.md index 2adf1db1..46ec426b 100644 --- a/radicle-cli/examples/rad-issue.md +++ b/radicle-cli/examples/rad-issue.md @@ -17,7 +17,11 @@ The issue is now listed under our project. ``` $ rad issue list -2e8c1bf3fe0532a314778357c886608a966a34bd "flux capacitor underpowered" ❲unassigned❳ +╭───────────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Tags Assignees Opened │ +├───────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● 2e8c1bf flux capacitor underpowered z6MknSL…StBU8Vi [ .. ] │ +╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Show the issue information issue. @@ -48,7 +52,11 @@ It will now show in the list of issues assigned to us. ``` $ rad issue list --assigned -2e8c1bf3fe0532a314778357c886608a966a34bd "flux capacitor underpowered" did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Tags Assignees Opened │ +├─────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● 2e8c1bf flux capacitor underpowered z6MknSL…StBU8Vi z6MknSL…StBU8Vi [ .. ] │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Note: this can always be undone with the `unassign` subcommand. diff --git a/radicle-cli/examples/workflow/3-issues.md b/radicle-cli/examples/workflow/3-issues.md index 65c85f4b..45692260 100644 --- a/radicle-cli/examples/workflow/3-issues.md +++ b/radicle-cli/examples/workflow/3-issues.md @@ -17,7 +17,11 @@ The issue is now listed under our project. ``` $ rad issue list -b05e945bb63c11bf80320f4e26ad1d1f7c51f755 "flux capacitor underpowered" ❲unassigned❳ +╭───────────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Tags Assignees Opened │ +├───────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● b05e945 flux capacitor underpowered z6Mkt67…v4N1tRk [ .. ] │ +╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Great! Now we've documented the issue for ourselves and others. @@ -35,7 +39,11 @@ It will now show in the list of issues assigned to us. ``` $ rad issue list --assigned -b05e945bb63c11bf80320f4e26ad1d1f7c51f755 "flux capacitor underpowered" did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk +╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ ● ID Title Author Tags Assignees Opened │ +├─────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ ● b05e945 flux capacitor underpowered z6Mkt67…v4N1tRk z6Mkt67…v4N1tRk [ .. ] │ +╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` Note: this can always be undone with the `unassign` subcommand. diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs index 365bc602..c26f5796 100644 --- a/radicle-cli/src/commands/issue.rs +++ b/radicle-cli/src/commands/issue.rs @@ -314,10 +314,22 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { None => None, }; - let mut t = term::Table::new(term::table::TableOptions::default()); + let mut t = term::Table::new(term::table::TableOptions::bordered()); + t.push([ + term::format::dim(String::from("●")), + term::format::bold(String::from("ID")), + term::format::bold(String::from("Title")), + term::format::bold(String::from("Author")), + term::format::bold(String::from("Tags")), + term::format::bold(String::from("Assignees")), + term::format::bold(String::from("Opened")), + ]); + t.divider(); + for result in issues.all()? { let (id, issue, _) = result?; let assigned: Vec<_> = issue.assigned().collect(); + let state = issue.state(); if Some(true) == assignee.map(|a| !assigned.contains(&Did::from(a))) { continue; @@ -325,17 +337,28 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { let assigned: String = assigned .iter() - .map(|p| p.to_string()) + .map(|p| term::format::did(p).to_string()) .collect::>() .join(", "); + + let mut tags = issue.tags().map(|t| t.to_string()).collect::>(); + tags.sort(); + t.push([ - id.to_string(), - format!("{:?}", issue.title()), - if assigned.is_empty() { - String::from("❲unassigned❳") - } else { - assigned.to_string() + match state { + State::Open => term::format::positive("●").into(), + State::Closed { .. } => term::format::negative("●").into(), }, + term::format::tertiary(term::format::cob(&id)).to_owned(), + term::format::default(issue.title().to_owned()), + term::format::did(&issue.author().id).dim(), + term::format::secondary(tags.join(", ")), + if assigned.is_empty() { + term::format::dim(String::default()) + } else { + term::format::default(assigned.to_string()) + }, + term::format::timestamp(&issue.timestamp()).dim().italic(), ]); } t.print(); diff --git a/radicle-cli/src/terminal/format.rs b/radicle-cli/src/terminal/format.rs index 648fe612..9f2e2131 100644 --- a/radicle-cli/src/terminal/format.rs +++ b/radicle-cli/src/terminal/format.rs @@ -5,6 +5,7 @@ pub use radicle_term::{style, Paint}; use radicle::cob::{ObjectId, Timestamp}; use radicle::node::NodeId; +use radicle::prelude::Did; use radicle::profile::Profile; use crate::terminal as term; @@ -33,13 +34,19 @@ pub fn cob(id: &ObjectId) -> String { format!("{:.7}", id.to_string()) } +/// Format a DID. +pub fn did(did: &Did) -> Paint { + let nid = did.as_key().to_human(); + Paint::new(format!("{}…{}", &nid[..7], &nid[nid.len() - 7..])) +} + /// Format a timestamp. -pub fn timestamp(time: &Timestamp) -> String { +pub fn timestamp(time: &Timestamp) -> Paint { let fmt = timeago::Formatter::new(); let now = Timestamp::now(); let duration = time::Duration::from_secs(now.as_secs() - time.as_secs()); - fmt.convert(duration) + Paint::new(fmt.convert(duration)) } /// Identity formatter that takes a profile and displays it as diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs index cf0a55b6..f7a18726 100644 --- a/radicle-cli/tests/commands.rs +++ b/radicle-cli/tests/commands.rs @@ -257,7 +257,6 @@ fn rad_node() { } #[test] -#[ignore] fn rad_patch() { let mut environment = Environment::new(); let profile = environment.profile("alice"); @@ -641,7 +640,6 @@ fn test_replication_via_seed() { } #[test] -#[ignore] fn rad_workflow() { let mut environment = Environment::new(); let alice = environment.node("alice"); diff --git a/radicle-crypto/src/lib.rs b/radicle-crypto/src/lib.rs index 3bb06988..a975f0b5 100644 --- a/radicle-crypto/src/lib.rs +++ b/radicle-crypto/src/lib.rs @@ -350,9 +350,7 @@ impl PublicKey { /// Encode public key in human-readable format. /// - /// We use the format specified by the DID `key` method, which is described as: - /// - /// `did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))` + /// `MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))` /// pub fn to_human(&self) -> String { let mut buf = [0; 2 + ed25519::PublicKey::BYTES]; diff --git a/radicle/src/cob/common.rs b/radicle/src/cob/common.rs index 9339f9ed..6bbf74ef 100644 --- a/radicle/src/cob/common.rs +++ b/radicle/src/cob/common.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::fmt::{self, Display}; use std::str::FromStr; use serde::{Deserialize, Serialize}; @@ -139,6 +139,12 @@ impl FromStr for Tag { } } +impl Display for Tag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + impl From for String { fn from(Tag(name): Tag) -> Self { name diff --git a/radicle/src/identity/did.rs b/radicle/src/identity/did.rs index 95f201b3..5392f25d 100644 --- a/radicle/src/identity/did.rs +++ b/radicle/src/identity/did.rs @@ -19,6 +19,10 @@ pub enum DidError { pub struct Did(crypto::PublicKey); impl Did { + /// We use the format specified by the DID `key` method, which is described as: + /// + /// `did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))` + /// pub fn encode(&self) -> String { format!("did:key:{}", self.0.to_human()) }