cli: Show repo summary on `rad clone`

After cloning, we show a cool little summary with some repo stats.

We had to make some adjustments to the way visual width is calculated
due to a bug surfaced around emoji width calculation. Unfortunately
there isn't a perfect solution that works across terminals yet, so the
"variation selector 16" code-point is filtered out for now.
This commit is contained in:
cloudhead 2023-08-30 17:20:34 +02:00
parent 0c43b05066
commit e59e196da8
No known key found for this signature in database
9 changed files with 84 additions and 14 deletions

View File

@ -10,6 +10,12 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope all
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/ ✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the project directory.
``` ```
We can now have a look at the new working copy that was created from the cloned We can now have a look at the new working copy that was created from the cloned

View File

@ -13,4 +13,10 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/ ✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the project directory.
``` ```

View File

@ -10,6 +10,12 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope trusted
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/ ✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the project directory.
``` ```
We can now have a look at the new working copy that was created from the cloned We can now have a look at the new working copy that was created from the cloned

View File

@ -24,6 +24,12 @@ $ rad clone rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/ ✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ radicle heartwood protocol & stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the project directory.
``` ```
We wait for Alice to sync our fork. We wait for Alice to sync our fork.

View File

@ -7,6 +7,7 @@ use std::time;
use anyhow::anyhow; use anyhow::anyhow;
use thiserror::Error; use thiserror::Error;
use radicle::cob;
use radicle::git::raw; use radicle::git::raw;
use radicle::identity::doc::{DocError, Id}; use radicle::identity::doc::{DocError, Id};
use radicle::identity::{doc, IdentityError}; use radicle::identity::{doc, IdentityError};
@ -23,6 +24,7 @@ use crate::commands::rad_sync as sync;
use crate::project; use crate::project;
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help}; use crate::terminal::args::{Args, Error, Help};
use crate::terminal::Element as _;
pub const HELP: Help = Help { pub const HELP: Help = Help {
name: "clone", name: "clone",
@ -113,7 +115,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
); );
} }
let (working, doc, proj) = clone( let (working, repo, doc, proj) = clone(
options.id, options.id,
options.announce, options.announce,
options.scope, options.scope,
@ -145,7 +147,28 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
term::success!( term::success!(
"Repository successfully cloned under {}", "Repository successfully cloned under {}",
term::format::highlight(Path::new(".").join(path).display()) term::format::dim(Path::new(".").join(path).display())
);
let mut info: term::Table<1, term::Line> = term::Table::new(term::TableOptions::bordered());
info.push([term::format::bold(proj.name()).into()]);
info.push([term::format::italic(proj.description()).into()]);
let issues = cob::issue::Issues::open(&repo)?.counts()?;
let patches = cob::patch::Patches::open(&repo)?.counts()?;
info.push([term::Line::spaced([
term::format::tertiary(issues.open).into(),
term::format::default("issues").into(),
term::format::dim("·").into(),
term::format::tertiary(patches.open).into(),
term::format::default("patches").into(),
])]);
info.print();
term::info!(
"Run {} to go to the project directory.",
term::format::command(format!("cd ./{}", proj.name())),
); );
Ok(()) Ok(())
@ -180,7 +203,15 @@ pub fn clone<G: Signer>(
node: &mut Node, node: &mut Node,
signer: &G, signer: &G,
storage: &Storage, storage: &Storage,
) -> Result<(raw::Repository, Doc<Verified>, Project), CloneError> { ) -> Result<
(
raw::Repository,
storage::git::Repository,
Doc<Verified>,
Project,
),
CloneError,
> {
let me = *signer.public_key(); let me = *signer.public_key();
// Track. // Track.
@ -244,9 +275,9 @@ pub fn clone<G: Signer>(
"Creating checkout in ./{}..", "Creating checkout in ./{}..",
term::format::tertiary(path.display()) term::format::tertiary(path.display())
)); ));
let repo = rad::checkout(id, &me, path, &storage)?; let working = rad::checkout(id, &me, path, &storage)?;
spinner.finish(); spinner.finish();
Ok((repo, doc, proj)) Ok((working, repository, doc, proj))
} }

View File

