Skip to content

Commit

Permalink
Add Regex::MatchData#captures, named_captures, to_a, to_h (#3783)
Browse files Browse the repository at this point in the history
  • Loading branch information
makenowjust authored and asterite committed Jun 12, 2017
1 parent aaf000c commit 61f21ae
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 0 deletions.
68 changes: 68 additions & 0 deletions spec/std/match_data_spec.cr
Expand Up @@ -107,6 +107,74 @@ describe "Regex::MatchData" do
end
end

describe "#captures" do
it "gets an array of unnamed captures" do
"Crystal".match(/(Cr)y/).not_nil!.captures.should eq(["Cr"])
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.captures.should eq(["Cr", "st"])
end

it "gets an array of unnamed captures with optional" do
"Crystal".match(/(Cr)(s)?/).not_nil!.captures.should eq(["Cr", nil])
"Crystal".match(/(Cr)(?<name1>s)?(tal)?/).not_nil!.captures.should eq(["Cr", nil])
end
end

describe "#named_captures" do
it "gets a hash of named captures" do
"Crystal".match(/(?<name1>Cr)y/).not_nil!.named_captures.should eq({"name1" => "Cr"})
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.named_captures.should eq({"name1" => "y", "name2" => "al"})
end

it "gets a hash of named captures with optional" do
"Crystal".match(/(?<name1>Cr)(?<name2>s)?/).not_nil!.named_captures.should eq({"name1" => "Cr", "name2" => nil})
"Crystal".match(/(Cr)(?<name1>s)?(t)?(?<name2>al)?/).not_nil!.named_captures.should eq({"name1" => nil, "name2" => nil})
end
end

describe "#to_a" do
it "converts into an array" do
"Crystal".match(/(?<name1>Cr)(y)/).not_nil!.to_a.should eq(["Cry", "Cr", "y"])
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.to_a.should eq(["Crystal", "Cr", "y", "st", "al"])
end

it "converts into an array having nil" do
"Crystal".match(/(?<name1>Cr)(s)?/).not_nil!.to_a.should eq(["Cr", "Cr", nil])
"Crystal".match(/(Cr)(?<name1>s)?(yst)?(?<name2>al)?/).not_nil!.to_a.should eq(["Crystal", "Cr", nil, "yst", "al"])
end
end

describe "#to_h" do
it "converts into a hash" do
"Crystal".match(/(?<name1>Cr)(y)/).not_nil!.to_h.should eq({
0 => "Cry",
"name1" => "Cr",
2 => "y",
})
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.to_h.should eq({
0 => "Crystal",
1 => "Cr",
"name1" => "y",
3 => "st",
"name2" => "al",
})
end

it "converts into a hash having nil" do
"Crystal".match(/(?<name1>Cr)(s)?/).not_nil!.to_h.should eq({
0 => "Cr",
"name1" => "Cr",
2 => nil,
})
"Crystal".match(/(Cr)(?<name1>s)?(yst)?(?<name2>al)?/).not_nil!.to_h.should eq({
0 => "Crystal",
1 => "Cr",
"name1" => nil,
3 => "yst",
"name2" => "al",
})
end
end

it "can check equality" do
re = /((?<hello>he)llo)/
m1 = re.match("hello")
Expand Down
85 changes: 85 additions & 0 deletions src/regex/match_data.cr
Expand Up @@ -182,6 +182,91 @@ class Regex
@string.byte_slice(byte_end(0))
end

# Returns an array of unnamed capture groups.
#
# It is a difference from `to_a` that the result array does not contain the match for the entire `Regex` (`self[0]`).
#
# ```
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
# match.captures # => ["Cr", "st"]
#
# # When this regex has an optional group, result array may contain
# # a `nil` if this group is not matched.
# match = "Crystal".match(/(Cr)(stal)?/).not_nil!
# match.captures # => ["Cr", nil]
# ```
def captures
name_table = @regex.name_table

caps = [] of String?
(1..size).each do |i|
caps << self[i]? unless name_table.has_key? i
end

caps
end

# Returns a hash of named capture groups.
#
# ```
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
# match.named_captures # => {"name1" => "y", "name2" => "al"}
#
# # When this regex has an optional group, result hash may contain
# # a `nil` if this group is not matched.
# match = "Crystal".match(/(?<name1>Cr)(?<name2>stal)?/).not_nil!
# match.named_captures # => {"name1" => "Cr", "name2" => nil}
# ```
def named_captures
name_table = @regex.name_table

caps = {} of String => String?
(1..size).each do |i|
if name = name_table[i]?
caps[name] = self[i]?
end
end

caps
end

# Convert this match data into an array.
#
# ```
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
# match.to_a # => ["Crystal", "Cr", "y", "st", "al"]
#
# # When this regex has an optional group, result array may contain
# # a `nil` if this group is not matched.
# match = "Crystal".match(/(Cr)(?<name1>stal)?/).not_nil!
# match.to_a # => ["Cr", "Cr", nil]
# ```
def to_a
(0..size).map { |i| self[i]? }
end

# Convert this match data into a hash.
#
# ```
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
# match.to_h # => {0 => "Crystal", 1 => "Cr", "name1" => "y", 3 => "st", "name2" => "al"}
#
# # When this regex has an optional group, result array may contain
# # a `nil` if this group is not matched.
# match = "Crystal".match(/(Cr)(?<name1>stal)?/).not_nil!
# match.to_h # => {0 => "Cr", 1 => "Cr", "name1" => nil}
# ```
def to_h
name_table = @regex.name_table

hash = {} of (String | Int32) => String?
(0..size).each do |i|
hash[name_table.fetch(i) { i }] = self[i]?
end

hash
end

def inspect(io : IO)
to_s(io)
end
Expand Down

0 comments on commit 61f21ae

Please sign in to comment.