├── .gitignore ├── .travis.yml ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── VerbalExpressions │ └── VerbalExpressions.swift └── Tests ├── LinuxMain.swift └── VerbalExpressionsTests └── VerbalExpressionsTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ###SublimeText### 2 | 3 | # cache files for sublime text 4 | *.tmlanguage.cache 5 | *.tmPreferences.cache 6 | *.stTheme.cache 7 | 8 | # workspace files are user-specific 9 | *.sublime-workspace 10 | 11 | # project files should be checked into the repository, unless a significant 12 | # proportion of contributors will probably not be using SublimeText 13 | # *.sublime-project 14 | 15 | # sftp configuration file 16 | sftp-config.json 17 | 18 | .DS_Store 19 | /.build 20 | /Packages 21 | /*.xcodeproj -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | osx_image: xcode8.3 4 | 5 | sudo: false # Enable container-based builds 6 | 7 | install: gem install scan 8 | 9 | script: fastlane scan 10 | 11 | branches: 12 | only: # whitelist 13 | - master 14 | 15 | notifications: 16 | email: false 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dominique d'Argent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.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: "VerbalExpressions", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "VerbalExpressions", 12 | targets: ["VerbalExpressions"]), 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: "VerbalExpressions", 23 | dependencies: []), 24 | .testTarget( 25 | name: "VerbalExpressionsTests", 26 | dependencies: ["VerbalExpressions"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SwiftVerbalExpressions 2 | ====================== 3 | 4 | [![Build Status](https://travis-ci.org/VerbalExpressions/SwiftVerbalExpressions.svg)](https://travis-ci.org/VerbalExpressions/SwiftVerbalExpressions) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | 7 | ## Swift Regular Expressions made easy 8 | 9 | SwiftVerbalExpressions is a Swift library that helps to construct difficult regular expressions - ported from the awesome JavaScript [VerbalExpressions](https://github.com/jehna/VerbalExpressions). 10 | 11 | 12 | ## Examples 13 | 14 | Here's a couple of simple examples to give an idea of how VerbalExpressions works: 15 | 16 | ### Testing if we have a valid URL 17 | 18 | ```swift 19 | // Create an example of how to test for correctly formed URLs 20 | let tester = VerEx() 21 | .startOfLine() 22 | .then("http") 23 | .maybe("s") 24 | .then("://") 25 | .maybe("www") 26 | .anythingBut(" ") 27 | .endOfLine() 28 | 29 | // Create an example URL 30 | let testMe = "https://www.google.com" 31 | 32 | // Use test() method 33 | if tester.test(testMe) { 34 | print("We have a correct URL") // This output will fire 35 | } 36 | else { 37 | print("The URL is incorrect") 38 | } 39 | 40 | // Use =~ operator 41 | if testMe =~ tester { 42 | print("We have a correct URL") // This output will fire 43 | } 44 | else { 45 | print("The URL is incorrect") 46 | } 47 | 48 | prin(tester) // Outputs the actual expression used: "^(?:http)(?:s)?(?::\/\/)(?:www)?(?:[^ ]*)$" 49 | ``` 50 | 51 | ### Replacing strings 52 | 53 | ```swift 54 | let replaceMe = "Replace bird with a duck" 55 | 56 | // Create an expression that seeks for word "bird" 57 | let verEx = VerEx().find("bird") 58 | 59 | // Execute the expression like a normal RegExp object 60 | let result = verEx.replace(replaceMe, with: "duck") 61 | 62 | print(result) // Outputs "Replace duck with a duck" 63 | ``` 64 | 65 | ### Shorthand for string replace: 66 | 67 | ```swift 68 | let result2 = VerEx().find("red").replace("We have a red house", with: "blue") 69 | 70 | print(result2) // Outputs "We have a blue house" 71 | ``` 72 | 73 | 74 | ## API documentation 75 | 76 | You can find the documentation for the original JavaScript repo on their [wiki](https://github.com/jehna/VerbalExpressions/wiki). 77 | 78 | 79 | ## Contributions 80 | 81 | Clone the repo and fork! 82 | Pull requests are warmly welcome! 83 | 84 | 85 | ## Thanks! 86 | 87 | Thank you to @jehna for coming up with the awesome original idea! 88 | Thank you to @kishikawakatsumi for ObjectiveCVerbalExpressions from which I borrowed some code! 89 | 90 | 91 | ## Other implementations 92 | 93 | You can view all implementations on [VerbalExpressions.github.io](http://VerbalExpressions.github.io) 94 | 95 | 96 | ## Installation and use 97 | 98 | This version is under testing, but it supports Swift Package Manager. Therefore it can be included in the project with: 99 | 100 | ``` 101 | .package( 102 | url: "https://github.com/VerbalExpressions/SwiftVerbalExpressions.git", 103 | from: " 104 | 105 | ``` 106 | 107 | And: 108 | 109 | ``` 110 | .target( 111 | name: "YourProject", 112 | dependencies: ["VerbalExpressions"]), 113 | ``` 114 | -------------------------------------------------------------------------------- /Sources/VerbalExpressions/VerbalExpressions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VerbalExpressions.swift 3 | // VerbalExpressions 4 | // 5 | // Created by Dominique d'Argent on 04/06/14. 6 | // Copyright (c) 2014 Dominique d'Argent. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func VerEx() -> VerbalExpressions { 12 | return VerbalExpressions() 13 | } 14 | 15 | public struct VerbalExpressions { 16 | // stored properties 17 | fileprivate let prefixes: String 18 | fileprivate let source: String 19 | fileprivate let suffixes: String 20 | fileprivate let options: NSRegularExpression.Options 21 | 22 | // computed properties 23 | public var pattern: String { return prefixes + source + suffixes } 24 | 25 | public var regularExpression: NSRegularExpression! { 26 | return try! NSRegularExpression(pattern: pattern, options: options) 27 | } 28 | 29 | // initializers 30 | public init() { 31 | self.init(prefixes: "", source: "", suffixes: "", options: .anchorsMatchLines) 32 | } 33 | 34 | fileprivate init(prefixes: String, source: String, suffixes: String, options: NSRegularExpression.Options) { 35 | self.prefixes = prefixes 36 | self.source = source 37 | self.suffixes = suffixes 38 | self.options = options 39 | } 40 | 41 | // instance methods 42 | public func startOfLine(enabled: Bool = true) -> VerbalExpressions { 43 | return setting(prefixes: enabled ? "^" : "") 44 | } 45 | 46 | public func endOfLine(enabled: Bool = true) -> VerbalExpressions { 47 | return setting(suffixes: enabled ? "$" : "") 48 | } 49 | 50 | public func then(_ string: String) -> VerbalExpressions { 51 | return adding("(?:\(string.sanitized))") 52 | } 53 | 54 | public func then(_ exp: VerbalExpressions) -> VerbalExpressions { 55 | return then(exp.source) 56 | } 57 | 58 | // alias for then 59 | public func find(_ string: String) -> VerbalExpressions { 60 | return then(string) 61 | } 62 | 63 | public func find(_ exp: VerbalExpressions) -> VerbalExpressions { 64 | return find(exp.source) 65 | } 66 | 67 | public func maybe(_ string: String) -> VerbalExpressions { 68 | return adding("(?:\(string.sanitized))?") 69 | } 70 | 71 | public func maybe(_ exp: VerbalExpressions) -> VerbalExpressions { 72 | return maybe(exp.source) 73 | } 74 | 75 | public func or() -> VerbalExpressions { 76 | return setting(prefixes: prefixes + "(?:", suffixes: ")" + suffixes) 77 | .adding(")|(?:") 78 | } 79 | 80 | public func or(_ string: String) -> VerbalExpressions { 81 | return or().then(string) 82 | } 83 | 84 | public func or(_ exp: VerbalExpressions) -> VerbalExpressions { 85 | return or(exp.source); 86 | } 87 | 88 | public func anything() -> VerbalExpressions { 89 | return adding("(?:.*)") 90 | } 91 | 92 | public func anythingBut(_ string: String) -> VerbalExpressions { 93 | return adding("(?:[^\(string.sanitized)]*)") 94 | } 95 | 96 | public func anythingBut(_ exp: VerbalExpressions) -> VerbalExpressions { 97 | return anythingBut(exp.source) 98 | } 99 | 100 | public func something() -> VerbalExpressions { 101 | return adding("(?:.+)") 102 | } 103 | 104 | public func somethingBut(_ string: String) -> VerbalExpressions { 105 | return adding("(?:[^\(string.sanitized)]+)") 106 | } 107 | 108 | public func somethingBut(_ exp: VerbalExpressions) -> VerbalExpressions { 109 | return somethingBut(exp.source) 110 | } 111 | 112 | public func lineBreak() -> VerbalExpressions { 113 | return adding("(?:(?:\n)|(?:\r\n))") 114 | } 115 | 116 | // alias for lineBreak 117 | public func br() -> VerbalExpressions { 118 | return lineBreak() 119 | } 120 | 121 | public func tab() -> VerbalExpressions { 122 | return adding("\t") 123 | } 124 | 125 | public func word() -> VerbalExpressions { 126 | return adding("\\w+") 127 | } 128 | 129 | public func anyOf(_ string: String) -> VerbalExpressions { 130 | return adding("(?:[\(string.sanitized)])") 131 | } 132 | 133 | public func anyOf(_ exp: VerbalExpressions) -> VerbalExpressions { 134 | return anyOf(exp.source) 135 | } 136 | 137 | // alias for anyOf 138 | public func any(_ string: String) -> VerbalExpressions { 139 | return anyOf(string) 140 | } 141 | 142 | public func any(_ exp: VerbalExpressions) -> VerbalExpressions { 143 | return anyOf(exp.source) 144 | } 145 | 146 | public func withAnyCase(enabled: Bool = true) -> VerbalExpressions { 147 | if enabled { 148 | return adding(modifier: "i") 149 | } 150 | else { 151 | return removing(modifier: "i") 152 | } 153 | } 154 | 155 | public func searchOneLine(enabled: Bool = true) -> VerbalExpressions { 156 | if enabled { 157 | return removing(modifier: "m") 158 | } 159 | else { 160 | return adding(modifier: "m") 161 | } 162 | } 163 | 164 | public func beginCapture() -> VerbalExpressions { 165 | return setting(suffixes: suffixes + ")") 166 | .adding("(") 167 | } 168 | 169 | public func endCapture() -> VerbalExpressions { 170 | return setting(suffixes: suffixes.substring(to: suffixes.endIndex)) 171 | .adding(")") 172 | } 173 | 174 | public func replace(_ string: String, template: String) -> String { 175 | let range = NSRange(location: 0, length: string.utf16.count) 176 | 177 | return regularExpression.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: template) 178 | } 179 | 180 | public func replace(_ string: String, with: String) -> String { 181 | let range = NSRange(location: 0, length: string.utf16.count) 182 | let template = NSRegularExpression.escapedTemplate(for: with) 183 | 184 | return regularExpression.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: template) 185 | } 186 | 187 | public func test(_ string: String) -> Bool { 188 | let range = NSRange(location: 0, length: string.utf16.count) 189 | 190 | if let result = regularExpression.firstMatch(in: string, options: [], range: range) { 191 | return result.range.location != NSNotFound 192 | } 193 | 194 | return false 195 | } 196 | 197 | } 198 | 199 | fileprivate extension VerbalExpressions { 200 | func setting(prefixes: String? = nil, source: String? = nil, suffixes: String? = nil, options: NSRegularExpression.Options? = nil) -> VerbalExpressions { 201 | guard prefixes != self.prefixes || source != self.source || suffixes != self.suffixes || options != self.options else { 202 | return self 203 | } 204 | 205 | return VerbalExpressions( 206 | prefixes: prefixes ?? self.prefixes, 207 | source: source ?? self.source, 208 | suffixes: suffixes ?? self.suffixes, 209 | options: options ?? self.options 210 | ) 211 | } 212 | 213 | func adding(_ string: String) -> VerbalExpressions { 214 | return setting(source: source + string) 215 | } 216 | 217 | func adding(modifier: Character) -> VerbalExpressions { 218 | return adding(options: NSRegularExpression.Options(modifier: modifier)) 219 | } 220 | 221 | func adding(options: NSRegularExpression.Options) -> VerbalExpressions { 222 | return setting(options: self.options.union(options)) 223 | } 224 | 225 | func removing(modifier: Character) -> VerbalExpressions { 226 | let removedOptions = NSRegularExpression.Options(modifier: modifier) 227 | let newOptions = options.subtracting(removedOptions) 228 | return setting(options: newOptions) 229 | } 230 | } 231 | 232 | // MARK: - CustomStringConvertible, CustomDebugStringConvertible conformance 233 | extension VerbalExpressions: CustomStringConvertible, CustomDebugStringConvertible { 234 | public var description: String { return pattern } 235 | public var debugDescription: String { return pattern } 236 | } 237 | 238 | // MARK: - Equatable conformance 239 | extension VerbalExpressions: Equatable { 240 | public static func ==(lhs: VerbalExpressions, rhs: VerbalExpressions) -> Bool { 241 | return lhs.pattern == rhs.pattern && lhs.options == rhs.options 242 | } 243 | } 244 | 245 | fileprivate extension String { 246 | var sanitized: String { 247 | return NSRegularExpression.escapedPattern(for: self) 248 | } 249 | } 250 | 251 | fileprivate extension NSRegularExpression.Options { 252 | init(modifier: Character) { 253 | switch modifier { 254 | case "d": // UREGEX_UNIX_LINES 255 | self = .useUnixLineSeparators 256 | case "i": // UREGEX_CASE_INSENSITIVE 257 | self = .caseInsensitive 258 | case "x": // UREGEX_COMMENTS 259 | self = .allowCommentsAndWhitespace 260 | case "m": // UREGEX_MULTILINE 261 | self = .anchorsMatchLines 262 | case "s": // UREGEX_DOTALL 263 | self = .dotMatchesLineSeparators 264 | case "u": // UREGEX_UWORD 265 | self = .useUnicodeWordBoundaries 266 | case "U": // UREGEX_LITERAL 267 | self = .ignoreMetacharacters 268 | default: 269 | self = [] 270 | } 271 | } 272 | } 273 | 274 | // MARK: - Operators 275 | // Adapted from https://gist.github.com/JimRoepcke/d68dd41ee2fedc6a0c67 276 | infix operator =~: ComparisonPrecedence 277 | infix operator !~: ComparisonPrecedence 278 | 279 | public func =~(lhs: String, rhs: VerbalExpressions) -> Bool { 280 | return rhs.test(lhs) 281 | } 282 | 283 | public func =~(lhs: VerbalExpressions, rhs: String) -> Bool { 284 | return lhs.test(rhs) 285 | } 286 | 287 | public func !~(lhs: String, rhs: VerbalExpressions) -> Bool { 288 | return !(lhs =~ rhs) 289 | } 290 | 291 | public func !~(lhs: VerbalExpressions, rhs: String) -> Bool { 292 | return !(lhs =~ rhs) 293 | } 294 | 295 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import VerbalExpressionsTests 3 | 4 | XCTMain([ 5 | testCase(VerbalExpressionsTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/VerbalExpressionsTests/VerbalExpressionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import VerbalExpressions 3 | 4 | class VerbalExpressionsTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(VerbalExpressions().text, "Hello, World!") 10 | } 11 | 12 | 13 | static var allTests = [ 14 | ("testExample", testExample), 15 | ] 16 | } 17 | --------------------------------------------------------------------------------