@ -2,6 +2,7 @@ use std::fmt::Display;
use super::{Element, Line, Paint}; use super::{Element, Line, Paint};
use unicode_segmentation::UnicodeSegmentation as _;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
/// Text that can be displayed on the terminal, measured, truncated and padded. /// Text that can be displayed on the terminal, measured, truncated and padded.
@ -26,7 +27,7 @@ impl Cell for Paint<String> {
type Padded = Self; type Padded = Self;
fn width(&self) -> usize { fn width(&self) -> usize {
UnicodeWidthStr::width(self.content()) Cell::width(self.content())
} }
fn truncate(&self, width: usize, delim: &str) -> Self { fn truncate(&self, width: usize, delim: &str) -> Self {
@ -110,12 +111,10 @@ impl Cell for str {
type Padded = String; type Padded = String;
fn width(&self) -> usize { fn width(&self) -> usize {
UnicodeWidthStr::width(self) self.graphemes(true).map(UnicodeWidthStr::width).sum()
} }
fn truncate(&self, width: usize, delim: &str) -> String { fn truncate(&self, width: usize, delim: &str) -> String {
use unicode_segmentation::UnicodeSegmentation as _;
if width < Cell::width(self) { if width < Cell::width(self) {
let d = Cell::width(delim); let d = Cell::width(delim);
if width < d { if width < d {

View File

@ -237,4 +237,12 @@ mod test {
actual = actual.truncate(0, ""); actual = actual.truncate(0, "");
assert_eq!(actual.to_string(), ""); assert_eq!(actual.to_string(), "");
} }
#[test]
fn test_width() {
let line = Line::new("Radicle Heartwood Protocol & Stack ❤️ 🪵");
assert_eq!(line.width(), 39, "{line}");
let line = Line::new("\u{fe0f}");
assert_eq!(line.width(), 1, "{line}");
}
} }

View File

@ -9,7 +9,7 @@ pub struct Label(Paint<String>);
impl Label { impl Label {
/// Create a new label. /// Create a new label.
pub fn new(s: &str) -> Self { pub fn new(s: &str) -> Self {
Self(Paint::new(s.replace('\n', " "))) Self(Paint::new(cleanup(s)))
} }
/// Create a blank label. /// Create a blank label.
@ -64,7 +64,7 @@ impl Cell for Label {
impl<D: fmt::Display> From<Paint<D>> for Label { impl<D: fmt::Display> From<Paint<D>> for Label {
fn from(paint: Paint<D>) -> Self { fn from(paint: Paint<D>) -> Self {
Self(Paint { Self(Paint {
item: paint.item.to_string().replace('\n', " "), item: cleanup(paint.item.to_string().as_str()),
style: paint.style, style: paint.style,
}) })
} }
@ -72,13 +72,13 @@ impl<D: fmt::Display> From<Paint<D>> for Label {
impl From<String> for Label { impl From<String> for Label {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Label::from(value.as_str()) Self::new(value.as_str())
} }
} }
impl From<&str> for Label { impl From<&str> for Label {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
Self(Paint::new(value.replace('\n', " "))) Self::new(value)
} }
} }
@ -86,3 +86,11 @@ impl From<&str> for Label {
pub fn label(s: impl Into<Paint<String>>) -> Label { pub fn label(s: impl Into<Paint<String>>) -> Label {
Label(s.into()) Label(s.into())
} }
/// Cleanup the input string for display as a label.
fn cleanup(input: &str) -> String {
input
.chars()
.filter(|c| *c != '\u{fe0f}' && *c != '\n' && *c != '\r')
.collect()
}

View File

@ -22,7 +22,7 @@ pub use inquire::ui::Styled;
pub use io::*; pub use io::*;
pub use label::{label, Label}; pub use label::{label, Label};
pub use spinner::{spinner, spinner_to, Spinner}; pub use spinner::{spinner, spinner_to, Spinner};
pub use table::Table; pub use table::{Table, TableOptions};
pub use textarea::{textarea, TextArea}; pub use textarea::{textarea, TextArea};
pub use vstack::{VStack, VStackOptions}; pub use vstack::{VStack, VStackOptions};