radicle_cli/commands/init/
args.rs1use std::path::PathBuf;
2
3use clap::Parser;
4use radicle::{
5 identity::{project::ProjectName, Visibility},
6 node::policy::Scope,
7 prelude::RepoId,
8};
9use radicle_term::Interactive;
10
11const ABOUT: &str = "Initialize a Radicle repository";
12
13#[derive(Debug, Parser)]
14#[command(about = ABOUT, disable_version_flag = true)]
15pub struct Args {
16 pub(super) path: Option<PathBuf>,
18 #[arg(long)]
20 pub(super) name: Option<ProjectName>,
21 #[arg(long)]
23 pub(super) description: Option<String>,
24 #[arg(long = "default-branch")]
26 pub(super) branch: Option<String>,
27 #[arg(
29 long,
30 default_value_t = Scope::All,
31 value_name = "SCOPE",
32 value_parser = ScopeParser,
33 )]
34 pub(super) scope: Scope,
35 #[arg(long, conflicts_with = "public")]
37 private: bool,
38 #[arg(long, conflicts_with = "private")]
40 public: bool,
41 #[arg(long, value_name = "RID")]
45 pub(super) existing: Option<RepoId>,
46 #[arg(short = 'u', long)]
48 pub(super) set_upstream: bool,
49 #[arg(long)]
51 pub(super) setup_signing: bool,
52 #[arg(long)]
54 no_confirm: bool,
55 #[arg(long)]
57 no_seed: bool,
58 #[arg(short, long)]
60 pub(super) verbose: bool,
61}
62
63impl Args {
64 pub(super) fn interactive(&self) -> Interactive {
65 if self.no_confirm {
66 Interactive::No
67 } else {
68 Interactive::Yes
69 }
70 }
71
72 pub(super) fn visibility(&self) -> Option<Visibility> {
73 if self.private {
74 debug_assert!(!self.public, "BUG: `private` and `public` should conflict");
75 Some(Visibility::private([]))
76 } else if self.public {
77 Some(Visibility::Public)
78 } else {
79 None
80 }
81 }
82
83 pub(super) fn seed(&self) -> bool {
84 !self.no_seed
85 }
86}
87
88#[derive(Clone, Debug)]
91struct ScopeParser;
92
93impl clap::builder::TypedValueParser for ScopeParser {
94 type Value = Scope;
95
96 fn parse_ref(
97 &self,
98 cmd: &clap::Command,
99 arg: Option<&clap::Arg>,
100 value: &std::ffi::OsStr,
101 ) -> Result<Self::Value, clap::Error> {
102 <Scope as std::str::FromStr>::from_str.parse_ref(cmd, arg, value)
103 }
104
105 fn possible_values(
106 &self,
107 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
108 use clap::builder::PossibleValue;
109 Some(Box::new(
110 [PossibleValue::new("all"), PossibleValue::new("followed")].into_iter(),
111 ))
112 }
113}
114
115#[cfg(test)]
116mod test {
117 use super::Args;
118 use clap::error::ErrorKind;
119 use clap::Parser;
120
121 #[test]
122 fn should_parse_rid_non_urn() {
123 let args = Args::try_parse_from(["init", "--existing", "z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]);
124 assert!(args.is_ok())
125 }
126
127 #[test]
128 fn should_parse_rid_urn() {
129 let args =
130 Args::try_parse_from(["init", "--existing", "rad:z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]);
131 assert!(args.is_ok())
132 }
133
134 #[test]
135 fn should_not_parse_rid_url() {
136 let err =
137 Args::try_parse_from(["init", "--existing", "rad://z3Tr6bC7ctEg2EHmLvknUr29mEDLH"])
138 .unwrap_err();
139 assert_eq!(err.kind(), ErrorKind::ValueValidation);
140 }
141}