Skip to content

Commit

Permalink
Merge pull request #8 from vadymmarkov/improve/public-api
Browse files Browse the repository at this point in the history
Improve public api + new methods
  • Loading branch information
vadymmarkov authored Jun 30, 2016
2 parents 69eeeef + fb70a9f commit 5123433
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 91 deletions.
Binary file added Images/BarcodeScannerPresentation.sketch
Binary file not shown.
53 changes: 44 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@
- [x] Matching operators
- [x] Representation of a regular expression error
- [x] Option sets with default constants for compilation flags (`cflag`) and regex matching flags (`eflag`)
- [x] Extensive unit test coverage
- [x] Unit test coverage
- [x] No dependencies

## Usage

### Pattern matching

When you need to check if a given string matches regular expression:
When you want to check if a given string matches regular expression:

```swift
import Rexy

// Regular way
do {
let regex = try Regex(pattern: "Tyrannosaurus")
regex.matches("Tyrannosaurus") // => true
regex.matches("Spinosaurus") // => false
let regex = try Regex("Tyrannosaurus")
regex.isMatch("Tyrannosaurus") // => true
regex.isMatch("Spinosaurus") // => false
} catch {
print(error)
}
Expand All @@ -42,15 +43,44 @@ do {
"Spinosaurus" !~ "T.*" // true
```

### Matches

When you want to search an input string for all occurrences of a regular
expression and get the matches.

```swift
import Rexy

do {
let regex = try Regex("[a-z]+")
regex.matches("a1b1") // ["a", "b"])
} catch {
print(error)
}
```

When you want to get the first occurrence:

```swift
import Rexy

do {
let regex = try Regex("[a-z]+")
regex.matches("a1b1") // "a"
} catch {
print(error)
}
```

### Capturing Groups

When you need to match and capture groups:
When you want to match and capture groups:

```swift
import Rexy

do {
let regex = try Regex(pattern: "(Tyrannosaurus) (Rex)")
let regex = try Regex("(Tyrannosaurus) (Rex)")
regex.groups("Tyrannosaurus Rex") // => ["Tyrannosaurus", "Rex"]
regex.groups("Spinosaurus") // => []
} catch {
Expand All @@ -60,14 +90,14 @@ do {

### Replace

When you need to replace all strings that match a regular expression pattern
When you want to replace all strings that match a regular expression pattern
with a specified replacement string:

```swift
import Rexy

