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