codeberg_cli/actions/issue/
comment.rs

1use forgejo_api::structs::{CreateIssueCommentOption, IssueListIssuesQuery};
2use miette::{Context, IntoDiagnostic};
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/// Add a comment to selected issue
18#[derive(Parser, Debug, Clone)]
19pub struct CommentIssueArgs {
20    /// number of the issue to comment
21    #[arg(short, long)]
22    pub number: Option<i64>,
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, global_args: GlobalArgs) -> miette::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            .into_diagnostic()?;
41
42        match ctx.global_args.output_mode {
43            OutputMode::Pretty => {
44                tracing::debug!("{comment:?}");
45            }
46            OutputMode::Json => {
47                comment.print_json()?;
48            }
49        }
50
51        Ok(())
52    }
53}
54
55async fn create_options(
56    ctx: &BergContext<CommentIssueArgs>,
57) -> miette::Result<(i64, CreateIssueCommentOption)> {
58    let OwnerRepo { owner, repo } = ctx.owner_repo()?;
59
60    let selected_issue = if ctx.global_args.non_interactive {
61        None
62    } else {
63        let (_, issues_list) = spin_until_ready(
64            ctx.client
65                .issue_list_issues(
66                    owner.as_str(),
67                    repo.as_str(),
68                    IssueListIssuesQuery::default(),
69                )
70                .send(),
71        )
72        .await
73        .into_diagnostic()?;
74
75        let selected_issue =
76            fuzzy_select_with_key(&issues_list, select_prompt_for("issue"), display_issue)?;
77
78        Some(selected_issue.clone())
79    };
80
81    let nr = match selected_issue
82        .as_ref()
83        .context("No issue selected")
84        .and_then(|issue| issue.number.context("Selected issue had no issue number"))
85    {
86        Ok(ok) => ok,
87        Err(e) => ctx
88            .args
89            .number
90            .context(e)
91            .context("You have to provide an issue number in non-interactive mode!")?,
92    };
93
94    let body = if ctx.global_args.non_interactive {
95        ctx.args
96            .body
97            .clone()
98            .context("You have to provide a body in non-interactive mode!")?
99    } else {
100        get_comment_input(ctx, selected_issue.and_then(|issue| issue.title.clone()))?
101    };
102
103    let options = CreateIssueCommentOption {
104        body,
105        updated_at: None,
106    };
107
108    Ok((nr, options))
109}
110
111fn get_comment_input(
112    ctx: &BergContext<CommentIssueArgs>,
113    issue_title: impl Into<Option<String>>,
114) -> miette::Result<String> {
115    let opt_issue_title = issue_title
116        .into()
117        .map(|title| format!(" for issue \"{title}\""))
118        .unwrap_or_default();
119    ctx.editor_for(
120        "a comment",
121        format!("Write a comment{opt_issue_title}").as_str(),
122    )
123}