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:
parent
0c43b05066
commit
e59e196da8
|
|
@ -10,6 +10,12 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope all
|
|||
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ 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
|
||||
|
|
|
|||
|
|
@ -13,4 +13,10 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
|||
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ Repository successfully cloned under [..]/heartwood/
|
||||
╭────────────────────────────────────╮
|
||||
│ heartwood │
|
||||
│ Radicle Heartwood Protocol & Stack │
|
||||
│ 0 issues · 0 patches │
|
||||
╰────────────────────────────────────╯
|
||||
Run `cd ./heartwood` to go to the project directory.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope trusted
|
|||
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ 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
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ $ rad clone rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
|
|||
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ 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.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use std::time;
|
|||
use anyhow::anyhow;
|
||||
use thiserror::Error;
|
||||
|
||||
use radicle::cob;
|
||||
use radicle::git::raw;
|
||||
use radicle::identity::doc::{DocError, Id};
|
||||
use radicle::identity::{doc, IdentityError};
|
||||
|
|
@ -23,6 +24,7 @@ use crate::commands::rad_sync as sync;
|
|||
use crate::project;
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
use crate::terminal::Element as _;
|
||||
|
||||
pub const HELP: Help = Help {
|
||||
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.announce,
|
||||
options.scope,
|
||||
|
|
@ -145,7 +147,28 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
|
||||
term::success!(
|
||||
"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(())
|
||||
|
|
@ -180,7 +203,15 @@ pub fn clone<G: Signer>(
|
|||
node: &mut Node,
|
||||
signer: &G,
|
||||
storage: &Storage,
|
||||
) -> Result<(raw::Repository, Doc<Verified>, Project), CloneError> {
|
||||
) -> Result<
|
||||
(
|
||||
raw::Repository,
|
||||
storage::git::Repository,
|
||||
Doc<Verified>,
|
||||
Project,
|
||||
),
|
||||
CloneError,
|
||||
> {
|
||||
let me = *signer.public_key();
|
||||
|
||||
// Track.
|
||||
|
|
@ -244,9 +275,9 @@ pub fn clone<G: Signer>(
|
|||
"Creating checkout in ./{}..",
|
||||
term::format::tertiary(path.display())
|
||||
));
|
||||
let repo = rad::checkout(id, &me, path, &storage)?;
|
||||
let working = rad::checkout(id, &me, path, &storage)?;
|
||||
|
||||
spinner.finish();
|
||||
|
||||
Ok((repo, doc, proj))
|
||||
Ok((working, repository, doc, proj))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::fmt::Display;
|
|||
|
||||
use super::{Element, Line, Paint};
|
||||
|
||||
use unicode_segmentation::UnicodeSegmentation as _;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
/// Text that can be displayed on the terminal, measured, truncated and padded.
|
||||
|
|
@ -26,7 +27,7 @@ impl Cell for Paint<String> {
|
|||
type Padded = Self;
|
||||
|
||||
fn width(&self) -> usize {
|
||||
UnicodeWidthStr::width(self.content())
|
||||
Cell::width(self.content())
|
||||
}
|
||||
|
||||
fn truncate(&self, width: usize, delim: &str) -> Self {
|
||||
|
|
@ -110,12 +111,10 @@ impl Cell for str {
|
|||
type Padded = String;
|
||||
|
||||
fn width(&self) -> usize {
|
||||
UnicodeWidthStr::width(self)
|
||||
self.graphemes(true).map(UnicodeWidthStr::width).sum()
|
||||
}
|
||||
|
||||
fn truncate(&self, width: usize, delim: &str) -> String {
|
||||
use unicode_segmentation::UnicodeSegmentation as _;
|
||||
|
||||
if width < Cell::width(self) {
|
||||
let d = Cell::width(delim);
|
||||
if width < d {
|
||||
|
|
|
|||
|
|
@ -237,4 +237,12 @@ mod test {
|
|||
actual = actual.truncate(0, "…");
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub struct Label(Paint<String>);
|
|||
impl Label {
|
||||
/// Create a new label.
|
||||
pub fn new(s: &str) -> Self {
|
||||
Self(Paint::new(s.replace('\n', " ")))
|
||||
Self(Paint::new(cleanup(s)))
|
||||
}
|
||||
|
||||
/// Create a blank label.
|
||||
|
|
@ -64,7 +64,7 @@ impl Cell for Label {
|
|||
impl<D: fmt::Display> From<Paint<D>> for Label {
|
||||
fn from(paint: Paint<D>) -> Self {
|
||||
Self(Paint {
|
||||
item: paint.item.to_string().replace('\n', " "),
|
||||
item: cleanup(paint.item.to_string().as_str()),
|
||||
style: paint.style,
|
||||
})
|
||||
}
|
||||
|
|
@ -72,13 +72,13 @@ impl<D: fmt::Display> From<Paint<D>> for Label {
|
|||
|
||||
impl From<String> for Label {
|
||||
fn from(value: String) -> Self {
|
||||
Label::from(value.as_str())
|
||||
Self::new(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Label {
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub use inquire::ui::Styled;
|
|||
pub use io::*;
|
||||
pub use label::{label, Label};
|
||||
pub use spinner::{spinner, spinner_to, Spinner};
|
||||
pub use table::Table;
|
||||
pub use table::{Table, TableOptions};
|
||||
pub use textarea::{textarea, TextArea};
|
||||
pub use vstack::{VStack, VStackOptions};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue