@@ -201,15 +201,15 @@ module LiquidInterpolatable |
||
| 201 | 201 |
'concat(' << subs.join(', ') << ')'
|
| 202 | 202 |
end |
| 203 | 203 |
end |
| 204 |
- |
|
| 205 |
- def regex_replace(input, regex, replacement = ''.freeze) |
|
| 206 |
- input.to_s.gsub(Regexp.new(regex), replacement.to_s) |
|
| 204 |
+ |
|
| 205 |
+ def regex_replace(input, regex, replacement = nil) |
|
| 206 |
+ input.to_s.gsub(Regexp.new(regex), unescape_replacement(replacement.to_s)) |
|
| 207 | 207 |
end |
| 208 |
- |
|
| 209 |
- def regex_replace_first(input, regex, replacement = ''.freeze) |
|
| 210 |
- input.to_s.sub(Regexp.new(regex), replacement.to_s) |
|
| 208 |
+ |
|
| 209 |
+ def regex_replace_first(input, regex, replacement = nil) |
|
| 210 |
+ input.to_s.sub(Regexp.new(regex), unescape_replacement(replacement.to_s)) |
|
| 211 | 211 |
end |
| 212 |
- |
|
| 212 |
+ |
|
| 213 | 213 |
private |
| 214 | 214 |
|
| 215 | 215 |
def logger |
@@ -221,6 +221,63 @@ module LiquidInterpolatable |
||
| 221 | 221 |
Logger.new(STDERR) |
| 222 | 222 |
end |
| 223 | 223 |
end |
| 224 |
+ |
|
| 225 |
+ BACKSLASH = "\\".freeze |
|
| 226 |
+ |
|
| 227 |
+ UNESCAPE = {
|
|
| 228 |
+ "a" => "\a", |
|
| 229 |
+ "b" => "\b", |
|
| 230 |
+ "e" => "\e", |
|
| 231 |
+ "f" => "\f", |
|
| 232 |
+ "n" => "\n", |
|
| 233 |
+ "r" => "\r", |
|
| 234 |
+ "s" => " ", |
|
| 235 |
+ "t" => "\t", |
|
| 236 |
+ "v" => "\v", |
|
| 237 |
+ } |
|
| 238 |
+ |
|
| 239 |
+ # Unescape a replacement text for use in the second argument of |
|
| 240 |
+ # gsub/sub. The following escape sequences are recognized: |
|
| 241 |
+ # |
|
| 242 |
+ # - "\\" (backslash itself) |
|
| 243 |
+ # - "\a" (alert) |
|
| 244 |
+ # - "\b" (backspace) |
|
| 245 |
+ # - "\e" (escape) |
|
| 246 |
+ # - "\f" (form feed) |
|
| 247 |
+ # - "\n" (new line) |
|
| 248 |
+ # - "\r" (carriage return) |
|
| 249 |
+ # - "\s" (space) |
|
| 250 |
+ # - "\t" (horizontal tab) |
|
| 251 |
+ # - "\u{XXXX}" (unicode codepoint)
|
|
| 252 |
+ # - "\v" (vertical tab) |
|
| 253 |
+ # - "\xXX" (hexadecimal character) |
|
| 254 |
+ # - "\1".."\9" (numbered capture groups) |
|
| 255 |
+ # - "\+" (last capture group) |
|
| 256 |
+ # - "\k<name>" (named capture group) |
|
| 257 |
+ # - "\&" or "\0" (complete matched text) |
|
| 258 |
+ # - "\`" (string before match) |
|
| 259 |
+ # - "\'" (string after match) |
|
| 260 |
+ # |
|
| 261 |
+ # Octal escape sequences are deliberately unsupported to avoid |
|
| 262 |
+ # conflict with numbered capture groups. Rather obscure Emacs |
|
| 263 |
+ # style character codes ("\C-x", "\M-\C-x" etc.) are also omitted
|
|
| 264 |
+ # from this implementation. |
|
| 265 |
+ def unescape_replacement(s) |
|
| 266 |
+ s.gsub(/\\(?:([\d+&`'\\]|k<\w+>)|u\{([[:xdigit:]]+)\}|x([[:xdigit:]]{2})|(.))/) {
|
|
| 267 |
+ if c = $1 |
|
| 268 |
+ BACKSLASH + c |
|
| 269 |
+ elsif c = ($2 && [$2.to_i(16)].pack('U')) ||
|
|
| 270 |
+ ($3 && [$3.to_i(16)].pack('C'))
|
|
| 271 |
+ if c == BACKSLASH |
|
| 272 |
+ BACKSLASH + c |
|
| 273 |
+ else |
|
| 274 |
+ c |
|
| 275 |
+ end |
|
| 276 |
+ else |
|
| 277 |
+ UNESCAPE[$4] || $4 |
|
| 278 |
+ end |
|
| 279 |
+ } |
|
| 280 |
+ end |
|
| 224 | 281 |
end |
| 225 | 282 |
Liquid::Template.register_filter(LiquidInterpolatable::Filters) |
| 226 | 283 |
|
@@ -0,0 +1,8 @@ |
||
| 1 |
+module Liquid |
|
| 2 |
+ # https://github.com/Shopify/liquid/pull/623 |
|
| 3 |
+ remove_const :PartialTemplateParser |
|
| 4 |
+ remove_const :TemplateParser |
|
| 5 |
+ |
|
| 6 |
+ PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}(?:(?:[^'"{}]+|#{QuotedString})*?|.*?)#{VariableIncompleteEnd}/m
|
|
| 7 |
+ TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/m
|
|
| 8 |
+end |
@@ -177,23 +177,37 @@ describe LiquidInterpolatable::Filters do |
||
| 177 | 177 |
expect(@agent.interpolated['long_url']).to eq('http://2many.x/6')
|
| 178 | 178 |
end |
| 179 | 179 |
end |
| 180 |
- |
|
| 181 |
- describe 'regex replace' do |
|
| 182 |
- let(:agent) { Agents::InterpolatableAgent.new(name: "test") }
|
|
| 183 |
- |
|
| 184 |
- it 'should replace the first occurrence of a string using regex' do |
|
| 185 |
- agent.interpolation_context['something'] = 'foobar foobar' |
|
| 186 |
- agent.options['cleaned'] = '{{ something | regex_replace_first: "\S+bar", "foobaz" }}'
|
|
| 187 |
- expect(agent.interpolated['cleaned']).to eq('foobaz foobar')
|
|
| 188 |
- end |
|
| 180 |
+ end |
|
| 189 | 181 |
|
| 190 |
- it 'should replace the all occurrences of a string using regex' do |
|
| 191 |
- agent.interpolation_context['something'] = 'foobar foobar' |
|
| 192 |
- agent.options['cleaned'] = '{{ something | regex_replace: "\S+bar", "foobaz" }}'
|
|
| 193 |
- expect(agent.interpolated['cleaned']).to eq('foobaz foobaz')
|
|
| 194 |
- end |
|
| 195 |
- |
|
| 182 |
+ describe 'regex_replace_first' do |
|
| 183 |
+ let(:agent) { Agents::InterpolatableAgent.new(name: "test") }
|
|
| 184 |
+ |
|
| 185 |
+ it 'should replace the first occurrence of a string using regex' do |
|
| 186 |
+ agent.interpolation_context['something'] = 'foobar foobar' |
|
| 187 |
+ agent.options['cleaned'] = '{{ something | regex_replace_first: "\S+bar", "foobaz" }}'
|
|
| 188 |
+ expect(agent.interpolated['cleaned']).to eq('foobaz foobar')
|
|
| 189 |
+ end |
|
| 190 |
+ |
|
| 191 |
+ it 'should support escaped characters' do |
|
| 192 |
+ agent.interpolation_context['something'] = "foo\\1\n\nfoo\\bar\n\nfoo\\baz" |
|
| 193 |
+ agent.options['test'] = "{{ something | regex_replace_first: '\\\\(\\w{2,})', '\\1\\\\' | regex_replace_first: '\\n+', '\\n' }}"
|
|
| 194 |
+ expect(agent.interpolated['test']).to eq("foo\\1\nfoobar\\\n\nfoo\\baz")
|
|
| 195 |
+ end |
|
| 196 |
+ end |
|
| 197 |
+ |
|
| 198 |
+ describe 'regex_replace' do |
|
| 199 |
+ let(:agent) { Agents::InterpolatableAgent.new(name: "test") }
|
|
| 200 |
+ |
|
| 201 |
+ it 'should replace the all occurrences of a string using regex' do |
|
| 202 |
+ agent.interpolation_context['something'] = 'foobar foobar' |
|
| 203 |
+ agent.options['cleaned'] = '{{ something | regex_replace: "\S+bar", "foobaz" }}'
|
|
| 204 |
+ expect(agent.interpolated['cleaned']).to eq('foobaz foobaz')
|
|
| 205 |
+ end |
|
| 206 |
+ |
|
| 207 |
+ it 'should support escaped characters' do |
|
| 208 |
+ agent.interpolation_context['something'] = "foo\\1\n\nfoo\\bar\n\nfoo\\baz" |
|
| 209 |
+ agent.options['test'] = "{{ something | regex_replace: '\\\\(\\w{2,})', '\\1\\\\' | regex_replace: '\\n+', '\\n' }}"
|
|
| 210 |
+ expect(agent.interpolated['test']).to eq("foo\\1\nfoobar\\\nfoobaz\\")
|
|
| 196 | 211 |
end |
| 197 |
- |
|
| 198 | 212 |
end |
| 199 | 213 |
end |