codeberg-cli 0.5.5

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

use crate::actions::GlobalArgs;
use crate::render::datetime::render_datetime_regular;
use crate::render::json::JsonToStdout;
use crate::render::option::{option_debug_display, option_display};
use crate::render::spinner::spin_until_ready;
use crate::render::ui::fuzzy_select_with_key;

use crate::actions::text_manipulation::select_prompt_for;
use crate::types::api::state_type::ViewStateType;
use crate::types::context::BergContext;
use crate::types::git::OwnerRepo;
use crate::types::output::OutputMode;

use super::display_milestone;

use clap::Parser;

/// View details of selected milestone
#[derive(Parser, Debug)]
pub struct ViewMilestonesArgs {
    /// Select from milestones with the chosen state
    #[arg(short, long, value_enum, default_value_t = ViewStateType::All)]
    pub state: ViewStateType,
}

impl ViewMilestonesArgs {
    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 (_, milestones_list) = spin_until_ready(
            ctx.client
                .issue_get_milestones_list(
                    owner.as_str(),
                    repo.as_str(),
                    IssueGetMilestonesListQuery::default(),
                )
                .send(),
        )
        .await
        .into_diagnostic()?;

        let selected_milestone = fuzzy_select_with_key(
            &milestones_list,
            select_prompt_for("milestone"),
            display_milestone,
        )?;

        match ctx.global_args.output_mode {
            OutputMode::Pretty => {
                present_milestone_overview(&ctx, selected_milestone).await?;
            }
            OutputMode::Json => selected_milestone.print_json()?,
        }

        Ok(())
    }
}

async fn present_milestone_overview(
    ctx: &BergContext<ViewMilestonesArgs>,
    milestone: &Milestone,
) -> miette::Result<()> {
    let OwnerRepo { repo, owner } = ctx.owner_repo()?;
    let (_, issues_list) = spin_until_ready(
        ctx.client
            .issue_list_issues(
                owner.as_str(),
                repo.as_str(),
                IssueListIssuesQuery {
                    milestones: milestone
                        .id
                        .as_ref()
                        .map(|id| id.to_string())
                        .or_else(|| milestone.title.clone()),
                    ..Default::default()
                },
            )
            .send(),
    )
    .await
    .into_diagnostic()?;

    let mut milestone_issues = issues_list
        .iter()
        .filter(|&issue| {
            issue.milestone.as_ref().is_some_and(|issue_milestone| {
                issue_milestone
                    .id
                    .is_some_and(|id| milestone.id == Some(id))
            })
        })
        .map(|issue| {
            format!(
                "#{}{}",
                option_display(&issue.number),
                // TODO: Adjust
                if issue.state == Some(StateType::Closed) {
                    ""
                } else {
                    ""
                }
            )
        })
        .collect::<Vec<_>>();

    milestone_issues.sort();

    let mut table = ctx
        .make_table()
        .set_header(vec![format!(
            "Milestone #{}",
            option_display(&milestone.id)
        )])
        .add_row(vec![String::from("Name"), option_display(&milestone.title)])
        .add_row(vec![
            String::from("Status"),
            option_debug_display(&milestone.state),
        ])
        .add_row(vec![
            String::from("Description"),
            option_display(&milestone.description),
        ]);

    if !milestone_issues.is_empty() {
        table = table.add_row(vec![
            String::from("Related Issues"),
            milestone_issues.join(", "),
        ]);
    }

    table = table
        .add_row(vec![
            String::from("Due On"),
            option_display(&milestone.due_on.as_ref().map(render_datetime_regular)),
        ])
        .add_row(vec![
            String::from("Progress"),
            format!(
                "Progress: {} / {} done",
                option_display(&milestone.closed_issues),
                milestone.open_issues.unwrap_or_default()
                    + milestone.closed_issues.unwrap_or_default()
            ),
        ]);

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

    Ok(())
}