Hutch/Extensions/SRHTWebURL.swift

b5af561d68e7535726b79d4ceec0d0a45abf0042
hutch/Hutch/Extensions/SRHTWebURL.swift history · blame · raw

132 lines · 4550 bytes

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