krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.5.0: Shared/SourceHutWebDeepLinkMapper.swift · raw
1import Foundation
2
3enum SourceHutWebDeepLinkMapper {
4 static let supportedHosts: Set<String> = [
5 "git.sr.ht",
6 "hg.sr.ht",
7 "todo.sr.ht",
8 "builds.sr.ht",
9 "lists.sr.ht",
10 "meta.sr.ht",
11 "sr.ht",
12 ]
13
14 static func deepLink(for webURL: URL) -> URL? {
15 guard let components = URLComponents(url: webURL, resolvingAgainstBaseURL: false),
16 components.scheme == "https",
17 let host = components.host?.lowercased(),
18 supportedHosts.contains(host)
19 else {
20 return nil
21 }
22
23 let path = normalizedPercentEncodedPath(from: components.percentEncodedPath)
24 let deepLinkPath = normalizedDeepLinkPath(for: host, path: path)
25 let service = deepLinkService(for: host, path: deepLinkPath)
26 var deepLinkComponents = URLComponents()
27 deepLinkComponents.scheme = "hutch"
28 deepLinkComponents.host = service
29
30 if !deepLinkPath.isEmpty {
31 deepLinkComponents.percentEncodedPath = "/" + deepLinkPath
32 }
33 deepLinkComponents.percentEncodedQuery = components.percentEncodedQuery
34 deepLinkComponents.percentEncodedFragment = components.percentEncodedFragment
35 return deepLinkComponents.url
36 }
37
38 private static func deepLinkService(for host: String, path: String) -> String {
39 if isOwnerRootPath(path) {
40 return "lookup"
41 }
42
43 switch host {
44 case "git.sr.ht":
45 return "git"
46 case "hg.sr.ht":
47 return "hg"
48 case "todo.sr.ht":
49 return "todo"
50 case "builds.sr.ht":
51 return "builds"
52 case "lists.sr.ht":
53 return "lists"
54 default:
55 return "lookup"
56 }
57 }
58
59 private static func isOwnerRootPath(_ path: String) -> Bool {
60 let components = path.split(separator: "/", omittingEmptySubsequences: true)
61 return components.count == 1 && components[0].hasPrefix("~")
62 }
63
64 private static func normalizedDeepLinkPath(for host: String, path: String) -> String {
65 let components = path.split(separator: "/", omittingEmptySubsequences: true)
66 if host == "sr.ht",
67 components.count == 2,
68 components[0] == "projects",
69 components[1].hasPrefix("~") {
70 return String(components[1])
71 }
72 return path
73 }
74
75 private static func normalizedPercentEncodedPath(from path: String) -> String {
76 path
77 .split(separator: "/", omittingEmptySubsequences: true)
78 .joined(separator: "/")
79 }
80}