Skip to main content

codeberg_cli/actions/label/
create.rs

1use crate::render::color::mk_color_validator;
2use crate::render::json::JsonToStdout;
3use crate::{actions::GlobalArgs, types::git::OwnerRepo};
4
5use crate::render::ui::multi_fuzzy_select_with_key;
6use crate::types::context::BergContext;
7use forgejo_api::structs::CreateLabelOption;
8use miette::IntoDiagnostic;
9use strum::*;
10
11use crate::actions::text_manipulation::input_prompt_for;
12
13use clap::Parser;
14
15/// Create a label
16#[derive(Parser, Debug)]
17pub struct CreateLabelArgs {
18    /// Label name
19    #[arg(short, long)]
20    pub name: Option<String>,
21
22    /// Label color (in hex format "#abcdef")
23    #[arg(short, long)]
24    pub color: Option<String>,
25
26    /// Label purpose description
27    #[arg(short, long)]
28    pub description: Option<String>,
29}
30
31#[derive(Display, PartialEq, Eq, VariantArray)]
32enum CreatableFields {
33    Description,
34    Color,
35}
36
37impl CreateLabelArgs {
38    pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
39        let ctx = BergContext::new(self, global_args).await?;
40
41        let OwnerRepo { repo, owner } = ctx.owner_repo()?;
42        let options = create_options(&ctx).await?;
43        let label = ctx
44            .client
45            .issue_create_label(owner.as_str(), repo.as_str(), options)
46            .await
47            .into_diagnostic()?;
48        match ctx.global_args.output_mode {
49            crate::types::output::OutputMode::Pretty => {
50                tracing::debug!("{label:?}");
51            }
52            crate::types::output::OutputMode::Json => label.print_json()?,
53        }
54        Ok(())
55    }
56}
57
58async fn create_options(ctx: &BergContext<CreateLabelArgs>) -> miette::Result<CreateLabelOption> {
59    let name = match ctx.args.name.as_ref().cloned() {
60        Some(name) => name,
61        None => {
62            if ctx.global_args.non_interactive {
63                miette::bail!("You need to specify a name in non-interactive mode!");
64            }
65            inquire::Text::new(input_prompt_for("Label Name").as_str())
66                .prompt()
67                .into_diagnostic()?
68        }
69    };
70
71    let color = ctx
72        .args
73        .color
74        .as_ref()
75        .cloned()
76        .unwrap_or(String::from("#ffffff"));
77
78    let mut options = CreateLabelOption {
79        name,
80        color,
81        description: ctx.args.description.clone(),
82        exclusive: None,
83        is_archived: None,
84    };
85
86    if !ctx.global_args.non_interactive {
87        let optional_data = {
88            use CreatableFields::*;
89            [
90                (Description, ctx.args.description.is_none()),
91                (Color, ctx.args.color.is_none()),
92            ]
93            .into_iter()
94            .filter_map(|(name, missing)| missing.then_some(name))
95            .collect::<Vec<_>>()
96        };
97
98        let chosen_optionals = multi_fuzzy_select_with_key(
99            &optional_data,
100            "Choose optional properties",
101            |_| false,
102            |o| o.to_string(),
103        )?;
104
105        {
106            use CreatableFields::*;
107            options.description = label_description(ctx, chosen_optionals.contains(&&Description))?;
108            if let Some(color) = label_color(ctx, chosen_optionals.contains(&&Color))? {
109                options.color = color;
110            }
111        }
112    }
113
114    Ok(options)
115}
116
117fn label_description(
118    ctx: &BergContext<CreateLabelArgs>,
119    interactive: bool,
120) -> miette::Result<Option<String>> {
121    let description = match ctx.args.description.as_ref() {
122        Some(desc) => desc.clone(),
123        None => {
124            if !interactive {
125                return Ok(None);
126            }
127            ctx.editor_for("a description", "Enter an issue description")?
128        }
129    };
130    Ok(Some(description))
131}
132
133fn label_color(
134    ctx: &BergContext<CreateLabelArgs>,
135    interactive: bool,
136) -> miette::Result<Option<String>> {
137    let color = match ctx.args.color.as_ref() {
138        Some(color) => color.clone(),
139        None => {
140            if !interactive {
141                return Ok(None);
142            }
143            mk_color_validator(inquire::Text::new(
144                input_prompt_for("Enter a color").as_str(),
145            ))
146            .prompt()
147            .into_diagnostic()?
148        }
149    };
150    Ok(Some(color))
151}