krz/hutch

an ios client for sourcehut

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

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

  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    static func paste(ownerCanonicalName: String, pasteId: String) -> URL? {
 80        userScopedURL(
 81            host: "paste.sr.ht",
 82            ownerCanonicalName: ownerCanonicalName,
 83            pathComponents: [pasteId]
 84        )
 85    }
 86
 87    private static func userScopedURL(
 88        host: String,
 89        ownerCanonicalName: String,
 90        pathComponents: [String]
 91    ) -> URL? {
 92        userScopedURL(
 93            host: host,
 94            ownerUsername: username(from: ownerCanonicalName),
 95            pathComponents: pathComponents
 96        )
 97    }
 98
 99    private static func userScopedURL(
100        host: String,
101        ownerUsername: String,
102        pathComponents: [String]
103    ) -> URL? {
104        var components = URLComponents()
105        components.scheme = "https"
106        components.host = host
107
108        let encodedComponents = (["~\(ownerUsername)"] + pathComponents).map { pathComponent in
109            pathComponent.addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? pathComponent
110        }
111        components.percentEncodedPath = "/" + encodedComponents.joined(separator: "/")
112        return components.url
113    }
114
115    private static func username(from canonicalName: String) -> String {
116        if canonicalName.hasPrefix("~") {
117            return String(canonicalName.dropFirst())
118        }
119        return canonicalName
120    }
121
122    private static func commitPathComponent(for service: SRHTService) -> String {
123        switch service {
124        case .hg:
125            return "rev"
126        default:
127            return "commit"
128        }
129    }
130
131    private static let pathComponentCharacterSet: CharacterSet = {
132        var characterSet = CharacterSet.urlPathAllowed
133        characterSet.remove(charactersIn: "/")
134        return characterSet
135    }()
136
137    private static func pathComponents(from path: String) -> [String] {
138        path.split(separator: "/").map(String.init)
139    }
140}