codeberg-cli 0.5.5

CLI Tool for codeberg similar to gh and glab
Documentation
use crate::actions::GlobalArgs;
use crate::actions::label::display_label;

use crate::render::color::mk_color_validator;
use crate::render::json::JsonToStdout;
use crate::render::spinner::spin_until_ready;
use crate::render::ui::{fuzzy_select_with_key, multi_fuzzy_select_with_key};
use crate::types::context::BergContext;
use crate::types::git::OwnerRepo;
use crate::types::output::OutputMode;
use forgejo_api::structs::{EditLabelOption, IssueListLabelsQuery, Label};
use miette::{Context, IntoDiagnostic};
use strum::{Display, VariantArray};

use crate::actions::text_manipulation::{input_prompt_for, select_prompt_for};

use clap::Parser;

/// Edit selected label
#[derive(Parser, Debug)]
pub struct EditLabelArgs {}

#[derive(Display, PartialEq, Eq, VariantArray)]
enum EditableFields {
    Name,
    Description,
    Color,
}

impl EditLabelArgs {
    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()?;
        // TODO: cleanup AI code ... no non-interactive version
        let label = select_label(&ctx).await?;
        let label_id = label.id.context("Selected label doesn't have an ID")?;

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

        let updated_label = ctx
            .client
            .issue_edit_label(owner.as_str(), repo.as_str(), label_id, options)
            .await
            .into_diagnostic()?;

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

        Ok(())
    }
}

async fn select_label(ctx: &BergContext<EditLabelArgs>) -> miette::Result<Label> {
    let OwnerRepo { repo, owner } = ctx.owner_repo()?;
    let (_, labels_list) = spin_until_ready(
        ctx.client
            .issue_list_labels(
                owner.as_str(),
                repo.as_str(),
                IssueListLabelsQuery::default(),
            )
            .send(),
    )
    .await
    .into_diagnostic()?;

    if labels_list.is_empty() {
        miette::bail!("No labels found in this repository");
    }

    fuzzy_select_with_key(&labels_list, select_prompt_for("label"), display_label).cloned()
}

async fn create_options(
    ctx: &BergContext<EditLabelArgs>,
    label: &Label,
) -> miette::Result<EditLabelOption> {
    let selected_update_fields = multi_fuzzy_select_with_key(
        EditableFields::VARIANTS,
        select_prompt_for("options"),
        |_| false,
        |f| f.to_string(),
    )?;

    let mut options = EditLabelOption {
        color: None,
        description: None,
        exclusive: None,
        is_archived: None,
        name: None,
    };

    if selected_update_fields.contains(&&EditableFields::Name) {
        let current_name = label.name.as_ref().cloned();
        options.name.replace(label_name(ctx, current_name).await?);
    }
    if selected_update_fields.contains(&&EditableFields::Description) {
        let current_description = label.description.as_ref().cloned();
        options
            .description
            .replace(label_description(ctx, current_description).await?);
    }
    if selected_update_fields.contains(&&EditableFields::Color) {
        let current_color = label.color.as_ref().cloned();
        options
            .color
            .replace(label_color(ctx, current_color).await?);
    }

    Ok(options)
}

async fn label_name(
    ctx: &BergContext<EditLabelArgs>,
    current_name: Option<String>,
) -> miette::Result<String> {
    ctx.editor_for(
        "Choose a new label name",
        current_name.as_deref().unwrap_or("Enter a label name"),
    )
}

async fn label_description(
    ctx: &BergContext<EditLabelArgs>,
    current_description: Option<String>,
) -> miette::Result<String> {
    ctx.editor_for(
        "a description",
        current_description
            .as_deref()
            .unwrap_or("Enter a label description"),
    )
}

async fn label_color(
    _ctx: &BergContext<EditLabelArgs>,
    current_color: Option<String>,
) -> miette::Result<String> {
    mk_color_validator(
        inquire::Text::new(input_prompt_for("Enter a color").as_str())
            .with_default(current_color.as_deref().unwrap_or("Enter a label color")),
    )
    .prompt()
    .into_diagnostic()
}