Skip to main content

radicle_cli/commands/init/
args.rs

1use 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    /// Directory to be initialized
17    pub(super) path: Option<PathBuf>,
18    /// Name of the repository
19    #[arg(long)]
20    pub(super) name: Option<ProjectName>,
21    /// Description of the repository
22    #[arg(long)]
23    pub(super) description: Option<String>,
24    /// The default branch of the repository
25    #[arg(long = "default-branch")]
26    pub(super) branch: Option<String>,
27    /// Repository follow scope
28    #[arg(
29        long,
30        default_value_t = Scope::All,
31        value_name = "SCOPE",
32        value_parser = ScopeParser,
33    )]
34    pub(super) scope: Scope,
35    /// Set repository visibility to *private*
36    #[arg(long, conflicts_with = "public")]
37    private: bool,
38    /// Set repository visibility to *public*
39    #[arg(long, conflicts_with = "private")]
40    public: bool,
41    /// Setup repository as an existing Radicle repository
42    ///
43    /// [example values: rad:z3Tr6bC7ctEg2EHmLvknUr29mEDLH, z3Tr6bC7ctEg2EHmLvknUr29mEDLH]
44    #[arg(long, value_name = "RID")]
45    pub(super) existing: Option<RepoId>,
46    /// Setup the upstream of the default branch
47    #[arg(short = 'u', long)]
48    pub(super) set_upstream: bool,
49    /// Setup the radicle key as a signing key for this repository
50    #[arg(long)]
51    pub(super) setup_signing: bool,
52    /// Don't ask for confirmation during setup
53    #[arg(long)]
54    no_confirm: bool,
55    /// Don't seed this repository after initializing it
56    #[arg(long)]
57    no_seed: bool,
58    /// Verbose mode
59    #[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// TODO(finto): this is duplicated from `clone::args`. Consolidate these once
89// the `clap` migration has finished and we can organise the shared code.
90#[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}