krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

remove-splash-highlighter: 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}