krz/hutch

an ios client for sourcehut

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

v2.13.1: Hutch/Extensions/SRHTWebURL.swift · raw

  1import Foundation
  2
  3enum SRHTWebURL {
  4    static let chat = URL(string: "https://chat.sr.ht")!
  5    static let status = URL(string: "https://status.sr.ht")!
  6
  7    static func repository(_ repository: RepositorySummary) -> URL? {
  8        userScopedURL(
  9            host: "\(repository.service.rawValue).sr.ht",
 10            ownerCanonicalName: repository.owner.canonicalName,
 11            pathComponents: [repository.name]
 12        )
 13    }
 14
 15    static func commit(repository: RepositorySummary, commitId: String) -> URL? {
 16        userScopedURL(
 17            host: "\(repository.service.rawValue).sr.ht",
 18            ownerCanonicalName: repository.owner.canonicalName,
 19            pathComponents: [repository.name, commitPathComponent(for: repository.service), commitId]
 20        )
 21    }
 22
 23    static func file(repository: RepositorySummary, revspec: String, path: String) -> URL? {
 24        switch repository.service {
 25        case .git:
 26            return userScopedURL(
 27                host: "git.sr.ht",
 28                ownerCanonicalName: repository.owner.canonicalName,
 29                pathComponents: [repository.name, "tree", revspec, "item"] + pathComponents(from: path)
 30            )
 31        case .hg:
 32            var components = URLComponents()
 33            components.scheme = "https"
 34            components.host = "hg.sr.ht"
 35
 36            let ownerUsername = username(from: repository.owner.canonicalName)
 37            let encodedRepository = repository.name.addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? repository.name
 38            let encodedPath = path.split(separator: "/").map {
 39                String($0).addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? String($0)
 40            }.joined(separator: "/")
 41
 42            components.percentEncodedPath = "/~\(ownerUsername)/\(encodedRepository)/browse/\(encodedPath)"
 43            components.queryItems = [URLQueryItem(name: "rev", value: revspec)]
 44            return components.url
 45        default:
 46            return nil
 47        }
 48    }
 49
 50    static func build(jobId: Int, ownerCanonicalName: String) -> URL? {
 51        userScopedURL(
 52            host: "builds.sr.ht",
 53            ownerCanonicalName: ownerCanonicalName,
 54            pathComponents: ["job", String(jobId)]
 55        )
 56    }
 57
 58    static func tracker(ownerUsername: String, trackerName: String) -> URL? {
 59        userScopedURL(
 60            host: "todo.sr.ht",
 61            ownerUsername: ownerUsername,
 62            pathComponents: [trackerName]
 63        )
 64    }
 65
 66    static func ticket(ownerUsername: String, trackerName: String, ticketId: Int) -> URL? {
 67        userScopedURL(
 68            host: "todo.sr.ht",
 69            ownerUsername: ownerUsername,
 70            pathComponents: [trackerName, String(ticketId)]
 71        )
 72    }
 73
 74    static func profile(canonicalName: String) -> URL? {
 75        userScopedURL(
 76            host: "sr.ht",
 77            ownerCanonicalName: canonicalName,
 78            pathComponents: []
 79        )
 80    }
 81
 82    static func paste(ownerCanonicalName: String, pasteId: String) -> URL? {
 83        userScopedURL(
 84            host: "paste.sr.ht",
 85            ownerCanonicalName: ownerCanonicalName,
 86            pathComponents: [pasteId]
 87        )
 88    }
 89
 90    private static func userScopedURL(
 91        host: String,
 92        ownerCanonicalName: String,
 93        pathComponents: [String]
 94    ) -> URL? {
 95        userScopedURL(
 96            host: host,
 97            ownerUsername: username(from: ownerCanonicalName),
 98            pathComponents: pathComponents
 99        )
100    }
101
102    private static func userScopedURL(
103        host: String,
104        ownerUsername: String,
105        pathComponents: [String]
106    ) -> URL? {
107        var components = URLComponents()
108        components.scheme = "https"
109        components.host = host
110
111        let encodedComponents = (["~\(ownerUsername)"] + pathComponents).map { pathComponent in
112            pathComponent.addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? pathComponent
113        }
114        components.percentEncodedPath = "/" + encodedComponents.joined(separator: "/")
115        return components.url
116    }
117
118    private static func username(from canonicalName: String) -> String {
119        if canonicalName.hasPrefix("~") {
120            return String(canonicalName.dropFirst())
121        }
122        return canonicalName
123    }
124
125    private static func commitPathComponent(for service: SRHTService) -> String {
126        switch service {
127        case .hg:
128            return "rev"
129        default:
130            return "commit"
131        }
132    }
133
134    private static let pathComponentCharacterSet: CharacterSet = {
135        var characterSet = CharacterSet.urlPathAllowed
136        characterSet.remove(charactersIn: "/")
137        return characterSet
138    }()
139
140    private static func pathComponents(from path: String) -> [String] {
141        path.split(separator: "/").map(String.init)
142    }
143}