It is easy to do with pre_match
($`
) and post_match
($'
):
def replace_matches(str, re, repl)
return enum_for(:replace_matches, str, re, repl) unless block_given?
str.scan(re) do
yield "#$`#{repl}#$'"
end
end
str = "foofoofoo"
# block usage
replace_matches(str, /foo/, "bar") { |x| puts x }
# enum usage
puts replace_matches(str, /foo/, "bar").to_a
EDIT: If you have overlapping matches, then it becomes harder, as regular expressions aren't really equipped to deal with it. So you can do it like this:
def replace_matches(str, re, repl)
return enum_for(:replace_matches, str, re, repl) unless block_given?
re = /(?=(?<pattern>#{re}))/
str.scan(re) do
pattern_start = $~.begin(0)
pattern_end = pattern_start + $~[:pattern].length
yield str[0 ... pattern_start] + repl + str[pattern_end .. -1]
end
end
str = "oooo"
replace_matches(str, /oo/, "x") { |x| puts x }
Here we abuse positive lookahead, which are 0-width, so we can get overlapping matches. However, we also need to know how many characters we matched, which we can't do as before now that match is 0-width, so we'll make a new capture of the contents of the lookahead, and calculate the new width from that.
(Disclaimer: it will still only match once per character; if you want to consider multiple possibilities at each character, like in your /f|o|fo/
case, it complicates things yet more.)
EDIT: A bit of a tweak and we can even support proper gsub-like behaviour:
def replace_matches(str, re, repl)
return enum_for(:replace_matches, str, re, repl) unless block_given?
new_re = /(?=(?<pattern>#{re}))/
str.scan(new_re) do
pattern_start = $~.begin(0)
pattern_end = pattern_start + $~[:pattern].length
new_repl = str[pattern_start ... pattern_end].gsub(re, repl)
yield str[0 ... pattern_start] + new_repl + str[pattern_end .. -1]
end
end
str = "abcd"
replace_matches(str, /(?<first>\w)(?<second>\w)/, '\k<second>\k<first>').to_a
# => ["bacd", "acbd", "abdc"]
(Disclaimer: the last snippet can't handle cases where the pattern uses lookbehind or lookahead to check outside the match region.)