# -*- encoding: utf-8 -*- require File.expand_path('../../../spec_helper', __FILE__) require File.expand_path('../fixtures/classes.rb', __FILE__) describe "String#gsub with pattern and replacement" do before :each do @kcode = $KCODE end after :each do $KCODE = @kcode end it "inserts the replacement around every character when the pattern collapses" do "hello".gsub(//, ".").should == ".h.e.l.l.o." end it "respects $KCODE when the pattern collapses" do str = "こにちわ" reg = %r!! $KCODE = "utf-8" str.gsub(reg, ".").should == ".こ.に.ち.わ." end it "doesn't freak out when replacing ^" do "Text\n".gsub(/^/, ' ').should == " Text\n" "Text\nFoo".gsub(/^/, ' ').should == " Text\n Foo" end it "returns a copy of self with all occurrences of pattern replaced with replacement" do "hello".gsub(/[aeiou]/, '*').should == "h*ll*" str = "hello homely world. hah!" str.gsub(/\Ah\S+\s*/, "huh? ").should == "huh? homely world. hah!" end it "ignores a block if supplied" do "food".gsub(/f/, "g") { "w" }.should == "good" end it "supports \\G which matches at the beginning of the remaining (non-matched) string" do str = "hello homely world. hah!" str.gsub(/\Gh\S+\s*/, "huh? ").should == "huh? huh? world. hah!" end it "supports /i for ignoring case" do str = "Hello. How happy are you?" str.gsub(/h/i, "j").should == "jello. jow jappy are you?" str.gsub(/H/i, "j").should == "jello. jow jappy are you?" end it "doesn't interpret regexp metacharacters if pattern is a string" do "12345".gsub('\d', 'a').should == "12345" '\d'.gsub('\d', 'a').should == "a" end it "replaces \\1 sequences with the regexp's corresponding capture" do str = "hello" str.gsub(/([aeiou])/, '<\1>').should == "hll" str.gsub(/(.)/, '\1\1').should == "hheelllloo" str.gsub(/.(.?)/, '<\0>(\1)').should == "(e)(l)()" str.gsub(/.(.)+/, '\1').should == "o" str = "ABCDEFGHIJKLabcdefghijkl" re = /#{"(.)" * 12}/ str.gsub(re, '\1').should == "Aa" str.gsub(re, '\9').should == "Ii" # Only the first 9 captures can be accessed in MRI str.gsub(re, '\10').should == "A0a0" end it "treats \\1 sequences without corresponding captures as empty strings" do str = "hello!" str.gsub("", '<\1>').should == "<>h<>e<>l<>l<>o<>!<>" str.gsub("h", '<\1>').should == "<>ello!" str.gsub(//, '<\1>').should == "<>h<>e<>l<>l<>o<>!<>" str.gsub(/./, '\1\2\3').should == "" str.gsub(/.(.{20})?/, '\1').should == "" end it "replaces \\& and \\0 with the complete match" do str = "hello!" str.gsub("", '<\0>').should == "<>h<>e<>l<>l<>o<>!<>" str.gsub("", '<\&>').should == "<>h<>e<>l<>l<>o<>!<>" str.gsub("he", '<\0>').should == "llo!" str.gsub("he", '<\&>').should == "llo!" str.gsub("l", '<\0>').should == "heo!" str.gsub("l", '<\&>').should == "heo!" str.gsub(//, '<\0>').should == "<>h<>e<>l<>l<>o<>!<>" str.gsub(//, '<\&>').should == "<>h<>e<>l<>l<>o<>!<>" str.gsub(/../, '<\0>').should == "" str.gsub(/../, '<\&>').should == "" str.gsub(/(.)./, '<\0>').should == "" end it "replaces \\` with everything before the current match" do str = "hello!" str.gsub("", '<\`>').should == "<>hello!" str.gsub("h", '<\`>').should == "<>ello!" str.gsub("l", '<\`>').should == "heo!" str.gsub("!", '<\`>').should == "hello" str.gsub(//, '<\`>').should == "<>hello!" str.gsub(/../, '<\`>').should == "<>" end it "replaces \\' with everything after the current match" do str = "hello!" str.gsub("", '<\\\'>').should == "hello!<>" str.gsub("h", '<\\\'>').should == "ello!" str.gsub("ll", '<\\\'>').should == "heo!" str.gsub("!", '<\\\'>').should == "hello<>" str.gsub(//, '<\\\'>').should == "hello!<>" str.gsub(/../, '<\\\'>').should == "<>" end it "replaces \\+ with the last paren that actually matched" do str = "hello!" str.gsub(/(.)(.)/, '\+').should == "el!" str.gsub(/(.)(.)+/, '\+').should == "!" str.gsub(/(.)()/, '\+').should == "" str.gsub(/(.)(.{20})?/, '<\+>').should == "" str = "ABCDEFGHIJKLabcdefghijkl" re = /#{"(.)" * 12}/ str.gsub(re, '\+').should == "Ll" end it "treats \\+ as an empty string if there was no captures" do "hello!".gsub(/./, '\+').should == "" end it "maps \\\\ in replacement to \\" do "hello".gsub(/./, '\\\\').should == '\\' * 5 end it "leaves unknown \\x escapes in replacement untouched" do "hello".gsub(/./, '\\x').should == '\\x' * 5 "hello".gsub(/./, '\\y').should == '\\y' * 5 end it "leaves \\ at the end of replacement untouched" do "hello".gsub(/./, 'hah\\').should == 'hah\\' * 5 end it "taints the result if the original string or replacement is tainted" do hello = "hello" hello_t = "hello" a = "a" a_t = "a" empty = "" empty_t = "" hello_t.taint; a_t.taint; empty_t.taint hello_t.gsub(/./, a).tainted?.should == true hello_t.gsub(/./, empty).tainted?.should == true hello.gsub(/./, a_t).tainted?.should == true hello.gsub(/./, empty_t).tainted?.should == true hello.gsub(//, empty_t).tainted?.should == true #hello.gsub(//.taint, "foo").tainted?.should == false end ruby_version_is "1.9" do it "untrusts the result if the original string or replacement is untrusted" do hello = "hello" hello_t = "hello" a = "a" a_t = "a" empty = "" empty_t = "" hello_t.untrust; a_t.untrust; empty_t.untrust hello_t.gsub(/./, a).untrusted?.should == true hello_t.gsub(/./, empty).untrusted?.should == true hello.gsub(/./, a_t).untrusted?.should == true hello.gsub(/./, empty_t).untrusted?.should == true hello.gsub(//, empty_t).untrusted?.should == true hello.gsub(//.untrust, "foo").untrusted?.should == false end end it "tries to convert pattern to a string using to_str" do pattern = mock('.') def pattern.to_str() "." end "hello.".gsub(pattern, "!").should == "hello!" end it "raises a TypeError when pattern can't be converted to a string" do lambda { "hello".gsub([], "x") }.should raise_error(TypeError) lambda { "hello".gsub(Object.new, "x") }.should raise_error(TypeError) lambda { "hello".gsub(nil, "x") }.should raise_error(TypeError) end it "tries to convert replacement to a string using to_str" do replacement = mock('hello_replacement') def replacement.to_str() "hello_replacement" end "hello".gsub(/hello/, replacement).should == "hello_replacement" end it "raises a TypeError when replacement can't be converted to a string" do lambda { "hello".gsub(/[aeiou]/, []) }.should raise_error(TypeError) lambda { "hello".gsub(/[aeiou]/, Object.new) }.should raise_error(TypeError) lambda { "hello".gsub(/[aeiou]/, nil) }.should raise_error(TypeError) end it "returns subclass instances when called on a subclass" do StringSpecs::MyString.new("").gsub(//, "").should be_kind_of(StringSpecs::MyString) StringSpecs::MyString.new("").gsub(/foo/, "").should be_kind_of(StringSpecs::MyString) StringSpecs::MyString.new("foo").gsub(/foo/, "").should be_kind_of(StringSpecs::MyString) StringSpecs::MyString.new("foo").gsub("foo", "").should be_kind_of(StringSpecs::MyString) end # Note: $~ cannot be tested because mspec messes with it it "sets $~ to MatchData of last match and nil when there's none" do 'hello.'.gsub('hello', 'x') $~[0].should == 'hello' 'hello.'.gsub('not', 'x') $~.should == nil 'hello.'.gsub(/.(.)/, 'x') $~[0].should == 'o.' 'hello.'.gsub(/not/, 'x') $~.should == nil end end ruby_version_is "1.9" do describe "String#gsub with pattern and Hash" do it "returns a copy of self with all occurrences of pattern replaced with the value of the corresponding hash key" do "hello".gsub(/./, 'l' => 'L').should == "LL" "hello!".gsub(/(.)(.)/, 'he' => 'she ', 'll' => 'said').should == 'she said' "hello".gsub('l', 'l' => 'el').should == 'heelelo' end it "ignores keys that don't correspond to matches" do "hello".gsub(/./, 'z' => 'L', 'h' => 'b', 'o' => 'ow').should == "bow" end it "returns an empty string if the pattern matches but the hash specifies no replacements" do "hello".gsub(/./, 'z' => 'L').should == "" end it "ignores non-String keys" do "hello".gsub(/(ll)/, 'll' => 'r', :ll => 'z').should == "hero" end it "uses a key's value as many times as needed" do "food".gsub(/o/, 'o' => '0').should == "f00d" end it "uses the hash's default value for missing keys" do hsh = new_hash hsh.default='?' hsh['o'] = '0' "food".gsub(/./, hsh).should == "?00?" end it "coerces the hash values with #to_s" do hsh = new_hash hsh.default=[] hsh['o'] = 0 obj = mock('!') obj.should_receive(:to_s).and_return('!') hsh['!'] = obj "food!".gsub(/./, hsh).should == "[]00[]!" end it "raises a TypeError if the hash has a default proc" do hsh = new_hash hsh.default_proc = lambda { |k,v| 'lamb' } lambda do "food!".gsub(/./, hsh) end.should_not raise_error(TypeError) end it "sets $~ to MatchData of last match and nil when there's none for access from outside" do 'hello.'.gsub('l', 'l' => 'L') $~.begin(0).should == 3 $~[0].should == 'l' 'hello.'.gsub('not', 'ot' => 'to') $~.should == nil 'hello.'.gsub(/.(.)/, 'o' => ' hole') $~[0].should == 'o.' 'hello.'.gsub(/not/, 'z' => 'glark') $~.should == nil end it "doesn't interpolate special sequences like \\1 for the block's return value" do repl = '\& \0 \1 \` \\\' \+ \\\\ foo' "hello".gsub(/(.+)/, 'hello' => repl ).should == repl end it "untrusts the result if the original string is untrusted" do str = "Ghana".untrust str.gsub(/[Aa]na/, 'ana' => '').untrusted?.should be_true end it "untrusts the result if a hash value is untrusted" do str = "Ghana" str.gsub(/a$/, 'a' => 'di'.untrust).untrusted?.should be_true end it "taints the result if the original string is tainted" do str = "Ghana".taint str.gsub(/[Aa]na/, 'ana' => '').tainted?.should be_true end it "taints the result if a hash value is tainted" do str = "Ghana" str.gsub(/a$/, 'a' => 'di'.taint).tainted?.should be_true end end end describe "String#gsub with pattern and block" do it "returns a copy of self with all occurrences of pattern replaced with the block's return value" do "hello".gsub(/./) { |s| s.succ + ' ' }.should == "i f m m p " "hello!".gsub(/(.)(.)/) { |*a| a.inspect }.should == '["he"]["ll"]["o!"]' "hello".gsub('l') { 'x'}.should == 'hexxo' end it "sets $~ for access from the block" do str = "hello" str.gsub(/([aeiou])/) { "<#{$~[1]}>" }.should == "hll" str.gsub(/([aeiou])/) { "<#{$1}>" }.should == "hll" str.gsub("l") { "<#{$~[0]}>" }.should == "heo" offsets = [] str.gsub(/([aeiou])/) do md = $~ md.string.should == str offsets << md.offset(0) str end.should == "hhellollhello" offsets.should == [[1, 2], [4, 5]] end it "restores $~ after leaving the block" do [/./, "l"].each do |pattern| old_md = nil "hello".gsub(pattern) do old_md = $~ "ok".match(/./) "x" end $~[0].should == old_md[0] $~.string.should == "hello" end end it "sets $~ to MatchData of last match and nil when there's none for access from outside" do 'hello.'.gsub('l') { 'x' } $~.begin(0).should == 3 $~[0].should == 'l' 'hello.'.gsub('not') { 'x' } $~.should == nil 'hello.'.gsub(/.(.)/) { 'x' } $~[0].should == 'o.' 'hello.'.gsub(/not/) { 'x' } $~.should == nil end ruby_version_is ""..."1.9" do it "raises a RuntimeError if the string is modified while substituting" do str = "hello" lambda { str.gsub(//) { str[0] = 'x' } }.should raise_error(RuntimeError) end end it "doesn't interpolate special sequences like \\1 for the block's return value" do repl = '\& \0 \1 \` \\\' \+ \\\\ foo' "hello".gsub(/(.+)/) { repl }.should == repl end it "converts the block's return value to a string using to_s" do replacement = mock('hello_replacement') def replacement.to_s() "hello_replacement" end "hello".gsub(/hello/) { replacement }.should == "hello_replacement" obj = mock('ok') def obj.to_s() "ok" end "hello".gsub(/.+/) { obj }.should == "ok" end ruby_version_is "1.9" do it "untrusts the result if the original string or replacement is untrusted" do hello = "hello" hello_t = "hello" a = "a" a_t = "a" empty = "" empty_t = "" hello_t.untrust; a_t.untrust; empty_t.untrust hello_t.gsub(/./) { a }.untrusted?.should == true hello_t.gsub(/./) { empty }.untrusted?.should == true hello.gsub(/./) { a_t }.untrusted?.should == true hello.gsub(/./) { empty_t }.untrusted?.should == true hello.gsub(//) { empty_t }.untrusted?.should == true hello.gsub(//.untrust) { "foo" }.untrusted?.should == false end end end describe "String#gsub! with pattern and replacement" do it "modifies self in place and returns self" do a = "hello" a.gsub!(/[aeiou]/, '*').should equal(a) a.should == "h*ll*" end #it "taints self if replacement is tainted" do # a = "hello" # a.gsub!(/./.taint, "foo").tainted?.should == false # a.gsub!(/./, "foo".taint).tainted?.should == true #end ruby_version_is "1.9" do it "untrusts self if replacement is untrusted" do a = "hello" a.gsub!(/./.untrust, "foo").untrusted?.should == false a.gsub!(/./, "foo".untrust).untrusted?.should == true end end it "returns nil if no modifications were made" do a = "hello" a.gsub!(/z/, '*').should == nil a.gsub!(/z/, 'z').should == nil a.should == "hello" end ruby_version_is ""..."1.9" do it "does not raise an error if the frozen string would not be modified" do s = "hello" s.freeze s.gsub!(/ROAR/, "x").should be_nil end it "raises a TypeError if the frozen string would be modified" do s = "hello" s.freeze lambda { s.gsub!(/e/, "e") }.should raise_error(TypeError) lambda { s.gsub!(/[aeiou]/, '*') }.should raise_error(TypeError) end end # See [ruby-core:23666] ruby_version_is "1.9" do it "raises a RuntimeError when self is frozen" do s = "hello" s.freeze lambda { s.gsub!(/ROAR/, "x") }.should raise_error(RuntimeError) lambda { s.gsub!(/e/, "e") }.should raise_error(RuntimeError) lambda { s.gsub!(/[aeiou]/, '*') }.should raise_error(RuntimeError) end end end describe "String#gsub! with pattern and block" do it "modifies self in place and returns self" do a = "hello" a.gsub!(/[aeiou]/) { '*' }.should equal(a) a.should == "h*ll*" end it "taints self if block's result is tainted" do a = "hello" #a.gsub!(/./.taint) { "foo" }.tainted?.should == false #a.gsub!(/./) { "foo".taint }.tainted?.should == true end ruby_version_is "1.9" do it "untrusts self if block's result is untrusted" do a = "hello" a.gsub!(/./.untrust) { "foo" }.untrusted?.should == false a.gsub!(/./) { "foo".untrust }.untrusted?.should == true end end it "returns nil if no modifications were made" do a = "hello" a.gsub!(/z/) { '*' }.should == nil a.gsub!(/z/) { 'z' }.should == nil a.should == "hello" end ruby_version_is ""..."1.9" do it "does not raise an error if the frozen string would not be modified" do s = "hello" s.freeze s.gsub!(/ROAR/) { "x" }.should be_nil end deviates_on :rubinius do # MRI 1.8.x is inconsistent here, raising a TypeError when not passed # a block and a RuntimeError when passed a block. This is arguably a # bug in MRI. In 1.9, both situations raise a RuntimeError. it "raises a TypeError if the frozen string would be modified" do s = "hello" s.freeze lambda { s.gsub!(/e/) { "e" } }.should raise_error(TypeError) lambda { s.gsub!(/[aeiou]/) { '*' } }.should raise_error(TypeError) end end not_compliant_on :rubinius do it "raises a RuntimeError if the frozen string would be modified" do s = "hello" s.freeze lambda { s.gsub!(/e/) { "e" } }.should raise_error(RuntimeError) lambda { s.gsub!(/[aeiou]/) { '*' } }.should raise_error(RuntimeError) end end end # See [ruby-core:23663] ruby_version_is "1.9" do it "raises a RuntimeError when self is frozen" do s = "hello" s.freeze lambda { s.gsub!(/ROAR/) { "x" } }.should raise_error(RuntimeError) lambda { s.gsub!(/e/) { "e" } }.should raise_error(RuntimeError) lambda { s.gsub!(/[aeiou]/) { '*' } }.should raise_error(RuntimeError) end end end