codeberg-cli 0.5.5

CLI Tool for codeberg similar to gh and glab
Documentation
use forgejo_api::structs::{CreateIssueCommentOption, IssueListIssuesQuery};
use miette::{Context, IntoDiagnostic};

use crate::actions::issue::display_issue;

use crate::actions::GlobalArgs;
use crate::actions::text_manipulation::select_prompt_for;
use crate::render::json::JsonToStdout;
use crate::render::spinner::spin_until_ready;
use crate::render::ui::fuzzy_select_with_key;
use crate::types::context::BergContext;
use crate::types::git::OwnerRepo;
use crate::types::output::OutputMode;

use clap::Parser;

/// Add a comment to selected issue
#[derive(Parser, Debug, Clone)]
pub struct CommentIssueArgs {
    /// number of the issue to comment
    #[arg(short, long)]
    pub number: Option<i64>,

    /// text body of the issue
    #[arg(short, long)]
    pub body: Option<String>,
}

impl CommentIssueArgs {
    pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
        let ctx = BergContext::new(self, global_args).await?;
        let OwnerRepo { owner, repo } = ctx.owner_repo()?;

        let (nr, options) = create_options(&ctx).await?;

        let comment = ctx
            .client
            .issue_create_comment(owner.as_str(), repo.as_str(), nr, options)
            .await
            .into_diagnostic()?;

        match ctx.global_args.output_mode {
            OutputMode::Pretty => {
                tracing::debug!("{comment:?}");
            }
            OutputMode::Json => {
                comment.print_json()?;
            }
        }

        Ok(())
    }
}

async fn create_options(
    ctx: &BergContext<CommentIssueArgs>,
) -> miette::Result<(i64, CreateIssueCommentOption)> {
    let OwnerRepo { owner, repo } = ctx.owner_repo()?;

    let selected_issue = if ctx.global_args.non_interactive {
        None
    } else {
        let (_, issues_list) = spin_until_ready(
            ctx.client
                .issue_list_issues(
                    owner.as_str(),
                    repo.as_str(),
                    IssueListIssuesQuery::default(),
                )
                .send(),
        )
        .await
        .into_diagnostic()?;

        let selected_issue =
            fuzzy_select_with_key(&issues_list, select_prompt_for("issue"), display_issue)?;

        Some(selected_issue.clone())
    };

    let nr = match selected_issue
        .as_ref()
        .context("No issue selected")
        .and_then(|issue| issue.number.context("Selected issue had no issue number"))
    {
        Ok(ok) => ok,
        Err(e) => ctx
            .args
            .number
            .context(e)
            .context("You have to provide an issue number in non-interactive mode!")?,
    };

    let body = if ctx.global_args.non_interactive {
        ctx.args
            .body
            .clone()
            .context("You have to provide a body in non-interactive mode!")?
    } else {
        get_comment_input(ctx, selected_issue.and_then(|issue| issue.title.clone()))?
    };

    let options = CreateIssueCommentOption {
        body,
        updated_at: None,
    };

    Ok((nr, options))
}

fn get_comment_input(
    ctx: &BergContext<CommentIssueArgs>,
    issue_title: impl Into<Option<String>>,
) -> miette::Result<String> {
    let opt_issue_title = issue_title
        .into()
        .map(|title| format!(" for issue \"{title}\""))
        .unwrap_or_default();
    ctx.editor_for(
        "a comment",
        format!("Write a comment{opt_issue_title}").as_str(),
    )
}