codeberg_cli/actions/milestone/
create.rs1use 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#[derive(Parser, Debug)]
15pub struct CreateMilestoneArgs {
16 #[arg(short, long)]
18 pub title: Option<String>,
19
20 #[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}