codeberg_cli/actions/issue/
comment.rs

1use anyhow::Context;
2use forgejo_api::structs::{CreateIssueCommentOption, IssueListIssuesQuery};
3
4use crate::actions::issue::display_issue;
5
6use crate::actions::GeneralArgs;
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/// Add a comment to selected issue
18#[derive(Parser, Debug, Clone)]
19pub struct CommentIssueArgs {
20    /// id of the issue to comment
21    #[arg(short, long)]
22    pub id: Option<u64>,
23
24    /// text body of the issue
25    #[arg(short, long)]
26    pub body: Option<String>,
27}
28
29impl CommentIssueArgs {
30    pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
31        let ctx = BergContext::new(self, general_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 general_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<(u64, CreateIssueCommentOption)> {
57    let OwnerRepo { owner, repo } = ctx.owner_repo()?;
58
59    let selected_issue = if ctx.general_args.non_interactive {
60        None
61    } else {
62        let (_, issues_list) = spin_until_ready(ctx.client.issue_list_issues(
63            owner.as_str(),
64            repo.as_str(),
65            IssueListIssuesQuery::default(),
66        ))
67        .await
68        .map_err(anyhow::Error::from)?;
69
70        let selected_issue =
71            fuzzy_select_with_key(&issues_list, select_prompt_for("issue"), display_issue)?;
72
73        Some(selected_issue.clone())
74    };
75
76    let nr = selected_issue
77        .as_ref()
78        .context("No issue selected")
79        .and_then(|issue| Ok(issue.id.context("Selected issue had no id")? as u64))
80        .or_else(|e| {
81            ctx.args
82                .id
83                .context(e)
84                .context("You have to provide an issue number in non-interactive mode!")
85        })?;
86
87    let body = if ctx.general_args.non_interactive {
88        ctx.args
89            .body
90            .clone()
91            .context("You have to provide a body in non-interactive mode!")?
92    } else {
93        get_comment_input(ctx, selected_issue.and_then(|issue| issue.title.clone()))?
94    };
95
96    let options = CreateIssueCommentOption {
97        body,
98        updated_at: None,
99    };
100
101    Ok((nr, options))
102}
103
104fn get_comment_input(
105    ctx: &BergContext<CommentIssueArgs>,
106    issue_title: impl Into<Option<String>>,
107) -> anyhow::Result<String> {
108    let opt_issue_title = issue_title
109        .into()
110        .map(|title| format!(" for issue \"{title}\""))
111        .unwrap_or_default();
112    ctx.editor_for(
113        "a comment",
114        format!("Write a comment{opt_issue_title}").as_str(),
115    )
116}