codeberg_cli/actions/repo/
create.rs

1use crate::actions::text_manipulation::{input_prompt_for, select_prompt_for};
2use crate::actions::GeneralArgs;
3use crate::render::ui::{fuzzy_select_with_key, multi_fuzzy_select_with_key};
4use crate::types::api::privacy_type::Privacy;
5use crate::types::context::BergContext;
6use clap::Parser;
7use forgejo_api::structs::CreateRepoOption;
8use strum::{Display, VariantArray};
9
10/// Create a new repository
11#[derive(Parser, Debug)]
12pub struct RepoCreateArgs {
13    /// Main branch to init repository with (usually "main")
14    #[arg(id = "default-branch", long)]
15    pub default_branch: Option<String>,
16
17    /// Repository description
18    #[arg(short, long)]
19    pub description: Option<String>,
20
21    /// Organization name
22    #[arg(long)]
23    pub org: Option<String>,
24
25    /// Repository name
26    #[arg(short, long)]
27    pub name: Option<String>,
28
29    /// Repository visibility
30    #[arg(short, long, value_enum, value_name = "VISIBILITY")]
31    pub private: Option<Privacy>,
32}
33
34#[derive(Display, PartialEq, Eq, VariantArray)]
35enum CreatableFields {
36    DefaultBranch,
37    Description,
38    Private,
39}
40
41impl RepoCreateArgs {
42    pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
43        let _ = general_args;
44        let ctx = BergContext::new(self, general_args).await?;
45
46        let options = create_options(&ctx).await?;
47        let pull_request = if let Some(org) = ctx.args.org.as_ref() {
48            ctx.client.create_org_repo(org, options).await
49        } else {
50            ctx.client.create_current_user_repo(options).await
51        }?;
52        tracing::debug!("{pull_request:?}");
53        Ok(())
54    }
55}
56
57async fn create_options(ctx: &BergContext<RepoCreateArgs>) -> anyhow::Result<CreateRepoOption> {
58    let name = match ctx.args.name.as_ref() {
59        Some(name) => name.clone(),
60        None => inquire::Text::new(input_prompt_for("New Repository Name").as_str()).prompt()?,
61    };
62
63    let mut options = CreateRepoOption {
64        name,
65        auto_init: None,
66        default_branch: None,
67        description: None,
68        gitignores: None,
69        issue_labels: None,
70        license: None,
71        private: None,
72        readme: None,
73        template: None,
74        trust_model: None,
75        object_format_name: None,
76    };
77
78    let optional_data = {
79        use CreatableFields::*;
80        [
81            (DefaultBranch, ctx.args.default_branch.is_none()),
82            (Description, ctx.args.description.is_none()),
83            (Private, ctx.args.private.is_none()),
84        ]
85        .into_iter()
86        .filter_map(|(name, missing)| missing.then_some(name))
87        .collect::<Vec<_>>()
88    };
89
90    if !optional_data.is_empty() {
91        let chosen_optionals = multi_fuzzy_select_with_key(
92            &optional_data,
93            "Choose optional properties",
94            |_| false,
95            |o| o.to_string(),
96        )?;
97
98        {
99            use CreatableFields::*;
100            options.default_branch.replace(repo_default_branch(
101                ctx,
102                chosen_optionals.contains(&&DefaultBranch),
103            )?);
104            options.private = repo_private(ctx, chosen_optionals.contains(&&Private)).await?;
105            options.description =
106                repo_description(ctx, chosen_optionals.contains(&&Description)).await?;
107        }
108    }
109
110    Ok(options)
111}
112
113fn repo_default_branch(
114    ctx: &BergContext<RepoCreateArgs>,
115    interactive: bool,
116) -> anyhow::Result<String> {
117    let branch = match ctx.args.default_branch.as_ref() {
118        Some(branch) => branch.clone(),
119        None => {
120            if !interactive {
121                return Ok(String::from("main"));
122            }
123            inquire::Text::new(input_prompt_for("Default Branch Name").as_str()).prompt()?
124        }
125    };
126    Ok(branch)
127}
128
129async fn repo_private(
130    ctx: &BergContext<RepoCreateArgs>,
131    interactive: bool,
132) -> anyhow::Result<Option<bool>> {
133    let privacy = match ctx.args.private {
134        Some(privacy) => match privacy {
135            Privacy::Private => true,
136            Privacy::Public => false,
137        },
138        None => {
139            if !interactive {
140                return Ok(None);
141            }
142            fuzzy_select_with_key(
143                &[true, false],
144                select_prompt_for("repo privacy"),
145                |private| {
146                    if *private {
147                        String::from("Private")
148                    } else {
149                        String::from("Public")
150                    }
151                },
152            )
153            .copied()?
154        }
155    };
156    Ok(Some(privacy))
157}
158
159async fn repo_description(
160    ctx: &BergContext<RepoCreateArgs>,
161    interactive: bool,
162) -> anyhow::Result<Option<String>> {
163    let description = match ctx.args.description.as_ref() {
164        Some(desc) => desc.clone(),
165        None => {
166            if !interactive {
167                return Ok(None);
168            }
169            ctx.editor_for("a description", "Enter Repository description")?
170        }
171    };
172    Ok(Some(description))
173}