From 0695bf13e54904593f484dda9988b5de6ad9e426 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 14:04:10 +0200 Subject: [PATCH 1/9] Made `Pathspec` conform to `ExpressibleByArrayLiteral` and made `init(patterns:)` receive an `Array` --- Sources/Pathspec/Pathspec.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/Pathspec/Pathspec.swift b/Sources/Pathspec/Pathspec.swift index f0b6f9b..ac04749 100644 --- a/Sources/Pathspec/Pathspec.swift +++ b/Sources/Pathspec/Pathspec.swift @@ -8,7 +8,7 @@ public final class Pathspec { private let specs: [Spec] - public init(patterns: String...) { + public init(patterns: [String]) { specs = patterns.compactMap { GitIgnoreSpec(pattern: $0) } @@ -24,3 +24,11 @@ public final class Pathspec { return specs.filter { $0.match(file: path) } } } + +extension Pathspec: ExpressibleByArrayLiteral { + public typealias ArrayLiteralElement = String + + public convenience init(arrayLiteral: String...) { + self.init(patterns: arrayLiteral) + } +} From d4efc1b2d2d49d8f09c03fd2675485e96a2252c4 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 14:13:20 +0200 Subject: [PATCH 2/9] Made `GitIgnoreSpec.init(pattern:) throws`, rather than `init?` to ease pattern debugging --- Sources/Pathspec/GitIgnoreSpec.swift | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Sources/Pathspec/GitIgnoreSpec.swift b/Sources/Pathspec/GitIgnoreSpec.swift index 38bd512..58ceb7e 100644 --- a/Sources/Pathspec/GitIgnoreSpec.swift +++ b/Sources/Pathspec/GitIgnoreSpec.swift @@ -8,14 +8,21 @@ import Foundation struct GitIgnoreSpec: Spec { + enum Error: Swift.Error { + case emptyPattern + case containsPoundPrefix + case containsTrippleStar + case emptyRoot + } + private(set) var inclusive: Bool = true let regex: NSRegularExpression - - init?(pattern: String) { - guard !pattern.isEmpty else { return nil } - guard !pattern.hasPrefix("#") else { return nil } - guard !pattern.contains("***") else { return nil } - guard pattern != "/" else { return nil } + + init(pattern: String) throws { + guard !pattern.isEmpty else { throw Error.emptyPattern } + guard !pattern.hasPrefix("#") else { throw Error.containsPoundPrefix } + guard !pattern.contains("***") else { throw Error.containsTrippleStar } + guard pattern != "/" else { throw Error.emptyPattern } var pattern = pattern if pattern.hasPrefix("!") { @@ -81,11 +88,7 @@ struct GitIgnoreSpec: Spec { regexString += "$" - do { - regex = try NSRegularExpression(pattern: regexString, options: []) - } catch { - return nil - } + regex = try NSRegularExpression(pattern: regexString, options: []) } func match(file: String) -> Bool { From 7e45a629a48f100fba049217387335af13988a6a Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 14:13:55 +0200 Subject: [PATCH 3/9] Made `Pathspec.init(pattern:) throws`, rather than `init?` to ease pattern debugging --- Sources/Pathspec/Pathspec.swift | 16 +++++++++++----- Tests/PathspecTests/PathspecTests.swift | 10 +++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Sources/Pathspec/Pathspec.swift b/Sources/Pathspec/Pathspec.swift index ac04749..a66e756 100644 --- a/Sources/Pathspec/Pathspec.swift +++ b/Sources/Pathspec/Pathspec.swift @@ -8,11 +8,15 @@ public final class Pathspec { private let specs: [Spec] - public init(patterns: [String]) { - specs = patterns.compactMap { - GitIgnoreSpec(pattern: $0) - } + public convenience init(patterns: [String]) throws { + self.init(specs: try patterns.map { + try GitIgnoreSpec(pattern: $0) + }) } + + public init(specs: [Spec]) { + self.specs = specs + } public func match(path: String) -> Bool { let matchingSpecs = self.matchingSpecs(path: path) @@ -29,6 +33,8 @@ extension Pathspec: ExpressibleByArrayLiteral { public typealias ArrayLiteralElement = String public convenience init(arrayLiteral: String...) { - self.init(patterns: arrayLiteral) + self.init(specs: arrayLiteral.compactMap { + try? GitIgnoreSpec(pattern: $0) + }) } } diff --git a/Tests/PathspecTests/PathspecTests.swift b/Tests/PathspecTests/PathspecTests.swift index 48deb9c..7175029 100644 --- a/Tests/PathspecTests/PathspecTests.swift +++ b/Tests/PathspecTests/PathspecTests.swift @@ -25,11 +25,11 @@ public func XCTUnwrap(_ expression: @autoclosure () throws -> T?, _ message: final class PathspecTests: XCTestCase { func testAbsoluteRoot() throws { - XCTAssertNil(GitIgnoreSpec(pattern: "/")) + XCTAssertThrowsError(try GitIgnoreSpec(pattern: "/")) } func testComment() throws { - XCTAssertNil(GitIgnoreSpec(pattern: "# Cork soakers.")) + XCTAssertThrowsError(try GitIgnoreSpec(pattern: "# Cork soakers.")) } func testIgnore() throws { @@ -255,8 +255,8 @@ final class PathspecTests: XCTestCase { ) } - func testFailingInitializers() { - XCTAssertNil(GitIgnoreSpec(pattern: "")) - XCTAssertNil(GitIgnoreSpec(pattern: "***")) + func testFailingInitializers() throws { + XCTAssertThrowsError(try GitIgnoreSpec(pattern: "")) + XCTAssertThrowsError(try GitIgnoreSpec(pattern: "***")) } } From c98fd4c7a33a01f0327c24a373983b7cb15c3276 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 15:14:37 +0200 Subject: [PATCH 4/9] Made `GitIgnoreSpec` conform to `Custom(Debug)StringConvertible` --- Sources/Pathspec/GitIgnoreSpec.swift | 19 +++++++++++++++++++ Tests/PathspecTests/PathspecTests.swift | 14 +++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Sources/Pathspec/GitIgnoreSpec.swift b/Sources/Pathspec/GitIgnoreSpec.swift index 58ceb7e..6b25257 100644 --- a/Sources/Pathspec/GitIgnoreSpec.swift +++ b/Sources/Pathspec/GitIgnoreSpec.swift @@ -16,9 +16,13 @@ struct GitIgnoreSpec: Spec { } private(set) var inclusive: Bool = true + + let pattern: String let regex: NSRegularExpression init(pattern: String) throws { + self.pattern = pattern + guard !pattern.isEmpty else { throw Error.emptyPattern } guard !pattern.hasPrefix("#") else { throw Error.containsPoundPrefix } guard !pattern.contains("***") else { throw Error.containsTrippleStar } @@ -155,3 +159,18 @@ struct GitIgnoreSpec: Spec { return regex } } + +extension GitIgnoreSpec: CustomStringConvertible { + var description: String { + let pattern = self.pattern.debugDescription + return "<\(type(of: self)) pattern: \(pattern)>" + } +} + +extension GitIgnoreSpec: CustomDebugStringConvertible { + var debugDescription: String { + let pattern = self.pattern.debugDescription + let regexPattern = self.regex.pattern.debugDescription + return "<\(type(of: self)) pattern: \(pattern) regex: \(regexPattern)>" + } +} diff --git a/Tests/PathspecTests/PathspecTests.swift b/Tests/PathspecTests/PathspecTests.swift index 7175029..009f83a 100644 --- a/Tests/PathspecTests/PathspecTests.swift +++ b/Tests/PathspecTests/PathspecTests.swift @@ -24,7 +24,19 @@ public func XCTUnwrap(_ expression: @autoclosure () throws -> T?, _ message: #endif final class PathspecTests: XCTestCase { - func testAbsoluteRoot() throws { + func testDescription() throws { + let spec = try XCTUnwrap(GitIgnoreSpec(pattern: "foobar")) + + XCTAssertEqual(spec.description, "") + } + + func testDebugDescription() throws { + let spec = try XCTUnwrap(GitIgnoreSpec(pattern: "foobar")) + + XCTAssertEqual(spec.debugDescription, "") + } + + func testAbsoluteRoot() throws { XCTAssertThrowsError(try GitIgnoreSpec(pattern: "/")) } From 5c4d5d41b5fc078fdec8cdcd6b2ec82a7311b967 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 16:30:41 +0200 Subject: [PATCH 5/9] Renamed `PathspecTests` to `GitIgnoreSpecTests` --- .../{PathspecTests.swift => GitIgnoreSpecTests.swift} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Tests/PathspecTests/{PathspecTests.swift => GitIgnoreSpecTests.swift} (99%) diff --git a/Tests/PathspecTests/PathspecTests.swift b/Tests/PathspecTests/GitIgnoreSpecTests.swift similarity index 99% rename from Tests/PathspecTests/PathspecTests.swift rename to Tests/PathspecTests/GitIgnoreSpecTests.swift index 009f83a..c6af326 100644 --- a/Tests/PathspecTests/PathspecTests.swift +++ b/Tests/PathspecTests/GitIgnoreSpecTests.swift @@ -23,7 +23,7 @@ public func XCTUnwrap(_ expression: @autoclosure () throws -> T?, _ message: } #endif -final class PathspecTests: XCTestCase { +final class GitIgnoreSpecTests: XCTestCase { func testDescription() throws { let spec = try XCTUnwrap(GitIgnoreSpec(pattern: "foobar")) From 7d0fc3fdc1803e213802439924143738b23dfa9f Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 16:31:54 +0200 Subject: [PATCH 6/9] Moved backport of `XCTUnwrap` into dedicated file --- Tests/PathspecTests/GitIgnoreSpecTests.swift | 15 ------------ Tests/PathspecTests/XCTUnwrap.swift | 24 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 Tests/PathspecTests/XCTUnwrap.swift diff --git a/Tests/PathspecTests/GitIgnoreSpecTests.swift b/Tests/PathspecTests/GitIgnoreSpecTests.swift index c6af326..197e7ca 100644 --- a/Tests/PathspecTests/GitIgnoreSpecTests.swift +++ b/Tests/PathspecTests/GitIgnoreSpecTests.swift @@ -8,21 +8,6 @@ import XCTest @testable import Pathspec -#if swift(<5.1) -private struct UnwrappingFailure: LocalizedError { - var errorDescription: String? { - return "XCTUnwrap failed: throwing an unknown exception" - } -} -public func XCTUnwrap(_ expression: @autoclosure () throws -> T?, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) throws -> T { - guard let unwrapped = try expression() else { - XCTFail(message(), file: file, line: line) - throw UnwrappingFailure() - } - return unwrapped -} -#endif - final class GitIgnoreSpecTests: XCTestCase { func testDescription() throws { let spec = try XCTUnwrap(GitIgnoreSpec(pattern: "foobar")) diff --git a/Tests/PathspecTests/XCTUnwrap.swift b/Tests/PathspecTests/XCTUnwrap.swift new file mode 100644 index 0000000..a55ecfa --- /dev/null +++ b/Tests/PathspecTests/XCTUnwrap.swift @@ -0,0 +1,24 @@ +// +// File.swift +// +// +// Created by Vincent Esche on 4/23/20. +// + +import XCTest +@testable import Pathspec + +#if swift(<5.1) +private struct UnwrappingFailure: LocalizedError { + var errorDescription: String? { + return "XCTUnwrap failed: throwing an unknown exception" + } +} +public func XCTUnwrap(_ expression: @autoclosure () throws -> T?, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) throws -> T { + guard let unwrapped = try expression() else { + XCTFail(message(), file: file, line: line) + throw UnwrappingFailure() + } + return unwrapped +} +#endif From dd295ec27977a4fd31b380634e6623dfb65e6be3 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 16:38:33 +0200 Subject: [PATCH 7/9] Made `Pathspec` conform to `Custom(Debug)StringConvertible` --- Sources/Pathspec/Pathspec.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/Pathspec/Pathspec.swift b/Sources/Pathspec/Pathspec.swift index a66e756..620c192 100644 --- a/Sources/Pathspec/Pathspec.swift +++ b/Sources/Pathspec/Pathspec.swift @@ -38,3 +38,21 @@ extension Pathspec: ExpressibleByArrayLiteral { }) } } + +extension Pathspec: CustomStringConvertible { + public var description: String { + let specsDescription = specs.map { spec in + " " + String(describing: spec) + }.joined(separator: ",\n") + return "<\(type(of: self)) specs: [\n\(specsDescription)\n]>" + } +} + +extension Pathspec: CustomDebugStringConvertible { + public var debugDescription: String { + let specsDescription = specs.map { spec in + " " + String(reflecting: spec) + }.joined(separator: ",\n") + return "<\(type(of: self)) specs: [\n\(specsDescription)\n]>" + } +} From 538dc24453d9c7d46d2985fc6b474498df08e1fb Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Thu, 23 Apr 2020 16:35:22 +0200 Subject: [PATCH 8/9] Added `PathspecTests` --- Tests/PathspecTests/PathspecTests.swift | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Tests/PathspecTests/PathspecTests.swift diff --git a/Tests/PathspecTests/PathspecTests.swift b/Tests/PathspecTests/PathspecTests.swift new file mode 100644 index 0000000..33ce4fd --- /dev/null +++ b/Tests/PathspecTests/PathspecTests.swift @@ -0,0 +1,39 @@ +// +// File.swift +// +// +// Created by Vincent Esche on 4/23/20. +// + +import XCTest +@testable import Pathspec + +final class PathspecTests: XCTestCase { + func testDescription() throws { + let spec: Pathspec = try XCTUnwrap(["foo", "foo/bar"]) + + XCTAssertEqual( + spec.description, + """ + , + + ]> + """ + ) + } + + func testDebugDescription() throws { + let spec: Pathspec = try XCTUnwrap(["foo", "foo/bar"]) + + XCTAssertEqual( + spec.debugDescription, + """ + , + + ]> + """ + ) + } +} From 7dd4ca514ee4576983053b48c056be50814805b6 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 27 Apr 2020 20:48:58 +0200 Subject: [PATCH 9/9] Fixed error names --- Sources/Pathspec/GitIgnoreSpec.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Pathspec/GitIgnoreSpec.swift b/Sources/Pathspec/GitIgnoreSpec.swift index 6b25257..97937a3 100644 --- a/Sources/Pathspec/GitIgnoreSpec.swift +++ b/Sources/Pathspec/GitIgnoreSpec.swift @@ -10,8 +10,8 @@ import Foundation struct GitIgnoreSpec: Spec { enum Error: Swift.Error { case emptyPattern - case containsPoundPrefix - case containsTrippleStar + case commented + case invalid case emptyRoot } @@ -24,9 +24,9 @@ struct GitIgnoreSpec: Spec { self.pattern = pattern guard !pattern.isEmpty else { throw Error.emptyPattern } - guard !pattern.hasPrefix("#") else { throw Error.containsPoundPrefix } - guard !pattern.contains("***") else { throw Error.containsTrippleStar } - guard pattern != "/" else { throw Error.emptyPattern } + guard !pattern.hasPrefix("#") else { throw Error.commented } + guard !pattern.contains("***") else { throw Error.invalid } + guard pattern != "/" else { throw Error.emptyRoot } var pattern = pattern if pattern.hasPrefix("!") {