krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.2.0: 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 /// RSS/Atom feed for status incidents (HTML snapshot uses `status`).
7 static let statusIncidentFeed = URL(string: "https://status.sr.ht/index.xml")!
8
9 static func repository(_ repository: RepositorySummary) -> URL? {
10 userScopedURL(
11 host: "\(repository.service.rawValue).sr.ht",
12 ownerCanonicalName: repository.owner.canonicalName,
13 pathComponents: [repository.name]
14 )
15 }
16
17 static func httpsCloneURL(_ repositorySummary: RepositorySummary) -> String? {
18 repository(repositorySummary)?.absoluteString
19 }
20
21 static func sshCloneURL(_ repositorySummary: RepositorySummary) -> String {
22 let host = "\(repositorySummary.service.rawValue).sr.ht"
23 let user = repositorySummary.service == .hg ? "hg" : "git"
24 return "\(user)@\(host):\(repositorySummary.owner.canonicalName)/\(repositorySummary.name)"
25 }
26
27 static func commit(repository: RepositorySummary, commitId: String) -> URL? {
28 userScopedURL(
29 host: "\(repository.service.rawValue).sr.ht",
30 ownerCanonicalName: repository.owner.canonicalName,
31 pathComponents: [repository.name, commitPathComponent(for: repository.service), commitId]
32 )
33 }
34
35 static func file(repository: RepositorySummary, revspec: String, path: String) -> URL? {
36 switch repository.service {
37 case .git:
38 return userScopedURL(
39 host: "git.sr.ht",
40 ownerCanonicalName: repository.owner.canonicalName,
41 pathComponents: [repository.name, "tree", revspec, "item"] + pathComponents(from: path)
42 )
43 case .hg:
44 var components = URLComponents()
45 components.scheme = "https"
46 components.host = "hg.sr.ht"
47
48 let ownerUsername = username(from: repository.owner.canonicalName)
49 let encodedRepository = repository.name.addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? repository.name
50 let encodedPath = path.split(separator: "/").map {
51 String($0).addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? String($0)
52 }.joined(separator: "/")
53
54 components.percentEncodedPath = "/~\(ownerUsername)/\(encodedRepository)/browse/\(encodedPath)"
55 components.queryItems = [URLQueryItem(name: "rev", value: revspec)]
56 return components.url
57 default:
58 return nil
59 }
60 }
61
62 static func build(jobId: Int, ownerCanonicalName: String) -> URL? {
63 userScopedURL(
64 host: "builds.sr.ht",
65 ownerCanonicalName: ownerCanonicalName,
66 pathComponents: ["job", String(jobId)]
67 )
68 }
69
70 static func tracker(ownerUsername: String, trackerName: String) -> URL? {
71 userScopedURL(
72 host: "todo.sr.ht",
73 ownerUsername: ownerUsername,
74 pathComponents: [trackerName]
75 )
76 }
77
78 static func tracker(_ trackerSummary: TrackerSummary) -> URL? {
79 tracker(ownerUsername: trackerSummary.owner.canonicalName.srhtUsername, trackerName: trackerSummary.name)
80 }
81
82 static func projectSource(_ source: Project.SourceRepo) -> URL? {
83 userScopedURL(
84 host: "\(source.repoType.service.rawValue).sr.ht",
85 ownerUsername: source.ownerUsername,
86 pathComponents: [source.name]
87 )
88 }
89
90 static func mailingList(ownerUsername: String, listName: String) -> URL? {
91 userScopedURL(
92 host: "lists.sr.ht",
93 ownerUsername: ownerUsername,
94 pathComponents: [listName]
95 )
96 }
97
98 static func ticket(ownerUsername: String, trackerName: String, ticketId: Int) -> URL? {
99 userScopedURL(
100 host: "todo.sr.ht",
101 ownerUsername: ownerUsername,
102 pathComponents: [trackerName, String(ticketId)]
103 )
104 }
105
106 static func profile(canonicalName: String) -> URL? {
107 userScopedURL(
108 host: "sr.ht",
109 ownerCanonicalName: canonicalName,
110 pathComponents: []
111 )
112 }
113
114 static func paste(ownerCanonicalName: String, pasteId: String) -> URL? {
115 userScopedURL(
116 host: "paste.sr.ht",
117 ownerCanonicalName: ownerCanonicalName,
118 pathComponents: [pasteId]
119 )
120 }
121
122 private static func userScopedURL(
123 host: String,
124 ownerCanonicalName: String,
125 pathComponents: [String]
126 ) -> URL? {
127 userScopedURL(
128 host: host,
129 ownerUsername: username(from: ownerCanonicalName),
130 pathComponents: pathComponents
131 )
132 }
133
134 private static func userScopedURL(
135 host: String,
136 ownerUsername: String,
137 pathComponents: [String]
138 ) -> URL? {
139 var components = URLComponents()
140 components.scheme = "https"
141 components.host = host
142
143 let encodedComponents = (["~\(ownerUsername)"] + pathComponents).map { pathComponent in
144 pathComponent.addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? pathComponent
145 }
146 components.percentEncodedPath = "/" + encodedComponents.joined(separator: "/")
147 return components.url
148 }
149
150 private static func username(from canonicalName: String) -> String {
151 if canonicalName.hasPrefix("~") {
152 return String(canonicalName.dropFirst())
153 }
154 return canonicalName
155 }
156
157 private static func commitPathComponent(for service: SRHTService) -> String {
158 switch service {
159 case .hg:
160 return "rev"
161 default:
162 return "commit"
163 }
164 }
165
166 private static let pathComponentCharacterSet: CharacterSet = {
167 var characterSet = CharacterSet.urlPathAllowed
168 characterSet.remove(charactersIn: "/")
169 return characterSet
170 }()
171
172 private static func pathComponents(from path: String) -> [String] {
173 path.split(separator: "/").map(String.init)
174 }
175}