Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli/inspect: use Clap
✗ CI failure Richard Levitte committed 6 months ago
commit c09fca6aee40b575d3224ce39bdf9bb6e85362e2
parent 38ca038a0d695875bc6ea73ae88e048622dbc846
2 failed 1 pending (3 total) View logs
6 files changed +182 -238
modified crates/radicle-cli/examples/rad-init-existing-bare.md
@@ -7,7 +7,7 @@ f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
```

We can see it's not a Radicle working copy:
-
``` (fail)
+
``` (stderr) (fail)
$ rad .
✗ Error: Current directory is not a Radicle repository
```
modified crates/radicle-cli/examples/rad-init-existing.md
@@ -7,7 +7,7 @@ f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
```

We can see it's not a Radicle working copy:
-
``` (fail)
+
``` (stderr) (fail)
$ rad .
✗ Error: Current directory is not a Radicle repository
```
modified crates/radicle-cli/src/commands/help.rs
@@ -72,7 +72,10 @@ const COMMANDS: &[CommandItem] = &[
        about: crate::commands::init::ABOUT,
    },
    CommandItem::Lexopt(crate::commands::inbox::HELP),
-
    CommandItem::Lexopt(crate::commands::inspect::HELP),
+
    CommandItem::Clap {
+
        name: "inspect",
+
        about: crate::commands::inspect::ABOUT,
+
    },
    CommandItem::Clap {
        name: "issue",
        about: crate::commands::issue::ABOUT,
modified crates/radicle-cli/src/commands/inspect.rs
@@ -1,6 +1,11 @@
#![allow(clippy::or_fun_call)]
+

+
mod args;
+

+
pub use args::Args;
+
pub(crate) use args::ABOUT;
+

use std::collections::HashMap;
-
use std::ffi::OsString;
use std::path::Path;
use std::str::FromStr;

@@ -16,262 +21,149 @@ use radicle::storage::refs::RefsAt;
use radicle::storage::{ReadRepository, ReadStorage};

use crate::terminal as term;
-
use crate::terminal::args::{Args, Error, Help};
use crate::terminal::json;
use crate::terminal::Element;

-
pub const HELP: Help = Help {
-
    name: "inspect",
-
    description: "Inspect a Radicle repository",
-
    version: env!("RADICLE_VERSION"),
-
    usage: r#"
-
Usage
-

-
    rad inspect <path> [<option>...]
-
    rad inspect <rid>  [<option>...]
-
    rad inspect [<option>...]
-

-
    Inspects the given path or RID. If neither is specified,
-
    the current repository is inspected.
-

-
Options
-

-
    --rid        Return the repository identifier (RID)
-
    --payload    Inspect the repository's identity payload
-
    --refs       Inspect the repository's refs on the local device
-
    --sigrefs    Inspect the values of `rad/sigrefs` for all remotes of this repository
-
    --identity   Inspect the identity document
-
    --visibility Inspect the repository's visibility
-
    --delegates  Inspect the repository's delegates
-
    --policy     Inspect the repository's seeding policy
-
    --history    Show the history of the repository identity document
-
    --help       Print help
-
"#,
-
};
-

-
#[derive(Default, Debug, Eq, PartialEq)]
-
pub enum Target {
-
    Refs,
-
    Payload,
-
    Delegates,
-
    Identity,
-
    Visibility,
-
    Sigrefs,
-
    Policy,
-
    History,
-
    #[default]
-
    RepoId,
-
}
-

-
#[derive(Default, Debug, Eq, PartialEq)]
-
pub struct Options {
-
    pub rid: Option<RepoId>,
-
    pub target: Target,
-
}
-

-
impl Args for Options {
-
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
-
        use lexopt::prelude::*;
-

-
        let mut parser = lexopt::Parser::from_args(args);
-
        let mut rid: Option<RepoId> = None;
-
        let mut target = Target::default();
-

-
        while let Some(arg) = parser.next()? {
-
            match arg {
-
                Long("help") | Short('h') => {
-
                    return Err(Error::Help.into());
-
                }
-
                Long("refs") => {
-
                    target = Target::Refs;
-
                }
-
                Long("payload") => {
-
                    target = Target::Payload;
-
                }
-
                Long("policy") => {
-
                    target = Target::Policy;
-
                }
-
                Long("delegates") => {
-
                    target = Target::Delegates;
-
                }
-
                Long("history") => {
-
                    target = Target::History;
-
                }
-
                Long("identity") => {
-
                    target = Target::Identity;
-
                }
-
                Long("sigrefs") => {
-
                    target = Target::Sigrefs;
-
                }
-
                Long("rid") => {
-
                    target = Target::RepoId;
-
                }
-
                Long("visibility") => {
-
                    target = Target::Visibility;
-
                }
-
                Value(val) if rid.is_none() => {
-
                    let val = val.to_string_lossy();
-

-
                    if let Ok(val) = RepoId::from_str(&val) {
-
                        rid = Some(val);
-
                    } else {
-
                        rid = radicle::rad::at(Path::new(val.as_ref()))
-
                            .map(|(_, id)| Some(id))
-
                            .context("Supplied argument is not a valid path")?;
-
                    }
-
                }
-
                _ => anyhow::bail!(arg.unexpected()),
+
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
+
    let rid = match args.repo {
+
        Some(rid) => {
+
            if let Ok(val) = RepoId::from_str(&rid) {
+
                val
+
            } else {
+
                radicle::rad::at(Path::new(&rid))
+
                    .map(|(_, id)| id)
+
                    .context("Supplied argument is not a valid path")?
            }
-
        }
-

-
        Ok((Options { rid, target }, vec![]))
-
    }
-
}
-

-
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
-
    let rid = match options.rid {
-
        Some(rid) => rid,
+
        },
        None => radicle::rad::cwd()
            .map(|(_, rid)| rid)
            .context("Current directory is not a Radicle repository")?,
    };

-
    if options.target == Target::RepoId {
-
        term::info!("{}", term::format::highlight(rid.urn()));
-
        return Ok(());
-
    }
    let profile = ctx.profile()?;
    let storage = &profile.storage;

-
    match options.target {
-
        Target::Refs => {
-
            let (repo, _) = repo(rid, storage)?;
-
            refs(&repo)?;
-
        }
-
        Target::Payload => {
-
            let (_, doc) = repo(rid, storage)?;
-
            json::to_pretty(&doc.payload(), Path::new("radicle.json"))?.print();
-
        }
-
        Target::Identity => {
-
            let (_, doc) = repo(rid, storage)?;
-
            json::to_pretty(&*doc, Path::new("radicle.json"))?.print();
+
    if args.target.refs {
+
        let (repo, _) = repo(rid, storage)?;
+
        refs(&repo)?;
+
    } else if args.target.payload {
+
        let (_, doc) = repo(rid, storage)?;
+
        json::to_pretty(&doc.payload(), Path::new("radicle.json"))?.print();
+
    } else if args.target.identity {
+
        let (_, doc) = repo(rid, storage)?;
+
        json::to_pretty(&*doc, Path::new("radicle.json"))?.print();
+
    } else if args.target.sigrefs {
+
        let (repo, _) = repo(rid, storage)?;
+
        for remote in repo.remote_ids()? {
+
            let remote = remote?;
+
            let refs = RefsAt::new(&repo, remote)?;
+

+
            println!(
+
                "{:<48} {}",
+
                term::format::tertiary(remote.to_human()),
+
                term::format::secondary(refs.at)
+
            );
        }
-
        Target::Sigrefs => {
-
            let (repo, _) = repo(rid, storage)?;
-
            for remote in repo.remote_ids()? {
-
                let remote = remote?;
-
                let refs = RefsAt::new(&repo, remote)?;
-

+
    } else if args.target.policy {
+
        let policies = profile.policies()?;
+
        let seed = policies.seed_policy(&rid)?;
+
        match seed.policy {
+
            SeedingPolicy::Allow { scope } => {
                println!(
-
                    "{:<48} {}",
-
                    term::format::tertiary(remote.to_human()),
-
                    term::format::secondary(refs.at)
+
                    "Repository {} is {} with scope {}",
+
                    term::format::tertiary(&rid),
+
                    term::format::positive("being seeded"),
+
                    term::format::dim(format!("`{scope}`"))
                );
            }
-
        }
-
        Target::Policy => {
-
            let policies = profile.policies()?;
-
            let seed = policies.seed_policy(&rid)?;
-
            match seed.policy {
-
                SeedingPolicy::Allow { scope } => {
-
                    println!(
-
                        "Repository {} is {} with scope {}",
-
                        term::format::tertiary(&rid),
-
                        term::format::positive("being seeded"),
-
                        term::format::dim(format!("`{scope}`"))
-
                    );
-
                }
-
                SeedingPolicy::Block => {
-
                    println!(
-
                        "Repository {} is {}",
-
                        term::format::tertiary(&rid),
-
                        term::format::negative("not being seeded"),
-
                    );
-
                }
+
            SeedingPolicy::Block => {
+
                println!(
+
                    "Repository {} is {}",
+
                    term::format::tertiary(&rid),
+
                    term::format::negative("not being seeded"),
+
                );
            }
        }
-
        Target::Delegates => {
-
            let (_, doc) = repo(rid, storage)?;
-
            let aliases = profile.aliases();
-
            for did in doc.delegates().iter() {
-
                if let Some(alias) = aliases.alias(did) {
-
                    println!(
-
                        "{} {}",
-
                        term::format::tertiary(&did),
-
                        term::format::parens(term::format::dim(alias))
-
                    );
-
                } else {
-
                    println!("{}", term::format::tertiary(&did));
-
                }
+
    } else if args.target.delegates {
+
        let (_, doc) = repo(rid, storage)?;
+
        let aliases = profile.aliases();
+
        for did in doc.delegates().iter() {
+
            if let Some(alias) = aliases.alias(did) {
+
                println!(
+
                    "{} {}",
+
                    term::format::tertiary(&did),
+
                    term::format::parens(term::format::dim(alias))
+
                );
+
            } else {
+
                println!("{}", term::format::tertiary(&did));
            }
        }
-
        Target::Visibility => {
-
            let (_, doc) = repo(rid, storage)?;
-
            println!("{}", term::format::visibility(doc.visibility()));
-
        }
-
        Target::History => {
-
            let (repo, _) = repo(rid, storage)?;
-
            let identity = Identity::load(&repo)?;
-
            let head = repo.identity_head()?;
-
            let history = repo.revwalk(head)?;
-

-
            for oid in history {
-
                let oid = oid?.into();
-
                let tip = repo.commit(oid)?;
-

-
                let Some(revision) = identity.revision(&tip.id().into()) else {
-
                    continue;
-
                };
-
                if !revision.is_accepted() {
-
                    continue;
-
                }
-
                let doc = &revision.doc;
-
                let timezone = if tip.time().sign() == '+' {
-
                    #[allow(deprecated)]
-
                    FixedOffset::east(tip.time().offset_minutes() * 60)
-
                } else {
-
                    #[allow(deprecated)]
-
                    FixedOffset::west(tip.time().offset_minutes() * 60)
-
                };
-
                let time = DateTime::<Utc>::from(
-
                    std::time::UNIX_EPOCH
-
                        + std::time::Duration::from_secs(tip.time().seconds() as u64),
-
                )
+
    } else if args.target.visibility {
+
        let (_, doc) = repo(rid, storage)?;
+
        println!("{}", term::format::visibility(doc.visibility()));
+
    } else if args.target.history {
+
        let (repo, _) = repo(rid, storage)?;
+
        let identity = Identity::load(&repo)?;
+
        let head = repo.identity_head()?;
+
        let history = repo.revwalk(head)?;
+

+
        for oid in history {
+
            let oid = oid?.into();
+
            let tip = repo.commit(oid)?;
+

+
            let Some(revision) = identity.revision(&tip.id().into()) else {
+
                continue;
+
            };
+
            if !revision.is_accepted() {
+
                continue;
+
            }
+
            let doc = &revision.doc;
+
            let timezone = if tip.time().sign() == '+' {
+
                #[allow(deprecated)]
+
                FixedOffset::east(tip.time().offset_minutes() * 60)
+
            } else {
+
                #[allow(deprecated)]
+
                FixedOffset::west(tip.time().offset_minutes() * 60)
+
            };
+
            let time = DateTime::<Utc>::from(
+
                std::time::UNIX_EPOCH
+
                    + std::time::Duration::from_secs(tip.time().seconds() as u64),
+
            )
                .with_timezone(&timezone)
                .to_rfc2822();

-
                println!(
-
                    "{} {}",
-
                    term::format::yellow("commit"),
-
                    term::format::yellow(oid),
-
                );
-
                if let Ok(parent) = tip.parent_id(0) {
-
                    println!("parent {parent}");
-
                }
-
                println!("blob   {}", revision.blob);
-
                println!("date   {time}");
-
                println!();
-

-
                if let Some(msg) = tip.message() {
-
                    for line in msg.lines() {
-
                        if line.is_empty() {
-
                            println!();
-
                        } else {
-
                            term::indented(term::format::dim(line));
-
                        }
+
            println!(
+
                "{} {}",
+
                term::format::yellow("commit"),
+
                term::format::yellow(oid),
+
            );
+
            if let Ok(parent) = tip.parent_id(0) {
+
                println!("parent {parent}");
+
            }
+
            println!("blob   {}", revision.blob);
+
            println!("date   {time}");
+
            println!();
+

+
            if let Some(msg) = tip.message() {
+
                for line in msg.lines() {
+
                    if line.is_empty() {
+
                        println!();
+
                    } else {
+
                        term::indented(term::format::dim(line));
                    }
-
                    term::blank();
-
                }
-
                for line in json::to_pretty(&doc, Path::new("radicle.json"))? {
-
                    println!(" {line}");
                }
-

-
                println!();
+
                term::blank();
            }
+
            for line in json::to_pretty(&doc, Path::new("radicle.json"))? {
+
                println!(" {line}");
+
            }
+

+
            println!();
        }
-
        Target::RepoId => {
-
            // Handled above.
-
        }
+
    } else {
+
        // Default is to assume --rid
+
        term::info!("{}", term::format::highlight(rid.urn()));
+
        return Ok(());
    }

    Ok(())
added crates/radicle-cli/src/commands/inspect/args.rs
@@ -0,0 +1,50 @@
+
use clap::Parser;
+

+
pub(crate) const ABOUT: &str = "Inspect a Radicle repository";
+
const LONG_ABOUT: &str = r#"Inspects the given path or RID. If neither is specified,
+
the current repository is inspected.
+
"#;
+

+
// Idea stolen from https://stackoverflow.com/a/76315811/6108256 - /RL
+

+
#[derive(Debug, clap::Args)]
+
#[group(multiple = false)]
+
pub(crate) struct Target {
+
    /// Return the repository identifier (RID)
+
    #[arg(long)]
+
    pub(crate) rid: bool,
+
    /// Inspect the repository's identity payload
+
    #[arg(long)]
+
    pub(crate) payload: bool,
+
    /// Inspect the repository's refs on the local device
+
    #[arg(long)]
+
    pub(crate) refs: bool,
+
    /// Inspect the values of `rad/sigrefs` for all remotes of this repository
+
    #[arg(long)]
+
    pub(crate) sigrefs: bool,
+
    /// Inspect the identity document
+
    #[arg(long)]
+
    pub(crate) identity: bool,
+
    /// Inspect the repository's visibility
+
    #[arg(long)]
+
    pub(crate) visibility: bool,
+
    /// Inspect the repository's delegates
+
    #[arg(long)]
+
    pub(crate) delegates: bool,
+
    /// Inspect the repository's seeding policy
+
    #[arg(long)]
+
    pub(crate) policy: bool,
+
    /// Show the history of the repository identity document
+
    #[arg(long)]
+
    pub(crate) history: bool,
+
}
+

+
#[derive(Debug, Parser)]
+
#[command(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
+
pub struct Args {
+
    /// Repository, byt RID or by path
+
    #[arg(value_name = "RID|PATH")]
+
    pub(crate) repo: Option<String>,
+
    #[clap(flatten)]
+
    pub(crate) target: Target,
+
}
modified crates/radicle-cli/src/main.rs
@@ -67,6 +67,7 @@ enum Commands {
    Fork(fork::Args),
    Id(id::Args),
    Init(init::Args),
+
    Inspect(inspect::Args),
    Issue(issue::Args),
    Ls(ls::Args),
    Path(path::Args),
@@ -267,11 +268,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
            }
        }
        "inspect" => {
-
            term::run_command_args::<inspect::Options, _>(
-
                inspect::HELP,
-
                inspect::run,
-
                args.to_vec(),
-
            );
+
            if let Some(Commands::Inspect(args)) = CliArgs::parse().command {
+
                term::run_command_fn(inspect::run, args);
+
            }
        }
        "issue" => {
            if let Some(Commands::Issue(args)) = CliArgs::parse().command {