1use nom::branch::alt;
2use nom::bytes::complete::{tag, take_until1};
3use nom::combinator::{eof, map};
4use nom::error::ErrorKind;
5use nom::sequence::terminated;
6use nom::{Err, IResult};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum MappedPath {
17 Git {
19 repo: String,
21 path: String,
23 rev: String,
25 },
26 Hg {
28 repo: String,
30 path: String,
32 rev: String,
34 },
35 S3 {
37 bucket: String,
39 digest: String,
41 path: String,
43 },
44 Cargo {
46 registry: String,
48 crate_name: String,
50 version: String,
52 path: String,
54 },
55}
56
57impl MappedPath {
58 pub fn from_special_path_str(special_path: &str) -> Option<Self> {
65 match parse_special_path(special_path) {
66 Ok((_, mapped_path)) => Some(mapped_path),
67 Err(_) => None,
68 }
69 }
70
71 pub fn from_url(url: &str) -> Option<Self> {
73 match parse_url(url) {
74 Ok((_, mapped_path)) => Some(mapped_path),
75 Err(_) => None,
76 }
77 }
78
79 pub fn to_special_path_str(&self) -> String {
81 match self {
82 MappedPath::Git { repo, path, rev } => format!("git:{repo}:{path}:{rev}"),
83 MappedPath::Hg { repo, path, rev } => format!("hg:{repo}:{path}:{rev}"),
84 MappedPath::S3 {
85 bucket,
86 digest,
87 path,
88 } => format!("s3:{bucket}:{digest}/{path}:"),
89 MappedPath::Cargo {
90 registry,
91 crate_name,
92 version,
93 path,
94 } => format!("cargo:{registry}:{crate_name}-{version}:{path}"),
95 }
96 }
97
98 pub fn display_path(&self) -> String {
100 match self {
101 MappedPath::Git { path, .. } => path.clone(),
102 MappedPath::Hg { path, .. } => path.clone(),
103 MappedPath::S3 { path, .. } => path.clone(),
104 MappedPath::Cargo {
105 crate_name,
106 version,
107 path,
108 ..
109 } => format!("{crate_name}-{version}/{path}"),
110 }
111 }
112}
113
114fn git_path(input: &str) -> IResult<&str, (String, String, String)> {
115 let (input, _) = tag("git:")(input)?;
116 let (input, repo) = terminated(take_until1(":"), tag(":"))(input)?;
117 let (rev, path) = terminated(take_until1(":"), tag(":"))(input)?;
118 Ok(("", (repo.to_owned(), path.to_owned(), rev.to_owned())))
119}
120
121fn hg_path(input: &str) -> IResult<&str, (String, String, String)> {
122 let (input, _) = tag("hg:")(input)?;
123 let (input, repo) = terminated(take_until1(":"), tag(":"))(input)?;
124 let (rev, path) = terminated(take_until1(":"), tag(":"))(input)?;
125 Ok(("", (repo.to_owned(), path.to_owned(), rev.to_owned())))
126}
127
128fn s3_path(input: &str) -> IResult<&str, (String, String, String)> {
129 let (input, _) = tag("s3:")(input)?;
130 let (input, bucket) = terminated(take_until1(":"), tag(":"))(input)?;
131 let (input, digest) = terminated(take_until1("/"), tag("/"))(input)?;
132 let (_, path) = terminated(take_until1(":"), terminated(tag(":"), eof))(input)?;
133 Ok(("", (bucket.to_owned(), digest.to_owned(), path.to_owned())))
134}
135
136fn cargo_path(input: &str) -> IResult<&str, (String, String, String, String)> {
137 let (input, _) = tag("cargo:")(input)?;
138 let (input, registry) = terminated(take_until1(":"), tag(":"))(input)?;
139 let (path, crate_name_and_version) = terminated(take_until1(":"), tag(":"))(input)?;
140 let (crate_name, version) = match crate_name_and_version.rfind('-') {
141 Some(pos) => (
142 &crate_name_and_version[..pos],
143 &crate_name_and_version[(pos + 1)..],
144 ),
145 None => {
146 return Err(Err::Error(nom::error::Error::new(
147 crate_name_and_version,
148 ErrorKind::Digit,
149 )))
150 }
151 };
152 Ok((
153 "",
154 (
155 registry.to_owned(),
156 crate_name.to_owned(),
157 version.to_owned(),
158 path.to_owned(),
159 ),
160 ))
161}
162
163fn parse_special_path(input: &str) -> IResult<&str, MappedPath> {
164 alt((
165 map(git_path, |(repo, path, rev)| MappedPath::Git {
166 repo,
167 path,
168 rev,
169 }),
170 map(hg_path, |(repo, path, rev)| MappedPath::Hg {
171 repo,
172 path,
173 rev,
174 }),
175 map(s3_path, |(bucket, digest, path)| MappedPath::S3 {
176 bucket,
177 digest,
178 path,
179 }),
180 map(cargo_path, |(registry, crate_name, version, path)| {
181 MappedPath::Cargo {
182 registry,
183 crate_name,
184 version,
185 path,
186 }
187 }),
188 ))(input)
189}
190
191fn github_url(input: &str) -> IResult<&str, (String, String, String)> {
192 let (input, _) = tag("https://raw.githubusercontent.com/")(input)?;
194 let (input, org) = terminated(take_until1("/"), tag("/"))(input)?;
195 let (input, repo_name) = terminated(take_until1("/"), tag("/"))(input)?;
196 let (input, rev) = terminated(take_until1("/"), tag("/"))(input)?;
197 let path = input;
198 Ok((
199 "",
200 (
201 format!("github.com/{org}/{repo_name}"),
202 path.to_owned(),
203 rev.to_owned(),
204 ),
205 ))
206}
207
208fn hg_url(input: &str) -> IResult<&str, (String, String, String)> {
209 let (input, _) = tag("https://hg.")(input)?;
211 let (input, host_rest) = terminated(take_until1("/"), tag("/"))(input)?;
212 let (input, repo) = terminated(take_until1("/raw-file/"), tag("/raw-file/"))(input)?;
213 let (input, rev) = terminated(take_until1("/"), tag("/"))(input)?;
214 let path = input;
215 Ok((
216 "",
217 (
218 format!("hg.{host_rest}/{repo}"),
219 path.to_owned(),
220 rev.to_owned(),
221 ),
222 ))
223}
224
225fn s3_url(input: &str) -> IResult<&str, (String, String, String)> {
226 let (input, _) = tag("https://")(input)?;
228 let (input, bucket) =
229 terminated(take_until1(".s3.amazonaws.com/"), tag(".s3.amazonaws.com/"))(input)?;
230 let (input, digest) = terminated(take_until1("/"), tag("/"))(input)?;
231 let path = input;
232 Ok(("", (bucket.to_owned(), digest.to_owned(), path.to_owned())))
233}
234
235fn parse_url(input: &str) -> IResult<&str, MappedPath> {
236 alt((
237 map(github_url, |(repo, path, rev)| MappedPath::Git {
238 repo,
239 path,
240 rev,
241 }),
242 map(hg_url, |(repo, path, rev)| MappedPath::Hg {
243 repo,
244 path,
245 rev,
246 }),
247 map(s3_url, |(bucket, digest, path)| MappedPath::S3 {
248 bucket,
249 digest,
250 path,
251 }),
252 ))(input)
253}
254
255#[cfg(test)]
256mod test {
257 use super::*;
258
259 #[test]
260 fn parse_hg_paths() {
261 assert_eq!(
262 MappedPath::from_special_path_str(
263 "hg:hg.mozilla.org/mozilla-central:widget/cocoa/nsAppShell.mm:997f00815e6bc28806b75448c8829f0259d2cb28"
264 ),
265 Some(MappedPath::Hg {
266 repo: "hg.mozilla.org/mozilla-central".to_string(),
267 path: "widget/cocoa/nsAppShell.mm".to_string(),
268 rev: "997f00815e6bc28806b75448c8829f0259d2cb28".to_string(),
269 })
270 );
271 assert_eq!(
272 MappedPath::from_url(
273 "https://hg.mozilla.org/mozilla-central/raw-file/1706d4d54ec68fae1280305b70a02cb24c16ff68/mozglue/baseprofiler/core/ProfilerBacktrace.cpp"
274 ),
275 Some(MappedPath::Hg {
276 repo: "hg.mozilla.org/mozilla-central".to_string(),
277 path: "mozglue/baseprofiler/core/ProfilerBacktrace.cpp".to_string(),
278 rev: "1706d4d54ec68fae1280305b70a02cb24c16ff68".to_string(),
279 })
280 );
281 }
282
283 #[test]
284 fn parse_git_paths() {
285 assert_eq!(
286 MappedPath::from_special_path_str(
287 "git:github.com/rust-lang/rust:library/std/src/sys/unix/thread.rs:53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b"
288 ),
289 Some(MappedPath::Git {
290 repo: "github.com/rust-lang/rust".to_string(),
291 path: "library/std/src/sys/unix/thread.rs".to_string(),
292 rev: "53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b".to_string(),
293 })
294 );
295 assert_eq!(
296 MappedPath::from_special_path_str(
297 "git:chromium.googlesource.com/chromium/src:content/gpu/gpu_main.cc:4dac2548d4812df2aa4a90ac1fc8912363f4d59c"
298 ),
299 Some(MappedPath::Git {
300 repo: "chromium.googlesource.com/chromium/src".to_string(),
301 path: "content/gpu/gpu_main.cc".to_string(),
302 rev: "4dac2548d4812df2aa4a90ac1fc8912363f4d59c".to_string(),
303 })
304 );
305 assert_eq!(
306 MappedPath::from_special_path_str(
307 "git:pdfium.googlesource.com/pdfium:core/fdrm/fx_crypt.cpp:dab1161c861cc239e48a17e1a5d729aa12785a53"
308 ),
309 Some(MappedPath::Git {
310 repo: "pdfium.googlesource.com/pdfium".to_string(),
311 path: "core/fdrm/fx_crypt.cpp".to_string(),
312 rev: "dab1161c861cc239e48a17e1a5d729aa12785a53".to_string(),
313 })
314 );
315 assert_eq!(
316 MappedPath::from_url(
317 "https://raw.githubusercontent.com/baldurk/renderdoc/v1.15/renderdoc/data/glsl/gl_texsample.h"
318 ),
319 Some(MappedPath::Git {
320 repo: "github.com/baldurk/renderdoc".to_string(),
321 path: "renderdoc/data/glsl/gl_texsample.h".to_string(),
322 rev: "v1.15".to_string(),
323 })
324 );
325 }
326
327 #[test]
328 fn parse_s3_paths() {
329 assert_eq!(
330 MappedPath::from_special_path_str(
331 "s3:gecko-generated-sources:a5d3747707d6877b0e5cb0a364e3cb9fea8aa4feb6ead138952c2ba46d41045297286385f0e0470146f49403e46bd266e654dfca986de48c230f3a71c2aafed4/ipc/ipdl/PBackgroundChild.cpp:"
332 ),
333 Some(MappedPath::S3 {
334 bucket: "gecko-generated-sources".to_string(),
335 path: "ipc/ipdl/PBackgroundChild.cpp".to_string(),
336 digest:
337 "a5d3747707d6877b0e5cb0a364e3cb9fea8aa4feb6ead138952c2ba46d41045297286385f0e0470146f49403e46bd266e654dfca986de48c230f3a71c2aafed4".to_string(),
338 })
339 );
340 assert_eq!(
341 MappedPath::from_special_path_str(
342 "s3:gecko-generated-sources:4fd754dd7ca7565035aaa3357b8cd99959a2dddceba0fc2f7018ef99fd78ea63d03f9bf928afdc29873089ee15431956791130b97f66ab8fcb88ec75f4ba6b04/aarch64-apple-darwin/release/build/swgl-580c7d646d09cf59/out/ps_text_run_ALPHA_PASS_TEXTURE_2D.h:"
343 ),
344 Some(MappedPath::S3 {
345 bucket: "gecko-generated-sources".to_string(),
346 path: "aarch64-apple-darwin/release/build/swgl-580c7d646d09cf59/out/ps_text_run_ALPHA_PASS_TEXTURE_2D.h".to_string(),
347 digest: "4fd754dd7ca7565035aaa3357b8cd99959a2dddceba0fc2f7018ef99fd78ea63d03f9bf928afdc29873089ee15431956791130b97f66ab8fcb88ec75f4ba6b04".to_string(),
348 })
349 );
350 assert_eq!(
351 MappedPath::from_url(
352 "https://gecko-generated-sources.s3.amazonaws.com/7a1db5dfd0061d0e0bcca227effb419a20439aef4f6c4e9cd391a9f136c6283e89043d62e63e7edbd63ad81c339c401092bcfeff80f74f9cae8217e072f0c6f3/x86_64-pc-windows-msvc/release/build/swgl-59e3a0e09f56f4ea/out/brush_solid_DEBUG_OVERDRAW.h"
353 ),
354 Some(MappedPath::S3 {
355 bucket: "gecko-generated-sources".to_string(),
356 path: "x86_64-pc-windows-msvc/release/build/swgl-59e3a0e09f56f4ea/out/brush_solid_DEBUG_OVERDRAW.h".to_string(),
357 digest: "7a1db5dfd0061d0e0bcca227effb419a20439aef4f6c4e9cd391a9f136c6283e89043d62e63e7edbd63ad81c339c401092bcfeff80f74f9cae8217e072f0c6f3".to_string(),
358 })
359 );
360 }
361
362 #[test]
363 fn parse_cargo_paths() {
364 assert_eq!(
365 MappedPath::from_special_path_str(
366 "cargo:github.com-1ecc6299db9ec823:addr2line-0.16.0:src/function.rs"
367 ),
368 Some(MappedPath::Cargo {
369 registry: "github.com-1ecc6299db9ec823".to_string(),
370 crate_name: "addr2line".to_string(),
371 version: "0.16.0".to_string(),
372 path: "src/function.rs".to_string(),
373 })
374 );
375 assert_eq!(
376 MappedPath::from_special_path_str(
377 "cargo:github.com-1ecc6299db9ec823:tokio-1.6.1:src/runtime/task/mod.rs"
378 ),
379 Some(MappedPath::Cargo {
380 registry: "github.com-1ecc6299db9ec823".to_string(),
381 crate_name: "tokio".to_string(),
382 version: "1.6.1".to_string(),
383 path: "src/runtime/task/mod.rs".to_string(),
384 })
385 );
386 assert_eq!(
387 MappedPath::from_special_path_str(
388 "cargo:github.com-1ecc6299db9ec823:fxprof-processed-profile-0.3.0:src/lib.rs"
389 ),
390 Some(MappedPath::Cargo {
391 registry: "github.com-1ecc6299db9ec823".to_string(),
392 crate_name: "fxprof-processed-profile".to_string(),
393 version: "0.3.0".to_string(),
394 path: "src/lib.rs".to_string(),
395 })
396 );
397 }
398
399 fn test_roundtrip(s: &str) {
400 let mapped_path = MappedPath::from_special_path_str(s).unwrap();
401 let roundtripped = mapped_path.to_special_path_str();
402 assert_eq!(&roundtripped, s);
403 }
404
405 #[test]
406 fn roundtrips() {
407 test_roundtrip("hg:hg.mozilla.org/mozilla-central:widget/cocoa/nsAppShell.mm:997f00815e6bc28806b75448c8829f0259d2cb28");
408 test_roundtrip("git:github.com/rust-lang/rust:library/std/src/sys/unix/thread.rs:53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b");
409 test_roundtrip("git:chromium.googlesource.com/chromium/src:content/gpu/gpu_main.cc:4dac2548d4812df2aa4a90ac1fc8912363f4d59c");
410 test_roundtrip("git:pdfium.googlesource.com/pdfium:core/fdrm/fx_crypt.cpp:dab1161c861cc239e48a17e1a5d729aa12785a53");
411 test_roundtrip("s3:gecko-generated-sources:a5d3747707d6877b0e5cb0a364e3cb9fea8aa4feb6ead138952c2ba46d41045297286385f0e0470146f49403e46bd266e654dfca986de48c230f3a71c2aafed4/ipc/ipdl/PBackgroundChild.cpp:");
412 test_roundtrip("s3:gecko-generated-sources:4fd754dd7ca7565035aaa3357b8cd99959a2dddceba0fc2f7018ef99fd78ea63d03f9bf928afdc29873089ee15431956791130b97f66ab8fcb88ec75f4ba6b04/aarch64-apple-darwin/release/build/swgl-580c7d646d09cf59/out/ps_text_run_ALPHA_PASS_TEXTURE_2D.h:");
413 test_roundtrip("cargo:github.com-1ecc6299db9ec823:addr2line-0.16.0:src/function.rs");
414 test_roundtrip("cargo:github.com-1ecc6299db9ec823:tokio-1.6.1:src/runtime/task/mod.rs");
415 test_roundtrip(
416 "cargo:github.com-1ecc6299db9ec823:fxprof-processed-profile-0.3.0:src/lib.rs",
417 );
418 }
419}