do {
let regex = try! Regex(pattern: "Tyrannosaurus")
let regex = try! Regex("Tyrannosaurus")
regex.replace("Tyrannosaurus Rex Tyrannosaurus", with: "Dinosaur") // => "Dinosaur Rex Dinosaur"
regex.replace("Spinosaurus", with: "Dinosaur") // => Spinosaurus
} catch {
Expand All @@ -88,6 +118,11 @@ To install it, simply add the following lines to your `Package.swift`:

Vadym Markov, markov.vadym@gmail.com

## Credits

Credits for inspiration go to [POSIXRegex](https://github.com/Zewo/POSIXRegex)
by [Zewo](https://github.com/Zewo)

## Contributing

Check the [CONTRIBUTING](https://github.com/vadymmarkov/Rexy/blob/master/CONTRIBUTING.md)
Expand Down
16 changes: 11 additions & 5 deletions Sources/Rexy/CFlags.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#if os(Linux)
@_exported import Glibc
import Glibc
#else
@_exported import Darwin.C
import Darwin.C
#endif

public extension Regex {
Expand All @@ -23,6 +23,12 @@ public extension Regex {
self.rawValue = rawValue
}

/// Default options
public static let regular = [extended]

// No options
public static let none = [CFlags]()

/// Use POSIX Basic Regular Expression syntax.
public static let basic = CFlags(rawValue: 0)

Expand All @@ -33,13 +39,13 @@ public extension Regex {
public static let caseInsensitive = CFlags(rawValue: 2)

/// Do not report position of matches.
public static let noPositions = CFlags(rawValue: 3)
public static let ignorePositions = CFlags(rawValue: 3)

// Newline-sensitive matching.
public static let newLineSensitive = CFlags(rawValue: 4)

/// Do not report position of matches.
public static let noSpecialCharacters = CFlags(rawValue: 5)
/// Ignore special characters.
public static let ignoreSpecialCharacters = CFlags(rawValue: 5)

/// Interpret the entire regex argument as a literal string.
public static let literal = CFlags(rawValue: 6)
Expand Down
10 changes: 5 additions & 5 deletions Sources/Rexy/EFlags.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#if os(Linux)
@_exported import Glibc
import Glibc
#else
@_exported import Darwin.C
import Darwin.C
#endif

public extension Regex {
Expand All @@ -24,12 +24,12 @@ public extension Regex {
}

/// First character not at beginning of line.
public static let notAtBeginningOfLine = EFlags(rawValue: REG_NOTBOL)
public static let notBeginningOfLine = EFlags(rawValue: 1)

/// Last character not at end of line.
public static let notAtEndOfLine = EFlags(rawValue: REG_NOTEOL)
public static let notEndOfLine = EFlags(rawValue: 2)

/// String start/end in pmatch[0].
public static let startEnd = EFlags(rawValue: REG_STARTEND)
public static let startEnd = EFlags(rawValue: 4)
}
}
16 changes: 8 additions & 8 deletions Sources/Rexy/Error.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#if os(Linux)
@_exported import Glibc
import Glibc
#else
@_exported import Darwin.C
import Darwin.C
#endif

/**
Representation of Regular Expression error.
*/
public struct Error: ErrorProtocol {
public struct Error: ErrorProtocol, CustomStringConvertible {

/// Error description.
public let description: String
Expand All @@ -18,11 +18,11 @@ public struct Error: ErrorProtocol {
- Parameter result: Compiled result.
- Parameter compiledPattern: Compiled regex pattern.
*/
public init(from result: Int32, compiledPattern: regex_t) {
var compiledPattern = compiledPattern
var buffer = [Int8](repeating: 0, count: Int(BUFSIZ))
public init(result: Int32, compiledPattern: regex_t) {
var compiled = compiledPattern
var buffer = [Int8](repeating: 0, count: 1024)

regerror(result, &compiledPattern, &buffer, buffer.count)
description = String(validatingUTF8: buffer) ?? ""
regerror(result, &compiled, &buffer, buffer.count)
description = String(cString: buffer) ?? ""
}
}
136 changes: 94 additions & 42 deletions Sources/Rexy/Regex.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#if os(Linux)
@_exported import Glibc
import Glibc
#else
@_exported import Darwin.C
import Darwin.C
#endif

/**
Protocol for types that could produce `Regex`
*/
public protocol RegexConvertible {
var regex: Regex? { get }
}
Expand All @@ -22,11 +25,11 @@ public final class Regex {
- Parameter pattern: Regular expression to be compiled by the regcomp.
- Parameter flags: Bitwise inclusive OR of 0 or more flags for the regcomp.
*/
public init(pattern: String, flags: CFlags = .extended) throws {
public init(_ pattern: String, flags: CFlags = .extended) throws {
let result = regcomp(&compiledPattern, pattern, flags.rawValue)

guard result == 0 else {
throw Error(from: result, compiledPattern: compiledPattern)
throw Error(result: result, compiledPattern: compiledPattern)
}
}

Expand All @@ -45,59 +48,59 @@ public final class Regex {

- Returns: True if a match is found.
*/
public func matches(_ source: String, flags: EFlags = []) -> Bool {
var elements = [regmatch_t](repeating: regmatch_t(), count: 1)
let result = regexec(&compiledPattern, source, elements.count, &elements, flags.rawValue)

return result == 0
public func isMatch(_ source: String, flags: EFlags = []) -> Bool {
return matches(source, count: 1, startAt: 0, max: 1, flags: flags).count > 0
}

// MARK: - Group

/**
Matches and captures groups.
Searches an input string for a substring that matches a regular expression
and returns the first occurrence.

- Parameter source: The string to search for a match.
- Parameter maxGroups: The maximum groups count.
- Parameter maxMatches: The maximum matches count.
- Parameter flags: Flags controlling the behavior of the regexec.

- Returns: Found groups.
- Returns: The found matches.
*/
public func groups(_ source: String, maxGroups: Int = 10, maxMatches: Int = Int.max,
flags: EFlags = []) -> [String] {
var string = source
var groups = [String]()
public func match(_ source: String, flags: EFlags = []) -> String? {
let results = matches(source, count: 1, startAt: 0, max: 1, flags: flags)

for _ in 0 ..< maxMatches {
var elements = [regmatch_t](repeating: regmatch_t(), count: maxGroups)
let result = regexec(&compiledPattern, string, elements.count, &elements, flags.rawValue)
guard results.count > 0 else {
return nil
}

guard result == 0 else {
break
}
return results[0]
}

for element in elements[1 ..< elements.count] where element.rm_so != -1 {
let start = Int(element.rm_so)
let end = Int(element.rm_eo)
let startIndex = string.index(string.startIndex, offsetBy: start)
let endIndex = string.index(string.startIndex, offsetBy: end)
let group = string[startIndex..<endIndex]
/**
Searches an input string for all occurrences of a regular expression and returns the matches.

groups.append(group)
}
- Parameter source: The string to search for a match.
- Parameter maxMatches: The maximum matches count.
- Parameter flags: Flags controlling the behavior of the regexec.

let offset = Int(elements[0].rm_eo)
let startIndex = string.utf8.index(string.utf8.startIndex, offsetBy: offset)
- Returns: The found matches.
*/
public func matches(_ source: String, maxMatches: Int = Int.max, flags: EFlags = []) -> [String] {
return matches(source, count: 1, startAt: 0, max: maxMatches, flags: flags)
}

guard let substring = String(string.utf8[startIndex ..< string.utf8.endIndex]) else {
break
}
// MARK: - Group

string = substring
}
/**
Matches and captures groups.

return groups
- Parameter source: The string to search for a match.
- Parameter maxGroups: The maximum groups count.
- Parameter maxMatches: The maximum matches count.
- Parameter flags: Flags controlling the behavior of the regexec.

- Returns: Found groups.
*/
public func groups(_ source: String,
maxGroups: Int = 10,
maxMatches: Int = Int.max,
flags: EFlags = []) -> [String] {
return matches(source, count: maxGroups, startAt: 1, max: maxMatches, flags: flags)
}

// MARK: - Replace
Expand All @@ -113,7 +116,9 @@ public final class Regex {

- Returns: A new string where replacement string takes the place of each matched string.
*/
public func replace(_ source: String, with replacement: String, maxMatches: Int = Int.max,
public func replace(_ source: String,
with replacement: String,
maxMatches: Int = Int.max,
flags: EFlags = []) -> String {
var string = source
var output: String = ""
Expand Down Expand Up @@ -149,6 +154,53 @@ public final class Regex {

return output + string
}

/**
Searches an input string for all occurrences of a regular expression and returns the matches.

- Parameter source: The string to search for a match.
- Parameter count: The maximum elements count.
- Parameter startAt: The start index.
- Parameter maxMatches: The maximum matches count.
- Parameter flags: Flags controlling the behavior of the regexec.

- Returns: The found matches.
*/
private func matches(_ source: String,
count: Int = 1,
startAt index: Int = 0,
max: Int = Int.max,
flags: EFlags = []) -> [String] {
var string = source
var results = [String]()

for _ in 0 ..< max {
var elements = [regmatch_t](repeating: regmatch_t(), count: count)
let result = regexec(&compiledPattern, string, elements.count, &elements, flags.rawValue)

guard result == 0 else {
break
}

for element in elements[index ..< elements.count] where element.rm_so != -1 {
let startIndex = string.index(string.startIndex, offsetBy: Int(element.rm_so))
let endIndex = string.index(string.startIndex, offsetBy: Int(element.rm_eo))
let result = string[startIndex ..< endIndex]

results.append(result)
}

let startIndex = string.utf8.index(string.utf8.startIndex, offsetBy: Int(elements[0].rm_eo))

guard let substring = String(string.utf8[startIndex ..< string.utf8.endIndex]) else {
break
}

string = substring
}

return results
}
}

// MARK: - RegexConvertible
Expand Down
Loading

0 comments on commit 5123433

Please sign in to comment.