From 1310a8b9b90ae821f7351295e0809407b16de05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Wed, 18 Mar 2020 08:52:51 +0100 Subject: [PATCH] Remove HandySwift dependency --- Package.resolved | 9 - Package.swift | 7 +- Sources/AnyLint/CheckInfo.swift | 1 - .../Checkers/FileContentsChecker.swift | 1 - .../AnyLint/Checkers/FilePathsChecker.swift | 1 - Sources/AnyLint/FilesSearch.swift | 1 - Sources/AnyLint/Lint.swift | 1 - Sources/AnyLint/Violation.swift | 1 - Sources/Utility/Extensions/RegexExt.swift | 1 - Sources/Utility/Regex.swift | 262 ++++++++++++++++++ Tests/AnyLintTests/FilesSearchTests.swift | 1 - Tests/AnyLintTests/LintTests.swift | 1 - .../Extensions/RegexExtTests.swift | 1 - 13 files changed, 265 insertions(+), 23 deletions(-) create mode 100644 Sources/Utility/Regex.swift diff --git a/Package.resolved b/Package.resolved index f02fdd2..aa38a47 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "HandySwift", - "repositoryURL": "https://github.com/Flinesoft/HandySwift.git", - "state": { - "branch": null, - "revision": "083707d9f9da65bd57b756294653ee0fc50d8662", - "version": "3.1.0" - } - }, { "package": "Rainbow", "repositoryURL": "https://github.com/onevcat/Rainbow.git", diff --git a/Package.swift b/Package.swift index 3a17ac1..c0ad908 100644 --- a/Package.swift +++ b/Package.swift @@ -8,14 +8,13 @@ let package = Package( .executable(name: "anylint", targets: ["AnyLintCLI"]), ], dependencies: [ - .package(url: "https://github.com/Flinesoft/HandySwift.git", from: "3.1.0"), .package(url: "https://github.com/onevcat/Rainbow.git", from: "3.1.5"), .package(url: "https://github.com/jakeheis/SwiftCLI.git", from: "6.0.1"), ], targets: [ .target( name: "AnyLint", - dependencies: ["HandySwift", "Utility"] + dependencies: ["Utility"] ), .testTarget( name: "AnyLintTests", @@ -23,7 +22,7 @@ let package = Package( ), .target( name: "AnyLintCLI", - dependencies: ["HandySwift", "Rainbow", "SwiftCLI", "Utility"] + dependencies: ["Rainbow", "SwiftCLI", "Utility"] ), .testTarget( name: "AnyLintCLITests", @@ -31,7 +30,7 @@ let package = Package( ), .target( name: "Utility", - dependencies: ["HandySwift", "Rainbow"] + dependencies: ["Rainbow"] ), .testTarget( name: "UtilityTests", diff --git a/Sources/AnyLint/CheckInfo.swift b/Sources/AnyLint/CheckInfo.swift index a7c6e22..58c8a90 100644 --- a/Sources/AnyLint/CheckInfo.swift +++ b/Sources/AnyLint/CheckInfo.swift @@ -1,5 +1,4 @@ import Foundation -import HandySwift import Utility /// Provides some basic information needed in each lint check. diff --git a/Sources/AnyLint/Checkers/FileContentsChecker.swift b/Sources/AnyLint/Checkers/FileContentsChecker.swift index a76a8e0..5eebf53 100644 --- a/Sources/AnyLint/Checkers/FileContentsChecker.swift +++ b/Sources/AnyLint/Checkers/FileContentsChecker.swift @@ -1,5 +1,4 @@ import Foundation -import HandySwift import Utility struct FileContentsChecker { diff --git a/Sources/AnyLint/Checkers/FilePathsChecker.swift b/Sources/AnyLint/Checkers/FilePathsChecker.swift index 08cff8c..0b4350a 100644 --- a/Sources/AnyLint/Checkers/FilePathsChecker.swift +++ b/Sources/AnyLint/Checkers/FilePathsChecker.swift @@ -1,5 +1,4 @@ import Foundation -import HandySwift import Utility struct FilePathsChecker { diff --git a/Sources/AnyLint/FilesSearch.swift b/Sources/AnyLint/FilesSearch.swift index 40e679e..179dcf8 100644 --- a/Sources/AnyLint/FilesSearch.swift +++ b/Sources/AnyLint/FilesSearch.swift @@ -1,5 +1,4 @@ import Foundation -import HandySwift import Utility /// Helper to search for files and filter using Regexes. diff --git a/Sources/AnyLint/Lint.swift b/Sources/AnyLint/Lint.swift index ce22843..970cc90 100644 --- a/Sources/AnyLint/Lint.swift +++ b/Sources/AnyLint/Lint.swift @@ -1,5 +1,4 @@ import Foundation -import HandySwift import Utility /// The linter type providing APIs for checking anything using regular expressions. diff --git a/Sources/AnyLint/Violation.swift b/Sources/AnyLint/Violation.swift index 5398b21..723db5a 100644 --- a/Sources/AnyLint/Violation.swift +++ b/Sources/AnyLint/Violation.swift @@ -1,5 +1,4 @@ import Foundation -import HandySwift import Utility /// A violation found in a check. diff --git a/Sources/Utility/Extensions/RegexExt.swift b/Sources/Utility/Extensions/RegexExt.swift index ca5b669..17226ef 100644 --- a/Sources/Utility/Extensions/RegexExt.swift +++ b/Sources/Utility/Extensions/RegexExt.swift @@ -1,5 +1,4 @@ import Foundation -import HandySwift extension Regex: ExpressibleByStringLiteral { public init(stringLiteral value: String) { diff --git a/Sources/Utility/Regex.swift b/Sources/Utility/Regex.swift new file mode 100644 index 0000000..158d87c --- /dev/null +++ b/Sources/Utility/Regex.swift @@ -0,0 +1,262 @@ +// Originally from: https://github.com/sharplet/Regex & https://github.com/Flinesoft/HandySwift (modified). + +import Foundation + +/// `Regex` is a swifty regex engine built on top of the NSRegularExpression api. +public struct Regex { + // MARK: - Properties + private let regularExpression: NSRegularExpression + + // MARK: - Initializers + /// Create a `Regex` based on a pattern string. + /// + /// If `pattern` is not a valid regular expression, an error is thrown + /// describing the failure. + /// + /// - parameters: + /// - pattern: A pattern string describing the regex. + /// - options: Configure regular expression matching options. + /// For details, see `Regex.Options`. + /// + /// - throws: A value of `ErrorType` describing the invalid regular expression. + public init(_ pattern: String, options: Options = []) throws { + regularExpression = try NSRegularExpression( + pattern: pattern, + options: options.toNSRegularExpressionOptions + ) + } + + // MARK: - Methods: Matching + /// Returns `true` if the regex matches `string`, otherwise returns `false`. + /// + /// - parameter string: The string to test. + /// + /// - returns: `true` if the regular expression matches, otherwise `false`. + public func matches(_ string: String) -> Bool { + firstMatch(in: string) != nil + } + + /// If the regex matches `string`, returns a `Match` describing the + /// first matched string and any captures. If there are no matches, returns + /// `nil`. + /// + /// - parameter string: The string to match against. + /// + /// - returns: An optional `Match` describing the first match, or `nil`. + public func firstMatch(in string: String) -> Match? { + let firstMatch = regularExpression + .firstMatch(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count)) + .map { Match(result: $0, in: string) } + return firstMatch + } + + /// If the regex matches `string`, returns an array of `Match`, describing + /// every match inside `string`. If there are no matches, returns an empty + /// array. + /// + /// - parameter string: The string to match against. + /// + /// - returns: An array of `Match` describing every match in `string`. + public func matches(in string: String) -> [Match] { + let matches = regularExpression + .matches(in: string, options: [], range: NSRange(location: 0, length: string.utf16.count)) + .map { Match(result: $0, in: string) } + return matches + } + + // MARK: Replacing + /// Returns a new string where each substring matched by `regex` is replaced + /// with `template`. + /// + /// The template string may be a literal string, or include template variables: + /// the variable `$0` will be replaced with the entire matched substring, `$1` + /// with the first capture group, etc. + /// + /// For example, to include the literal string "$1" in the replacement string, + /// you must escape the "$": `\$1`. + /// + /// - parameters: + /// - regex: A regular expression to match against `self`. + /// - template: A template string used to replace matches. + /// - count: The maximum count of matches to replace, beginning with the first match. + /// + /// - returns: A string with all matches of `regex` replaced by `template`. + public func replacingMatches(in input: String, with template: String, count: Int? = nil) -> String { + var output = input + let matches = self.matches(in: input) + let rangedMatches = Array(matches[0 ..< min(matches.count, count ?? .max)]) + for match in rangedMatches.reversed() { + let replacement = match.string(applyingTemplate: template) + output.replaceSubrange(match.range, with: replacement) + } + + return output + } +} + +// MARK: - CustomStringConvertible +extension Regex: CustomStringConvertible { + /// Returns a string describing the regex using its pattern string. + public var description: String { + "Regex<\"\(regularExpression.pattern)\">" + } +} + +// MARK: - Equatable +extension Regex: Equatable { + /// Determines the equality of to `Regex`` instances. + /// Two `Regex` are considered equal, if both the pattern string and the options + /// passed on initialization are equal. + public static func == (lhs: Regex, rhs: Regex) -> Bool { + lhs.regularExpression.pattern == rhs.regularExpression.pattern && + lhs.regularExpression.options == rhs.regularExpression.options + } +} + +// MARK: - Hashable +extension Regex: Hashable { + /// Manages hashing of the `Regex` instance. + public func hash(into hasher: inout Hasher) { + hasher.combine(regularExpression) + } +} + +// MARK: - Options +extension Regex { + /// `Options` defines alternate behaviours of regular expressions when matching. + public struct Options: OptionSet { + // MARK: - Properties + /// Ignores the case of letters when matching. + public static let ignoreCase = Options(rawValue: 1) + + /// Ignore any metacharacters in the pattern, treating every character as + /// a literal. + public static let ignoreMetacharacters = Options(rawValue: 1 << 1) + + /// By default, "^" matches the beginning of the string and "$" matches the + /// end of the string, ignoring any newlines. With this option, "^" will + /// the beginning of each line, and "$" will match the end of each line. + public static let anchorsMatchLines = Options(rawValue: 1 << 2) + + /// Usually, "." matches all characters except newlines (\n). Using this, + /// options will allow "." to match newLines + public static let dotMatchesLineSeparators = Options(rawValue: 1 << 3) + + /// The raw value of the `OptionSet` + public let rawValue: Int + + /// Transform an instance of `Regex.Options` into the equivalent `NSRegularExpression.Options`. + /// + /// - returns: The equivalent `NSRegularExpression.Options`. + var toNSRegularExpressionOptions: NSRegularExpression.Options { + var options = NSRegularExpression.Options() + if contains(.ignoreCase) { options.insert(.caseInsensitive) } + if contains(.ignoreMetacharacters) { options.insert(.ignoreMetacharacters) } + if contains(.anchorsMatchLines) { options.insert(.anchorsMatchLines) } + if contains(.dotMatchesLineSeparators) { options.insert(.dotMatchesLineSeparators) } + return options + } + + // MARK: - Initializers + /// The raw value init for the `OptionSet` + public init(rawValue: Int) { + self.rawValue = rawValue + } + } +} + +// MARK: - Match +extension Regex { + /// A `Match` encapsulates the result of a single match in a string, + /// providing access to the matched string, as well as any capture groups within + /// that string. + public class Match: CustomStringConvertible { + // MARK: Properties + /// The entire matched string. + public lazy var string: String = { + String(describing: self.baseString[self.range]) + }() + + /// The range of the matched string. + public lazy var range: Range = { + Range(self.result.range, in: self.baseString)! + }() + + /// The matching string for each capture group in the regular expression + /// (if any). + /// + /// **Note:** Usually if the match was successful, the captures will by + /// definition be non-nil. However if a given capture group is optional, the + /// captured string may also be nil, depending on the particular string that + /// is being matched against. + /// + /// Example: + /// + /// let regex = Regex("(a)?(b)") + /// + /// regex.matches(in: "ab")first?.captures // [Optional("a"), Optional("b")] + /// regex.matches(in: "b").first?.captures // [nil, Optional("b")] + public lazy var captures: [String?] = { + let captureRanges = stride(from: 0, to: result.numberOfRanges, by: 1) + .map(result.range) + .dropFirst() + .map { [unowned self] in + Range($0, in: self.baseString) + } + + return captureRanges.map { [unowned self] captureRange in + if let captureRange = captureRange { + return String(describing: self.baseString[captureRange]) + } + + return nil + } + }() + + private let result: NSTextCheckingResult + + private let baseString: String + + // MARK: - Initializers + internal init(result: NSTextCheckingResult, in string: String) { + precondition( + result.regularExpression != nil, + "NSTextCheckingResult must originate from regular expression parsing." + ) + + self.result = result + self.baseString = string + } + + // MARK: - Methods + /// Returns a new string where the matched string is replaced according to the `template`. + /// + /// The template string may be a literal string, or include template variables: + /// the variable `$0` will be replaced with the entire matched substring, `$1` + /// with the first capture group, etc. + /// + /// For example, to include the literal string "$1" in the replacement string, + /// you must escape the "$": `\$1`. + /// + /// - parameters: + /// - template: The template string used to replace matches. + /// + /// - returns: A string with `template` applied to the matched string. + public func string(applyingTemplate template: String) -> String { + let replacement = result.regularExpression!.replacementString( + for: result, + in: baseString, + offset: 0, + template: template + ) + + return replacement + } + + // MARK: - CustomStringConvertible + /// Returns a string describing the match. + public var description: String { + "Match<\"\(string)\">" + } + } +} diff --git a/Tests/AnyLintTests/FilesSearchTests.swift b/Tests/AnyLintTests/FilesSearchTests.swift index 9d60838..115f919 100644 --- a/Tests/AnyLintTests/FilesSearchTests.swift +++ b/Tests/AnyLintTests/FilesSearchTests.swift @@ -1,5 +1,4 @@ @testable import AnyLint -import HandySwift @testable import Utility import XCTest diff --git a/Tests/AnyLintTests/LintTests.swift b/Tests/AnyLintTests/LintTests.swift index 623d7b7..7d0444d 100644 --- a/Tests/AnyLintTests/LintTests.swift +++ b/Tests/AnyLintTests/LintTests.swift @@ -1,5 +1,4 @@ @testable import AnyLint -import HandySwift @testable import Utility import XCTest diff --git a/Tests/UtilityTests/Extensions/RegexExtTests.swift b/Tests/UtilityTests/Extensions/RegexExtTests.swift index ef0ad54..9c90a31 100644 --- a/Tests/UtilityTests/Extensions/RegexExtTests.swift +++ b/Tests/UtilityTests/Extensions/RegexExtTests.swift @@ -1,4 +1,3 @@ -import HandySwift @testable import Utility import XCTest