@@ -419,50 +419,71 @@ public struct CanonicalPackageLocation: Equatable, CustomStringConvertible {
419
419
420
420
/// Instantiates an instance of the conforming type from a string representation.
421
421
public init ( _ string: String ) {
422
- var description = string. precomposedStringWithCanonicalMapping. lowercased ( )
422
+ self . description = computeCanonicalLocation ( string) . description
423
+ }
424
+ }
423
425
424
- // Remove the scheme component, if present.
425
- let detectedScheme = description. dropSchemeComponentPrefixIfPresent ( )
426
+ /// Similar to `CanonicalPackageLocation` but differentiates based on the scheme.
427
+ public struct CanonicalPackageURL : CustomStringConvertible {
428
+ public let description : String
429
+ public let scheme : String ?
426
430
427
- // Remove the userinfo subcomponent (user / password), if present.
428
- if case ( let user, _) ? = description. dropUserinfoSubcomponentPrefixIfPresent ( ) {
429
- // If a user was provided, perform tilde expansion, if applicable.
430
- description. replaceFirstOccurenceIfPresent ( of: " /~/ " , with: " /~ \( user) / " )
431
- }
431
+ public init ( _ string: String ) {
432
+ let location = computeCanonicalLocation ( string)
433
+ self . description = location. description
434
+ self . scheme = location. scheme
435
+ }
436
+ }
432
437
433
- // Remove the port subcomponent, if present.
434
- description . removePortComponentIfPresent ( )
438
+ private func computeCanonicalLocation ( _ string : String ) -> ( description : String , scheme : String ? ) {
439
+ var description = string . precomposedStringWithCanonicalMapping . lowercased ( )
435
440
436
- // Remove the fragment component, if present.
437
- description. removeFragmentComponentIfPresent ( )
441
+ // Remove the scheme component, if present.
442
+ let detectedScheme = description. dropSchemeComponentPrefixIfPresent ( )
443
+ var scheme = detectedScheme
438
444
439
- // Remove the query component, if present.
440
- description. removeQueryComponentIfPresent ( )
445
+ // Remove the userinfo subcomponent (user / password), if present.
446
+ if case ( let user, _) ? = description. dropUserinfoSubcomponentPrefixIfPresent ( ) {
447
+ // If a user was provided, perform tilde expansion, if applicable.
448
+ description. replaceFirstOccurenceIfPresent ( of: " /~/ " , with: " /~ \( user) / " )
441
449
442
- // Accomodate "`scp`-style" SSH URLs
443
- if detectedScheme == nil || detectedScheme == " ssh " {
444
- description. replaceFirstOccurenceIfPresent ( of: " : " , before: description. firstIndex ( of: " / " ) , with: " / " )
450
+ if user == " git " , scheme == nil {
451
+ scheme = " ssh "
445
452
}
453
+ }
446
454
447
- // Split the remaining string into path components,
448
- // filtering out empty path components and removing valid percent encodings.
449
- var components = description. split ( omittingEmptySubsequences: true , whereSeparator: isSeparator)
450
- . compactMap { $0. removingPercentEncoding ?? String ( $0) }
455
+ // Remove the port subcomponent, if present.
456
+ description. removePortComponentIfPresent ( )
451
457
452
- // Remove the `.git` suffix from the last path component.
453
- var lastPathComponent = components. popLast ( ) ?? " "
454
- lastPathComponent. removeSuffixIfPresent ( " .git " )
455
- components. append ( lastPathComponent)
458
+ // Remove the fragment component, if present.
459
+ description. removeFragmentComponentIfPresent ( )
456
460
457
- description = components. joined ( separator: " / " )
461
+ // Remove the query component, if present.
462
+ description. removeQueryComponentIfPresent ( )
458
463
459
- // Prepend a leading slash for file URLs and paths
460
- if detectedScheme == " file " || string. first. flatMap ( isSeparator) ?? false {
461
- description. insert ( " / " , at: description. startIndex)
462
- }
464
+ // Accomodate "`scp`-style" SSH URLs
465
+ if detectedScheme == nil || detectedScheme == " ssh " {
466
+ description. replaceFirstOccurenceIfPresent ( of: " : " , before: description. firstIndex ( of: " / " ) , with: " / " )
467
+ }
468
+
469
+ // Split the remaining string into path components,
470
+ // filtering out empty path components and removing valid percent encodings.
471
+ var components = description. split ( omittingEmptySubsequences: true , whereSeparator: isSeparator)
472
+ . compactMap { $0. removingPercentEncoding ?? String ( $0) }
463
473
464
- self . description = description
474
+ // Remove the `.git` suffix from the last path component.
475
+ var lastPathComponent = components. popLast ( ) ?? " "
476
+ lastPathComponent. removeSuffixIfPresent ( " .git " )
477
+ components. append ( lastPathComponent)
478
+
479
+ description = components. joined ( separator: " / " )
480
+
481
+ // Prepend a leading slash for file URLs and paths
482
+ if detectedScheme == " file " || string. first. flatMap ( isSeparator) ?? false {
483
+ description. insert ( " / " , at: description. startIndex)
465
484
}
485
+
486
+ return ( description, scheme)
466
487
}
467
488
468
489
#if os(Windows)
0 commit comments