1use std::fmt::Display;
2
3use nom::{
4 IResult, Parser,
5 branch::alt,
6 bytes::complete::take_till1,
7 character::complete::char,
8 combinator::{cut, opt, value},
9 error::context,
10 sequence::{preceded, separated_pair, terminated},
11};
12use serde::{Deserialize, Serialize};
13
14use crate::{IErr, error::tag};
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17pub enum GitForgePlatform {
18 GitHub,
19 GitLab,
20 SourceHut,
21}
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23pub struct GitForge {
24 pub platform: GitForgePlatform,
25 pub owner: String,
26 pub repo: String,
27 pub ref_or_rev: Option<String>,
28}
29
30impl GitForgePlatform {
31 pub fn parse(input: &str) -> IResult<&str, Self, IErr<&str>> {
34 alt((
35 value(Self::GitHub, tag("github")),
36 value(Self::GitLab, tag("gitlab")),
37 value(Self::SourceHut, tag("sourcehut")),
38 ))
39 .parse(input)
40 }
41 pub fn parse_terminated(input: &str) -> IResult<&str, Self, IErr<&str>> {
42 terminated(Self::parse, char(':')).parse(input)
43 }
44}
45
46impl GitForge {
47 fn parse_owner_repo(input: &str) -> IResult<&str, (&str, &str), IErr<&str>> {
50 context(
51 "owner and repo",
52 cut(separated_pair(
53 context("owner", take_till1(|c| c == '/')),
54 char('/'),
55 context("repo", take_till1(|c| c == '/' || c == '?' || c == '#')),
56 )),
57 )
58 .parse(input)
59 }
60
61 fn parse_rev_ref(input: &str) -> IResult<&str, Option<&str>, IErr<&str>> {
63 preceded(char('/'), opt(take_till1(|c| c == '?' || c == '#'))).parse(input)
64 }
65 pub(crate) fn parse_owner_repo_ref(
70 input: &str,
71 ) -> IResult<&str, (&str, &str, Option<&str>), IErr<&str>> {
72 let (input, (owner, repo)) = Self::parse_owner_repo(input)?;
73 let (input, maybe_refrev) = opt(Self::parse_rev_ref).parse(input)?;
75 Ok((input, (owner, repo, maybe_refrev.flatten())))
78 }
79 pub fn parse(input: &str) -> IResult<&str, Self, IErr<&str>> {
80 let (rest, platform) = terminated(GitForgePlatform::parse, char(':')).parse(input)?;
81 let (rest, forge_path) = Self::parse_owner_repo_ref(rest)?;
82 let res = Self {
83 platform,
84 owner: forge_path.0.to_string(),
85 repo: forge_path.1.to_string(),
86 ref_or_rev: forge_path.2.map(str::to_string),
87 };
88 Ok((rest, res))
89 }
90}
91
92impl Display for GitForgePlatform {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 write!(
95 f,
96 "{}",
97 match self {
98 Self::GitHub => "github",
99 Self::GitLab => "gitlab",
100 Self::SourceHut => "sourcehut",
101 }
102 )
103 }
104}
105
106#[cfg(test)]
107mod inc_parse_platform {
108 use super::*;
109
110 #[test]
111 fn platform() {
112 let remain = ":nixos/nixpkgs";
113
114 let uri = "github:nixos/nixpkgs";
115
116 let (rest, platform) = GitForgePlatform::parse(uri).unwrap();
117 assert_eq!(rest, remain);
118 assert_eq!(platform, GitForgePlatform::GitHub);
119
120 let (rest, platform) = GitForgePlatform::parse_terminated(uri).unwrap();
121 assert_eq!(rest, &remain[1..]);
122 assert_eq!(platform, GitForgePlatform::GitHub);
123
124 let uri = "gitlab:nixos/nixpkgs";
125
126 let (rest, platform) = GitForgePlatform::parse(uri).unwrap();
127 assert_eq!(rest, remain);
128 assert_eq!(platform, GitForgePlatform::GitLab);
129
130 let uri = "sourcehut:nixos/nixpkgs";
131
132 let (rest, platform) = GitForgePlatform::parse(uri).unwrap();
133 assert_eq!(rest, remain);
134 assert_eq!(platform, GitForgePlatform::SourceHut);
135 }
137}
138#[cfg(test)]
139mod err_msgs {
140 use cool_asserts::assert_matches;
141 use nom::{Finish, error::ErrorKind};
142
143 use super::*;
144 use crate::error::{BaseErrorKind, ErrorTree, Expectation, StackContext};
145
146 #[test]
147 fn just_owner() {
148 let input = "owner";
149 let input_slash = "owner/";
150
151 let err = GitForge::parse_owner_repo_ref(input).finish().unwrap_err();
152 assert_matches!(
154 err,
155 ErrorTree::Stack {
156 base, contexts,
158 } => {
159 assert_matches!(*base, ErrorTree::Base {
160 location: "",
161 kind: BaseErrorKind::Expected(Expectation::Char('/'))
162 });
163 assert_eq!(contexts, [
164 ("owner", StackContext::Context("owner and repo")),
165 ]);
166 }
167 );
168 let err_slash = GitForge::parse_owner_repo_ref(input_slash)
169 .finish()
170 .unwrap_err();
171 assert_matches!(
172 err_slash,
173 ErrorTree::Stack {
174 base, contexts,
176 } => {
177 assert_matches!(*base, ErrorTree::Base {
178 location: "",
179 kind: BaseErrorKind::Kind(ErrorKind::TakeTill1)
180 });
181 assert_eq!(contexts, [
182 ("", StackContext::Context("repo")),
183 ("owner/", StackContext::Context("owner and repo")),
184 ]);
185 }
186 );
187
188 }
191 #[test]
192 #[ignore = "bad github ownerstring not yet impld"]
193 fn git_owner() {
194 let _input = "bad-owner/";
195
196 }
199 #[test]
200 #[ignore = "bad github repostring not yet impld"]
201 fn git_repo() {
202 let _input = "owner/bad-string";
203
204 }
207 #[test]
208 #[ignore = "bad mercurial ownerstring not yet impld"]
209 fn merc_owner() {
210 let _input = "bad-owner/";
211
212 }
215 #[test]
216 #[ignore = "bad mercurial repostring not yet impld"]
217 fn merc_repo() {
218 let _input = "owner/bad-string";
219
220 }
223}
224#[cfg(test)]
225mod inc_parse {
226 use super::*;
227
228 #[test]
229 fn plain() {
230 let input = "owner/repo";
231 let (rest, res) = GitForge::parse_owner_repo_ref(input).unwrap();
232 let expected = ("owner", "repo", None);
233 assert_eq!(rest, "");
234 assert_eq!(expected, res);
235 }
236
237 #[test]
238 fn param_terminated() {
239 let input = "owner/repo?🤡";
240 let (rest, res) = GitForge::parse_owner_repo_ref(input).unwrap();
241 let expected = ("owner", "repo", None);
242 assert_eq!(rest, "?🤡");
243 assert_eq!(expected, res);
244 assert_eq!(rest, "?🤡");
245
246 let input = "owner/repo#🤡";
247 let (rest, res) = GitForge::parse_owner_repo_ref(input).unwrap();
248 let expected = ("owner", "repo", None);
249 assert_eq!(expected, res);
250 assert_eq!(rest, "#🤡");
251
252 let input = "owner/repo?#🤡";
253 let (rest, res) = GitForge::parse_owner_repo_ref(input).unwrap();
254 let expected = ("owner", "repo", None);
255 assert_eq!(expected, res);
256 assert_eq!(rest, "?#🤡");
257 }
258
259 #[test]
260 fn attr_terminated() {
261 let input = "owner/repo#fizz.bar";
262 let (rest, res) = GitForge::parse_owner_repo_ref(input).unwrap();
263 let expected = ("owner", "repo", None);
264 assert_eq!(rest, "#fizz.bar");
265 assert_eq!(expected, res);
266 }
267
268 #[test]
269 fn rev_param_terminated() {
270 let input = "owner/repo/rev?foo=bar";
271 let (rest, res) = GitForge::parse_owner_repo_ref(input).unwrap();
272 let expected = ("owner", "repo", Some("rev"));
273 assert_eq!(rest, "?foo=bar");
274 assert_eq!(expected, res);
275 }
276
277 #[test]
278 fn rev_attr_terminated() {
279 let input = "owner/repo/rev#fizz.bar";
280 let (rest, res) = GitForge::parse_owner_repo_ref(input).unwrap();
281 let expected = ("owner", "repo", Some("rev"));
282 assert_eq!(rest, "#fizz.bar");
283 assert_eq!(expected, res);
284 }
285}