use std::str::FromStr;
use clap::{Parser, Subcommand};
use radicle::{
cob::{Label, Reaction, Title},
identity::{did::DidError, Did, RepoId},
issue::{CloseReason, State},
};
use crate::{git::Rev, terminal::patch::Message};
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum Assigned {
#[default]
Me,
Peer(Did),
}
#[derive(Parser, Debug)]
#[command(about = super::ABOUT, disable_version_flag = true)]
pub struct Args {
#[command(subcommand)]
pub(crate) command: Option<Command>,
#[arg(short, long)]
#[clap(global = true)]
pub(crate) quiet: bool,
#[arg(long)]
#[arg(value_name = "no-announce")]
#[clap(global = true)]
pub(crate) no_announce: bool,
#[arg(long)]
#[clap(global = true)]
pub(crate) header: bool,
#[arg(value_name = "RID")]
#[arg(long, short)]
#[clap(global = true)]
pub(crate) repo: Option<RepoId>,
#[arg(long, short)]
#[clap(global = true)]
pub(crate) verbose: bool,
#[clap(flatten)]
pub(crate) empty: EmptyArgs,
}
#[derive(Subcommand, Debug)]
pub(crate) enum Command {
Assign {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
#[arg(long, short)]
#[arg(value_name = "DID")]
#[arg(action = clap::ArgAction::Append)]
add: Vec<Did>,
#[arg(long, short)]
#[arg(value_name = "DID")]
#[arg(action = clap::ArgAction::Append)]
delete: Vec<Did>,
},
Cache {
#[arg(value_name = "ISSUE_ID")]
id: Option<Rev>,
#[arg(long)]
storage: bool,
},
#[clap(long_about = include_str!("comment.txt"))]
Comment(CommentArgs),
Edit {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
#[arg(long, short)]
title: Option<Title>,
#[arg(long, short)]
description: Option<String>,
},
Delete {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
},
Label {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
#[arg(long, short)]
#[arg(value_name = "label")]
#[arg(action = clap::ArgAction::Append)]
add: Vec<Label>,
#[arg(long, short)]
#[arg(value_name = "label")]
#[arg(action = clap::ArgAction::Append)]
delete: Vec<Label>,
},
List(ListArgs),
Open {
#[arg(long, short)]
title: Option<Title>,
#[arg(long, short)]
description: Option<String>,
#[arg(long)]
labels: Vec<Label>,
#[arg(value_name = "DID")]
#[arg(long)]
assignees: Vec<Did>,
},
React {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
#[arg(long = "emoji")]
#[arg(value_name = "CHAR")]
reaction: Option<Reaction>,
#[arg(long = "to")]
#[arg(value_name = "COMMENT_ID")]
comment_id: Option<Rev>,
},
Show {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
},
State {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
#[clap(flatten)]
target_state: StateArgs,
},
}
impl Command {
pub(crate) fn should_announce_for(&self) -> bool {
match self {
Command::Open { .. }
| Command::React { .. }
| Command::State { .. }
| Command::Delete { .. }
| Command::Assign { .. }
| Command::Label { .. }
| Command::Edit { .. } => true,
Command::Comment(args) => !args.is_edit(),
Command::Cache{..} | Command::Show { .. } | Command::List(_) => false,
}
}
}
#[derive(Parser, Debug, Default)]
pub(crate) struct EmptyArgs {
#[arg(long, name = "DID")]
#[arg(default_missing_value = "me")]
#[arg(num_args = 0..=1)]
#[arg(hide = true)]
pub(crate) assigned: Option<Assigned>,
#[clap(flatten)]
pub(crate) state: EmptyStateArgs,
}
#[derive(Parser, Debug, Default)]
#[group(multiple = false)]
pub(crate) struct EmptyStateArgs {
#[arg(long, hide = true)]
all: bool,
#[arg(long, hide = true)]
open: bool,
#[arg(long, hide = true)]
closed: bool,
#[arg(long, hide = true)]
solved: bool,
}
#[derive(Parser, Debug, Default)]
pub(crate) struct ListArgs {
#[arg(long, name = "DID")]
#[arg(default_missing_value = "me")]
#[arg(num_args = 0..=1)]
pub(crate) assigned: Option<Assigned>,
#[clap(flatten)]
pub(crate) state: ListStateArgs,
}
#[derive(Parser, Debug, Default)]
#[group(multiple = false)]
pub(crate) struct ListStateArgs {
#[arg(long)]
all: bool,
#[arg(long)]
open: bool,
#[arg(long)]
closed: bool,
#[arg(long)]
solved: bool,
}
impl From<&ListStateArgs> for Option<State> {
fn from(args: &ListStateArgs) -> Self {
match (args.all, args.open, args.closed, args.solved) {
(true, false, false, false) => None,
(false, true, false, false) | (false, false, false, false) => Some(State::Open),
(false, false, true, false) => Some(State::Closed {
reason: CloseReason::Other,
}),
(false, false, false, true) => Some(State::Closed {
reason: CloseReason::Solved,
}),
_ => unreachable!(),
}
}
}
impl From<EmptyStateArgs> for ListStateArgs {
fn from(args: EmptyStateArgs) -> Self {
Self {
all: args.all,
open: args.open,
closed: args.closed,
solved: args.solved,
}
}
}
impl From<EmptyArgs> for ListArgs {
fn from(args: EmptyArgs) -> Self {
Self {
assigned: args.assigned,
state: ListStateArgs::from(args.state),
}
}
}
#[derive(Parser, Debug)]
pub(crate) struct CommentArgs {
#[arg(value_name = "ISSUE_ID")]
id: Rev,
#[arg(long, short)]
#[arg(value_name = "MESSAGE")]
message: Option<Message>,
#[arg(long, value_name = "COMMENT_ID")]
#[arg(conflicts_with = "edit")]
reply_to: Option<Rev>,
#[arg(long, value_name = "COMMENT_ID")]
#[arg(conflicts_with = "reply_to")]
edit: Option<Rev>,
}
impl CommentArgs {
pub(crate) fn is_edit(&self) -> bool {
self.edit.is_some()
}
}
#[derive(Parser, Debug)]
#[group(required = true, multiple = false)]
pub(crate) struct StateArgs {
#[arg(long)]
pub(crate) open: bool,
#[arg(long)]
pub(crate) closed: bool,
#[arg(long)]
pub(crate) solved: bool,
}
impl From<StateArgs> for StateArg {
fn from(state: StateArgs) -> Self {
match (state.open, state.closed, state.solved) {
(true, _, _) => StateArg::Open,
(_, true, _) => StateArg::Closed,
(_, _, true) => StateArg::Solved,
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum StateArg {
Open,
Closed,
Solved,
}
impl From<StateArg> for State {
fn from(value: StateArg) -> Self {
match value {
StateArg::Open => Self::Open,
StateArg::Closed => Self::Closed {
reason: CloseReason::Other,
},
StateArg::Solved => Self::Closed {
reason: CloseReason::Solved,
},
}
}
}
impl FromStr for Assigned {
type Err = DidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "me" {
Ok(Assigned::Me)
} else {
let value = s.parse::<Did>()?;
Ok(Assigned::Peer(value))
}
}
}
pub(crate) enum CommentAction {
Comment {
id: Rev,
message: Message,
},
Reply {
id: Rev,
message: Message,
reply_to: Rev,
},
Edit {
id: Rev,
message: Message,
to_edit: Rev,
},
}
impl From<CommentArgs> for CommentAction {
fn from(
CommentArgs {
id,
message,
reply_to,
edit,
}: CommentArgs,
) -> Self {
let message = message.unwrap_or(Message::Edit);
match (reply_to, edit) {
(Some(_), Some(_)) => {
unreachable!("the argument '--reply-to' cannot be used with '--edit'")
}
(Some(reply_to), None) => Self::Reply {
id,
message,
reply_to,
},
(None, Some(to_edit)) => Self::Edit {
id,
message,
to_edit,
},
(None, None) => Self::Comment { id, message },
}
}
}