codeberg_cli/actions/label/
create.rs

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