reference_query/core/
identity.rs1use std::fmt;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum RepoIdentity {
11 Remote(String),
13 Local(String),
15}
16
17impl RepoIdentity {
18 pub fn from_remote_url(url: &str) -> Option<RepoIdentity> {
28 normalize_remote(url).map(RepoIdentity::Remote)
29 }
30
31 pub fn local(path: &str) -> RepoIdentity {
33 RepoIdentity::Local(path.to_string())
34 }
35}
36
37impl fmt::Display for RepoIdentity {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 RepoIdentity::Remote(s) => f.write_str(s),
41 RepoIdentity::Local(p) => write!(f, "local:{p}"),
42 }
43 }
44}
45
46fn normalize_remote(url: &str) -> Option<String> {
49 let url = url.trim();
50 if url.is_empty() {
51 return None;
52 }
53
54 let rest = if let Some(stripped) = strip_scheme(url) {
56 stripped
58 } else if let Some((host_part, path)) = url.split_once(':') {
59 let host = host_part.rsplit('@').next().unwrap_or(host_part);
61 return assemble(host, path);
62 } else {
63 return None;
64 };
65
66 let (authority, path) = rest.split_once('/')?;
67 let host_with_user = authority;
68 let host = host_with_user.rsplit('@').next().unwrap_or(host_with_user);
69 let host = host.split(':').next().unwrap_or(host); assemble(host, path)
71}
72
73fn strip_scheme(url: &str) -> Option<&str> {
75 for scheme in ["https://", "http://", "ssh://", "git://"] {
76 if let Some(rest) = url.strip_prefix(scheme) {
77 return Some(rest);
78 }
79 }
80 None
81}
82
83fn assemble(host: &str, path: &str) -> Option<String> {
84 let host = host.trim().trim_matches('/');
85 let path = path.trim().trim_matches('/');
86 let path = path.strip_suffix(".git").unwrap_or(path);
87 if host.is_empty() || path.is_empty() {
88 return None;
89 }
90 Some(format!("{host}/{path}"))
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn normalizes_scp_syntax() {
99 assert_eq!(
100 RepoIdentity::from_remote_url("git@github.com:dpep/rq.git"),
101 Some(RepoIdentity::Remote("github.com/dpep/rq".into()))
102 );
103 }
104
105 #[test]
106 fn normalizes_https() {
107 assert_eq!(
108 RepoIdentity::from_remote_url("https://github.com/dpep/rq.git"),
109 Some(RepoIdentity::Remote("github.com/dpep/rq".into()))
110 );
111 }
112
113 #[test]
114 fn normalizes_ssh_with_user_and_port() {
115 assert_eq!(
116 RepoIdentity::from_remote_url("ssh://git@github.com:22/dpep/rq"),
117 Some(RepoIdentity::Remote("github.com/dpep/rq".into()))
118 );
119 }
120
121 #[test]
122 fn forks_and_clones_share_identity() {
123 let a = RepoIdentity::from_remote_url("git@github.com:dpep/rq.git");
124 let b = RepoIdentity::from_remote_url("https://github.com/dpep/rq");
125 assert_eq!(a, b);
126 }
127
128 #[test]
129 fn empty_and_garbage_return_none() {
130 assert_eq!(RepoIdentity::from_remote_url(""), None);
131 assert_eq!(RepoIdentity::from_remote_url("not-a-url"), None);
132 }
133
134 #[test]
135 fn display_renders_each_variant() {
136 assert_eq!(
137 RepoIdentity::Remote("github.com/dpep/rq".into()).to_string(),
138 "github.com/dpep/rq"
139 );
140 assert_eq!(
141 RepoIdentity::local("/home/dpep/rq").to_string(),
142 "local:/home/dpep/rq"
143 );
144 }
145}