Hutch/Extensions/SRHTWebURL.swift
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}