diff --git a/radicle-cli/src/commands/help.rs b/radicle-cli/src/commands/help.rs index bd6c5cca..7d0e5d47 100644 --- a/radicle-cli/src/commands/help.rs +++ b/radicle-cli/src/commands/help.rs @@ -39,6 +39,7 @@ const COMMANDS: &[Help] = &[ rad_unassign::HELP, rad_untag::HELP, rad_untrack::HELP, + rad_remote::HELP, ]; #[derive(Default)] diff --git a/radicle-cli/src/commands/remote.rs b/radicle-cli/src/commands/remote.rs new file mode 100644 index 00000000..da2b31af --- /dev/null +++ b/radicle-cli/src/commands/remote.rs @@ -0,0 +1,122 @@ +//! Remote Command implementation +#[path = "remote/add.rs"] +pub mod add; +#[path = "remote/list.rs"] +pub mod list; +#[path = "remote/rm.rs"] +pub mod rm; + +use std::ffi::OsString; + +use anyhow::anyhow; + +use radicle::prelude::NodeId; + +use crate::terminal::args::{self, string, Error}; +use crate::terminal::{Args, Context, Help}; + +pub const HELP: Help = Help { + name: "remote", + description: "Manage a project's remotes", + version: env!("CARGO_PKG_VERSION"), + usage: r#" +Usage + + rad remote + rad remote list + rad remote add ( | ) [--name ] + rad remote rm + +Options + + --name Override the name of the remote that by default is set to the node alias + --help Print help +"#, +}; + +#[derive(Debug, Default, PartialEq, Eq)] +pub enum OperationName { + Add, + Rm, + #[default] + List, +} + +#[derive(Debug)] +pub enum Operation { + Add { id: NodeId, name: Option }, + Rm { name: String }, + List, +} + +#[derive(Debug)] +pub struct Options { + pub op: Operation, +} + +impl Args for Options { + fn from_args(args: Vec) -> anyhow::Result<(Self, Vec)> { + use lexopt::prelude::*; + + let mut parser = lexopt::Parser::from_args(args); + let mut op: Option = None; + let mut id: Option = None; + let mut name: Option = None; + + while let Some(arg) = parser.next()? { + match arg { + Long("help") => { + return Err(Error::Help.into()); + } + Long("name") | Short('n') => { + let value = parser.value()?; + let value = string(&value); + name = Some(value); + } + Value(val) if op.is_none() => match val.to_string_lossy().as_ref() { + "a" | "add" => op = Some(OperationName::Add), + "l" | "list" => op = Some(OperationName::List), + "r" | "rm" => op = Some(OperationName::Rm), + unknown => anyhow::bail!("unknown operation '{}'", unknown), + }, + Value(val) if op == Some(OperationName::Add) && id.is_none() => { + let nid = args::pubkey(&val)?; + id = Some(nid); + } + Value(val) if op == Some(OperationName::Rm) && name.is_none() => { + let val = string(&val); + name = Some(val); + } + _ => return Err(anyhow::anyhow!(arg.unexpected())), + } + } + + let op = match op.unwrap_or_default() { + OperationName::Add => Operation::Add { + id: id.ok_or(anyhow!( + "`DID` required, try running `rad remote add `" + ))?, + name, + }, + OperationName::List => Operation::List, + OperationName::Rm => Operation::Rm { + name: name.ok_or(anyhow!("name required, see `rad remote`"))?, + }, + }; + + Ok((Options { op }, vec![])) + } +} + +pub fn run(options: Options, ctx: impl Context) -> anyhow::Result<()> { + let (working, rid) = radicle::rad::cwd() + .map_err(|_| anyhow!("this command must be run in the context of a project"))?; + let profile = ctx.profile()?; + + match options.op { + Operation::Add { ref id, name } => self::add::run(&working, &profile, id, name, rid)?, + Operation::Rm { ref name } => self::rm::run(&working, name)?, + Operation::List => self::list::run(&working)?, + }; + Ok(()) +}