codeberg-cli 0.5.5

CLI Tool for codeberg similar to gh and glab
Documentation
use crate::render::color::mk_color_validator;
use crate::render::json::JsonToStdout;
use crate::{actions::GlobalArgs, types::git::OwnerRepo};

use crate::render::ui::multi_fuzzy_select_with_key;
use crate::types::context::BergContext;
use forgejo_api::structs::CreateLabelOption;
use miette::IntoDiagnostic;
use strum::*;

use crate::actions::text_manipulation::input_prompt_for;

use clap::Parser;

/// Create a label
#[derive(Parser, Debug)]
pub struct CreateLabelArgs {
    /// Label name
    #[arg(short, long)]
    pub name: Option<String>,

    /// Label color (in hex format "#abcdef")
    #[arg(short, long)]
    pub color: Option<String>,

    /// Label purpose description
    #[arg(short, long)]
    pub description: Option<String>,
}

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

impl CreateLabelArgs {
    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 options = create_options(&ctx).await?;
        let label = ctx
            .client
            .issue_create_label(owner.as_str(), repo.as_str(), options)
            .await
            .into_diagnostic()?;
        match ctx.global_args.output_mode {
            crate::types::output::OutputMode::Pretty => {
                tracing::debug!("{label:?}");
            }
            crate::types::output::OutputMode::Json => label.print_json()?,
        }
        Ok(())
    }
}

async fn create_options(ctx: &BergContext<CreateLabelArgs>) -> miette::Result<CreateLabelOption> {
    let name = match ctx.args.name.as_ref().cloned() {
        Some(name) => name,
        None => {
            if ctx.global_args.non_interactive {
                miette::bail!("You need to specify a name in non-interactive mode!");
            }
            inquire::Text::new(input_prompt_for("Label Name").as_str())
                .prompt()
                .into_diagnostic()?
        }
    };

    let color = ctx
        .args
        .color
        .as_ref()
        .cloned()
        .unwrap_or(String::from("#ffffff"));

    let mut options = CreateLabelOption {
        name,
        color,
        description: ctx.args.description.clone(),
        exclusive: None,
        is_archived: None,
    };

    if !ctx.global_args.non_interactive {
        let optional_data = {
            use CreatableFields::*;
            [
                (Description, ctx.args.description.is_none()),
                (Color, ctx.args.color.is_none()),
            ]
            .into_iter()
            .filter_map(|(name, missing)| missing.then_some(name))
            .collect::<Vec<_>>()
        };

        let chosen_optionals = multi_fuzzy_select_with_key(
            &optional_data,
            "Choose optional properties",
            |_| false,
            |o| o.to_string(),
        )?;

        {
            use CreatableFields::*;
            options.description = label_description(ctx, chosen_optionals.contains(&&Description))?;
            if let Some(color) = label_color(ctx, chosen_optionals.contains(&&Color))? {
                options.color = color;
            }
        }
    }

    Ok(options)
}

fn label_description(
    ctx: &BergContext<CreateLabelArgs>,
    interactive: bool,
) -> miette::Result<Option<String>> {
    let description = match ctx.args.description.as_ref() {
        Some(desc) => desc.clone(),
        None => {
            if !interactive {
                return Ok(None);
            }
            ctx.editor_for("a description", "Enter an issue description")?
        }
    };
    Ok(Some(description))
}

fn label_color(
    ctx: &BergContext<CreateLabelArgs>,
    interactive: bool,
) -> miette::Result<Option<String>> {
    let color = match ctx.args.color.as_ref() {
        Some(color) => color.clone(),
        None => {
            if !interactive {
                return Ok(None);
            }
            mk_color_validator(inquire::Text::new(
                input_prompt_for("Enter a color").as_str(),
            ))
            .prompt()
            .into_diagnostic()?
        }
    };
    Ok(Some(color))
}