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