├── Sources ├── URLPatternClient │ └── main.swift ├── URLPatternMacros │ ├── Extensions │ │ └── String+Extensions.swift │ ├── URLPatternPlugin.swift │ ├── Utils │ │ └── URLPatternError.swift │ ├── URLPatternMacro.swift │ └── URLPathMacro.swift └── URLPattern │ └── URLPattern.swift ├── URLPatternExample ├── URLPatternExample │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── View │ │ ├── HomeView.swift │ │ ├── PostView.swift │ │ ├── SettingView.swift │ │ └── CommentView.swift │ ├── URLPatternExampleApp.swift │ ├── DeepLink.swift │ ├── Route.swift │ └── RootView.swift ├── URLPatternExample.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── swiftpm │ │ │ └── Package.resolved │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── URLPatternExample.xcscheme │ └── project.pbxproj └── URLPatternExampleTests │ └── URLPatternExampleTests.swift ├── Package.resolved ├── .github └── workflows │ └── swift.yml ├── LICENSE ├── Package.swift ├── .gitignore ├── README.md └── Tests └── URLPatternTests └── URLPatternTests.swift /Sources/URLPatternClient/main.swift: -------------------------------------------------------------------------------- 1 | import URLPattern 2 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/URLPatternMacros/Extensions/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | var isURLPathValue: Bool { self.hasPrefix("{") && self.hasSuffix("}") && self.utf16.count >= 3 } 5 | } 6 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/URLPatternMacros/URLPatternPlugin.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | import Foundation 6 | 7 | @main 8 | struct URLPatternPlugin: CompilerPlugin { 9 | let providingMacros: [Macro.Type] = [URLPatternMacro.self, URLPathMacro.self] 10 | } 11 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/View/HomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeView.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HomeView: View { 11 | var body: some View { 12 | Text("🏠") 13 | .font(.largeTitle) 14 | .navigationTitle("Home") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/URLPattern/URLPattern.swift: -------------------------------------------------------------------------------- 1 | @_exported import Foundation 2 | 3 | @attached(member, names: arbitrary) 4 | public macro URLPattern() = #externalMacro(module: "URLPatternMacros", type: "URLPatternMacro") 5 | 6 | @attached(peer, names: arbitrary) 7 | public macro URLPath(_ path: String) = #externalMacro(module: "URLPatternMacros", type: "URLPathMacro") 8 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/URLPatternExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLPatternExampleApp.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/3/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct URLPatternExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | RootView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/URLPatternMacros/Utils/URLPatternError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct URLPatternError: LocalizedError { 4 | let errorDescription: String 5 | 6 | init(_ errorDescription: String) { 7 | self.errorDescription = errorDescription 8 | } 9 | } 10 | 11 | extension URLPatternError: CustomStringConvertible { 12 | var description: String { self.errorDescription } 13 | } 14 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/View/PostView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostView.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PostView: View { 11 | let postId: String 12 | 13 | var body: some View { 14 | Text("📮 postId: \(postId)") 15 | .font(.largeTitle) 16 | .navigationTitle("Post") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/View/SettingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingView.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SettingView: View { 11 | let number: Int 12 | 13 | var body: some View { 14 | Text("⚙️ number: \(number)") 15 | .font(.largeTitle) 16 | .navigationTitle("Setting") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/View/CommentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommentView.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CommentView: View { 11 | let commentId: String 12 | 13 | var body: some View { 14 | Text("💬 commentId: \(commentId)") 15 | .font(.largeTitle) 16 | .navigationTitle("Comment") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "a3de0173b246e82aff67cee90e8455823733a63c034b091a81ef3d81558d8a5a", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-syntax", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/swiftlang/swift-syntax.git", 8 | "state" : { 9 | "revision" : "0687f71944021d616d34d922343dcef086855920", 10 | "version" : "600.0.1" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/DeepLink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeepLink.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/4/25. 6 | // 7 | 8 | import URLPattern 9 | 10 | @URLPattern 11 | enum DeepLink: Equatable { 12 | @URLPath("/home") 13 | case home 14 | 15 | @URLPath("/posts/{postId}") 16 | case post(postId: String) 17 | 18 | @URLPath("/posts/{postId}/comments/{commentId}") 19 | case postComment(postId: String, commentId: String) 20 | 21 | @URLPath("/setting/{number}") 22 | case setting(number: Int) 23 | } 24 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "b71e74ae1ae16b256751a8f504b75f549eaf477761821c6159aa06486b495baa", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-syntax", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/swiftlang/swift-syntax.git", 8 | "state" : { 9 | "revision" : "0687f71944021d616d34d922343dcef086855920", 10 | "version" : "600.0.1" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/Route.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Route.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum Route: Hashable, View { 11 | case home 12 | case post(postId: String) 13 | case comment(commentId: String) 14 | case setting(number: Int) 15 | 16 | var body: some View { 17 | switch self { 18 | case .home: 19 | HomeView() 20 | case .post(let postId): 21 | PostView(postId: postId) 22 | case .comment(let commentId): 23 | CommentView(commentId: commentId) 24 | case .setting(let number): 25 | SettingView(number: number) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Swift project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Swift 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | runs-on: macos-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set Xcode Version 16.1.0 20 | run: sudo xcode-select -s /Applications/Xcode_16.1.app 21 | 22 | - name: Test URLPattern 23 | run: swift test -v 24 | 25 | - name: Test URLPatternExample 26 | run: | 27 | xcodebuild \ 28 | -project URLPatternExample/URLPatternExample.xcodeproj \ 29 | -scheme URLPatternExample \ 30 | -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \ 31 | test 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Won Heo (Woody) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | import CompilerPluginSupport 6 | 7 | let package = Package( 8 | name: "URLPattern", 9 | platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], 10 | products: [ 11 | .library( 12 | name: "URLPattern", 13 | targets: ["URLPattern"] 14 | ), 15 | .executable( 16 | name: "URLPatternClient", 17 | targets: ["URLPatternClient"] 18 | ), 19 | ], 20 | dependencies: [ 21 | .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"603.0.0"), 22 | ], 23 | targets: [ 24 | .macro( 25 | name: "URLPatternMacros", 26 | dependencies: [ 27 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 28 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax") 29 | ] 30 | ), 31 | .target(name: "URLPattern", dependencies: ["URLPatternMacros"]), 32 | .executableTarget(name: "URLPatternClient", dependencies: ["URLPattern"]), 33 | .testTarget( 34 | name: "URLPatternTests", 35 | dependencies: [ 36 | "URLPatternMacros", 37 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), 38 | ] 39 | ), 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootView.swift 3 | // URLPatternExample 4 | // 5 | // Created by woody on 4/3/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RootView: View { 11 | @State private var path = NavigationPath() 12 | 13 | var body: some View { 14 | NavigationStack(path: $path) { 15 | List { 16 | Section("DeepLink Example") { 17 | Text("https://domain/home") 18 | Text("https://domain/posts/1") 19 | Text("https://domain/posts/1/comments/2") 20 | Text("https://domain/setting/1") 21 | } 22 | Section("Invalid DeepLink") { 23 | Text("https://home") 24 | Text("https://domain/homes") 25 | Text("https://domain/post/1") 26 | Text("https://domain/setting/string") 27 | } 28 | } 29 | .navigationTitle("URLPattern Example") 30 | .environment(\.openURL, OpenURLAction { url in 31 | guard let deepLink = DeepLink(url: url) else { 32 | return .discarded 33 | } 34 | 35 | switch deepLink { 36 | case .home: 37 | path.append(Route.home) 38 | case .post(let postId): 39 | path.append(Route.post(postId: postId)) 40 | case .postComment(let postId, let commentId): 41 | path.append(Route.post(postId: postId)) 42 | path.append(Route.comment(commentId: commentId)) 43 | case .setting(let number): 44 | path.append(Route.setting(number: number)) 45 | } 46 | return .handled 47 | }) 48 | .navigationDestination(for: Route.self) { route in 49 | route 50 | } 51 | } 52 | } 53 | } 54 | 55 | #Preview { 56 | RootView() 57 | } 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## Obj-C/Swift specific 9 | *.hmap 10 | 11 | ## App packaging 12 | *.ipa 13 | *.dSYM.zip 14 | *.dSYM 15 | 16 | ## Playgrounds 17 | timeline.xctimeline 18 | playground.xcworkspace 19 | 20 | # Swift Package Manager 21 | # 22 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 23 | # Packages/ 24 | # Package.pins 25 | # Package.resolved 26 | # *.xcodeproj 27 | # 28 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 29 | # hence it is not needed unless you have added a package configuration file to your project 30 | # .swiftpm 31 | 32 | .build/ 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 39 | # 40 | # Pods/ 41 | # 42 | # Add this line if you want to avoid checking in source code from the Xcode workspace 43 | # *.xcworkspace 44 | 45 | # Carthage 46 | # 47 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 48 | # Carthage/Checkouts 49 | 50 | Carthage/Build/ 51 | 52 | # fastlane 53 | # 54 | # It is recommended to not store the screenshots in the git repo. 55 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 56 | # For more information about the recommended setup visit: 57 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 58 | 59 | fastlane/report.xml 60 | fastlane/Preview.html 61 | fastlane/screenshots/**/*.png 62 | fastlane/test_output 63 | .DS_Store 64 | /.build 65 | /Packages 66 | xcuserdata/ 67 | DerivedData/ 68 | .swiftpm/configuration/registries.json 69 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 70 | .netrc 71 | -------------------------------------------------------------------------------- /Sources/URLPatternMacros/URLPatternMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | import Foundation 6 | 7 | public struct URLPatternMacro: MemberMacro { 8 | public static func expansion( 9 | of node: AttributeSyntax, 10 | providingMembersOf declaration: some DeclGroupSyntax, 11 | in context: some MacroExpansionContext 12 | ) throws -> [DeclSyntax] { 13 | guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { 14 | throw URLPatternError("@URLPatternMacro can only be applied to enums") 15 | } 16 | 17 | let cases = enumDecl.memberBlock.members.compactMap { member -> String? in 18 | guard 19 | let caseDecl = member.decl.as(EnumCaseDeclSyntax.self), 20 | caseDecl.attributes.contains(where: { 21 | $0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "URLPath" 22 | }) 23 | else { 24 | return nil 25 | } 26 | 27 | return caseDecl.elements.first?.name.text 28 | } 29 | 30 | guard Set(cases).count == cases.count else { 31 | throw URLPatternError("Duplicate case names are not allowed") 32 | } 33 | 34 | let urlInitializer = try InitializerDeclSyntax("init?(url: URL)") { 35 | for caseName in cases { 36 | """ 37 | if let urlPattern = Self.\(raw: caseName)(url) { 38 | self = urlPattern 39 | return 40 | } 41 | """ 42 | } 43 | 44 | """ 45 | return nil 46 | """ 47 | } 48 | 49 | let isValidURLPathsMethod = try FunctionDeclSyntax(""" 50 | static func isValidURLPaths(inputPaths inputs: [String], patternPaths patterns: [String]) -> Bool { 51 | guard inputs.count == patterns.count else { return false } 52 | 53 | return zip(inputs, patterns).allSatisfy { input, pattern in 54 | guard Self.isURLPathValue(pattern) else { return input == pattern } 55 | 56 | return true 57 | } 58 | } 59 | """) 60 | 61 | let isURLPathValueMethod = try FunctionDeclSyntax(""" 62 | static func isURLPathValue(_ string: String) -> Bool { 63 | return string.hasPrefix("{") && string.hasSuffix("}") && string.utf16.count >= 3 64 | } 65 | """) 66 | 67 | return [ 68 | DeclSyntax(urlInitializer), 69 | DeclSyntax(isValidURLPathsMethod), 70 | DeclSyntax(isURLPathValueMethod) 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample.xcodeproj/xcshareddata/xcschemes/URLPatternExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 36 | 42 | 43 | 44 | 45 | 46 | 56 | 58 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # URLPattern 2 | [![Swift](https://github.com/heoblitz/URLPattern/actions/workflows/swift.yml/badge.svg?branch=main)](https://github.com/heoblitz/URLPattern/actions/workflows/swift.yml) 3 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fheoblitz%2FURLPattern%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/heoblitz/URLPattern) 4 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fheoblitz%2FURLPattern%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/heoblitz/URLPattern) 5 | 6 | A Swift Macro that helps mapping URLs to Enum cases. 7 | 8 | https://github.com/user-attachments/assets/07bb37fd-f82f-43eb-b34a-ec9bed7f29c8 9 | 10 | ## Overview 11 | 12 | URL deep linking is a fundamental technology widely used in most services today. However, in Swift environments, implementing deep linking typically requires direct URL path manipulation or regex usage: 13 | 14 | ```swift 15 | // Traditional approach with manual URL handling 16 | let paths = url.pathComponents 17 | 18 | if paths.count == 2 && paths[1] == "home" { 19 | // Handle home 20 | } else let match = try? url.path.firstMatch(of: /\/posts\/([^\/]+)$/) { 21 | // Handle posts 22 | } 23 | ``` 24 | 25 | This approach reduces code readability and scalability, and importantly, cannot validate incorrect patterns at compile-time. 26 | 27 | URLPattern solves these issues by providing compile-time URL validation and value mapping: 28 | 29 | ```swift 30 | @URLPattern 31 | enum DeepLink { 32 | @URLPath("/home") 33 | case home 34 | 35 | @URLPath("/posts/{postId}") 36 | case post(postId: String) 37 | 38 | @URLPath("/posts/{postId}/comments/{commentId}") 39 | case postComment(postId: String, commentId: String) 40 | } 41 | ``` 42 | 43 | ## Features 44 | 45 | - **Compile-time Validation**: Ensures URL path values and associated value names match correctly 46 | - **Automatic Enum Generation**: Creates initializers that map URL components to enum associated values 47 | - **Type Support**: 48 | - Built-in support for `String`, `Int`, `Float`, and `Double` 49 | - Non-String types (Int, Float, Double) use String-based initialization 50 | 51 | ## Usage 52 | 53 | ```swift 54 | @URLPattern 55 | enum DeepLink { 56 | @URLPath("/posts/{postId}") 57 | case post(postId: String) 58 | 59 | @URLPath("/posts/{postId}/comments/{commentId}") 60 | case postComment(postId: String, commentId: String) 61 | 62 | @URLPath("/f/{first}/s/{second}") 63 | case reverse(second: Int, first: Int) 64 | } 65 | ``` 66 | 67 | 1. Declare the `@URLPattern` macro on your enum. 68 | 69 | 2. Add `@URLPath` macro to enum cases with the desired URL pattern. 70 | 71 | 3. Use path values with `{associated_value_name}` syntax to map URL components to associated value names. If mapping code is duplicated, the topmost enum case takes precedence. 72 | 73 | 74 | ```swift 75 | // ✅ Valid URLs 76 | DeepLink(url: URL(string: "/posts/1")!) == .post(postId: "1") 77 | DeepLink(url: URL(string: "/posts/1/comments/2")!) == .postComment(postId: "1", commentId: "2") 78 | DeepLink(url: URL(string: "/f/1/s/2")!) == .reverse(second: 2, first: 1) 79 | 80 | // ❌ Invalid URLs 81 | DeepLink(url: URL(string: "/post/1")!) == nil 82 | DeepLink(url: URL(string: "/posts/1/comments")!) == nil 83 | DeepLink(url: URL(string: "/f/string/s/string")!) == nil 84 | ``` 85 | 4. Use the `Enum.init(url: URL)` generated initializer. 86 | ```swift 87 | if let deepLink = DeepLink(url: incomingURL) { 88 | switch deepLink { 89 | case .post(let postId): 90 | // Handle post 91 | case .postComment(let postId, let commentId): 92 | // Handle postComment 93 | } 94 | } 95 | ``` 96 | 5. Implement a deep link using an enum switch statement. 97 | - For more detailed examples, please refer to the [Example project](https://github.com/heoblitz/URLPattern/tree/main/URLPatternExample). 98 | 99 | ## Rules 100 | 101 | - **Unique Enum Case Names**: Enum case names must be unique for better readability of expanded macro code. 102 | - **Unique Associated Value Names**: Associated value names within each case must be unique. 103 | - **Valid URL Patterns**: Arguments passed to @URLPath macro must be in valid URL path format. 104 | - **Supported Types**: Only String, Int, Float, and Double are supported. 105 | 106 | ## Installation 107 | ### Swift Package Manager 108 | Project > Project Dependencies > Add   `https://github.com/heoblitz/URLPattern.git` 109 | -------------------------------------------------------------------------------- /Sources/URLPatternMacros/URLPathMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | import Foundation 6 | 7 | public struct URLPathMacro: PeerMacro { 8 | enum SupportedType: String { 9 | case String 10 | case Int 11 | case Double 12 | case Float 13 | } 14 | 15 | struct PatternParam: Hashable { 16 | let name: String 17 | let type: SupportedType 18 | let pathIndex: Int 19 | let caseIndex: Int 20 | } 21 | 22 | public static func expansion( 23 | of node: AttributeSyntax, 24 | providingPeersOf declaration: some DeclSyntaxProtocol, 25 | in context: some MacroExpansionContext 26 | ) throws -> [DeclSyntax] { 27 | guard 28 | let enumCase = declaration.as(EnumCaseDeclSyntax.self), 29 | let element = enumCase.elements.first 30 | else { 31 | throw URLPatternError("@URLPathMacro can only be applied to enum cases") 32 | } 33 | 34 | guard 35 | let argument = node.arguments?.as(LabeledExprListSyntax.self)?.first, 36 | let pathString = argument.expression.as(StringLiteralExprSyntax.self)?.segments.first?.description 37 | else { 38 | throw URLPatternError("URLPath is nil") 39 | } 40 | 41 | guard let pathURL = URL(string: pathString) else { 42 | throw URLPatternError("URLPath is not in a valid URL format") 43 | } 44 | 45 | let patternPaths = pathURL.pathComponents 46 | 47 | let caseAssociatedTypes = try element.parameterClause?.parameters.map { param -> (name: String, type: SupportedType) in 48 | let name = param.firstName?.text ?? "" 49 | let type = param.type.description 50 | 51 | guard let supportedType = SupportedType(rawValue: type) else { 52 | throw URLPatternError("\(type) is not supported as an associated value") 53 | } 54 | return (name: name, type: supportedType) 55 | } ?? [] 56 | 57 | let patternParams: [PatternParam] = try patternPaths.enumerated() 58 | .filter { index, value in value.isURLPathValue } 59 | .map { pathIndex, value -> PatternParam in 60 | let name = String(value.dropFirst().dropLast()) 61 | 62 | guard let (caseIndex, caseAssociatedType) = caseAssociatedTypes.enumerated().first(where: { name == $0.element.name }) else { 63 | throw URLPatternError("URLPath value \"\(name)\" cannot be found in the associated value") 64 | } 65 | 66 | return PatternParam( 67 | name: name, 68 | type: caseAssociatedType.type, 69 | pathIndex: pathIndex, 70 | caseIndex: caseIndex 71 | ) 72 | } 73 | .sorted(by: { $0.caseIndex < $1.caseIndex }) 74 | 75 | let patternNames = Set(patternParams.map(\.name)) 76 | let caseNames = Set(caseAssociatedTypes.map(\.name)) 77 | 78 | guard patternNames.count == patternParams.count else { 79 | throw URLPatternError("The name of an URLPath value cannot be duplicated") 80 | } 81 | 82 | guard caseNames.count == caseAssociatedTypes.count else { 83 | throw URLPatternError("The name of an associated value cannot be duplicated") 84 | } 85 | 86 | guard patternNames.count == caseNames.count else { 87 | throw URLPatternError("The number of associated values does not match URLPath") 88 | } 89 | 90 | guard patternNames == caseNames else { 91 | throw URLPatternError("The name of the URLPath value does not match the associated value") 92 | } 93 | 94 | let staticMethod = try FunctionDeclSyntax(""" 95 | static func \(element.name)(_ url: URL) -> Self? { 96 | let inputPaths = url.pathComponents 97 | let patternPaths = \(raw: patternPaths) 98 | 99 | guard isValidURLPaths(inputPaths: inputPaths, patternPaths: patternPaths) else { return nil } 100 | \(raw: patternParams.map { param in 101 | switch param.type { 102 | case .Double, .Float, .Int: 103 | """ 104 | guard let \(param.name) = \(param.type.rawValue)(inputPaths[\(param.pathIndex)]) else { return nil } 105 | """ 106 | case .String: 107 | """ 108 | let \(param.name) = inputPaths[\(param.pathIndex)] 109 | """ 110 | } 111 | }.joined(separator: "\n")) 112 | return \(raw: patternParams.isEmpty 113 | ? ".\(element.name.text)" 114 | : ".\(element.name.text)(\(patternParams.map { "\($0.name): \($0.name)" }.joined(separator: ", ")))") 115 | } 116 | """) 117 | 118 | return [DeclSyntax(staticMethod)] 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Tests/URLPatternTests/URLPatternTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | import SwiftSyntaxMacrosTestSupport 5 | import XCTest 6 | 7 | #if canImport(URLPatternMacros) 8 | import URLPatternMacros 9 | 10 | let testMacros: [String: Macro.Type] = [ 11 | "URLPattern": URLPatternMacro.self, 12 | "URLPath": URLPathMacro.self 13 | ] 14 | #endif 15 | 16 | final class URLPatternTests: XCTestCase { 17 | func testDeepLinkMacro_normalCase() throws { 18 | assertMacroExpansion( 19 | """ 20 | @URLPattern 21 | enum DeepLink: Equatable { 22 | @URLPath("/home") 23 | case home 24 | 25 | @URLPath("/posts/{postId}") 26 | case post(postId: String) 27 | 28 | @URLPath("/posts/{postId}/comments/{commentId}") 29 | case postComment(postId: String, commentId: String) 30 | 31 | @URLPath("/c/{cNum}/b/{bNum}/a/{aNum}") 32 | case complex(aNum: Int, bNum: Int, cNum: Int) 33 | } 34 | """, 35 | expandedSource: """ 36 | enum DeepLink: Equatable { 37 | case home 38 | 39 | static func home(_ url: URL) -> Self? { 40 | let inputPaths = url.pathComponents 41 | let patternPaths = ["/", "home"] 42 | 43 | guard isValidURLPaths(inputPaths: inputPaths, patternPaths: patternPaths) else { 44 | return nil 45 | } 46 | 47 | return .home 48 | } 49 | 50 | case post(postId: String) 51 | 52 | static func post(_ url: URL) -> Self? { 53 | let inputPaths = url.pathComponents 54 | let patternPaths = ["/", "posts", "{postId}"] 55 | 56 | guard isValidURLPaths(inputPaths: inputPaths, patternPaths: patternPaths) else { 57 | return nil 58 | } 59 | let postId = inputPaths[2] 60 | return .post(postId: postId) 61 | } 62 | 63 | case postComment(postId: String, commentId: String) 64 | 65 | static func postComment(_ url: URL) -> Self? { 66 | let inputPaths = url.pathComponents 67 | let patternPaths = ["/", "posts", "{postId}", "comments", "{commentId}"] 68 | 69 | guard isValidURLPaths(inputPaths: inputPaths, patternPaths: patternPaths) else { 70 | return nil 71 | } 72 | let postId = inputPaths[2] 73 | let commentId = inputPaths[4] 74 | return .postComment(postId: postId, commentId: commentId) 75 | } 76 | 77 | case complex(aNum: Int, bNum: Int, cNum: Int) 78 | 79 | static func complex(_ url: URL) -> Self? { 80 | let inputPaths = url.pathComponents 81 | let patternPaths = ["/", "c", "{cNum}", "b", "{bNum}", "a", "{aNum}"] 82 | 83 | guard isValidURLPaths(inputPaths: inputPaths, patternPaths: patternPaths) else { 84 | return nil 85 | } 86 | guard let aNum = Int(inputPaths[6]) else { 87 | return nil 88 | } 89 | guard let bNum = Int(inputPaths[4]) else { 90 | return nil 91 | } 92 | guard let cNum = Int(inputPaths[2]) else { 93 | return nil 94 | } 95 | return .complex(aNum: aNum, bNum: bNum, cNum: cNum) 96 | } 97 | 98 | init?(url: URL) { 99 | if let urlPattern = Self.home(url) { 100 | self = urlPattern 101 | return 102 | } 103 | if let urlPattern = Self.post(url) { 104 | self = urlPattern 105 | return 106 | } 107 | if let urlPattern = Self.postComment(url) { 108 | self = urlPattern 109 | return 110 | } 111 | if let urlPattern = Self.complex(url) { 112 | self = urlPattern 113 | return 114 | } 115 | return nil 116 | } 117 | 118 | static func isValidURLPaths(inputPaths inputs: [String], patternPaths patterns: [String]) -> Bool { 119 | guard inputs.count == patterns.count else { 120 | return false 121 | } 122 | 123 | return zip(inputs, patterns).allSatisfy { input, pattern in 124 | guard Self.isURLPathValue(pattern) else { 125 | return input == pattern 126 | } 127 | 128 | return true 129 | } 130 | } 131 | 132 | static func isURLPathValue(_ string: String) -> Bool { 133 | return string.hasPrefix("{") && string.hasSuffix("}") && string.utf16.count >= 3 134 | } 135 | } 136 | """, 137 | macros: testMacros 138 | ) 139 | } 140 | 141 | func testDeepLinkMacro_shouldIgnoreNoneMacroCase() throws { 142 | assertMacroExpansion( 143 | """ 144 | @URLPattern 145 | enum DeepLink: Equatable { 146 | @URLPath("/home") 147 | case home 148 | 149 | case complex(aNum: Int, bNum: Int, cNum: Int) 150 | } 151 | """, 152 | expandedSource: """ 153 | enum DeepLink: Equatable { 154 | case home 155 | 156 | static func home(_ url: URL) -> Self? { 157 | let inputPaths = url.pathComponents 158 | let patternPaths = ["/", "home"] 159 | 160 | guard isValidURLPaths(inputPaths: inputPaths, patternPaths: patternPaths) else { 161 | return nil 162 | } 163 | 164 | return .home 165 | } 166 | 167 | case complex(aNum: Int, bNum: Int, cNum: Int) 168 | 169 | init?(url: URL) { 170 | if let urlPattern = Self.home(url) { 171 | self = urlPattern 172 | return 173 | } 174 | return nil 175 | } 176 | 177 | static func isValidURLPaths(inputPaths inputs: [String], patternPaths patterns: [String]) -> Bool { 178 | guard inputs.count == patterns.count else { 179 | return false 180 | } 181 | 182 | return zip(inputs, patterns).allSatisfy { input, pattern in 183 | guard Self.isURLPathValue(pattern) else { 184 | return input == pattern 185 | } 186 | 187 | return true 188 | } 189 | } 190 | 191 | static func isURLPathValue(_ string: String) -> Bool { 192 | return string.hasPrefix("{") && string.hasSuffix("}") && string.utf16.count >= 3 193 | } 194 | } 195 | """, 196 | macros: testMacros 197 | ) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExampleTests/URLPatternExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLPatternExampleTests.swift 3 | // URLPatternExampleTests 4 | // 5 | // Created by woody on 4/3/25. 6 | // 7 | 8 | import Testing 9 | import URLPattern 10 | 11 | @URLPattern 12 | enum DeepLinkMock: Equatable { 13 | @URLPath("/home") 14 | case home 15 | 16 | @URLPath("/posts/{postId}") 17 | case post(postId: String) 18 | 19 | @URLPath("/posts/{postId}/comments/{commentId}") 20 | case postComment(postId: String, commentId: String) 21 | 22 | @URLPath("/setting/phone/{number}") 23 | case setting(number: Int) 24 | 25 | @URLPath("/int/{int}") 26 | case int(int: Int) 27 | 28 | @URLPath("/float/{float}") 29 | case float(float: Float) 30 | 31 | @URLPath("/string/{string}") 32 | case string(string: String) 33 | 34 | @URLPath("/c/{cNum}/b/{bNum}/a/{aNum}") 35 | case complex(aNum: Int, bNum: Int, cNum: Int) 36 | } 37 | 38 | struct URLPatternExampleTests { 39 | // MARK: - Home Tests 40 | @Test("Valid Home URLPatterns", arguments: [ 41 | "https://domain.com/home", 42 | "/home" 43 | ]) 44 | func parseHome_success(urlString: String) async throws { 45 | let url = try #require(URL(string: urlString)) 46 | let deepLink = try #require(DeepLinkMock(url: url)) 47 | #expect(deepLink == .home) 48 | } 49 | 50 | @Test("Invalid Home URLPatterns", arguments: [ 51 | "https://domain.com/home/12", 52 | "https://home", 53 | "home" 54 | ]) 55 | func parseHome_failure(urlString: String) async throws { 56 | let url = try #require(URL(string: urlString)) 57 | let deepLink = DeepLinkMock(url: url) 58 | #expect(deepLink == nil) 59 | } 60 | 61 | // MARK: - Post Tests 62 | @Test("Valid Post URLPatterns", arguments: [ 63 | "https://domain.com/posts/1", 64 | "/posts/1" 65 | ]) 66 | func parsePost_success(urlString: String) async throws { 67 | let url = try #require(URL(string: urlString)) 68 | let deepLink = try #require(DeepLinkMock(url: url)) 69 | #expect(deepLink == .post(postId: "1")) 70 | } 71 | 72 | @Test("Invalid Post URLPatterns", arguments: [ 73 | "https://domain.com/posts/1/test", 74 | "https://domain.com/post/1", 75 | "/post/1" 76 | ]) 77 | func parsePost_failure(urlString: String) async throws { 78 | let url = try #require(URL(string: urlString)) 79 | let deepLink = DeepLinkMock(url: url) 80 | #expect(deepLink == nil) 81 | } 82 | 83 | // MARK: - Post Comment Tests 84 | @Test("Valid PostComment URLPatterns", arguments: [ 85 | "https://domain.com/posts/1/comments/2", 86 | "/posts/1/comments/2" 87 | ]) 88 | func parsePostComment_success(urlString: String) async throws { 89 | let url = try #require(URL(string: urlString)) 90 | let deepLink = try #require(DeepLinkMock(url: url)) 91 | #expect(deepLink == .postComment(postId: "1", commentId: "2")) 92 | } 93 | 94 | @Test("Invalid PostComment URLPatterns", arguments: [ 95 | "https://domain.com/posts/1/comment/2", 96 | "/posts/1/comments", 97 | "/posts/comments/2" 98 | ]) 99 | func parsePostComment_failure(urlString: String) async throws { 100 | let url = try #require(URL(string: urlString)) 101 | let deepLink = DeepLinkMock(url: url) 102 | #expect(deepLink == nil) 103 | } 104 | 105 | // MARK: - Setting Tests 106 | @Test("Valid Setting URLPatterns", arguments: [ 107 | "https://domain.com/setting/phone/42", 108 | "/setting/phone/42" 109 | ]) 110 | func parseSetting_success(urlString: String) async throws { 111 | let url = try #require(URL(string: urlString)) 112 | let deepLink = try #require(DeepLinkMock(url: url)) 113 | #expect(deepLink == .setting(number: 42)) 114 | } 115 | 116 | @Test("Invalid Setting URLPatterns", arguments: [ 117 | "https://domain.com/setting/abc/42", 118 | "/setting/phone/12.34", 119 | "/setting/phone" 120 | ]) 121 | func parseSetting_failure(urlString: String) async throws { 122 | let url = try #require(URL(string: urlString)) 123 | let deepLink = DeepLinkMock(url: url) 124 | #expect(deepLink == nil) 125 | } 126 | 127 | // MARK: - Int Tests 128 | @Test("Valid Int URLPatterns", arguments: [ 129 | "https://domain.com/int/-42", 130 | "/int/-42" 131 | ]) 132 | func parseInt_success(urlString: String) async throws { 133 | let url = try #require(URL(string: urlString)) 134 | let deepLink = try #require(DeepLinkMock(url: url)) 135 | #expect(deepLink == .int(int: -42)) 136 | } 137 | 138 | @Test("Invalid Int URLPatterns", arguments: [ 139 | "https://domain.com/int/abc", 140 | "/int/12.34", 141 | "/int/" 142 | ]) 143 | func parseInt_failure(urlString: String) async throws { 144 | let url = try #require(URL(string: urlString)) 145 | let deepLink = DeepLinkMock(url: url) 146 | #expect(deepLink == nil) 147 | } 148 | 149 | // MARK: - Float Tests 150 | @Test("Valid Float URLPatterns", arguments: [ 151 | "https://domain.com/float/42.5", 152 | "/float/42.5" 153 | ]) 154 | func parseFloat_success(urlString: String) async throws { 155 | let url = try #require(URL(string: urlString)) 156 | let deepLink = try #require(DeepLinkMock(url: url)) 157 | #expect(deepLink == .float(float: 42.5)) 158 | } 159 | 160 | @Test("Invalid Float URLPatterns", arguments: [ 161 | "https://domain.com/float/abc", 162 | "/float/", 163 | "/float/." 164 | ]) 165 | func parseFloat_failure(urlString: String) async throws { 166 | let url = try #require(URL(string: urlString)) 167 | let deepLink = DeepLinkMock(url: url) 168 | #expect(deepLink == nil) 169 | } 170 | 171 | // MARK: - String Tests 172 | @Test("Valid String URLPatterns", arguments: [ 173 | "https://domain.com/string/hello", 174 | "/string/hello" 175 | ]) 176 | func parseString_success(urlString: String) async throws { 177 | let url = try #require(URL(string: urlString)) 178 | let deepLink = try #require(DeepLinkMock(url: url)) 179 | #expect(deepLink == .string(string: "hello")) 180 | } 181 | 182 | @Test("Invalid String URLPatterns", arguments: [ 183 | "https://domain.com/string", 184 | "/string/", 185 | "/string/test/extra" 186 | ]) 187 | func parseString_failure(urlString: String) async throws { 188 | let url = try #require(URL(string: urlString)) 189 | let deepLink = DeepLinkMock(url: url) 190 | #expect(deepLink == nil) 191 | } 192 | 193 | // MARK: - Complex Tests 194 | @Test("Valid Complex URLPatterns", arguments: [ 195 | "https://domain.com/c/1/b/2/a/3", 196 | "/c/1/b/2/a/3" 197 | ]) 198 | func parseComplex_success(urlString: String) async throws { 199 | let url = try #require(URL(string: urlString)) 200 | let deepLink = try #require(DeepLinkMock(url: url)) 201 | #expect(deepLink == .complex(aNum: 3, bNum: 2, cNum: 1)) 202 | } 203 | 204 | @Test("Invalid Complex URLPatterns", arguments: [ 205 | "https://domain.com/c/1/b/2/a/abc", 206 | "/c/1/b/abc/a/3", 207 | "/c/abc/b/2/a/3", 208 | "/c/1/b/2/a", 209 | "/c/1/b/a/3", 210 | "/a/1/b/2/c/3" 211 | ]) 212 | func parseComplex_failure(urlString: String) async throws { 213 | let url = try #require(URL(string: urlString)) 214 | let deepLink = DeepLinkMock(url: url) 215 | #expect(deepLink == nil) 216 | } 217 | 218 | // MARK: - Unicode Tests 219 | @Test("Valid Unicodes", arguments: [ 220 | "안녕하세요", 221 | "こんにちは", 222 | "☺️👍" 223 | ]) 224 | 225 | func paresPost_success_with_unicode(value: String) async throws { 226 | let url = try #require(URL(string: "/posts/\(value)")) 227 | let deepLink = DeepLinkMock(url: url) 228 | #expect(deepLink == .post(postId: value)) 229 | } 230 | 231 | // MARK: - Priority Tests 232 | @URLPattern 233 | enum PriorityTest: Equatable { 234 | @URLPath("/{a}/{b}") 235 | case all(a: String, b: String) 236 | 237 | @URLPath("/post/{postId}") 238 | case post(postId: Int) 239 | } 240 | 241 | @Test("Test Scope") 242 | func checkPriorityCases() async throws { 243 | let url = try #require(URL(string: "/post/1")) 244 | #expect(PriorityTest(url: url) == .all(a: "post", b: "1")) 245 | } 246 | 247 | // MARK: - Scope Tests 248 | @URLPattern 249 | enum ScopeTest { 250 | @URLPath("/") 251 | case zero 252 | 253 | @URLPath("/{a}") 254 | case one(a: String) 255 | 256 | @URLPath("/{a}/{b}") 257 | case two(a: String, b: String) 258 | 259 | @URLPath("/{a}/{b}/{c}") 260 | case three(a: String, b: String, c: String) 261 | 262 | @URLPath("/{a}/{b}/{c}/{d}") 263 | case four(a: String, b: String, c: String, d: String) 264 | 265 | @URLPath("/{a}/{b}/{c}/{d}/{e}") 266 | case five(a: String, b: String, c: String, d: String, e: String) 267 | 268 | @URLPath("/{a}/{b}/{c}/{d}/{e}/{f}") 269 | case six(a: String, b: String, c: String, d: String, e: String, f: String) 270 | 271 | @URLPath("/{a}/{b}/{c}/{d}/{e}/{f}/{g}") 272 | case seven(a: String, b: String, c: String, d: String, e: String, f: String, g: String) 273 | 274 | @URLPath("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}") 275 | case eight(a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String) 276 | 277 | @URLPath("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}") 278 | case nine(a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String, i: String) 279 | 280 | @URLPath("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}") 281 | case ten(a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String, i: String, j: String) 282 | } 283 | 284 | @Test("Test scope", arguments: Array(0...20)) 285 | func checkScope(num: Int) async throws { 286 | let url = try #require(URL(string: "/" + (0.. 10 { 289 | #expect(ScopeTest(url: url) == nil) 290 | } else { 291 | #expect(ScopeTest(url: url) != nil) 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /URLPatternExample/URLPatternExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EF43E6482D9ED87500809F76 /* URLPattern in Frameworks */ = {isa = PBXBuildFile; productRef = EF43E6472D9ED87500809F76 /* URLPattern */; }; 11 | EF43E64B2D9ED8A800809F76 /* URLPattern in Frameworks */ = {isa = PBXBuildFile; productRef = EF43E64A2D9ED8A800809F76 /* URLPattern */; }; 12 | EF43E64D2D9ED8A800809F76 /* URLPatternClient in Frameworks */ = {isa = PBXBuildFile; productRef = EF43E64C2D9ED8A800809F76 /* URLPatternClient */; }; 13 | EF43E6502D9ED93E00809F76 /* URLPattern in Frameworks */ = {isa = PBXBuildFile; productRef = EF43E64F2D9ED93E00809F76 /* URLPattern */; }; 14 | EF43E72D2D9FE2C300809F76 /* URLPattern in Frameworks */ = {isa = PBXBuildFile; productRef = EF43E72C2D9FE2C300809F76 /* URLPattern */; }; 15 | EF43E7302D9FE2CC00809F76 /* URLPattern in Frameworks */ = {isa = PBXBuildFile; productRef = EF43E72F2D9FE2CC00809F76 /* URLPattern */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | EF43E62A2D9ED86100809F76 /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = EF43E6112D9ED86000809F76 /* Project object */; 22 | proxyType = 1; 23 | remoteGlobalIDString = EF43E6182D9ED86000809F76; 24 | remoteInfo = URLPatternExample; 25 | }; 26 | /* End PBXContainerItemProxy section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | EF43E6192D9ED86000809F76 /* URLPatternExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = URLPatternExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | EF43E6292D9ED86100809F76 /* URLPatternExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = URLPatternExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 34 | EF43E61B2D9ED86000809F76 /* URLPatternExample */ = { 35 | isa = PBXFileSystemSynchronizedRootGroup; 36 | path = URLPatternExample; 37 | sourceTree = ""; 38 | }; 39 | EF43E62C2D9ED86100809F76 /* URLPatternExampleTests */ = { 40 | isa = PBXFileSystemSynchronizedRootGroup; 41 | path = URLPatternExampleTests; 42 | sourceTree = ""; 43 | }; 44 | /* End PBXFileSystemSynchronizedRootGroup section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | EF43E6162D9ED86000809F76 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | EF43E72D2D9FE2C300809F76 /* URLPattern in Frameworks */, 52 | EF43E6482D9ED87500809F76 /* URLPattern in Frameworks */, 53 | EF43E64D2D9ED8A800809F76 /* URLPatternClient in Frameworks */, 54 | EF43E64B2D9ED8A800809F76 /* URLPattern in Frameworks */, 55 | EF43E6502D9ED93E00809F76 /* URLPattern in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | EF43E6262D9ED86100809F76 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | EF43E7302D9FE2CC00809F76 /* URLPattern in Frameworks */, 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXFrameworksBuildPhase section */ 68 | 69 | /* Begin PBXGroup section */ 70 | EF43E6102D9ED86000809F76 = { 71 | isa = PBXGroup; 72 | children = ( 73 | EF43E61B2D9ED86000809F76 /* URLPatternExample */, 74 | EF43E62C2D9ED86100809F76 /* URLPatternExampleTests */, 75 | EF43E72E2D9FE2CC00809F76 /* Frameworks */, 76 | EF43E61A2D9ED86000809F76 /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | EF43E61A2D9ED86000809F76 /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | EF43E6192D9ED86000809F76 /* URLPatternExample.app */, 84 | EF43E6292D9ED86100809F76 /* URLPatternExampleTests.xctest */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | EF43E72E2D9FE2CC00809F76 /* Frameworks */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | ); 93 | name = Frameworks; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXNativeTarget section */ 99 | EF43E6182D9ED86000809F76 /* URLPatternExample */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = EF43E63D2D9ED86100809F76 /* Build configuration list for PBXNativeTarget "URLPatternExample" */; 102 | buildPhases = ( 103 | EF43E6152D9ED86000809F76 /* Sources */, 104 | EF43E6162D9ED86000809F76 /* Frameworks */, 105 | EF43E6172D9ED86000809F76 /* Resources */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | fileSystemSynchronizedGroups = ( 112 | EF43E61B2D9ED86000809F76 /* URLPatternExample */, 113 | ); 114 | name = URLPatternExample; 115 | packageProductDependencies = ( 116 | EF43E6472D9ED87500809F76 /* URLPattern */, 117 | EF43E64A2D9ED8A800809F76 /* URLPattern */, 118 | EF43E64C2D9ED8A800809F76 /* URLPatternClient */, 119 | EF43E64F2D9ED93E00809F76 /* URLPattern */, 120 | EF43E72C2D9FE2C300809F76 /* URLPattern */, 121 | ); 122 | productName = URLPatternExample; 123 | productReference = EF43E6192D9ED86000809F76 /* URLPatternExample.app */; 124 | productType = "com.apple.product-type.application"; 125 | }; 126 | EF43E6282D9ED86100809F76 /* URLPatternExampleTests */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = EF43E6402D9ED86100809F76 /* Build configuration list for PBXNativeTarget "URLPatternExampleTests" */; 129 | buildPhases = ( 130 | EF43E6252D9ED86100809F76 /* Sources */, 131 | EF43E6262D9ED86100809F76 /* Frameworks */, 132 | EF43E6272D9ED86100809F76 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | EF43E62B2D9ED86100809F76 /* PBXTargetDependency */, 138 | ); 139 | fileSystemSynchronizedGroups = ( 140 | EF43E62C2D9ED86100809F76 /* URLPatternExampleTests */, 141 | ); 142 | name = URLPatternExampleTests; 143 | packageProductDependencies = ( 144 | EF43E72F2D9FE2CC00809F76 /* URLPattern */, 145 | ); 146 | productName = URLPatternExampleTests; 147 | productReference = EF43E6292D9ED86100809F76 /* URLPatternExampleTests.xctest */; 148 | productType = "com.apple.product-type.bundle.unit-test"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | EF43E6112D9ED86000809F76 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | BuildIndependentTargetsInParallel = 1; 157 | LastSwiftUpdateCheck = 1620; 158 | LastUpgradeCheck = 1620; 159 | TargetAttributes = { 160 | EF43E6182D9ED86000809F76 = { 161 | CreatedOnToolsVersion = 16.2; 162 | }; 163 | EF43E6282D9ED86100809F76 = { 164 | CreatedOnToolsVersion = 16.2; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = EF43E6142D9ED86000809F76 /* Build configuration list for PBXProject "URLPatternExample" */; 169 | developmentRegion = en; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = EF43E6102D9ED86000809F76; 176 | minimizedProjectReferenceProxies = 1; 177 | packageReferences = ( 178 | EF43E72B2D9FE2C300809F76 /* XCLocalSwiftPackageReference "../../URLPattern" */, 179 | ); 180 | preferredProjectObjectVersion = 77; 181 | productRefGroup = EF43E61A2D9ED86000809F76 /* Products */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | EF43E6182D9ED86000809F76 /* URLPatternExample */, 186 | EF43E6282D9ED86100809F76 /* URLPatternExampleTests */, 187 | ); 188 | }; 189 | /* End PBXProject section */ 190 | 191 | /* Begin PBXResourcesBuildPhase section */ 192 | EF43E6172D9ED86000809F76 /* Resources */ = { 193 | isa = PBXResourcesBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | EF43E6272D9ED86100809F76 /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXSourcesBuildPhase section */ 209 | EF43E6152D9ED86000809F76 /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | EF43E6252D9ED86100809F76 /* Sources */ = { 217 | isa = PBXSourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | /* End PBXSourcesBuildPhase section */ 224 | 225 | /* Begin PBXTargetDependency section */ 226 | EF43E62B2D9ED86100809F76 /* PBXTargetDependency */ = { 227 | isa = PBXTargetDependency; 228 | target = EF43E6182D9ED86000809F76 /* URLPatternExample */; 229 | targetProxy = EF43E62A2D9ED86100809F76 /* PBXContainerItemProxy */; 230 | }; 231 | /* End PBXTargetDependency section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | EF43E63B2D9ED86100809F76 /* Debug */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 239 | CLANG_ANALYZER_NONNULL = YES; 240 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 242 | CLANG_ENABLE_MODULES = YES; 243 | CLANG_ENABLE_OBJC_ARC = YES; 244 | CLANG_ENABLE_OBJC_WEAK = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 261 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 262 | CLANG_WARN_STRICT_PROTOTYPES = YES; 263 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 264 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | COPY_PHASE_STRIP = NO; 268 | DEBUG_INFORMATION_FORMAT = dwarf; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | ENABLE_TESTABILITY = YES; 271 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu17; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 282 | GCC_WARN_UNDECLARED_SELECTOR = YES; 283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 284 | GCC_WARN_UNUSED_FUNCTION = YES; 285 | GCC_WARN_UNUSED_VARIABLE = YES; 286 | IPHONEOS_DEPLOYMENT_TARGET = 18.2; 287 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 288 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 289 | MTL_FAST_MATH = YES; 290 | ONLY_ACTIVE_ARCH = YES; 291 | SDKROOT = iphoneos; 292 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 293 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 294 | }; 295 | name = Debug; 296 | }; 297 | EF43E63C2D9ED86100809F76 /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_ENABLE_OBJC_WEAK = YES; 308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 309 | CLANG_WARN_BOOL_CONVERSION = YES; 310 | CLANG_WARN_COMMA = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 313 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 314 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INFINITE_RECURSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | COPY_PHASE_STRIP = NO; 331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 332 | ENABLE_NS_ASSERTIONS = NO; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu17; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 18.2; 344 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 345 | MTL_ENABLE_DEBUG_INFO = NO; 346 | MTL_FAST_MATH = YES; 347 | SDKROOT = iphoneos; 348 | SWIFT_COMPILATION_MODE = wholemodule; 349 | VALIDATE_PRODUCT = YES; 350 | }; 351 | name = Release; 352 | }; 353 | EF43E63E2D9ED86100809F76 /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 358 | CODE_SIGN_STYLE = Automatic; 359 | CURRENT_PROJECT_VERSION = 1; 360 | DEVELOPMENT_ASSET_PATHS = "\"URLPatternExample/Preview Content\""; 361 | DEVELOPMENT_TEAM = HK54HM2TC6; 362 | ENABLE_PREVIEWS = YES; 363 | GENERATE_INFOPLIST_FILE = YES; 364 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 365 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 366 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 367 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 368 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 369 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 370 | LD_RUNPATH_SEARCH_PATHS = ( 371 | "$(inherited)", 372 | "@executable_path/Frameworks", 373 | ); 374 | MARKETING_VERSION = 1.0; 375 | PRODUCT_BUNDLE_IDENTIFIER = com.heoblitz.URLPatternExample; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | SWIFT_EMIT_LOC_STRINGS = YES; 378 | SWIFT_VERSION = 5.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Debug; 382 | }; 383 | EF43E63F2D9ED86100809F76 /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 388 | CODE_SIGN_STYLE = Automatic; 389 | CURRENT_PROJECT_VERSION = 1; 390 | DEVELOPMENT_ASSET_PATHS = "\"URLPatternExample/Preview Content\""; 391 | DEVELOPMENT_TEAM = HK54HM2TC6; 392 | ENABLE_PREVIEWS = YES; 393 | GENERATE_INFOPLIST_FILE = YES; 394 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 395 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 396 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 397 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 398 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 399 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 400 | LD_RUNPATH_SEARCH_PATHS = ( 401 | "$(inherited)", 402 | "@executable_path/Frameworks", 403 | ); 404 | MARKETING_VERSION = 1.0; 405 | PRODUCT_BUNDLE_IDENTIFIER = com.heoblitz.URLPatternExample; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_EMIT_LOC_STRINGS = YES; 408 | SWIFT_VERSION = 5.0; 409 | TARGETED_DEVICE_FAMILY = "1,2"; 410 | }; 411 | name = Release; 412 | }; 413 | EF43E6412D9ED86100809F76 /* Debug */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | CODE_SIGN_STYLE = Automatic; 417 | CURRENT_PROJECT_VERSION = 1; 418 | DEVELOPMENT_TEAM = HK54HM2TC6; 419 | GENERATE_INFOPLIST_FILE = YES; 420 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 421 | MARKETING_VERSION = 1.0; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.heoblitz.URLPatternExampleTests; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_EMIT_LOC_STRINGS = NO; 425 | SWIFT_VERSION = 5.0; 426 | TARGETED_DEVICE_FAMILY = "1,2"; 427 | }; 428 | name = Debug; 429 | }; 430 | EF43E6422D9ED86100809F76 /* Release */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | CODE_SIGN_STYLE = Automatic; 434 | CURRENT_PROJECT_VERSION = 1; 435 | DEVELOPMENT_TEAM = HK54HM2TC6; 436 | GENERATE_INFOPLIST_FILE = YES; 437 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 438 | MARKETING_VERSION = 1.0; 439 | PRODUCT_BUNDLE_IDENTIFIER = com.heoblitz.URLPatternExampleTests; 440 | PRODUCT_NAME = "$(TARGET_NAME)"; 441 | SWIFT_EMIT_LOC_STRINGS = NO; 442 | SWIFT_VERSION = 5.0; 443 | TARGETED_DEVICE_FAMILY = "1,2"; 444 | }; 445 | name = Release; 446 | }; 447 | /* End XCBuildConfiguration section */ 448 | 449 | /* Begin XCConfigurationList section */ 450 | EF43E6142D9ED86000809F76 /* Build configuration list for PBXProject "URLPatternExample" */ = { 451 | isa = XCConfigurationList; 452 | buildConfigurations = ( 453 | EF43E63B2D9ED86100809F76 /* Debug */, 454 | EF43E63C2D9ED86100809F76 /* Release */, 455 | ); 456 | defaultConfigurationIsVisible = 0; 457 | defaultConfigurationName = Release; 458 | }; 459 | EF43E63D2D9ED86100809F76 /* Build configuration list for PBXNativeTarget "URLPatternExample" */ = { 460 | isa = XCConfigurationList; 461 | buildConfigurations = ( 462 | EF43E63E2D9ED86100809F76 /* Debug */, 463 | EF43E63F2D9ED86100809F76 /* Release */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | EF43E6402D9ED86100809F76 /* Build configuration list for PBXNativeTarget "URLPatternExampleTests" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | EF43E6412D9ED86100809F76 /* Debug */, 472 | EF43E6422D9ED86100809F76 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | /* End XCConfigurationList section */ 478 | 479 | /* Begin XCLocalSwiftPackageReference section */ 480 | EF43E72B2D9FE2C300809F76 /* XCLocalSwiftPackageReference "../../URLPattern" */ = { 481 | isa = XCLocalSwiftPackageReference; 482 | relativePath = ../../URLPattern; 483 | }; 484 | /* End XCLocalSwiftPackageReference section */ 485 | 486 | /* Begin XCSwiftPackageProductDependency section */ 487 | EF43E6472D9ED87500809F76 /* URLPattern */ = { 488 | isa = XCSwiftPackageProductDependency; 489 | productName = URLPattern; 490 | }; 491 | EF43E64A2D9ED8A800809F76 /* URLPattern */ = { 492 | isa = XCSwiftPackageProductDependency; 493 | productName = URLPattern; 494 | }; 495 | EF43E64C2D9ED8A800809F76 /* URLPatternClient */ = { 496 | isa = XCSwiftPackageProductDependency; 497 | productName = URLPatternClient; 498 | }; 499 | EF43E64F2D9ED93E00809F76 /* URLPattern */ = { 500 | isa = XCSwiftPackageProductDependency; 501 | productName = URLPattern; 502 | }; 503 | EF43E72C2D9FE2C300809F76 /* URLPattern */ = { 504 | isa = XCSwiftPackageProductDependency; 505 | productName = URLPattern; 506 | }; 507 | EF43E72F2D9FE2CC00809F76 /* URLPattern */ = { 508 | isa = XCSwiftPackageProductDependency; 509 | package = EF43E72B2D9FE2C300809F76 /* XCLocalSwiftPackageReference "../../URLPattern" */; 510 | productName = URLPattern; 511 | }; 512 | /* End XCSwiftPackageProductDependency section */ 513 | }; 514 | rootObject = EF43E6112D9ED86000809F76 /* Project object */; 515 | } 516 | --------------------------------------------------------------------------------