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