codeberg_cli/actions/milestone/
create.rs

1use crate::render::json::JsonToStdout;
2use crate::render::ui::multi_fuzzy_select_with_key;
3use crate::types::context::BergContext;
4use crate::types::output::OutputMode;
5use crate::{actions::GeneralArgs, types::git::OwnerRepo};
6use forgejo_api::structs::CreateMilestoneOption;
7use strum::{Display, VariantArray};
8
9use crate::actions::text_manipulation::input_prompt_for;
10
11use clap::Parser;
12
13/// Create an issue
14#[derive(Parser, Debug)]
15pub struct CreateMilestoneArgs {
16    /// Title or summary
17    #[arg(short, long)]
18    pub title: Option<String>,
19
20    /// Main description of milestone
21    #[arg(short, long)]
22    pub description: Option<String>,
23}
24
25#[derive(Display, PartialEq, Eq, VariantArray)]
26enum CreatableFields {
27    Title,
28    Description,
29}
30
31impl CreateMilestoneArgs {
32    pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
33        let ctx = BergContext::new(self, general_args).await?;
34
35        let OwnerRepo { owner, repo } = ctx.owner_repo()?;
36        let options = create_options(&ctx).await?;
37        let milestone = ctx
38            .client
39            .issue_create_milestone(owner.as_str(), repo.as_str(), options)
40            .await?;
41        match general_args.output_mode {
42            OutputMode::Pretty => {
43                tracing::debug!("{milestone:?}");
44            }
45            OutputMode::Json => milestone.print_json()?,
46        }
47        Ok(())
48    }
49}
50
51async fn create_options(
52    ctx: &BergContext<CreateMilestoneArgs>,
53) -> anyhow::Result<CreateMilestoneOption> {
54    let mut options = CreateMilestoneOption {
55        description: None,
56        due_on: None,
57        state: None,
58        title: None,
59    };
60
61    let optional_data = {
62        use CreatableFields::*;
63        [
64            (Title, ctx.args.title.is_none()),
65            (Description, ctx.args.description.is_none()),
66        ]
67        .into_iter()
68        .filter_map(|(name, missing)| missing.then_some(name))
69        .collect::<Vec<_>>()
70    };
71
72    let chosen_optionals = multi_fuzzy_select_with_key(
73        &optional_data,
74        "Choose optional properties",
75        |_| false,
76        |o| o.to_string(),
77    )?;
78
79    {
80        use CreatableFields::*;
81        options.title = milestone_title(ctx, chosen_optionals.contains(&&Title))?;
82        options.description = milestone_description(ctx, chosen_optionals.contains(&&Description))?;
83    }
84
85    Ok(options)
86}
87
88fn milestone_title(
89    ctx: &BergContext<CreateMilestoneArgs>,
90    interactive: bool,
91) -> anyhow::Result<Option<String>> {
92    let title = match ctx.args.title.as_ref() {
93        Some(title) => title.clone(),
94        None => {
95            if !interactive {
96                return Ok(None);
97            }
98            inquire::Text::new(input_prompt_for("Label Name").as_str()).prompt()?
99        }
100    };
101    Ok(Some(title))
102}
103
104fn milestone_description(
105    ctx: &BergContext<CreateMilestoneArgs>,
106    interactive: bool,
107) -> anyhow::Result<Option<String>> {
108    let description = match ctx.args.description.as_ref() {
109        Some(desc) => desc.clone(),
110        None => {
111            if !interactive {
112                return Ok(None);
113            }
114            ctx.editor_for("a description", "Enter a milestone description")?
115        }
116    };
117    Ok(Some(description))
118}