├── .gitignore ├── .travis.yml ├── LICENCE ├── Package.swift ├── Readme.md ├── Sources └── Regex │ ├── CaptureResult.swift │ ├── InternalRegex.swift │ ├── Regex.swift │ ├── StringExtensions.swift │ └── Types.swift └── Tests ├── LinuxMain.swift └── RegexTests └── RegexTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift,xcode,appcode,vim,objective-c 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xcuserstate 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | 71 | 72 | ### Xcode ### 73 | # Xcode 74 | # 75 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 76 | 77 | ## Build generated 78 | 79 | ## Various settings 80 | 81 | ## Other 82 | *.xccheckout 83 | *.xcscmblueprint 84 | 85 | 86 | ### AppCode ### 87 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 88 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 89 | 90 | # User-specific stuff: 91 | .idea/workspace.xml 92 | .idea/tasks.xml 93 | 94 | # Sensitive or high-churn files: 95 | .idea/dataSources/ 96 | .idea/dataSources.ids 97 | .idea/dataSources.xml 98 | .idea/dataSources.local.xml 99 | .idea/sqlDataSources.xml 100 | .idea/dynamic.xml 101 | .idea/uiDesigner.xml 102 | 103 | # Gradle: 104 | .idea/gradle.xml 105 | .idea/libraries 106 | 107 | # Mongo Explorer plugin: 108 | .idea/mongoSettings.xml 109 | 110 | ## File-based project format: 111 | *.iws 112 | 113 | ## Plugin-specific files: 114 | 115 | # IntelliJ 116 | /out/ 117 | 118 | # mpeltonen/sbt-idea plugin 119 | .idea_modules/ 120 | 121 | # JIRA plugin 122 | atlassian-ide-plugin.xml 123 | 124 | # Crashlytics plugin (for Android Studio and IntelliJ) 125 | com_crashlytics_export_strings.xml 126 | crashlytics.properties 127 | crashlytics-build.properties 128 | fabric.properties 129 | 130 | ### AppCode Patch ### 131 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 132 | 133 | # *.iml 134 | # modules.xml 135 | # .idea/misc.xml 136 | # *.ipr 137 | 138 | 139 | ### Vim ### 140 | # swap 141 | [._]*.s[a-w][a-z] 142 | [._]s[a-w][a-z] 143 | # session 144 | Session.vim 145 | # temporary 146 | .netrwhist 147 | *~ 148 | # auto-generated tag files 149 | tags 150 | 151 | 152 | ### Objective-C ### 153 | # Xcode 154 | # 155 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 156 | 157 | ## Build generated 158 | 159 | ## Various settings 160 | 161 | ## Other 162 | 163 | ## Obj-C/Swift specific 164 | 165 | # CocoaPods 166 | # 167 | # We recommend against adding the Pods directory to your .gitignore. However 168 | # you should judge for yourself, the pros and cons are mentioned at: 169 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 170 | # 171 | # Pods/ 172 | 173 | # Carthage 174 | # 175 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 176 | # Carthage/Checkouts 177 | 178 | 179 | # fastlane 180 | # 181 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 182 | # screenshots whenever they are needed. 183 | # For more information about the recommended setup visit: 184 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 185 | 186 | 187 | # Code Injection 188 | # 189 | # After new code Injection tools there's a generated folder /iOSInjectionProject 190 | # https://github.com/johnno1962/injectionforxcode 191 | 192 | iOSInjectionProject/ 193 | 194 | Regex.xcodeproj 195 | Packages/ 196 | 197 | ### Objective-C Patch ### 198 | 0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: objective-c 3 | osx_image: xcode9.4.1 4 | 5 | script: 6 | - swift package generate-xcodeproj --enable-code-coverage 7 | - xcodebuild -project Regex.xcodeproj -scheme Regex-Package test 8 | 9 | after_success: 10 | - bash <(curl -s https://codecov.io/bash) 11 | 12 | notifications: 13 | email: false 14 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Omar Abdelhafith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | 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 OR 14 | 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 FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Regex", 6 | products: [ 7 | .library(name: "Regex", targets: ["Regex"]) 8 | ], 9 | targets: [ 10 | .target(name: "Regex"), 11 | .testTarget(name: "RegexTests", dependencies: ["Regex"]) 12 | ], 13 | swiftLanguageVersions: [4] 14 | ) 15 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Regex 2 | 3 | [![Build Status](https://travis-ci.com/getGuaka/Regex.svg?branch=master)](https://travis-ci.com/getGuaka/Regex) 4 | [![codecov](https://codecov.io/gh/getGuaka/Regex/branch/master/graph/badge.svg)](https://codecov.io/gh/getGuaka/Regex) 5 | [![Platform](https://img.shields.io/badge/platform-osx-lightgrey.svg)](https://travis-ci.com/getGuaka/Regex) 6 | [![Language: Swift](https://img.shields.io/badge/language-swift-orange.svg)](https://travis-ci.com/getGuaka/Regex) 7 | [![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | 9 | 10 | ## Why? 11 | 12 | If you are developing cross platform (macOS and Linux) command line apps, and 13 | you want to use Regular expressions, `Regex` is just what you need. 14 | 15 | You can use `Regex` with [Guaka](https://github.com/nsomar/Guaka) to create 16 | aweseome command line applications. 17 | 18 | ## Usage 19 | 20 | - Checking if a string matches a pattern. 21 | 22 | ```swift 23 | let r = try! Regex(pattern: "Hello [a-z]+ame") 24 | r.matches("Hello Name") 25 | ``` 26 | 27 | - Capture a string with a regex capture group 28 | 29 | ```swift 30 | let r = try! Regex(pattern: "Hello (.*) name") 31 | let result = r.captures(string: "Hello mr name") 32 | ``` 33 | 34 | `result` is an array of `CaptureResult` 35 | 36 | - Using the `~=` operator 37 | 38 | ```swift 39 | // Regex ~= String 40 | let value = try! Regex(pattern: "Hello [a-z]+ame") ~= "Hello Name" 41 | // value is true 42 | 43 | // String ~= Regex 44 | let value = "Hello Name" ~= try! Regex(pattern: "Hello [a-z]+ame") 45 | // value is true 46 | ``` 47 | 48 | - Using regex matching with switch 49 | 50 | ```swift 51 | switch "Hello I am on swift" { 52 | case try! Regex(pattern: "Hello [a-z] am .*"): 53 | // First 54 | case try! Regex(pattern: ".*"): 55 | // Second 56 | } 57 | ``` 58 | 59 | The first passing regex will be matched. In the example above, the first case 60 | is matched. 61 | 62 | ### String extensions 63 | 64 | Replace a pattern with a string: 65 | 66 | ```swift 67 | "This string is wrong".replacing(pattern: "w.*g", withString: "right") 68 | // "This string is right" 69 | ``` 70 | 71 | ### CaptureResult 72 | 73 | `CaptureResult` represent a captured string, it contains: 74 | 75 | - `originalString` the original string 76 | - `startIndex` the capture start index 77 | - `endIndex` the capture end index 78 | - `range` the range of the captured string 79 | - `string` the captured string 80 | 81 | ### RegexOptions 82 | 83 | `RegexOptions` optionset can be passed when initilaizing a `Regex` object. For 84 | a discussion on the meaning of these flags, refer to [GNU regex 85 | documentation](http://www.gnu.org/software/libc/manual/html_node/POSIX-Regexp-Compilation.html#POSIX-Regexp-Compilation) 86 | 87 | ### MatchOptions 88 | `matches(_:options:)` accepts the `MatchOptions` option set. For a discussion 89 | on the meaning of these flags, refer to [GNU regex 90 | documentation](http://www.gnu.org/software/libc/manual/html_node/Matching-POSIX-Regexps.html#Matching-POSIX-Regexps) 91 | 92 | ## Installation 93 | You can install Regex using Swift package manager (SPM) and carthage 94 | 95 | ### Swift Package Manager 96 | Add Regex as dependency in your `Package.swift` 97 | 98 | ```swift 99 | import PackageDescription 100 | 101 | let package = Package(name: "YourPackage", 102 | dependencies: [ 103 | .Package(url: "https://github.com/getGuaka/Regex.git", majorVersion: 0), 104 | ] 105 | ) 106 | ``` 107 | 108 | ### Carthage 109 | github "getGuaka/Regex" 110 | 111 | ## Tests 112 | Tests can be found [here](https://github.com/getGuaka/Regex/tree/master/Tests). 113 | 114 | Run them with 115 | ``` 116 | swift test 117 | ``` 118 | 119 | ## Contributing 120 | 121 | Just send a PR! We don't bite ;) 122 | -------------------------------------------------------------------------------- /Sources/Regex/CaptureResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CaptureResult.swift 3 | // Regex 4 | // 5 | // Created by Omar Abdelhafith on 13/11/2016. 6 | // 7 | // 8 | 9 | /// Capture result strucute representing a captured result 10 | public struct CaptureResult { 11 | 12 | /// The start index captures 13 | public let startIndex: String.Index 14 | 15 | /// The end index captures 16 | public let endIndex: String.Index 17 | 18 | /// The original string containing the captured string 19 | public let originalString: String 20 | 21 | /// The captured string 22 | public var string: String { 23 | return internalValue.string 24 | } 25 | 26 | /// Closed range of the capture 27 | public var range: Range { 28 | return startIndex.. Bool { 64 | var regexMatches = [regmatch_t](repeating: regmatch_t(), count: 1) 65 | let result = regexec(&preg, string, regexMatches.count, ®exMatches, options.rawValue) 66 | 67 | if result == 1 { 68 | return false 69 | } 70 | 71 | return true 72 | } 73 | 74 | func groups(_ string: String, options: MatchOptions = []) 75 | -> [(String.Index, String.Index)] { 76 | let original = string 77 | var string = string 78 | let maxMatches = 20 79 | var groups: [(String.Index, String.Index)] = [] 80 | var accOffset = 0 81 | 82 | while true { 83 | var regexMatches = [regmatch_t](repeating: regmatch_t(), count: maxMatches) 84 | let result = regexec(&preg, string, regexMatches.count, ®exMatches, options.rawValue) 85 | 86 | if result == 1 { 87 | break 88 | } 89 | 90 | var j = 1 91 | 92 | while regexMatches[j].rm_so != -1 { 93 | let start = accOffset + Int(regexMatches[j].rm_so) 94 | let end = accOffset + Int(regexMatches[j].rm_eo) 95 | let startIndex = original.index(original.startIndex, offsetBy: start) 96 | let endIndex = original.index(original.startIndex, offsetBy: end) 97 | groups.append((startIndex, endIndex)) 98 | j += 1 99 | } 100 | 101 | let offset = Int(regexMatches[0].rm_eo) 102 | accOffset += offset 103 | let startIndex = string.utf8.index(string.utf8.startIndex, offsetBy: offset) 104 | if let offsetString = String(string.utf8[startIndex ..< string.utf8.endIndex]) { 105 | string = offsetString 106 | } else { 107 | break 108 | } 109 | } 110 | 111 | return groups 112 | } 113 | 114 | } 115 | 116 | public struct RegexError: Error { 117 | public let description: String 118 | 119 | fileprivate static func error(from result: Int32, preg: regex_t) -> RegexError { 120 | var preg = preg 121 | var buffer = [Int8](repeating: 0, count: Int(BUFSIZ)) 122 | regerror(result, &preg, &buffer, buffer.count) 123 | let description = String(validatingUTF8: buffer)! 124 | return RegexError(description: description) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/Regex/Regex.swift: -------------------------------------------------------------------------------- 1 | 2 | // Regex.swift 3 | // Regex 4 | // 5 | // Created by Omar Abdelhafith on 13/11/2016. 6 | // 7 | // 8 | 9 | /// Regex structure 10 | public struct Regex { 11 | 12 | /// The regex pattern 13 | public let pattern: String 14 | 15 | let internalRegex: __Regex 16 | 17 | /// Initialize a Regex matcher with a pattern 18 | /// 19 | /// - parameter pattern: the pattern to try to match 20 | /// 21 | /// - throws: a RegexError error if the pattern is incorrect 22 | /// 23 | /// - returns: a regex matcher instance 24 | public init(pattern: String, options: RegexOptions = .extended) throws { 25 | self.pattern = pattern 26 | self.internalRegex = try __Regex(pattern: pattern, options: options) 27 | } 28 | 29 | /// Matches a string against the regex pattern 30 | /// 31 | /// - parameter string: the string to match 32 | /// 33 | /// - returns: true if matched, otherwise false 34 | public func matches(_ string: String, options: MatchOptions = []) -> Bool { 35 | return internalRegex.matches(string, options: options) 36 | } 37 | 38 | /// Return the list of captures for the passed string 39 | /// 40 | /// - parameter string: the string to get the captures 41 | /// 42 | /// - returns: a list of Capture result instances 43 | public func captures(string: String) -> [CaptureResult] { 44 | return internalRegex.groups(string).map { 45 | CaptureResult(originalString: string, startIndex: $0.0, endIndex: $0.1) 46 | } 47 | } 48 | 49 | } 50 | 51 | /// MARK: operator 52 | 53 | /// Matches the string the regex on the right with on the left 54 | /// 55 | /// - parameter string: string to match 56 | /// - parameter regex: regex to match 57 | /// 58 | /// - returns: true if it matches otherwise false 59 | public func ~= (regex: Regex, string: String) -> Bool { 60 | return regex.matches(string) 61 | } 62 | 63 | 64 | /// Matches the string on the left with the regex on the right 65 | /// 66 | /// - parameter string: string to match 67 | /// - parameter regex: regex to match 68 | /// 69 | /// - returns: true if it matches otherwise false 70 | public func ~= (string: String, regex: Regex) -> Bool { 71 | return regex.matches(string) 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Regex/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtensions.swift 3 | // Regex 4 | // 5 | // Created by Omar Abdelhafith on 14/11/2016. 6 | // 7 | // 8 | 9 | extension String { 10 | 11 | public func replacing(pattern: String, withString string: String) -> String { 12 | guard let regex = try? Regex(pattern: "(\(pattern))") else { 13 | return self 14 | } 15 | 16 | return regex.captures(string: self).reversed().reduce(self) { acc, capture in 17 | return acc.replacing(range: capture.range, withString: string) 18 | } 19 | } 20 | 21 | 22 | public func replacing(range: Range, withString string: String) -> String { 23 | let firstPart = self[self.startIndex.. () throws -> Void)] { 117 | return [ 118 | ("testItCanMatchASimpleString", testItCanMatchASimpleString), 119 | ("testItMoreComplicatedStuff", testItMoreComplicatedStuff), 120 | ("testItCaptureAGroup", testItCaptureAGroup), 121 | ("testItCaptureMultipleStrings", testItCaptureMultipleStrings), 122 | ("testItHandlesNoCaptures", testItHandlesNoCaptures), 123 | ("testItReplacesStringInRange", testItReplacesStringInRange), 124 | ("testItReplacesPatternWithString", testItReplacesPatternWithString), 125 | ("testItReplacesPatternWithStringMultipleTimes", testItReplacesPatternWithStringMultipleTimes), 126 | ("testItReplacesStringWithString", testItReplacesStringWithString), 127 | ("testItReplacesStringWithStringMultipleTimes", testItReplacesStringWithStringMultipleTimes), 128 | ("testTheOperator", testTheOperator), 129 | ("testItCanBeMatchedInASwitch", testItCanBeMatchedInASwitch), 130 | ("testItHandlesWrongPatterns", testItHandlesWrongPatterns) 131 | ] 132 | } 133 | } 134 | --------------------------------------------------------------------------------