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