codeberg_cli/actions/label/
edit.rs1use crate::actions::GeneralArgs;
2use crate::actions::label::display_label;
3
4use crate::render::color::mk_color_validator;
5use crate::render::json::JsonToStdout;
6use crate::render::spinner::spin_until_ready;
7use crate::render::ui::{fuzzy_select_with_key, multi_fuzzy_select_with_key};
8use crate::types::context::BergContext;
9use crate::types::git::OwnerRepo;
10use crate::types::output::OutputMode;
11use anyhow::Context;
12use forgejo_api::structs::{EditLabelOption, IssueListLabelsQuery, Label};
13use strum::{Display, VariantArray};
14
15use crate::actions::text_manipulation::{input_prompt_for, select_prompt_for};
16
17use clap::Parser;
18
19#[derive(Parser, Debug)]
21pub struct EditLabelArgs {}
22
23#[derive(Display, PartialEq, Eq, VariantArray)]
24enum EditableFields {
25 Name,
26 Description,
27 Color,
28}
29
30impl EditLabelArgs {
31 pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
32 let ctx = BergContext::new(self, general_args).await?;
33
34 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
35 let label = select_label(&ctx).await?;
36 let label_id = label.id.context("Selected label doesn't have an ID")?;
37
38 let options = create_options(&ctx, &label).await?;
39
40 let updated_label = ctx
41 .client
42 .issue_edit_label(owner.as_str(), repo.as_str(), label_id as u64, options)
43 .await?;
44
45 match general_args.output_mode {
46 OutputMode::Pretty => {
47 tracing::debug!("{updated_label:?}");
48 }
49 OutputMode::Json => updated_label.print_json()?,
50 }
51
52 Ok(())
53 }
54}
55
56async fn select_label(ctx: &BergContext<EditLabelArgs>) -> anyhow::Result<Label> {
57 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
58 let (_, labels_list) = spin_until_ready(ctx.client.issue_list_labels(
59 owner.as_str(),
60 repo.as_str(),
61 IssueListLabelsQuery::default(),
62 ))
63 .await?;
64
65 if labels_list.is_empty() {
66 anyhow::bail!("No labels found in this repository");
67 }
68
69 fuzzy_select_with_key(&labels_list, select_prompt_for("label"), display_label).cloned()
70}
71
72async fn create_options(
73 ctx: &BergContext<EditLabelArgs>,
74 label: &Label,
75) -> anyhow::Result<EditLabelOption> {
76 let selected_update_fields = multi_fuzzy_select_with_key(
77 EditableFields::VARIANTS,
78 select_prompt_for("options"),
79 |_| false,
80 |f| f.to_string(),
81 )?;
82
83 let mut options = EditLabelOption {
84 color: None,
85 description: None,
86 exclusive: None,
87 is_archived: None,
88 name: None,
89 };
90
91 if selected_update_fields.contains(&&EditableFields::Name) {
92 let current_name = label.name.as_ref().cloned();
93 options.name.replace(label_name(ctx, current_name).await?);
94 }
95 if selected_update_fields.contains(&&EditableFields::Description) {
96 let current_description = label.description.as_ref().cloned();
97 options
98 .description
99 .replace(label_description(ctx, current_description).await?);
100 }
101 if selected_update_fields.contains(&&EditableFields::Color) {
102 let current_color = label.color.as_ref().cloned();
103 options
104 .color
105 .replace(label_color(ctx, current_color).await?);
106 }
107
108 Ok(options)
109}
110
111async fn label_name(
112 ctx: &BergContext<EditLabelArgs>,
113 current_name: Option<String>,
114) -> anyhow::Result<String> {
115 ctx.editor_for(
116 "Choose a new label name",
117 current_name.as_deref().unwrap_or("Enter a label name"),
118 )
119}
120
121async fn label_description(
122 ctx: &BergContext<EditLabelArgs>,
123 current_description: Option<String>,
124) -> anyhow::Result<String> {
125 ctx.editor_for(
126 "a description",
127 current_description
128 .as_deref()
129 .unwrap_or("Enter a label description"),
130 )
131}
132
133async fn label_color(
134 _ctx: &BergContext<EditLabelArgs>,
135 current_color: Option<String>,
136) -> anyhow::Result<String> {
137 mk_color_validator(
138 inquire::Text::new(input_prompt_for("Enter a color").as_str())
139 .with_default(current_color.as_deref().unwrap_or("Enter a label color")),
140 )
141 .prompt()
142 .map_err(anyhow::Error::from)
143}