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