├── .gitignore
├── .github
├── FUNDING.yml
└── workflows
│ ├── ci.yml
│ └── documentation.yml
├── RegularExpressionDecoder.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── RegularExpressionDecoder_Info.plist
├── RegularExpressionDecoderTests_Info.plist
├── xcshareddata
│ └── xcschemes
│ │ └── RegularExpressionDecoder.xcscheme
└── project.pbxproj
├── RegularExpressionDecoder.playground
├── contents.xcplayground
├── Contents.swift
└── Sources
│ └── Stock.swift
├── RegularExpressionDecoder.xcworkspace
├── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── contents.xcworkspacedata
├── LICENSE.md
├── Package.swift
├── Sources
└── RegularExpressionDecoder
│ ├── RegularExpressionPattern.swift
│ ├── SingleValueDecodingContainer.swift
│ ├── UnkeyedDecodingContainer.swift
│ ├── KeyedDecodingContainer.swift
│ └── RegularExpressionDecoder.swift
├── Tests
└── RegularExpressionDecoderTests
│ └── RegularExpressionDecodingTests.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [mattt]
2 | custom: https://flight.school/books/strings
3 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import RegularExpressionDecoder
2 |
3 | let ticker = """
4 | AAPL 170.69▲0.51
5 | GOOG 1122.57▲2.41
6 | AMZN 1621.48▼18.52
7 | MSFT 106.57=0.00
8 | SWIFT 5.0.0▲1.0.0
9 | """
10 |
11 | let pattern: RegularExpressionPattern = #"""
12 | \b
13 | (?<\#(.symbol)>[A-Z]{1,4}) \s+
14 | (?<\#(.price)>\d{1,}\.\d{2}) \s*
15 | (?<\#(.sign)>([▲▼](?!0\.00))|(=(?=0\.00)))
16 | (?<\#(.change)>\d{1,}\.\d{2})
17 | \b
18 | """#
19 |
20 | let decoder = try RegularExpressionDecoder(pattern: pattern, options: .allowCommentsAndWhitespace)
21 |
22 | try decoder.decode([Stock].self, from: ticker)
23 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | macos:
11 | runs-on: macos-latest
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v1
16 | - name: Build and Test
17 | run: swift test
18 |
19 | linux:
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | matrix:
24 | swift: ["5.1", "5.2", "latest"]
25 |
26 | container:
27 | image: swift:${{ matrix.swift }}
28 |
29 | steps:
30 | - name: Checkout
31 | uses: actions/checkout@v1
32 | - name: Build and Test
33 | run: swift test --enable-test-discovery
34 |
--------------------------------------------------------------------------------
/.github/workflows/documentation.yml:
--------------------------------------------------------------------------------
1 | name: Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths:
8 | - .github/workflows/documentation.yml
9 | - Sources/**.swift
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v1
18 | - name: Generate Documentation
19 | uses: SwiftDocOrg/swift-doc@master
20 | with:
21 | inputs: "Sources/SwiftDoc"
22 | output: "Documentation"
23 | - name: Upload Documentation to Wiki
24 | uses: SwiftDocOrg/github-wiki-publish-action@v1
25 | with:
26 | path: "Documentation"
27 | env:
28 | GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
29 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcodeproj/RegularExpressionDecoder_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcodeproj/RegularExpressionDecoderTests_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | BNDL
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.playground/Sources/Stock.swift:
--------------------------------------------------------------------------------
1 | public struct Stock: Decodable {
2 | public let symbol: String
3 | public var price: Double
4 |
5 | enum Sign: String, Decodable {
6 | case gain = "▲"
7 | case unchanged = "="
8 | case loss = "▼"
9 | }
10 |
11 | private var sign: Sign
12 | private var change: Double = 0.0
13 | public var movement: Double {
14 | switch sign {
15 | case .gain: return +change
16 | case .unchanged: return 0.0
17 | case .loss: return -change
18 | }
19 | }
20 |
21 | public enum CodingKeys: String, CodingKey {
22 | case symbol
23 | case price
24 | case sign
25 | case change
26 | }
27 | }
28 |
29 | extension Stock.Sign: CustomStringConvertible {
30 | var description: String {
31 | return self.rawValue
32 | }
33 | }
34 |
35 | extension Stock: CustomStringConvertible {
36 | public var description: String {
37 | return "\(self.symbol) \(self.price)\(self.sign)\(self.change)"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2019 Read Evaluate Press, LLC
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | DEALINGS IN THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "RegularExpressionDecoder",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "RegularExpressionDecoder",
12 | targets: ["RegularExpressionDecoder"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "RegularExpressionDecoder",
23 | dependencies: [],
24 | path: "Sources"
25 | ),
26 | .testTarget(
27 | name: "RegularExpressionDecoderTests",
28 | dependencies: ["RegularExpressionDecoder"],
29 | path: "Tests"
30 | ),
31 | ]
32 | )
33 |
--------------------------------------------------------------------------------
/Sources/RegularExpressionDecoder/RegularExpressionPattern.swift:
--------------------------------------------------------------------------------
1 | public struct RegularExpressionPattern: LosslessStringConvertible, ExpressibleByStringInterpolation where T: Decodable, CodingKeys: CodingKey & Hashable {
2 | public var description: String
3 | public var captures: Set?
4 |
5 | public init?(_ description: String) {
6 | self.description = description
7 | }
8 |
9 | public init(stringLiteral value: String) {
10 | self.init(value)!
11 | }
12 |
13 | public init(stringInterpolation: StringInterpolation) {
14 | self.init(stringInterpolation.string)!
15 | self.captures = stringInterpolation.captures
16 | }
17 |
18 | public struct StringInterpolation: StringInterpolationProtocol {
19 | var string: String = ""
20 | var captures: Set = []
21 |
22 | public init(literalCapacity: Int, interpolationCount: Int) {
23 | self.string.reserveCapacity(literalCapacity)
24 | }
25 |
26 | public mutating func appendLiteral(_ literal: String) {
27 | self.string.append(literal)
28 | }
29 |
30 | public mutating func appendInterpolation(_ key: CodingKeys) {
31 | precondition(!self.captures.contains(key), "\(key) already captured")
32 | precondition(!key.stringValue.contains { !$0.isLetter }, "invalid capture name \(key.stringValue)")
33 | self.string.append(key.stringValue)
34 | self.captures.insert(key)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/RegularExpressionDecoder/SingleValueDecodingContainer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
4 | extension _RegularExpressionDecoder {
5 | final class SingleValueContainer {
6 | let string: String
7 | let match: NSTextCheckingResult?
8 | var codingPath: [CodingKey]
9 | var userInfo: [CodingUserInfoKey: Any]
10 |
11 | init(string: String, match: NSTextCheckingResult?, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
12 | self.string = string
13 | self.match = match
14 | self.codingPath = codingPath
15 | self.userInfo = userInfo
16 | }
17 | }
18 | }
19 |
20 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
21 | extension _RegularExpressionDecoder.SingleValueContainer: SingleValueDecodingContainer {
22 | func decodeNil() -> Bool {
23 | return self.match == nil
24 | }
25 |
26 | func decode(_ type: T.Type) throws -> T where T : Decodable {
27 | let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot decode type \(type)")
28 | throw DecodingError.typeMismatch(type, context)
29 | }
30 |
31 | func decode(_ type: T.Type) throws -> T where T: Decodable, T: LosslessStringConvertible {
32 | guard let value = T(self.string) else {
33 | let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot decode type \(type)")
34 | throw DecodingError.typeMismatch(type, context)
35 | }
36 | return value
37 | }
38 | }
39 |
40 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
41 | extension _RegularExpressionDecoder.SingleValueContainer: RegularExpressionDecodingContainer {}
42 |
--------------------------------------------------------------------------------
/Sources/RegularExpressionDecoder/UnkeyedDecodingContainer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
4 | extension _RegularExpressionDecoder {
5 | final class UnkeyedContainer {
6 | let string: String
7 | let matches: [NSTextCheckingResult]
8 | var codingPath: [CodingKey]
9 | var userInfo: [CodingUserInfoKey: Any]
10 |
11 | var currentIndex: Int = 0
12 |
13 | init(string: String, matches: [NSTextCheckingResult], codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
14 | self.string = string
15 | self.matches = matches
16 | self.codingPath = codingPath
17 | self.userInfo = userInfo
18 | }
19 | }
20 | }
21 |
22 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
23 | extension _RegularExpressionDecoder.UnkeyedContainer: UnkeyedDecodingContainer {
24 | var count: Int? {
25 | return self.matches.count
26 | }
27 |
28 | var isAtEnd: Bool {
29 | return self.currentIndex >= self.count ?? 0
30 | }
31 |
32 | func decodeNil() throws -> Bool {
33 | return !isAtEnd
34 | }
35 |
36 | func decode(_ type: T.Type) throws -> T where T : Decodable {
37 | guard !isAtEnd else {
38 | throw DecodingError.dataCorruptedError(in: self, debugDescription: "no more matches")
39 | }
40 |
41 | defer { self.currentIndex += 1 }
42 |
43 | let decoder = _RegularExpressionDecoder(string: self.string, matches: [self.matches[self.currentIndex]])
44 |
45 | return try T(from: decoder)
46 | }
47 |
48 | func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
49 | defer { self.currentIndex += 1 }
50 |
51 | return _RegularExpressionDecoder.UnkeyedContainer(string: self.string, matches: [self.matches[self.currentIndex]], codingPath: self.codingPath, userInfo: self.userInfo)
52 | }
53 |
54 | func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey {
55 | defer { self.currentIndex += 1 }
56 |
57 | let container = _RegularExpressionDecoder.KeyedContainer(string: self.string, match: self.matches[self.currentIndex], codingPath: self.codingPath, userInfo: self.userInfo)
58 | return KeyedDecodingContainer(container)
59 | }
60 |
61 | func superDecoder() throws -> Decoder {
62 | fatalError("Unimplemented")
63 | }
64 | }
65 |
66 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
67 | extension _RegularExpressionDecoder.UnkeyedContainer: RegularExpressionDecodingContainer {}
68 |
--------------------------------------------------------------------------------
/Tests/RegularExpressionDecoderTests/RegularExpressionDecodingTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import Foundation
3 | @testable import RegularExpressionDecoder
4 |
5 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
6 | struct Stock: Decodable {
7 | let symbol: String
8 | var price: Double
9 |
10 | enum Sign: String, Decodable {
11 | case gain = "▲"
12 | case unchanged = "="
13 | case loss = "▼"
14 | }
15 |
16 | private var sign: Sign
17 | private var change: Double = 0.0
18 | var movement: Double {
19 | switch sign {
20 | case .gain: return +change
21 | case .unchanged: return 0.0
22 | case .loss: return -change
23 | }
24 | }
25 |
26 | enum CodingKeys: String, CodingKey {
27 | case symbol
28 | case price
29 | case sign
30 | case change
31 | }
32 | }
33 |
34 | // swiftlint:disable force_try
35 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
36 | class RegularExpressionDecodingTests: XCTestCase {
37 | var decoder: RegularExpressionDecoder!
38 |
39 | override func setUp() {
40 | let pattern: RegularExpressionPattern = #"""
41 | \b
42 | (?<\#(.symbol)>[A-Z]{1,4}) \s+
43 | (?<\#(.price)>\d{1,}\.\d{2}) \s*
44 | (?<\#(.sign)>([▲▼](?!0\.00))|(=(?=0\.00)))
45 | (?<\#(.change)>\d{1,}\.\d{2})
46 | \b
47 | """#
48 |
49 | self.decoder = try! RegularExpressionDecoder(pattern: pattern, options: .allowCommentsAndWhitespace)
50 | }
51 |
52 | func testDecodeSingle() {
53 | let string = "AAPL 170.69▲0.51"
54 | let stock = try! self.decoder.decode(Stock.self, from: string)
55 |
56 | XCTAssertEqual(stock.symbol, "AAPL")
57 | XCTAssertEqual(stock.price, 170.69, accuracy: 0.01)
58 | XCTAssertEqual(stock.movement, 0.51, accuracy: 0.01)
59 | }
60 |
61 | func testDecodeMultiple() {
62 | let string = """
63 | AAPL 170.69▲0.51
64 | GOOG 1122.57▲2.41
65 | AMZN 1621.48▼18.52
66 | MSFT 106.57=0.00
67 | SWIFT 5.0▲1.0.0
68 | """
69 |
70 | let stocks = try! self.decoder.decode([Stock].self, from: string)
71 |
72 | guard stocks.count == 4 else {
73 | XCTFail("decoded \(stocks.count) of 4 valid stocks")
74 | return
75 | }
76 |
77 | let AAPL = stocks[0]
78 | XCTAssertEqual(AAPL.symbol, "AAPL")
79 | XCTAssertEqual(AAPL.price, 170.69, accuracy: 0.01)
80 | XCTAssertEqual(AAPL.movement, 0.51, accuracy: 0.01)
81 |
82 | let GOOG = stocks[1]
83 | XCTAssertEqual(GOOG.symbol, "GOOG")
84 | XCTAssertEqual(GOOG.price, 1122.57, accuracy: 0.01)
85 | XCTAssertEqual(GOOG.movement, 2.41, accuracy: 0.01)
86 |
87 | let AMZN = stocks[2]
88 | XCTAssertEqual(AMZN.symbol, "AMZN")
89 | XCTAssertEqual(AMZN.price, 1621.48, accuracy: 0.01)
90 | XCTAssertEqual(AMZN.movement, -18.52, accuracy: 0.01)
91 |
92 | let MSFT = stocks[3]
93 | XCTAssertEqual(MSFT.symbol, "MSFT")
94 | XCTAssertEqual(MSFT.price, 106.57, accuracy: 0.01)
95 | XCTAssertEqual(MSFT.movement, 0.0, accuracy: 0.01)
96 | }
97 |
98 | func testDecodeInvalid() {
99 | let string = "AAPL 170.69" // missing sign and change
100 |
101 | XCTAssertThrowsError(try self.decoder.decode(Stock.self, from: string))
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Sources/RegularExpressionDecoder/KeyedDecodingContainer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
4 | extension NSTextCheckingResult {
5 | func range(for key: Key) -> NSRange? where Key: CodingKey {
6 | if let position = key.intValue {
7 | return self.range(at: position)
8 | } else {
9 | return self.range(withName: key.stringValue)
10 | }
11 | }
12 | }
13 |
14 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
15 | extension _RegularExpressionDecoder {
16 | final class KeyedContainer where Key: CodingKey {
17 | let string: String
18 | let match: NSTextCheckingResult?
19 | var codingPath: [CodingKey]
20 | var userInfo: [CodingUserInfoKey: Any]
21 |
22 | init(string: String, match: NSTextCheckingResult?, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
23 | self.string = string
24 | self.match = match
25 | self.codingPath = codingPath
26 | self.userInfo = userInfo
27 | }
28 |
29 | func range(for key: Key) -> Range? {
30 | guard let nsrange = self.match?.range(for: key),
31 | nsrange.location != NSNotFound
32 | else {
33 | return nil
34 | }
35 |
36 | return Range(nsrange, in: self.string)
37 | }
38 |
39 | func string(for key: Key) -> String? {
40 | guard let range = self.range(for: key) else {
41 | return nil
42 | }
43 |
44 | return String(self.string[range])
45 | }
46 | }
47 | }
48 |
49 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
50 | extension _RegularExpressionDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
51 | var allKeys: [Key] {
52 | guard let captureGroupNames = self.userInfo[._captureGroupNames] as? [String] else {
53 | return []
54 | }
55 |
56 | return captureGroupNames.compactMap { Key(stringValue: $0) }
57 | }
58 |
59 | func contains(_ key: Key) -> Bool {
60 | return self.range(for: key) != nil
61 | }
62 |
63 | func decodeNil(forKey key: Key) throws -> Bool {
64 | return self.match == nil || !contains(key)
65 | }
66 |
67 | func decode(_ type: String.Type, forKey key: Key) throws -> String {
68 | guard let string = self.string(for: key) else {
69 | let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "String: \(self.string)")
70 | throw DecodingError.keyNotFound(key, context)
71 | }
72 |
73 | return string
74 | }
75 |
76 | func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable, T: LosslessStringConvertible {
77 | let string = try self.decode(String.self, forKey: key)
78 |
79 | guard let value = T(string) else {
80 | let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "String: \(self.string)")
81 | throw DecodingError.typeMismatch(type, context)
82 | }
83 |
84 | return value
85 | }
86 |
87 | func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
88 | let string = try self.decode(String.self, forKey: key)
89 |
90 | let decoder = _RegularExpressionDecoder(string: string, matches: [self.match].compactMap {$0})
91 | let value = try T(from: decoder)
92 |
93 | return value
94 | }
95 |
96 | func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
97 | fatalError("Unimplemented")
98 | }
99 |
100 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey {
101 | fatalError("Unimplemented")
102 | }
103 |
104 | func superDecoder() throws -> Decoder {
105 | fatalError("Unimplemented")
106 | }
107 |
108 | func superDecoder(forKey key: Key) throws -> Decoder {
109 | fatalError("Unimplemented")
110 | }
111 | }
112 |
113 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
114 | extension _RegularExpressionDecoder.KeyedContainer: RegularExpressionDecodingContainer {}
115 |
--------------------------------------------------------------------------------
/Sources/RegularExpressionDecoder/RegularExpressionDecoder.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /**
4 |
5 | */
6 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
7 | final public class RegularExpressionDecoder {
8 | private(set) var regularExpression: NSRegularExpression
9 | var captureGroupNames: [String]?
10 |
11 | public enum Error: Swift.Error {
12 | case noMatches
13 | case tooManyMatches
14 | }
15 |
16 | public init(pattern: RegularExpressionPattern, options: NSRegularExpression.Options = []) throws {
17 | self.regularExpression = try NSRegularExpression(pattern: pattern.description, options: options)
18 | self.captureGroupNames = pattern.captures?.map { $0.stringValue }
19 | }
20 |
21 | public func decode(_ type: T.Type, from string: String, options: NSRegularExpression.MatchingOptions = []) throws -> T {
22 | let range = NSRange(string.startIndex.. [T] {
39 | let range = NSRange(string.startIndex.. SingleValueDecodingContainer {
83 | assertCanCreateContainer()
84 |
85 | let container = SingleValueContainer(string: self.string, match: self.matches.first, codingPath: self.codingPath, userInfo: self.userInfo)
86 | self.container = container
87 |
88 | return container
89 | }
90 |
91 | func container(keyedBy type: Key.Type) -> KeyedDecodingContainer where Key : CodingKey {
92 | assertCanCreateContainer()
93 |
94 | let container = KeyedContainer(string: self.string, match: self.matches.first, codingPath: self.codingPath, userInfo: self.userInfo)
95 | self.container = container
96 |
97 | return KeyedDecodingContainer(container)
98 | }
99 |
100 | func unkeyedContainer() -> UnkeyedDecodingContainer {
101 | assertCanCreateContainer()
102 |
103 | let container = UnkeyedContainer(string: self.string, matches: self.matches, codingPath: self.codingPath, userInfo: self.userInfo)
104 | self.container = container
105 |
106 | return container
107 | }
108 | }
109 |
110 | @available(OSX 10.13, iOS 11, tvOS 11, watchOS 4, *)
111 | protocol RegularExpressionDecodingContainer: class {}
112 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcodeproj/xcshareddata/xcschemes/RegularExpressionDecoder.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Regular Expression Decoder
2 |
3 | [![Build Status][build status badge]][build status]
4 | [![License][license badge]][license]
5 | [![Swift Version][swift version badge]][swift version]
6 |
7 | A decoder that constructs objects from regular expression matches.
8 |
9 | ---
10 |
11 | For more information about creating your own custom decoders,
12 | consult Chapter 7 of the
13 | [Flight School Guide to Swift Codable](https://flight.school/books/codable).
14 | For more information about using regular expressions in Swift,
15 | check out Chapter 6 of the
16 | [Flight School Guide to Swift Strings](https://flight.school/books/strings).
17 |
18 | ## Requirements
19 |
20 | - Swift 5+
21 | - iOS 11+ or macOS 10.13+
22 |
23 | ## Usage
24 |
25 | ```swift
26 | import RegularExpressionDecoder
27 |
28 | let ticker = """
29 | AAPL 170.69▲0.51
30 | GOOG 1122.57▲2.41
31 | AMZN 1621.48▼18.52
32 | MSFT 106.57=0.00
33 | SWIFT 5.0.0▲1.0.0
34 | """
35 |
36 | let pattern: RegularExpressionPattern = #"""
37 | \b
38 | (?<\#(.symbol)>[A-Z]{1,4}) \s+
39 | (?<\#(.price)>\d{1,}\.\d{2}) \s*
40 | (?<\#(.sign)>([▲▼=])
41 | (?<\#(.change)>\d{1,}\.\d{2})
42 | \b
43 | """#
44 |
45 | let decoder = try RegularExpressionDecoder(
46 | pattern: pattern,
47 | options: .allowCommentsAndWhitespace
48 | )
49 |
50 | try decoder.decode([Stock].self, from: ticker)
51 | // Decodes [AAPL, GOOG, AMZN, MSFT] (but not SWIFT, which is invalid)
52 | ```
53 |
54 | ## Explanation
55 |
56 | Let's say that you're building an app that parses stock quotes
57 | from a text-based stream of price changes.
58 |
59 | ```swift
60 | let ticker = """
61 | AAPL 170.69▲0.51
62 | GOOG 1122.57▲2.41
63 | AMZN 1621.48▼18.52
64 | MSFT 106.57=0.00
65 | """
66 | ```
67 |
68 | Each stock is represented by the following structure:
69 |
70 | - The **symbol**, consisting of 1 to 4 uppercase letters, followed by a space
71 | - The **price**, formatted as a number with 2 decimal places
72 | - A **sign**, indicating a price gain (`▲`), loss (`▼`), or no change (`=`)
73 | - The **magnitude** of the gain or loss, formatted the same as the price
74 |
75 | These format constraints lend themselves naturally
76 | to representation by a regular expression,
77 | such as:
78 |
79 | ```perl
80 | /\b[A-Z]{1,4} \d{1,}\.\d{2}[▲▼=]\d{1,}\.\d{2}\b/
81 | ```
82 |
83 | > Note:
84 | > The `\b` metacharacter anchors matches to word boundaries.
85 |
86 | This regular expression can distinguish between
87 | valid and invalid stock quotes.
88 |
89 | ```swift
90 | "AAPL 170.69▲0.51" // valid
91 | "SWIFT 5.0.0▲1.0.0" // invalid
92 | ```
93 |
94 | However, to extract individual components from a quote
95 | (symbol, price, etc.)
96 | the regular expression must contain capture groups,
97 | of which there are two varieties:
98 | positional capture groups and
99 | named capture groups.
100 |
101 | Positional capture groups are demarcated in the pattern
102 | by enclosing parentheses (`(___)`).
103 | With some slight modifications,
104 | we can make original regular expression capture each part of the stock quote:
105 |
106 | ```perl
107 | /\b([A-Z]{1,4}) (\d{1,}\.\d{2})([▲▼=])(\d{1,}\.\d{2})\b/
108 | ```
109 |
110 | When matched,
111 | the symbol can be accessed by the first capture group,
112 | the price by the second,
113 | and so on.
114 |
115 | For large numbers of capture groups ---
116 | especially in patterns with nested groups ---
117 | one can easily lose track of which parts correspond to which positions.
118 | So another approach is to assign names to capture groups,
119 | which are denoted by the syntax `(?___)`.
120 |
121 | ```perl
122 | /\b
123 | (?[A-Z]{1,4}) \s+
124 | (?\d{1,}\.\d{2}) \s*
125 | (?([▲▼=])
126 | (?\d{1,}\.\d{2})
127 | \b/
128 | ```
129 |
130 | > Note:
131 | > Most regular expression engines ---
132 | > including the one used by `NSRegularExpression` ---
133 | > provide a mode to ignore whitespace;
134 | > this lets you segment long patterns over multiple lines,
135 | > making them easier to read and understand.
136 |
137 | Theoretically, this approach allows you to access each group by name
138 | for each match of the regular expression.
139 | In practice, doing this in Swift can be inconvenient,
140 | as it requires you to interact with cumbersome `NSRegularExpression` APIs
141 | and somehow incorporate it into your model layer.
142 |
143 | `RegularExpressionDecoder` provides a convenient solution
144 | to constructing `Decodable` objects from regular expression matches
145 | by automatically matching coding keys to capture group names.
146 | And it can do so safely,
147 | thanks to the new `ExpressibleByStringInterpolation` protocol in Swift 5.
148 |
149 | To understand how,
150 | let's start by considering the following `Stock` model,
151 | which adopts the `Decodable` protocol:
152 |
153 | ```swift
154 | struct Stock: Decodable {
155 | let symbol: String
156 | var price: Double
157 |
158 | enum Sign: String, Decodable {
159 | case gain = "▲"
160 | case unchanged = "="
161 | case loss = "▼"
162 | }
163 |
164 | private var sign: Sign
165 | private var change: Double = 0.0
166 | var movement: Double {
167 | switch sign {
168 | case .gain: return +change
169 | case .unchanged: return 0.0
170 | case .loss: return -change
171 | }
172 | }
173 | }
174 | ```
175 |
176 | So far, so good.
177 |
178 | Now, normally, the Swift compiler
179 | automatically synthesizes conformance to `Decodable`,
180 | including a nested `CodingKeys` type.
181 | But in order to make this next part work correctly,
182 | we'll have to do this ourselves:
183 |
184 | ```swift
185 | extension Stock {
186 | enum CodingKeys: String, CodingKey {
187 | case symbol
188 | case price
189 | case sign
190 | case change
191 | }
192 | }
193 | ```
194 |
195 | Here's where things get really interesting:
196 | remember our regular expression with named capture patterns from before?
197 | _We can replace the hard-coded names
198 | with interpolations of the `Stock` type's coding keys._
199 |
200 | ```swift
201 | import RegularExpressionDecoder
202 |
203 | let pattern: RegularExpressionPattern = #"""
204 | \b
205 | (?<\#(.symbol)>[A-Z]{1,4}) \s+
206 | (?<\#(.price)>\d{1,}\.\d{2}) \s*
207 | (?<\#(.sign)>[▲▼=])
208 | (?<\#(.change)>\d{1,}\.\d{2})
209 | \b
210 | """#
211 | ```
212 |
213 | > Note:
214 | > This example benefits greatly from another new feature in Swift 5:
215 | > raw string literals.
216 | > Those octothorps (`#`) at the start and end
217 | > tell the compiler to ignore escape characters (`\`)
218 | > unless they also include an octothorp (`\#( )`).
219 | > Using raw string literals,
220 | > we can write regular expression metacharacters like `\b`, `\d`, and `\s`
221 | > without double escaping them (i.e. `\\b`).
222 |
223 | Thanks to `ExpressibleByStringInterpolation`,
224 | we can restrict interpolation segments to only accept those coding keys,
225 | thereby ensuring a direct 1:1 match between capture groups
226 | and their decoded properties.
227 | And not only that ---
228 | this approach lets us to verify that keys have valid regex-friendly names
229 | and aren't captured more than once.
230 | It's enormously powerful,
231 | allowing code to be incredibly expressive
232 | without compromising safety or performance.
233 |
234 | When all is said and done,
235 | `RegularExpressionDecoder` lets you decode types
236 | from a string according to a regular expression pattern
237 | much the same as you might from JSON or a property list
238 | using a decoder:
239 |
240 | ```swift
241 | let decoder = try RegularExpressionDecoder(
242 | pattern: pattern,
243 | options: .allowCommentsAndWhitespace
244 | )
245 |
246 | try decoder.decode([Stock].self, from: ticker)
247 | // Decodes [AAPL, GOOG, AMZN, MSFT]
248 | ```
249 |
250 | ## License
251 |
252 | MIT
253 |
254 | ## Contact
255 |
256 | Mattt ([@mattt](https://twitter.com/mattt))
257 |
258 | [build status]: https://github.com/Flight-School/RegularExpressionDecoder/actions?query=workflow%3ACI
259 | [build status badge]: https://github.com/Flight-School/RegularExpressionDecoder/workflows/CI/badge.svg
260 | [license]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat
261 | [license badge]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat
262 | [swift version]: https://swift.org/download/
263 | [swift version badge]: http://img.shields.io/badge/swift%20version-5.0-orange.svg?style=flat
264 |
--------------------------------------------------------------------------------
/RegularExpressionDecoder.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXAggregateTarget section */
10 | "RegularExpressionDecoder::RegularExpressionDecoderPackageTests::ProductTarget" /* RegularExpressionDecoderPackageTests */ = {
11 | isa = PBXAggregateTarget;
12 | buildConfigurationList = OBJ_38 /* Build configuration list for PBXAggregateTarget "RegularExpressionDecoderPackageTests" */;
13 | buildPhases = (
14 | );
15 | dependencies = (
16 | OBJ_41 /* PBXTargetDependency */,
17 | );
18 | name = RegularExpressionDecoderPackageTests;
19 | productName = RegularExpressionDecoderPackageTests;
20 | };
21 | /* End PBXAggregateTarget section */
22 |
23 | /* Begin PBXBuildFile section */
24 | F866325422184A0E00637DE5 /* RegularExpressionPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866325322184A0E00637DE5 /* RegularExpressionPattern.swift */; };
25 | OBJ_26 /* KeyedDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* KeyedDecodingContainer.swift */; };
26 | OBJ_27 /* RegularExpressionDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* RegularExpressionDecoder.swift */; };
27 | OBJ_28 /* SingleValueDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* SingleValueDecodingContainer.swift */; };
28 | OBJ_29 /* UnkeyedDecodingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* UnkeyedDecodingContainer.swift */; };
29 | OBJ_36 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
30 | OBJ_47 /* RegularExpressionDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* RegularExpressionDecodingTests.swift */; };
31 | OBJ_49 /* RegularExpressionDecoder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "RegularExpressionDecoder::RegularExpressionDecoder::Product" /* RegularExpressionDecoder.framework */; };
32 | /* End PBXBuildFile section */
33 |
34 | /* Begin PBXContainerItemProxy section */
35 | F89F2BD52216E93E0063EB2D /* PBXContainerItemProxy */ = {
36 | isa = PBXContainerItemProxy;
37 | containerPortal = OBJ_1 /* Project object */;
38 | proxyType = 1;
39 | remoteGlobalIDString = "RegularExpressionDecoder::RegularExpressionDecoder";
40 | remoteInfo = RegularExpressionDecoder;
41 | };
42 | F89F2BD62216E9400063EB2D /* PBXContainerItemProxy */ = {
43 | isa = PBXContainerItemProxy;
44 | containerPortal = OBJ_1 /* Project object */;
45 | proxyType = 1;
46 | remoteGlobalIDString = "RegularExpressionDecoder::RegularExpressionDecoderTests";
47 | remoteInfo = RegularExpressionDecoderTests;
48 | };
49 | /* End PBXContainerItemProxy section */
50 |
51 | /* Begin PBXFileReference section */
52 | F866325322184A0E00637DE5 /* RegularExpressionPattern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegularExpressionPattern.swift; sourceTree = ""; };
53 | OBJ_10 /* RegularExpressionDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegularExpressionDecoder.swift; sourceTree = ""; };
54 | OBJ_11 /* SingleValueDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleValueDecodingContainer.swift; sourceTree = ""; };
55 | OBJ_12 /* UnkeyedDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnkeyedDecodingContainer.swift; sourceTree = ""; };
56 | OBJ_15 /* RegularExpressionDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegularExpressionDecodingTests.swift; sourceTree = ""; };
57 | OBJ_19 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; };
58 | OBJ_20 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
59 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
60 | OBJ_9 /* KeyedDecodingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedDecodingContainer.swift; sourceTree = ""; };
61 | "RegularExpressionDecoder::RegularExpressionDecoder::Product" /* RegularExpressionDecoder.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = RegularExpressionDecoder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
62 | "RegularExpressionDecoder::RegularExpressionDecoderTests::Product" /* RegularExpressionDecoderTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = RegularExpressionDecoderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63 | /* End PBXFileReference section */
64 |
65 | /* Begin PBXFrameworksBuildPhase section */
66 | OBJ_30 /* Frameworks */ = {
67 | isa = PBXFrameworksBuildPhase;
68 | buildActionMask = 0;
69 | files = (
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | OBJ_48 /* Frameworks */ = {
74 | isa = PBXFrameworksBuildPhase;
75 | buildActionMask = 0;
76 | files = (
77 | OBJ_49 /* RegularExpressionDecoder.framework in Frameworks */,
78 | );
79 | runOnlyForDeploymentPostprocessing = 0;
80 | };
81 | /* End PBXFrameworksBuildPhase section */
82 |
83 | /* Begin PBXGroup section */
84 | OBJ_13 /* Tests */ = {
85 | isa = PBXGroup;
86 | children = (
87 | OBJ_14 /* RegularExpressionDecoderTests */,
88 | );
89 | path = Tests;
90 | sourceTree = SOURCE_ROOT;
91 | };
92 | OBJ_14 /* RegularExpressionDecoderTests */ = {
93 | isa = PBXGroup;
94 | children = (
95 | OBJ_15 /* RegularExpressionDecodingTests.swift */,
96 | );
97 | path = RegularExpressionDecoderTests;
98 | sourceTree = "";
99 | };
100 | OBJ_16 /* Products */ = {
101 | isa = PBXGroup;
102 | children = (
103 | "RegularExpressionDecoder::RegularExpressionDecoderTests::Product" /* RegularExpressionDecoderTests.xctest */,
104 | "RegularExpressionDecoder::RegularExpressionDecoder::Product" /* RegularExpressionDecoder.framework */,
105 | );
106 | name = Products;
107 | sourceTree = BUILT_PRODUCTS_DIR;
108 | };
109 | OBJ_5 = {
110 | isa = PBXGroup;
111 | children = (
112 | OBJ_6 /* Package.swift */,
113 | OBJ_7 /* Sources */,
114 | OBJ_13 /* Tests */,
115 | OBJ_16 /* Products */,
116 | OBJ_19 /* LICENSE.md */,
117 | OBJ_20 /* README.md */,
118 | );
119 | sourceTree = "";
120 | };
121 | OBJ_7 /* Sources */ = {
122 | isa = PBXGroup;
123 | children = (
124 | OBJ_8 /* RegularExpressionDecoder */,
125 | );
126 | path = Sources;
127 | sourceTree = SOURCE_ROOT;
128 | };
129 | OBJ_8 /* RegularExpressionDecoder */ = {
130 | isa = PBXGroup;
131 | children = (
132 | F866325322184A0E00637DE5 /* RegularExpressionPattern.swift */,
133 | OBJ_10 /* RegularExpressionDecoder.swift */,
134 | OBJ_11 /* SingleValueDecodingContainer.swift */,
135 | OBJ_9 /* KeyedDecodingContainer.swift */,
136 | OBJ_12 /* UnkeyedDecodingContainer.swift */,
137 | );
138 | path = RegularExpressionDecoder;
139 | sourceTree = "";
140 | };
141 | /* End PBXGroup section */
142 |
143 | /* Begin PBXNativeTarget section */
144 | "RegularExpressionDecoder::RegularExpressionDecoder" /* RegularExpressionDecoder */ = {
145 | isa = PBXNativeTarget;
146 | buildConfigurationList = OBJ_22 /* Build configuration list for PBXNativeTarget "RegularExpressionDecoder" */;
147 | buildPhases = (
148 | OBJ_25 /* Sources */,
149 | OBJ_30 /* Frameworks */,
150 | );
151 | buildRules = (
152 | );
153 | dependencies = (
154 | );
155 | name = RegularExpressionDecoder;
156 | productName = RegularExpressionDecoder;
157 | productReference = "RegularExpressionDecoder::RegularExpressionDecoder::Product" /* RegularExpressionDecoder.framework */;
158 | productType = "com.apple.product-type.framework";
159 | };
160 | "RegularExpressionDecoder::RegularExpressionDecoderTests" /* RegularExpressionDecoderTests */ = {
161 | isa = PBXNativeTarget;
162 | buildConfigurationList = OBJ_43 /* Build configuration list for PBXNativeTarget "RegularExpressionDecoderTests" */;
163 | buildPhases = (
164 | OBJ_46 /* Sources */,
165 | OBJ_48 /* Frameworks */,
166 | );
167 | buildRules = (
168 | );
169 | dependencies = (
170 | OBJ_50 /* PBXTargetDependency */,
171 | );
172 | name = RegularExpressionDecoderTests;
173 | productName = RegularExpressionDecoderTests;
174 | productReference = "RegularExpressionDecoder::RegularExpressionDecoderTests::Product" /* RegularExpressionDecoderTests.xctest */;
175 | productType = "com.apple.product-type.bundle.unit-test";
176 | };
177 | "RegularExpressionDecoder::SwiftPMPackageDescription" /* RegularExpressionDecoderPackageDescription */ = {
178 | isa = PBXNativeTarget;
179 | buildConfigurationList = OBJ_32 /* Build configuration list for PBXNativeTarget "RegularExpressionDecoderPackageDescription" */;
180 | buildPhases = (
181 | OBJ_35 /* Sources */,
182 | );
183 | buildRules = (
184 | );
185 | dependencies = (
186 | );
187 | name = RegularExpressionDecoderPackageDescription;
188 | productName = RegularExpressionDecoderPackageDescription;
189 | productType = "com.apple.product-type.framework";
190 | };
191 | /* End PBXNativeTarget section */
192 |
193 | /* Begin PBXProject section */
194 | OBJ_1 /* Project object */ = {
195 | isa = PBXProject;
196 | attributes = {
197 | LastSwiftMigration = 9999;
198 | LastUpgradeCheck = 9999;
199 | };
200 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "RegularExpressionDecoder" */;
201 | compatibilityVersion = "Xcode 3.2";
202 | developmentRegion = English;
203 | hasScannedForEncodings = 0;
204 | knownRegions = (
205 | en,
206 | );
207 | mainGroup = OBJ_5;
208 | productRefGroup = OBJ_16 /* Products */;
209 | projectDirPath = "";
210 | projectRoot = "";
211 | targets = (
212 | "RegularExpressionDecoder::RegularExpressionDecoder" /* RegularExpressionDecoder */,
213 | "RegularExpressionDecoder::SwiftPMPackageDescription" /* RegularExpressionDecoderPackageDescription */,
214 | "RegularExpressionDecoder::RegularExpressionDecoderPackageTests::ProductTarget" /* RegularExpressionDecoderPackageTests */,
215 | "RegularExpressionDecoder::RegularExpressionDecoderTests" /* RegularExpressionDecoderTests */,
216 | );
217 | };
218 | /* End PBXProject section */
219 |
220 | /* Begin PBXSourcesBuildPhase section */
221 | OBJ_25 /* Sources */ = {
222 | isa = PBXSourcesBuildPhase;
223 | buildActionMask = 0;
224 | files = (
225 | OBJ_26 /* KeyedDecodingContainer.swift in Sources */,
226 | F866325422184A0E00637DE5 /* RegularExpressionPattern.swift in Sources */,
227 | OBJ_27 /* RegularExpressionDecoder.swift in Sources */,
228 | OBJ_28 /* SingleValueDecodingContainer.swift in Sources */,
229 | OBJ_29 /* UnkeyedDecodingContainer.swift in Sources */,
230 | );
231 | runOnlyForDeploymentPostprocessing = 0;
232 | };
233 | OBJ_35 /* Sources */ = {
234 | isa = PBXSourcesBuildPhase;
235 | buildActionMask = 0;
236 | files = (
237 | OBJ_36 /* Package.swift in Sources */,
238 | );
239 | runOnlyForDeploymentPostprocessing = 0;
240 | };
241 | OBJ_46 /* Sources */ = {
242 | isa = PBXSourcesBuildPhase;
243 | buildActionMask = 0;
244 | files = (
245 | OBJ_47 /* RegularExpressionDecodingTests.swift in Sources */,
246 | );
247 | runOnlyForDeploymentPostprocessing = 0;
248 | };
249 | /* End PBXSourcesBuildPhase section */
250 |
251 | /* Begin PBXTargetDependency section */
252 | OBJ_41 /* PBXTargetDependency */ = {
253 | isa = PBXTargetDependency;
254 | target = "RegularExpressionDecoder::RegularExpressionDecoderTests" /* RegularExpressionDecoderTests */;
255 | targetProxy = F89F2BD62216E9400063EB2D /* PBXContainerItemProxy */;
256 | };
257 | OBJ_50 /* PBXTargetDependency */ = {
258 | isa = PBXTargetDependency;
259 | target = "RegularExpressionDecoder::RegularExpressionDecoder" /* RegularExpressionDecoder */;
260 | targetProxy = F89F2BD52216E93E0063EB2D /* PBXContainerItemProxy */;
261 | };
262 | /* End PBXTargetDependency section */
263 |
264 | /* Begin XCBuildConfiguration section */
265 | OBJ_23 /* Debug */ = {
266 | isa = XCBuildConfiguration;
267 | buildSettings = {
268 | ENABLE_TESTABILITY = YES;
269 | FRAMEWORK_SEARCH_PATHS = (
270 | "$(inherited)",
271 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
272 | );
273 | HEADER_SEARCH_PATHS = "$(inherited)";
274 | INFOPLIST_FILE = RegularExpressionDecoder.xcodeproj/RegularExpressionDecoder_Info.plist;
275 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
276 | OTHER_CFLAGS = "$(inherited)";
277 | OTHER_LDFLAGS = "$(inherited)";
278 | OTHER_SWIFT_FLAGS = "$(inherited)";
279 | PRODUCT_BUNDLE_IDENTIFIER = RegularExpressionDecoder;
280 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
281 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
282 | SKIP_INSTALL = YES;
283 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
284 | SWIFT_VERSION = 4.0;
285 | TARGET_NAME = RegularExpressionDecoder;
286 | };
287 | name = Debug;
288 | };
289 | OBJ_24 /* Release */ = {
290 | isa = XCBuildConfiguration;
291 | buildSettings = {
292 | ENABLE_TESTABILITY = YES;
293 | FRAMEWORK_SEARCH_PATHS = (
294 | "$(inherited)",
295 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
296 | );
297 | HEADER_SEARCH_PATHS = "$(inherited)";
298 | INFOPLIST_FILE = RegularExpressionDecoder.xcodeproj/RegularExpressionDecoder_Info.plist;
299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
300 | OTHER_CFLAGS = "$(inherited)";
301 | OTHER_LDFLAGS = "$(inherited)";
302 | OTHER_SWIFT_FLAGS = "$(inherited)";
303 | PRODUCT_BUNDLE_IDENTIFIER = RegularExpressionDecoder;
304 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
305 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
306 | SKIP_INSTALL = YES;
307 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
308 | SWIFT_VERSION = 4.0;
309 | TARGET_NAME = RegularExpressionDecoder;
310 | };
311 | name = Release;
312 | };
313 | OBJ_3 /* Debug */ = {
314 | isa = XCBuildConfiguration;
315 | buildSettings = {
316 | CLANG_ENABLE_OBJC_ARC = YES;
317 | COMBINE_HIDPI_IMAGES = YES;
318 | COPY_PHASE_STRIP = NO;
319 | DEBUG_INFORMATION_FORMAT = dwarf;
320 | DYLIB_INSTALL_NAME_BASE = "@rpath";
321 | ENABLE_NS_ASSERTIONS = YES;
322 | GCC_OPTIMIZATION_LEVEL = 0;
323 | GCC_PREPROCESSOR_DEFINITIONS = (
324 | "DEBUG=1",
325 | "$(inherited)",
326 | );
327 | MACOSX_DEPLOYMENT_TARGET = 10.10;
328 | ONLY_ACTIVE_ARCH = YES;
329 | OTHER_SWIFT_FLAGS = "-DXcode";
330 | PRODUCT_NAME = "$(TARGET_NAME)";
331 | SDKROOT = macosx;
332 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
333 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE DEBUG";
334 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
335 | USE_HEADERMAP = NO;
336 | };
337 | name = Debug;
338 | };
339 | OBJ_33 /* Debug */ = {
340 | isa = XCBuildConfiguration;
341 | buildSettings = {
342 | LD = /usr/bin/true;
343 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
344 | SWIFT_VERSION = 4.0;
345 | };
346 | name = Debug;
347 | };
348 | OBJ_34 /* Release */ = {
349 | isa = XCBuildConfiguration;
350 | buildSettings = {
351 | LD = /usr/bin/true;
352 | OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
353 | SWIFT_VERSION = 4.0;
354 | };
355 | name = Release;
356 | };
357 | OBJ_39 /* Debug */ = {
358 | isa = XCBuildConfiguration;
359 | buildSettings = {
360 | };
361 | name = Debug;
362 | };
363 | OBJ_4 /* Release */ = {
364 | isa = XCBuildConfiguration;
365 | buildSettings = {
366 | CLANG_ENABLE_OBJC_ARC = YES;
367 | COMBINE_HIDPI_IMAGES = YES;
368 | COPY_PHASE_STRIP = YES;
369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
370 | DYLIB_INSTALL_NAME_BASE = "@rpath";
371 | GCC_OPTIMIZATION_LEVEL = s;
372 | MACOSX_DEPLOYMENT_TARGET = 10.10;
373 | OTHER_SWIFT_FLAGS = "-DXcode";
374 | PRODUCT_NAME = "$(TARGET_NAME)";
375 | SDKROOT = macosx;
376 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
377 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
378 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
379 | USE_HEADERMAP = NO;
380 | };
381 | name = Release;
382 | };
383 | OBJ_40 /* Release */ = {
384 | isa = XCBuildConfiguration;
385 | buildSettings = {
386 | };
387 | name = Release;
388 | };
389 | OBJ_44 /* Debug */ = {
390 | isa = XCBuildConfiguration;
391 | buildSettings = {
392 | CLANG_ENABLE_MODULES = YES;
393 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
394 | FRAMEWORK_SEARCH_PATHS = (
395 | "$(inherited)",
396 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
397 | );
398 | HEADER_SEARCH_PATHS = "$(inherited)";
399 | INFOPLIST_FILE = RegularExpressionDecoder.xcodeproj/RegularExpressionDecoderTests_Info.plist;
400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
401 | OTHER_CFLAGS = "$(inherited)";
402 | OTHER_LDFLAGS = "$(inherited)";
403 | OTHER_SWIFT_FLAGS = "$(inherited)";
404 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
405 | SWIFT_VERSION = 4.0;
406 | TARGET_NAME = RegularExpressionDecoderTests;
407 | };
408 | name = Debug;
409 | };
410 | OBJ_45 /* Release */ = {
411 | isa = XCBuildConfiguration;
412 | buildSettings = {
413 | CLANG_ENABLE_MODULES = YES;
414 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
415 | FRAMEWORK_SEARCH_PATHS = (
416 | "$(inherited)",
417 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
418 | );
419 | HEADER_SEARCH_PATHS = "$(inherited)";
420 | INFOPLIST_FILE = RegularExpressionDecoder.xcodeproj/RegularExpressionDecoderTests_Info.plist;
421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
422 | OTHER_CFLAGS = "$(inherited)";
423 | OTHER_LDFLAGS = "$(inherited)";
424 | OTHER_SWIFT_FLAGS = "$(inherited)";
425 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
426 | SWIFT_VERSION = 4.0;
427 | TARGET_NAME = RegularExpressionDecoderTests;
428 | };
429 | name = Release;
430 | };
431 | /* End XCBuildConfiguration section */
432 |
433 | /* Begin XCConfigurationList section */
434 | OBJ_2 /* Build configuration list for PBXProject "RegularExpressionDecoder" */ = {
435 | isa = XCConfigurationList;
436 | buildConfigurations = (
437 | OBJ_3 /* Debug */,
438 | OBJ_4 /* Release */,
439 | );
440 | defaultConfigurationIsVisible = 0;
441 | defaultConfigurationName = Release;
442 | };
443 | OBJ_22 /* Build configuration list for PBXNativeTarget "RegularExpressionDecoder" */ = {
444 | isa = XCConfigurationList;
445 | buildConfigurations = (
446 | OBJ_23 /* Debug */,
447 | OBJ_24 /* Release */,
448 | );
449 | defaultConfigurationIsVisible = 0;
450 | defaultConfigurationName = Release;
451 | };
452 | OBJ_32 /* Build configuration list for PBXNativeTarget "RegularExpressionDecoderPackageDescription" */ = {
453 | isa = XCConfigurationList;
454 | buildConfigurations = (
455 | OBJ_33 /* Debug */,
456 | OBJ_34 /* Release */,
457 | );
458 | defaultConfigurationIsVisible = 0;
459 | defaultConfigurationName = Release;
460 | };
461 | OBJ_38 /* Build configuration list for PBXAggregateTarget "RegularExpressionDecoderPackageTests" */ = {
462 | isa = XCConfigurationList;
463 | buildConfigurations = (
464 | OBJ_39 /* Debug */,
465 | OBJ_40 /* Release */,
466 | );
467 | defaultConfigurationIsVisible = 0;
468 | defaultConfigurationName = Release;
469 | };
470 | OBJ_43 /* Build configuration list for PBXNativeTarget "RegularExpressionDecoderTests" */ = {
471 | isa = XCConfigurationList;
472 | buildConfigurations = (
473 | OBJ_44 /* Debug */,
474 | OBJ_45 /* Release */,
475 | );
476 | defaultConfigurationIsVisible = 0;
477 | defaultConfigurationName = Release;
478 | };
479 | /* End XCConfigurationList section */
480 | };
481 | rootObject = OBJ_1 /* Project object */;
482 | }
483 |
--------------------------------------------------------------------------------