codeberg_cli/actions/label/
edit.rs1use crate::actions::GlobalArgs;
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, global_args: GlobalArgs) -> anyhow::Result<()> {
32 let ctx = BergContext::new(self, global_args).await?;
33
34 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
35 let label = select_label(&ctx).await?;
37 let label_id = label.id.context("Selected label doesn't have an ID")?;
38
39 let options = create_options(&ctx, &label).await?;
40
41 let updated_label = ctx
42 .client
43 .issue_edit_label(owner.as_str(), repo.as_str(), label_id, options)
44 .await?;
45
46 match ctx.global_args.output_mode {
47 OutputMode::Pretty => {
48 tracing::debug!("{updated_label:?}");
49 }
50 OutputMode::Json => updated_label.print_json()?,
51 }
52
53 Ok(())
54 }
55}
56
57async fn select_label(ctx: &BergContext<EditLabelArgs>) -> anyhow::Result<Label> {
58 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
59 let (_, labels_list) = spin_until_ready(
60 ctx.client
61 .issue_list_labels(
62 owner.as_str(),
63 repo.as_str(),
64 IssueListLabelsQuery::default(),
65 )
66 .send(),
67 )
68 .await?;
69
70 if labels_list.is_empty() {
71 anyhow::bail!("No labels found in this repository");
72 }
73
74 fuzzy_select_with_key(&labels_list, select_prompt_for("label"), display_label).cloned()
75}
76
77async fn create_options(
78 ctx: &BergContext<EditLabelArgs>,
79 label: &Label,
80) -> anyhow::Result<EditLabelOption> {
81 let selected_update_fields = multi_fuzzy_select_with_key(
82 EditableFields::VARIANTS,
83 select_prompt_for("options"),
84 |_| false,
85 |f| f.to_string(),
86 )?;
87
88 let mut options = EditLabelOption {
89 color: None,
90 description: None,
91 exclusive: None,
92 is_archived: None,
93 name: None,
94 };
95
96 if selected_update_fields.contains(&&EditableFields::Name) {
97 let current_name = label.name.as_ref().cloned();
98 options.name.replace(label_name(ctx, current_name).await?);
99 }
100 if selected_update_fields.contains(&&EditableFields::Description) {
101 let current_description = label.description.as_ref().cloned();
102 options
103 .description
104 .replace(label_description(ctx, current_description).await?);
105 }
106 if selected_update_fields.contains(&&EditableFields::Color) {
107 let current_color = label.color.as_ref().cloned();
108 options
109 .color
110 .replace(label_color(ctx, current_color).await?);
111 }
112
113 Ok(options)
114}
115
116async fn label_name(
117 ctx: &BergContext<EditLabelArgs>,
118 current_name: Option<String>,
119) -> anyhow::Result<String> {
120 ctx.editor_for(
121 "Choose a new label name",
122 current_name.as_deref().unwrap_or("Enter a label name"),
123 )
124}
125
126async fn label_description(
127 ctx: &BergContext<EditLabelArgs>,
128 current_description: Option<String>,
129) -> anyhow::Result<String> {
130 ctx.editor_for(
131 "a description",
132 current_description
133 .as_deref()
134 .unwrap_or("Enter a label description"),
135 )
136}
137
138async fn label_color(
139 _ctx: &BergContext<EditLabelArgs>,
140 current_color: Option<String>,
141) -> anyhow::Result<String> {
142 mk_color_validator(
143 inquire::Text::new(input_prompt_for("Enter a color").as_str())
144 .with_default(current_color.as_deref().unwrap_or("Enter a label color")),
145 )
146 .prompt()
147 .map_err(anyhow::Error::from)
148}