1use std::{env, path::PathBuf, str::FromStr};
6
7use anyhow::{self, Result, bail};
8use clap::{ArgAction, Parser};
9use thiserror::Error;
10
11#[derive(Parser)]
19#[command(author, version)]
20pub(crate) struct Args {
21 #[arg(short, long)]
25 pub feature: String,
26
27 pub root: Option<PathBuf>,
32
33 #[arg(short, long = "dir")]
43 pub directories: Vec<Directory>,
44
45 #[arg(short, long = "package")]
48 pub packages: Vec<String>,
49
50 #[arg(long="no-workspace-update", action=ArgAction::SetFalse)]
52 pub workspace_update: bool,
53
54 #[arg(long)]
56 pub dry_run: bool,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub(crate) struct Directory {
61 pub src: PathBuf,
62 pub dst: PathBuf,
63 pub suffix: Option<String>,
64}
65
66#[derive(Error, Debug)]
67pub(crate) enum DirectoryParseError {
68 #[error("Can't parse an existing source directory from '{0}'")]
69 NoSrc(String),
70
71 #[error("Can't parse a destination directory from '{0}'")]
72 NoDst(String),
73}
74
75impl FromStr for Directory {
76 type Err = anyhow::Error;
77
78 fn from_str(s: &str) -> Result<Self> {
79 let mut parts = s.split(':');
80
81 let Some(src_part) = parts.next() else {
82 bail!(DirectoryParseError::NoSrc(s.to_string()))
83 };
84
85 let Some(dst_part) = parts.next() else {
86 bail!(DirectoryParseError::NoDst(s.to_string()))
87 };
88
89 let suffix = parts.next().map(|sfx| sfx.to_string());
90
91 let cwd = env::current_dir()?;
92 let src = cwd.join(src_part);
93 let dst = cwd.join(dst_part);
94
95 if !src.is_dir() {
96 bail!(DirectoryParseError::NoSrc(src_part.to_string()));
97 }
98
99 Ok(Self { src, dst, suffix })
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use expect_test::expect;
106
107 use super::*;
108
109 #[test]
110 fn test_directory_parsing_everything() {
111 let dir = Directory::from_str("src:dst:suffix").unwrap();
113
114 let cwd = env::current_dir().unwrap();
115 let src = cwd.join("src");
116 let dst = cwd.join("dst");
117
118 assert_eq!(
119 dir,
120 Directory {
121 src,
122 dst,
123 suffix: Some("suffix".to_string()),
124 }
125 )
126 }
127
128 #[test]
129 fn test_directory_parsing_no_suffix() {
130 let dir = Directory::from_str("src:dst").unwrap();
132
133 let cwd = env::current_dir().unwrap();
134 let src = cwd.join("src");
135 let dst = cwd.join("dst");
136
137 assert_eq!(
138 dir,
139 Directory {
140 src,
141 dst,
142 suffix: None,
143 }
144 )
145 }
146
147 #[test]
148 fn test_directory_parsing_no_dst() {
149 let err = Directory::from_str("src").unwrap_err();
151 expect!["Can't parse a destination directory from 'src'"].assert_eq(&format!("{err}"));
152 }
153
154 #[test]
155 fn test_directory_parsing_src_non_existent() {
156 let err = Directory::from_str("i_dont_exist:dst").unwrap_err();
158 expect!["Can't parse an existing source directory from 'i_dont_exist'"]
159 .assert_eq(&format!("{err}"));
160 }
161
162 #[test]
163 fn test_directory_parsing_empty() {
164 let err = Directory::from_str("").unwrap_err();
166 expect!["Can't parse a destination directory from ''"].assert_eq(&format!("{err}"));
167 }
168}