codeberg_cli/actions/repo/
create.rs

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