Skip to main content

codeberg_cli/actions/label/
edit.rs

1use 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 forgejo_api::structs::{EditLabelOption, IssueListLabelsQuery, Label};
12use miette::{Context, IntoDiagnostic};
13use strum::{Display, VariantArray};
14
15use crate::actions::text_manipulation::{input_prompt_for, select_prompt_for};
16
17use clap::Parser;
18
19/// Edit selected label
20#[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) -> miette::Result<()> {
32        let ctx = BergContext::new(self, global_args).await?;
33
34        let OwnerRepo { repo, owner } = ctx.owner_repo()?;
35        // TODO: cleanup AI code ... no non-interactive version
36        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            .into_diagnostic()?;
46
47        match ctx.global_args.output_mode {
48            OutputMode::Pretty => {
49                tracing::debug!("{updated_label:?}");
50            }
51            OutputMode::Json => updated_label.print_json()?,
52        }
53
54        Ok(())
55    }
56}
57
58async fn select_label(ctx: &BergContext<EditLabelArgs>) -> miette::Result<Label> {
59    let OwnerRepo { repo, owner } = ctx.owner_repo()?;
60    let (_, labels_list) = spin_until_ready(
61        ctx.client
62            .issue_list_labels(
63                owner.as_str(),
64                repo.as_str(),
65                IssueListLabelsQuery::default(),
66            )
67            .send(),
68    )
69    .await
70    .into_diagnostic()?;
71
72    if labels_list.is_empty() {
73        miette::bail!("No labels found in this repository");
74    }
75
76    fuzzy_select_with_key(&labels_list, select_prompt_for("label"), display_label).cloned()
77}
78
79async fn create_options(
80    ctx: &BergContext<EditLabelArgs>,
81    label: &Label,
82) -> miette::Result<EditLabelOption> {
83    let selected_update_fields = multi_fuzzy_select_with_key(
84        EditableFields::VARIANTS,
85        select_prompt_for("options"),
86        |_| false,
87        |f| f.to_string(),
88    )?;
89
90    let mut options = EditLabelOption {
91        color: None,
92        description: None,
93        exclusive: None,
94        is_archived: None,
95        name: None,
96    };
97
98    if selected_update_fields.contains(&&EditableFields::Name) {
99        let current_name = label.name.as_ref().cloned();
100        options.name.replace(label_name(ctx, current_name).await?);
101    }
102    if selected_update_fields.contains(&&EditableFields::Description) {
103        let current_description = label.description.as_ref().cloned();
104        options
105            .description
106            .replace(label_description(ctx, current_description).await?);
107    }
108    if selected_update_fields.contains(&&EditableFields::Color) {
109        let current_color = label.color.as_ref().cloned();
110        options
111            .color
112            .replace(label_color(ctx, current_color).await?);
113    }
114
115    Ok(options)
116}
117
118async fn label_name(
119    ctx: &BergContext<EditLabelArgs>,
120    current_name: Option<String>,
121) -> miette::Result<String> {
122    ctx.editor_for(
123        "Choose a new label name",
124        current_name.as_deref().unwrap_or("Enter a label name"),
125    )
126}
127
128async fn label_description(
129    ctx: &BergContext<EditLabelArgs>,
130    current_description: Option<String>,
131) -> miette::Result<String> {
132    ctx.editor_for(
133        "a description",
134        current_description
135            .as_deref()
136            .unwrap_or("Enter a label description"),
137    )
138}
139
140async fn label_color(
141    _ctx: &BergContext<EditLabelArgs>,
142    current_color: Option<String>,
143) -> miette::Result<String> {
144    mk_color_validator(
145        inquire::Text::new(input_prompt_for("Enter a color").as_str())
146            .with_default(current_color.as_deref().unwrap_or("Enter a label color")),
147    )
148    .prompt()
149    .into_diagnostic()
150}