codeberg_cli/actions/repo/
create.rs1use 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#[derive(Parser, Debug)]
12pub struct RepoCreateArgs {
13 #[arg(id = "default-branch", long)]
15 pub default_branch: Option<String>,
16
17 #[arg(short, long)]
19 pub description: Option<String>,
20
21 #[arg(long)]
23 pub org: Option<String>,
24
25 #[arg(short, long)]
27 pub name: Option<String>,
28
29 #[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}