Skip to main content

radicle_cli/commands/clone/
args.rs

1use std::path::PathBuf;
2use std::time;
3
4use clap::Parser;
5
6use crate::node::SyncSettings;
7use radicle::identity::doc::RepoId;
8use radicle::identity::IdError;
9use radicle::node::policy::Scope;
10use radicle::prelude::*;
11
12use crate::terminal;
13
14const ABOUT: &str = "Clone a Radicle repository";
15
16const LONG_ABOUT: &str = r#"
17The `clone` command will use your local node's routing table to find seeds from
18which it can clone the repository.
19
20For private repositories, use the `--seed` options, to clone directly
21from known seeds in the privacy set."#;
22
23/// Parse an RID, optionally stripping "rad://" prefix.
24fn parse_rid(value: &str) -> Result<RepoId, IdError> {
25    value.strip_prefix("rad://").unwrap_or(value).parse()
26}
27
28#[derive(Debug, Parser)]
29pub(super) struct SyncArgs {
30    /// Clone from this seed (may be specified multiple times)
31    #[arg(short, long = "seed", value_name = "NID", action = clap::ArgAction::Append)]
32    seeds: Vec<NodeId>,
33
34    /// Timeout for fetching repository in seconds
35    #[arg(long, default_value_t = 9, value_name = "SECS")]
36    timeout: usize,
37}
38
39impl From<SyncArgs> for SyncSettings {
40    fn from(args: SyncArgs) -> Self {
41        SyncSettings {
42            timeout: time::Duration::from_secs(args.timeout as u64),
43            seeds: args.seeds.into_iter().collect(),
44            ..SyncSettings::default()
45        }
46    }
47}
48
49#[derive(Debug, Parser)]
50#[clap(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
51pub struct Args {
52    /// ID of the repository to clone
53    ///
54    /// [example values: rad:z3Tr6bC7ctEg2EHmLvknUr29mEDLH, rad://z3Tr6bC7ctEg2EHmLvknUr29mEDLH]
55    #[arg(value_name = "RID", value_parser = parse_rid)]
56    pub(super) repo: RepoId,
57
58    /// The target directory for the repository to be cloned into
59    #[arg(value_name = "PATH")]
60    pub(super) directory: Option<PathBuf>,
61
62    /// Follow scope
63    #[arg(
64        long,
65        default_value_t = Scope::Followed,
66        value_parser = terminal::args::ScopeParser
67    )]
68    pub(super) scope: Scope,
69
70    #[clap(flatten)]
71    pub(super) sync: SyncArgs,
72
73    /// Make a bare repository
74    #[arg(long)]
75    pub(super) bare: bool,
76
77    // We keep this flag here for consistency though it doesn't have any effect,
78    // since the command is fully non-interactive.
79    #[arg(long, hide = true)]
80    pub(super) no_confirm: bool,
81}
82
83#[cfg(test)]
84mod test {
85    use super::Args;
86    use clap::Parser;
87
88    #[test]
89    fn should_parse_rid_non_urn() {
90        let args = Args::try_parse_from(["clone", "z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]);
91        assert!(args.is_ok())
92    }
93
94    #[test]
95    fn should_parse_rid_urn() {
96        let args = Args::try_parse_from(["clone", "rad:z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]);
97        assert!(args.is_ok())
98    }
99
100    #[test]
101    fn should_parse_rid_url() {
102        let args = Args::try_parse_from(["clone", "rad://z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]);
103        assert!(args.is_ok())
104    }
105}