@@ -94,6 +94,163 @@ extension PackageIdentity: JSONMappable, JSONSerializable {
94
94
95
95
// MARK: -
96
96
97
+ extension PackageIdentity {
98
+ /// Provides a namespace for related packages within a package registry.
99
+ public struct Scope : LosslessStringConvertible , Hashable , Equatable , Comparable , ExpressibleByStringLiteral {
100
+ public let description : String
101
+
102
+ public init ( validating description: String ) throws {
103
+ guard !description. isEmpty else {
104
+ throw StringError ( " The minimum length of a package scope is 1 character. " )
105
+ }
106
+
107
+ guard description. count <= 39 else {
108
+ throw StringError ( " The maximum length of a package scope is 39 characters. " )
109
+ }
110
+
111
+ for (index, character) in zip ( description. indices, description) {
112
+ guard character. isASCII,
113
+ character. isLetter ||
114
+ character. isNumber ||
115
+ character == " - "
116
+ else {
117
+ throw StringError ( " A package scope consists of alphanumeric characters and hyphens. " )
118
+ }
119
+
120
+ if character. isPunctuation {
121
+ switch ( index, description. index ( after: index) ) {
122
+ case ( description. startIndex, _) :
123
+ throw StringError ( " Hyphens may not occur at the beginning of a scope. " )
124
+ case ( _, description. endIndex) :
125
+ throw StringError ( " Hyphens may not occur at the end of a scope. " )
126
+ case ( _, let nextIndex) where description [ nextIndex] . isPunctuation:
127
+ throw StringError ( " Hyphens may not occur consecutively within a scope. " )
128
+ default :
129
+ continue
130
+ }
131
+ }
132
+ }
133
+
134
+ self . description = description
135
+ }
136
+
137
+ public init ? ( _ description: String ) {
138
+ guard let scope = try ? Scope ( validating: description) else { return nil }
139
+ self = scope
140
+ }
141
+
142
+ // MARK: - Equatable & Comparable
143
+
144
+ private func compare( to other: Scope ) -> ComparisonResult {
145
+ // Package scopes are case-insensitive (for example, `mona` ≍ `MONA`).
146
+ return self . description. caseInsensitiveCompare ( other. description)
147
+ }
148
+
149
+ public static func == ( lhs: Scope , rhs: Scope ) -> Bool {
150
+ return lhs. compare ( to: rhs) == . orderedSame
151
+ }
152
+
153
+ public static func < ( lhs: Scope , rhs: Scope ) -> Bool {
154
+ return lhs. compare ( to: rhs) == . orderedAscending
155
+ }
156
+
157
+ public static func > ( lhs: Scope , rhs: Scope ) -> Bool {
158
+ return lhs. compare ( to: rhs) == . orderedDescending
159
+ }
160
+
161
+ // MARK: - Hashable
162
+
163
+ public func hash( into hasher: inout Hasher ) {
164
+ hasher. combine ( description. lowercased ( ) )
165
+ }
166
+
167
+ // MARK: - ExpressibleByStringLiteral
168
+
169
+ public init ( stringLiteral value: StringLiteralType ) {
170
+ try ! self . init ( validating: value)
171
+ }
172
+ }
173
+
174
+ /// Uniquely identifies a package in a scope
175
+ public struct Name : LosslessStringConvertible , Hashable , Equatable , Comparable , ExpressibleByStringLiteral {
176
+ public let description : String
177
+
178
+ public init ( validating description: String ) throws {
179
+ guard !description. isEmpty else {
180
+ throw StringError ( " The minimum length of a package name is 1 character. " )
181
+ }
182
+
183
+ guard description. count <= 100 else {
184
+ throw StringError ( " The maximum length of a package name is 100 characters. " )
185
+ }
186
+
187
+ for (index, character) in zip ( description. indices, description) {
188
+ guard character. isASCII,
189
+ character. isLetter ||
190
+ character. isNumber ||
191
+ character == " - " ||
192
+ character == " _ "
193
+ else {
194
+ throw StringError ( " A package name consists of alphanumeric characters, underscores, and hyphens. " )
195
+ }
196
+
197
+ if character. isPunctuation {
198
+ switch ( index, description. index ( after: index) ) {
199
+ case ( description. startIndex, _) :
200
+ throw StringError ( " Hyphens and underscores may not occur at the beginning of a name. " )
201
+ case ( _, description. endIndex) :
202
+ throw StringError ( " Hyphens and underscores may not occur at the end of a name. " )
203
+ case ( _, let nextIndex) where description [ nextIndex] . isPunctuation:
204
+ throw StringError ( " Hyphens and underscores may not occur consecutively within a name. " )
205
+ default :
206
+ continue
207
+ }
208
+ }
209
+ }
210
+
211
+ self . description = description
212
+ }
213
+
214
+ public init ? ( _ description: String ) {
215
+ guard let name = try ? Name ( validating: description) else { return nil }
216
+ self = name
217
+ }
218
+
219
+ // MARK: - Equatable & Comparable
220
+
221
+ private func compare( to other: Name ) -> ComparisonResult {
222
+ // Package scopes are case-insensitive (for example, `LinkedList` ≍ `LINKEDLIST`).
223
+ return self . description. caseInsensitiveCompare ( other. description)
224
+ }
225
+
226
+ public static func == ( lhs: Name , rhs: Name ) -> Bool {
227
+ return lhs. compare ( to: rhs) == . orderedSame
228
+ }
229
+
230
+ public static func < ( lhs: Name , rhs: Name ) -> Bool {
231
+ return lhs. compare ( to: rhs) == . orderedAscending
232
+ }
233
+
234
+ public static func > ( lhs: Name , rhs: Name ) -> Bool {
235
+ return lhs. compare ( to: rhs) == . orderedDescending
236
+ }
237
+
238
+ // MARK: - Hashable
239
+
240
+ public func hash( into hasher: inout Hasher ) {
241
+ hasher. combine ( description. lowercased ( ) )
242
+ }
243
+
244
+ // MARK: - ExpressibleByStringLiteral
245
+
246
+ public init ( stringLiteral value: StringLiteralType ) {
247
+ try ! self . init ( validating: value)
248
+ }
249
+ }
250
+ }
251
+
252
+ // MARK: -
253
+
97
254
struct LegacyPackageIdentity : PackageIdentityProvider , Equatable {
98
255
/// A textual representation of this instance.
99
256
public let description : String
0 commit comments