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::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#[derive(Parser, Debug)]
16pub struct CreateMilestoneArgs {
17 #[arg(short, long)]
19 pub title: Option<String>,
20
21 #[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}