1#![warn(clippy::all)]
23#![warn(clippy::cargo)]
24#![warn(clippy::nursery)]
26#![warn(clippy::pedantic)]
28#![allow(clippy::derive_partial_eq_without_eq)]
29#![allow(clippy::enum_glob_use)]
30#![allow(clippy::match_wildcard_for_single_variants)]
31#![allow(clippy::missing_errors_doc)]
32#![allow(clippy::module_name_repetitions)]
33#![allow(clippy::option_if_let_else)]
34use std::error::Error as StdError;
35use std::fmt::{Display, Formatter, Result as FmtResult};
36use std::str::FromStr;
37
38#[derive(Debug)]
39pub struct ParseGitUrlError(String);
40
41impl Display for ParseGitUrlError {
42 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
43 f.write_str(&self.0)
44 }
45}
46
47impl StdError for ParseGitUrlError {}
48
49#[derive(Clone)]
50pub struct GitUrl {
51 host: String,
52 path: String,
53}
54
55impl GitUrl {
56 const HTTP_PREFIX: &'static str = "http://";
57 const HTTPS_PREFIX: &'static str = "https://";
58
59 #[allow(dead_code)]
60 #[must_use]
61 pub fn pop(&self) -> Option<Self> {
62 let mut temp = self.clone();
63 if temp.pop_mut() {
64 Some(temp)
65 } else {
66 None
67 }
68 }
69
70 #[allow(dead_code)]
71 pub fn pop_mut(&mut self) -> bool {
72 Self::pop_helper(&mut self.path)
73 }
74
75 #[allow(dead_code)]
76 #[must_use]
77 pub fn join(&self, child_path: &str) -> Option<Self> {
78 let mut temp = self.clone();
79 if temp.join_mut(child_path) {
80 Some(temp)
81 } else {
82 None
83 }
84 }
85
86 #[allow(dead_code)]
87 pub fn join_mut(&mut self, child_path: &str) -> bool {
88 let mut path = self.path.clone();
89 for part in child_path.split('/') {
90 if part.is_empty() {
91 return false;
92 } else if part == ".." {
93 if !Self::pop_helper(&mut path) {
94 return false;
95 }
96 } else if part != "." {
97 if !path.is_empty() {
98 path += "/";
99 }
100 path += part;
101 }
102 }
103 self.path = path;
104 true
105 }
106
107 fn pop_helper(path: &mut String) -> bool {
108 if path.is_empty() {
109 false
110 } else {
111 match path.rfind('/') {
112 Some(pos) => path.truncate(pos),
113 None => path.clear(),
114 }
115 true
116 }
117 }
118}
119
120impl FromStr for GitUrl {
121 type Err = ParseGitUrlError;
122
123 #[allow(clippy::manual_strip)]
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 let opt = if s.starts_with(Self::HTTP_PREFIX) {
126 s[Self::HTTP_PREFIX.len()..].find('/').map(|p| Self {
127 host: s[..Self::HTTP_PREFIX.len() + p].to_string(),
128 path: s[Self::HTTP_PREFIX.len() + p + 1..].to_string(),
129 })
130 } else if s.starts_with(Self::HTTPS_PREFIX) {
131 s[Self::HTTPS_PREFIX.len()..].find('/').map(|p| Self {
132 host: s[..Self::HTTPS_PREFIX.len() + p].to_string(),
133 path: s[Self::HTTPS_PREFIX.len() + p + 1..].to_string(),
134 })
135 } else {
136 s.find(':').map(|p| Self {
137 host: s[..p].to_string(),
138 path: s[p + 1..].to_string(),
139 })
140 };
141 opt.ok_or_else(|| ParseGitUrlError(String::from(s)))
142 }
143}
144
145impl Display for GitUrl {
146 fn fmt(&self, f: &mut Formatter) -> FmtResult {
147 write!(
148 f,
149 "{}",
150 match self.path.len() {
151 0 => self.host.to_string(),
152 _ => self.host.to_string() + ":" + &self.path,
153 }
154 )
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::{GitUrl, ParseGitUrlError};
161 use std::result::Result as StdResult;
162
163 #[test]
164 fn test_pop_https() -> StdResult<(), ParseGitUrlError> {
165 let x0 = "https://github.com/user/foo/bar/quux.git".parse::<GitUrl>()?;
166 assert_eq!(x0.host, "https://github.com");
167 assert_eq!(x0.path, "user/foo/bar/quux.git");
168
169 let x1 = "http://github.com/user/foo/bar/quux.git".parse::<GitUrl>()?;
170 assert_eq!(x1.host, "http://github.com");
171 assert_eq!(x1.path, "user/foo/bar/quux.git");
172
173 let x2 = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
174 assert_eq!(x2.host, "git@github.com");
175 assert_eq!(x2.path, "user/foo/bar/quux.git");
176
177 Ok(())
178 }
179
180 #[test]
181 fn test_pop() -> StdResult<(), ParseGitUrlError> {
182 let x0 = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
183
184 assert_eq!(x0.host, "git@github.com");
185 assert_eq!(x0.path, "user/foo/bar/quux.git");
186 assert_eq!(x0.to_string(), "git@github.com:user/foo/bar/quux.git");
187
188 let x1 = x0.pop().expect("pop failed");
189 assert_eq!(x1.host, "git@github.com");
190 assert_eq!(x1.path, "user/foo/bar");
191 assert_eq!(x1.to_string(), "git@github.com:user/foo/bar");
192
193 let x2 = x1.pop().expect("pop failed");
194 assert_eq!(x2.host, "git@github.com");
195 assert_eq!(x2.path, "user/foo");
196 assert_eq!(x2.to_string(), "git@github.com:user/foo");
197
198 let x3 = x2.pop().expect("pop failed");
199 assert_eq!(x3.host, "git@github.com");
200 assert_eq!(x3.path, "user");
201 assert_eq!(x3.to_string(), "git@github.com:user");
202
203 let x4 = x3.pop().expect("pop failed");
204 assert_eq!(x4.host, "git@github.com");
205 assert_eq!(x4.path, "");
206 assert_eq!(x4.to_string(), "git@github.com");
207
208 assert!(x4.pop().is_none());
209
210 Ok(())
211 }
212
213 #[test]
214 fn test_pop_mut() -> StdResult<(), ParseGitUrlError> {
215 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
216
217 assert_eq!(git_url.host, "git@github.com");
218 assert_eq!(git_url.path, "user/foo/bar/quux.git");
219 assert_eq!(git_url.to_string(), "git@github.com:user/foo/bar/quux.git");
220
221 assert!(git_url.pop_mut());
222 assert_eq!(git_url.host, "git@github.com");
223 assert_eq!(git_url.path, "user/foo/bar");
224 assert_eq!(git_url.to_string(), "git@github.com:user/foo/bar");
225
226 assert!(git_url.pop_mut());
227 assert_eq!(git_url.host, "git@github.com");
228 assert_eq!(git_url.path, "user/foo");
229 assert_eq!(git_url.to_string(), "git@github.com:user/foo");
230
231 assert!(git_url.pop_mut());
232 assert_eq!(git_url.host, "git@github.com");
233 assert_eq!(git_url.path, "user");
234 assert_eq!(git_url.to_string(), "git@github.com:user");
235
236 assert!(git_url.pop_mut());
237 assert_eq!(git_url.host, "git@github.com");
238 assert_eq!(git_url.path, "");
239 assert_eq!(git_url.to_string(), "git@github.com");
240
241 assert!(!git_url.pop_mut());
242 assert_eq!(git_url.host, "git@github.com");
243 assert_eq!(git_url.path, "");
244 assert_eq!(git_url.to_string(), "git@github.com");
245
246 Ok(())
247 }
248
249 #[test]
250 fn test_join() -> StdResult<(), ParseGitUrlError> {
251 let git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
252
253 assert_eq!(
254 git_url.join("aaa").expect("join failed").to_string(),
255 "git@github.com:user/foo/bar/quux.git/aaa"
256 );
257
258 assert_eq!(
259 git_url.join("aaa/bbb").expect("join failed").to_string(),
260 "git@github.com:user/foo/bar/quux.git/aaa/bbb"
261 );
262
263 assert_eq!(
264 git_url.join(".").expect("join failed").to_string(),
265 "git@github.com:user/foo/bar/quux.git"
266 );
267
268 assert_eq!(
269 git_url.join("..").expect("join failed").to_string(),
270 "git@github.com:user/foo/bar"
271 );
272
273 assert_eq!(
274 git_url.join("../aaa").expect("join failed").to_string(),
275 "git@github.com:user/foo/bar/aaa"
276 );
277
278 assert_eq!(
279 git_url.join("../aaa/bbb").expect("join failed").to_string(),
280 "git@github.com:user/foo/bar/aaa/bbb"
281 );
282
283 assert_eq!(
284 git_url
285 .join("../../../aaa/bbb")
286 .expect("join failed")
287 .to_string(),
288 "git@github.com:user/aaa/bbb"
289 );
290
291 assert_eq!(
292 git_url
293 .join("../../../../aaa/bbb")
294 .expect("join failed")
295 .to_string(),
296 "git@github.com:aaa/bbb"
297 );
298
299 assert!(git_url.join("/aaa").is_none());
300
301 Ok(())
302 }
303
304 #[test]
305 fn test_join_mut() -> StdResult<(), ParseGitUrlError> {
306 {
307 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
308 assert!(git_url.join_mut("aaa"));
309 assert_eq!(
310 git_url.to_string(),
311 "git@github.com:user/foo/bar/quux.git/aaa"
312 );
313 }
314
315 {
316 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
317 assert!(git_url.join_mut("aaa/bbb"));
318 assert_eq!(
319 git_url.to_string(),
320 "git@github.com:user/foo/bar/quux.git/aaa/bbb"
321 );
322 }
323
324 {
325 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
326 assert!(git_url.join_mut("."));
327 assert_eq!(git_url.to_string(), "git@github.com:user/foo/bar/quux.git");
328 }
329
330 {
331 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
332 assert!(git_url.join_mut(".."));
333 assert_eq!(git_url.to_string(), "git@github.com:user/foo/bar");
334 }
335
336 {
337 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
338 assert!(git_url.join_mut("../aaa"));
339 assert_eq!(git_url.to_string(), "git@github.com:user/foo/bar/aaa");
340 }
341
342 {
343 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
344 assert!(git_url.join_mut("../aaa/bbb"));
345 assert_eq!(git_url.to_string(), "git@github.com:user/foo/bar/aaa/bbb");
346 }
347
348 {
349 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
350 assert!(git_url.join_mut("../../../aaa/bbb"));
351 assert_eq!(git_url.to_string(), "git@github.com:user/aaa/bbb");
352 }
353
354 {
355 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
356 assert!(git_url.join_mut("../../../../aaa/bbb"));
357 assert_eq!(git_url.to_string(), "git@github.com:aaa/bbb");
358 }
359
360 {
361 let mut git_url = "git@github.com:user/foo/bar/quux.git".parse::<GitUrl>()?;
362 assert!(!git_url.join_mut("/aaa"));
363 }
364
365 Ok(())
366 }
367}