codeberg_cli/actions/issue/
comment.rs1use anyhow::Context;
2use forgejo_api::structs::{CreateIssueCommentOption, IssueListIssuesQuery};
3
4use crate::actions::issue::display_issue;
5
6use crate::actions::GlobalArgs;
7use crate::actions::text_manipulation::select_prompt_for;
8use crate::render::json::JsonToStdout;
9use crate::render::spinner::spin_until_ready;
10use crate::render::ui::fuzzy_select_with_key;
11use crate::types::context::BergContext;
12use crate::types::git::OwnerRepo;
13use crate::types::output::OutputMode;
14
15use clap::Parser;
16
17#[derive(Parser, Debug, Clone)]
19pub struct CommentIssueArgs {
20 #[arg(short, long)]
22 pub number: Option<i64>,
23
24 #[arg(short, long)]
26 pub body: Option<String>,
27}
28
29impl CommentIssueArgs {
30 pub async fn run(self, global_args: GlobalArgs) -> anyhow::Result<()> {
31 let ctx = BergContext::new(self, global_args).await?;
32 let OwnerRepo { owner, repo } = ctx.owner_repo()?;
33
34 let (nr, options) = create_options(&ctx).await?;
35
36 let comment = ctx
37 .client
38 .issue_create_comment(owner.as_str(), repo.as_str(), nr, options)
39 .await?;
40
41 match ctx.global_args.output_mode {
42 OutputMode::Pretty => {
43 tracing::debug!("{comment:?}");
44 }
45 OutputMode::Json => {
46 comment.print_json()?;
47 }
48 }
49
50 Ok(())
51 }
52}
53
54async fn create_options(
55 ctx: &BergContext<CommentIssueArgs>,
56) -> anyhow::Result<(i64, CreateIssueCommentOption)> {
57 let OwnerRepo { owner, repo } = ctx.owner_repo()?;
58
59 let selected_issue = if ctx.global_args.non_interactive {
60 None
61 } else {
62 let (_, issues_list) = spin_until_ready(
63 ctx.client
64 .issue_list_issues(
65 owner.as_str(),
66 repo.as_str(),
67 IssueListIssuesQuery::default(),
68 )
69 .send(),
70 )
71 .await
72 .map_err(anyhow::Error::from)?;
73
74 let selected_issue =
75 fuzzy_select_with_key(&issues_list, select_prompt_for("issue"), display_issue)?;
76
77 Some(selected_issue.clone())
78 };
79
80 let nr = match selected_issue
81 .as_ref()
82 .context("No issue selected")
83 .and_then(|issue| issue.number.context("Selected issue had no issue number"))
84 {
85 Ok(ok) => ok,
86 Err(e) => ctx
87 .args
88 .number
89 .context(e)
90 .context("You have to provide an issue number in non-interactive mode!")?,
91 };
92
93 let body = if ctx.global_args.non_interactive {
94 ctx.args
95 .body
96 .clone()
97 .context("You have to provide a body in non-interactive mode!")?
98 } else {
99 get_comment_input(ctx, selected_issue.and_then(|issue| issue.title.clone()))?
100 };
101
102 let options = CreateIssueCommentOption {
103 body,
104 updated_at: None,
105 };
106
107 Ok((nr, options))
108}
109
110fn get_comment_input(
111 ctx: &BergContext<CommentIssueArgs>,
112 issue_title: impl Into<Option<String>>,
113) -> anyhow::Result<String> {
114 let opt_issue_title = issue_title
115 .into()
116 .map(|title| format!(" for issue \"{title}\""))
117 .unwrap_or_default();
118 ctx.editor_for(
119 "a comment",
120 format!("Write a comment{opt_issue_title}").as_str(),
121 )
122}