1use regex::Regex;
3use std::str::FromStr;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ParsedVcs {
8 pub repo_url: String,
10
11 pub branch: Option<String>,
13
14 pub subpath: Option<String>,
16}
17
18impl FromStr for ParsedVcs {
19 type Err = &'static str;
20
21 fn from_str(s: &str) -> Result<Self, Self::Err> {
22 let s = s.trim();
23 let mut subpath: Option<String> = None;
24 let branch: Option<String>;
25 let repo_url: String;
26 let re = Regex::new(r" \[([^] ]+)\]").unwrap();
27
28 let remaining = if let Some(m) = re.find(s) {
29 let substr = &m.as_str()[2..m.as_str().len() - 1];
30 subpath = Some(substr.to_string());
31 format!("{}{}", &s[..m.start()], &s[m.end()..])
32 } else {
33 s.to_string()
34 };
35
36 if let Some(index) = remaining.find(" -b ") {
37 let (url, branch_str) = remaining.split_at(index);
38 branch = Some(branch_str[4..].to_string());
39 repo_url = url.to_string();
40 } else {
41 branch = None;
42 repo_url = remaining;
43 }
44
45 Ok(ParsedVcs {
46 repo_url,
47 branch,
48 subpath,
49 })
50 }
51}
52
53impl std::fmt::Display for ParsedVcs {
54 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
55 f.write_str(&self.repo_url)?;
56
57 if let Some(branch) = &self.branch {
58 write!(f, " -b {}", branch)?;
59 }
60
61 if let Some(subpath) = &self.subpath {
62 write!(f, " [{}]", subpath)?;
63 }
64
65 Ok(())
66 }
67}
68
69#[derive(Debug, Clone)]
71pub enum Vcs {
72 Git {
74 repo_url: String,
76
77 branch: Option<String>,
79
80 subpath: Option<String>,
82 },
83 Bzr {
85 repo_url: String,
87
88 subpath: Option<String>,
90 },
91
92 Hg {
94 repo_url: String,
96 },
97 Svn {
99 url: String,
101 },
102 Cvs {
104 root: String,
106
107 module: Option<String>,
109 },
110}
111
112impl Vcs {
113 pub fn from_field(name: &str, value: &str) -> Result<Vcs, String> {
119 match name {
120 "Git" => {
121 let parsed_vcs: ParsedVcs =
122 value.parse::<ParsedVcs>().map_err(|e| e.to_string())?;
123 Ok(Vcs::Git {
124 repo_url: parsed_vcs.repo_url,
125 branch: parsed_vcs.branch,
126 subpath: parsed_vcs.subpath,
127 })
128 }
129 "Bzr" => {
130 let parsed_vcs: ParsedVcs =
131 value.parse::<ParsedVcs>().map_err(|e| e.to_string())?;
132 if parsed_vcs.branch.is_some() {
133 return Err("Invalid branch value for Vcs-Bzr".to_string());
134 }
135 Ok(Vcs::Bzr {
136 repo_url: parsed_vcs.repo_url,
137 subpath: parsed_vcs.subpath,
138 })
139 }
140 "Hg" => Ok(Vcs::Hg {
141 repo_url: value.to_string(),
142 }),
143 "Svn" => Ok(Vcs::Svn {
144 url: value.to_string(),
145 }),
146 "Cvs" => {
147 if let Some((root, module)) = value.split_once(' ') {
148 Ok(Vcs::Cvs {
149 root: root.to_string(),
150 module: Some(module.to_string()),
151 })
152 } else {
153 Ok(Vcs::Cvs {
154 root: value.to_string(),
155 module: None,
156 })
157 }
158 }
159 n => Err(format!("Unknown VCS: {}", n)),
160 }
161 }
162
163 pub fn to_field(&self) -> (&str, String) {
167 match self {
168 Vcs::Git {
169 repo_url,
170 branch,
171 subpath,
172 } => (
173 "Git",
174 ParsedVcs {
175 repo_url: repo_url.clone(),
176 branch: branch.clone(),
177 subpath: subpath.clone(),
178 }
179 .to_string(),
180 ),
181 Vcs::Bzr { repo_url, subpath } => (
182 "Bzr",
183 match subpath {
184 Some(subpath) => format!("{} [{}]", repo_url, subpath),
185 None => repo_url.clone(),
186 },
187 ),
188 Vcs::Hg { repo_url } => ("Hg", repo_url.clone()),
189 Vcs::Svn { url } => ("Svn", url.clone()),
190 Vcs::Cvs { root, module } => (
191 "Cvs",
192 match module {
193 Some(module) => format!("{} {}", root, module),
194 None => root.clone(),
195 },
196 ),
197 }
198 }
199
200 pub fn subpath(&self) -> Option<&str> {
202 match self {
203 Vcs::Git { subpath, .. } => subpath.as_deref(),
204 Vcs::Bzr { subpath, .. } => subpath.as_deref(),
205 _ => None,
206 }
207 }
208
209 pub fn to_branch_url(&self) -> Option<String> {
211 match self {
212 Vcs::Git {
213 repo_url,
214 branch,
215 subpath: _,
216 } => Some(format!("{},branch={}", repo_url, branch.as_ref().unwrap())),
218 Vcs::Bzr {
219 repo_url,
220 subpath: _,
221 } => Some(repo_url.to_string()),
222 Vcs::Hg { repo_url } => Some(repo_url.to_string()),
223 Vcs::Svn { url } => Some(url.to_string()),
224 _ => None,
225 }
226 }
227}
228
229#[cfg(test)]
230mod test {
231 use super::*;
232
233 #[test]
234 fn test_vcs_info() {
235 let vcs_info = ParsedVcs::from_str("https://github.com/jelmer/example").unwrap();
236 assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example");
237 assert_eq!(vcs_info.branch, None);
238 assert_eq!(vcs_info.subpath, None);
239 }
240
241 #[test]
242 fn test_vcs_info_with_branch() {
243 let vcs_info = ParsedVcs::from_str("https://github.com/jelmer/example -b branch").unwrap();
244 assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example");
245 assert_eq!(vcs_info.branch, Some("branch".to_string()));
246 assert_eq!(vcs_info.subpath, None);
247 }
248
249 #[test]
250 fn test_vcs_info_with_subpath() {
251 let vcs_info = ParsedVcs::from_str("https://github.com/jelmer/example [subpath]").unwrap();
252 assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example");
253 assert_eq!(vcs_info.branch, None);
254 assert_eq!(vcs_info.subpath, Some("subpath".to_string()));
255 }
256
257 #[test]
258 fn test_vcs_info_with_branch_and_subpath() {
259 let vcs_info =
260 ParsedVcs::from_str("https://github.com/jelmer/example -b branch [subpath]").unwrap();
261 assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example");
262 assert_eq!(vcs_info.branch, Some("branch".to_string()));
263 assert_eq!(vcs_info.subpath, Some("subpath".to_string()));
264 }
265
266 #[test]
267 fn test_eq() {
268 let vcs_info1 =
269 ParsedVcs::from_str("https://github.com/jelmer/example -b branch [subpath]").unwrap();
270 let vcs_info2 =
271 ParsedVcs::from_str("https://github.com/jelmer/example -b branch [subpath]").unwrap();
272 let vcs_info3 =
273 ParsedVcs::from_str("https://example.com/jelmer/example -b branch [subpath]").unwrap();
274
275 assert_eq!(vcs_info1, vcs_info2);
276 assert_ne!(vcs_info1, vcs_info3);
277 }
278}