Skip to main content

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