codeberg_cli/actions/milestone/
create.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use crate::render::ui::multi_fuzzy_select_with_key;
use crate::types::context::BergContext;
use crate::{actions::GeneralArgs, types::git::OwnerRepo};
use forgejo_api::structs::CreateMilestoneOption;
use strum::{Display, VariantArray};

use crate::actions::text_manipulation::{edit_prompt_for, input_prompt_for};

use clap::Parser;

/// Create an issue
#[derive(Parser, Debug)]
pub struct CreateMilestoneArgs {
    /// Title or summary
    #[arg(short, long)]
    pub title: Option<String>,

    /// Main description of milestone
    #[arg(short, long)]
    pub description: Option<String>,
}

#[derive(Display, PartialEq, Eq, VariantArray)]
enum CreatableFields {
    Title,
    Description,
}

impl CreateMilestoneArgs {
    pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
        let _ = general_args;
        let ctx = BergContext::new(self).await?;

        let OwnerRepo { owner, repo } = ctx.owner_repo()?;
        let options = create_options(&ctx).await?;
        let milestone = ctx
            .client
            .issue_create_milestone(owner.as_str(), repo.as_str(), options)
            .await?;
        tracing::debug!("{milestone:?}");
        Ok(())
    }
}

async fn create_options(
    ctx: &BergContext<CreateMilestoneArgs>,
) -> anyhow::Result<CreateMilestoneOption> {
    let mut options = CreateMilestoneOption {
        description: None,
        due_on: None,
        state: None,
        title: None,
    };

    let optional_data = {
        use CreatableFields::*;
        [
            (Title, ctx.args.title.is_none()),
            (Description, ctx.args.description.is_none()),
        ]
        .into_iter()
        .filter_map(|(name, missing)| missing.then_some(name))
        .collect::<Vec<_>>()
    };

    let chosen_optionals = multi_fuzzy_select_with_key(
        &optional_data,
        "Choose optional properties",
        |_| false,
        |o| o.to_string(),
    )?;

    {
        use CreatableFields::*;
        options.title = milestone_title(ctx, chosen_optionals.contains(&&Title))?;
        options.description = milestone_description(ctx, chosen_optionals.contains(&&Description))?;
    }

    Ok(options)
}

fn milestone_title(
    ctx: &BergContext<CreateMilestoneArgs>,
    interactive: bool,
) -> anyhow::Result<Option<String>> {
    let title = match ctx.args.title.as_ref() {
        Some(title) => title.clone(),
        None => {
            if !interactive {
                return Ok(None);
            }
            inquire::Text::new(input_prompt_for("Label Name").as_str()).prompt()?
        }
    };
    Ok(Some(title))
}

fn milestone_description(
    ctx: &BergContext<CreateMilestoneArgs>,
    interactive: bool,
) -> anyhow::Result<Option<String>> {
    let description = match ctx.args.description.as_ref() {
        Some(desc) => desc.clone(),
        None => {
            if !interactive {
                return Ok(None);
            }
            inquire::Editor::new(edit_prompt_for("a description").as_str())
                .with_predefined_text("Enter a milestone description")
                .prompt()?
        }
    };
    Ok(Some(description))
}