codeberg_cli/actions/label/
create.rs1use 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#[derive(Parser, Debug)]
17pub struct CreateLabelArgs {
18 #[arg(short, long)]
20 pub name: Option<String>,
21
22 #[arg(short, long)]
24 pub color: Option<String>,
25
26 #[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}