codeberg-cli 0.5.5

CLI Tool for codeberg similar to gh and glab
Documentation
use super::display_pull_request;
use crate::actions::{GlobalArgs, text_manipulation::select_prompt_for};
use crate::render::comment::render_comment;
use crate::render::datetime::render_datetime_and_info;
use crate::render::json::JsonToStdout;
use crate::render::option::option_display;
use crate::render::spinner::spin_until_ready;
use crate::render::ui::fuzzy_select_with_key;
use crate::types::api::state_type::ViewStateType;
use crate::types::context::BergContext;
use crate::types::git::OwnerRepo;
use crate::types::output::OutputMode;
use clap::Parser;
use forgejo_api::structs::{
    IssueGetCommentsQuery, PullRequest, RepoListPullRequestsQuery, RepoListPullRequestsQueryState,
};
use itertools::Itertools;
use miette::{Context, IntoDiagnostic};

#[derive(Parser, Debug)]
#[command(about = "View details of a selected pull request")]
pub struct ViewPullRequestsArgs {
    /// Optionally, specify a selected issue in the command line
    pub index: Option<i64>,

    /// Select from pull requests with the chosen state
    #[arg(
        short,
        long,
        value_enum,
        default_value_t = ViewStateType::All,
    )]
    pub state: ViewStateType,

    /// Disabled: display issue summary | Enabled: display issue comment history
    #[arg(short, long)]
    pub comments: bool,
}

impl ViewPullRequestsArgs {
    pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
        let ctx = BergContext::new(self, global_args).await?;

        let OwnerRepo { repo, owner } = ctx.owner_repo()?;
        let state = match ctx.args.state {
            ViewStateType::Closed => RepoListPullRequestsQueryState::Closed,
            ViewStateType::Open => RepoListPullRequestsQueryState::Open,
            ViewStateType::All => RepoListPullRequestsQueryState::All,
        };

        let selected_pull_requst = if let Some(index) = ctx.args.index {
            ctx.client
                .repo_get_pull_request(owner.as_str(), repo.as_str(), index)
                .await
                .into_diagnostic()?
        } else {
            if ctx.global_args.non_interactive {
                miette::bail!(
                    "non-interactive mode enabled. You have to specify an pull requests #number"
                );
            };
            let (_, pull_requests_list) = spin_until_ready(
                ctx.client
                    .repo_list_pull_requests(
                        owner.as_str(),
                        repo.as_str(),
                        RepoListPullRequestsQuery {
                            state: Some(state),
                            ..Default::default()
                        },
                    )
                    .send(),
            )
            .await
            .into_diagnostic()?;

            fuzzy_select_with_key(
                &pull_requests_list,
                select_prompt_for("pull request"),
                display_pull_request,
            )
            .cloned()?
        };

        match ctx.global_args.output_mode {
            OutputMode::Pretty => {
                if ctx.args.comments {
                    spin_until_ready(present_pull_request_comments(&ctx, &selected_pull_requst))
                        .await?;
                } else {
                    present_pull_request_overview(&ctx, &selected_pull_requst);
                }
            }
            OutputMode::Json => selected_pull_requst.print_json()?,
        }

        Ok(())
    }
}

fn present_pull_request_overview(
    ctx: &BergContext<ViewPullRequestsArgs>,
    pull_request: &PullRequest,
) {
    let rendered_datetime = pull_request
        .created_at
        .as_ref()
        .map(render_datetime_and_info)
        .unwrap_or_else(|| String::from("?"));

    let table = ctx
        .make_table()
        .set_header(vec![format!(
            "Pull Request #{}",
            option_display(&pull_request.number)
        )])
        .add_row(vec![
            String::from("Title"),
            option_display(&pull_request.title),
        ])
        .add_row(vec![String::from("Created"), rendered_datetime])
        .add_row(vec![
            String::from("Labels"),
            pull_request
                .labels
                .iter()
                .flatten()
                .map(|label| option_display(&label.name))
                .join(", "),
        ])
        .add_row(vec![
            String::from("Description"),
            option_display(&pull_request.body),
        ]);

    println!("{table}", table = table.show());
}

async fn present_pull_request_comments(
    ctx: &BergContext<ViewPullRequestsArgs>,
    pull_request: &PullRequest,
) -> miette::Result<()> {
    let OwnerRepo { repo, owner } = ctx.owner_repo()?;
    let nr = pull_request
        .number
        .context("Selected pull request has no ID")?;
    let (_, comments) = ctx
        .client
        .issue_get_comments(
            owner.as_str(),
            repo.as_str(),
            nr,
            IssueGetCommentsQuery::default(),
        )
        .await
        .into_diagnostic()?;
    let header = format!(
        "Pull Request #{} {}",
        option_display(&pull_request.number),
        if comments.is_empty() {
            "(no comments)"
        } else {
            "comments"
        }
    );

    let table =
        ctx.make_table()
            .set_header(vec![header])
            .add_rows(comments.into_iter().filter_map(|comment| {
                let username = comment.user.as_ref()?.login.as_ref()?.as_str();
                let creation_time = comment.created_at.as_ref()?;
                let comment = comment.body.as_ref()?;
                let comment = render_comment(username, creation_time, comment);
                Some(vec![comment])
            }));

    println!("{table}", table = table.show());

    Ok(())
}