Skip to content

Commit

Permalink
Paywalls: changed variable handling to use Swift Regex (#2811)
Browse files Browse the repository at this point in the history
Had to join in the fun!

This PR updates the regex logic for variables to use RegexBuilder.
  • Loading branch information
aboedo authored and NachoSoto committed Sep 6, 2023
1 parent abd7869 commit 8484355
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 22 deletions.
1 change: 1 addition & 0 deletions RevenueCatUI/Helpers/ProcessedLocalizedConfiguration.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import RevenueCat

/// A `PaywallData.LocalizedConfiguration` with processed variables
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
struct ProcessedLocalizedConfiguration: PaywallLocalizedConfiguration {

var title: String
Expand Down
53 changes: 31 additions & 22 deletions RevenueCatUI/Helpers/Variables.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import RegexBuilder
import RevenueCat

@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
Expand All @@ -21,44 +22,51 @@ protocol VariableDataProvider {
}

/// Processes strings, replacing `{{variable}}` with their associated content.
@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
enum VariableHandler {

static func processVariables(
in string: String,
with provider: VariableDataProvider
) -> String {
let matches = Self.extractVariables(from: string)
var replacedString = string
let range = NSRange(string.startIndex..., in: string)
let matches = Self.regex.matches(in: string, options: [], range: range)

for match in matches.reversed() {
let variableNameRange = match.range(at: 1)
if let variableNameRange = Range(variableNameRange, in: string) {
let variableName = String(string[variableNameRange])
let replacementValue = provider.value(for: variableName)

let adjustedRange = NSRange(
location: variableNameRange.lowerBound.utf16Offset(in: string) - Self.pattern.count / 2,
length: string.distance(from: variableNameRange.lowerBound,
to: variableNameRange.upperBound) + Self.pattern.count
)
let replacementRange = Range(adjustedRange, in: replacedString)!

replacedString = replacedString.replacingCharacters(in: replacementRange, with: replacementValue)
}
for variableMatch in matches.reversed() {
let replacementValue = provider.value(for: variableMatch.variable)
replacedString = replacedString.replacingCharacters(in: variableMatch.range, with: replacementValue)
}

return replacedString
}

private static let pattern = "{{ }}"
// Fix-me: this can be implemented using the new Regex from Swift.
// This regex is known at compile time and tested:
// swiftlint:disable:next force_try
private static let regex = try! NSRegularExpression(pattern: "\\{\\{ (\\w+) \\}\\}", options: [])
private static func extractVariables(from expression: String) -> [VariableMatch] {
let variablePattern = Regex {
OneOrMore {
"{{ "
Capture {
OneOrMore(.word)
}
" }}"
}
}

return expression.matches(of: variablePattern).map { match in
let (_, variable) = match.output
return VariableMatch(variable: String(variable), range: match.range)
}
}

private struct VariableMatch {

let variable: String
let range: Range<String.Index>

}

}

@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
extension String {

func processed(with provider: VariableDataProvider) -> Self {
Expand All @@ -67,6 +75,7 @@ extension String {

}

@available(iOS 16.0, macOS 13.0, tvOS 16.0, *)
private extension VariableDataProvider {

func value(for variableName: String) -> String {
Expand Down

0 comments on commit 8484355

Please sign in to comment.