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 strum::*;
9
10use crate::actions::text_manipulation::input_prompt_for;
11
12use clap::Parser;
13
14#[derive(Parser, Debug)]
16pub struct CreateLabelArgs {
17 #[arg(short, long)]
19 pub name: Option<String>,
20
21 #[arg(short, long)]
23 pub color: Option<String>,
24
25 #[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}