@@ -94,6 +94,163 @@ extension PackageIdentity: JSONMappable, JSONSerializable {
9494
9595// MARK: -
9696
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+
97254struct LegacyPackageIdentity : PackageIdentityProvider , Equatable {
98255 /// A textual representation of this instance.
99256 public let description : String
0 commit comments