Skip to content

Commit 61f21ae

Browse files
makenowjustasterite
authored andcommittedJun 12, 2017
Add Regex::MatchData#captures, named_captures, to_a, to_h (#3783)
1 parent aaf000c commit 61f21ae

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed
 

‎spec/std/match_data_spec.cr

+68
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,74 @@ describe "Regex::MatchData" do
107107
end
108108
end
109109

110+
describe "#captures" do
111+
it "gets an array of unnamed captures" do
112+
"Crystal".match(/(Cr)y/).not_nil!.captures.should eq(["Cr"])
113+
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.captures.should eq(["Cr", "st"])
114+
end
115+
116+
it "gets an array of unnamed captures with optional" do
117+
"Crystal".match(/(Cr)(s)?/).not_nil!.captures.should eq(["Cr", nil])
118+
"Crystal".match(/(Cr)(?<name1>s)?(tal)?/).not_nil!.captures.should eq(["Cr", nil])
119+
end
120+
end
121+
122+
describe "#named_captures" do
123+
it "gets a hash of named captures" do
124+
"Crystal".match(/(?<name1>Cr)y/).not_nil!.named_captures.should eq({"name1" => "Cr"})
125+
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.named_captures.should eq({"name1" => "y", "name2" => "al"})
126+
end
127+
128+
it "gets a hash of named captures with optional" do
129+
"Crystal".match(/(?<name1>Cr)(?<name2>s)?/).not_nil!.named_captures.should eq({"name1" => "Cr", "name2" => nil})
130+
"Crystal".match(/(Cr)(?<name1>s)?(t)?(?<name2>al)?/).not_nil!.named_captures.should eq({"name1" => nil, "name2" => nil})
131+
end
132+
end
133+
134+
describe "#to_a" do
135+
it "converts into an array" do
136+
"Crystal".match(/(?<name1>Cr)(y)/).not_nil!.to_a.should eq(["Cry", "Cr", "y"])
137+
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.to_a.should eq(["Crystal", "Cr", "y", "st", "al"])
138+
end
139+
140+
it "converts into an array having nil" do
141+
"Crystal".match(/(?<name1>Cr)(s)?/).not_nil!.to_a.should eq(["Cr", "Cr", nil])
142+
"Crystal".match(/(Cr)(?<name1>s)?(yst)?(?<name2>al)?/).not_nil!.to_a.should eq(["Crystal", "Cr", nil, "yst", "al"])
143+
end
144+
end
145+
146+
describe "#to_h" do
147+
it "converts into a hash" do
148+
"Crystal".match(/(?<name1>Cr)(y)/).not_nil!.to_h.should eq({
149+
0 => "Cry",
150+
"name1" => "Cr",
151+
2 => "y",
152+
})
153+
"Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!.to_h.should eq({
154+
0 => "Crystal",
155+
1 => "Cr",
156+
"name1" => "y",
157+
3 => "st",
158+
"name2" => "al",
159+
})
160+
end
161+
162+
it "converts into a hash having nil" do
163+
"Crystal".match(/(?<name1>Cr)(s)?/).not_nil!.to_h.should eq({
164+
0 => "Cr",
165+
"name1" => "Cr",
166+
2 => nil,
167+
})
168+
"Crystal".match(/(Cr)(?<name1>s)?(yst)?(?<name2>al)?/).not_nil!.to_h.should eq({
169+
0 => "Crystal",
170+
1 => "Cr",
171+
"name1" => nil,
172+
3 => "yst",
173+
"name2" => "al",
174+
})
175+
end
176+
end
177+
110178
it "can check equality" do
111179
re = /((?<hello>he)llo)/
112180
m1 = re.match("hello")

‎src/regex/match_data.cr

+85
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,91 @@ class Regex
182182
@string.byte_slice(byte_end(0))
183183
end
184184

185+
# Returns an array of unnamed capture groups.
186+
#
187+
# It is a difference from `to_a` that the result array does not contain the match for the entire `Regex` (`self[0]`).
188+
#
189+
# ```
190+
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
191+
# match.captures # => ["Cr", "st"]
192+
#
193+
# # When this regex has an optional group, result array may contain
194+
# # a `nil` if this group is not matched.
195+
# match = "Crystal".match(/(Cr)(stal)?/).not_nil!
196+
# match.captures # => ["Cr", nil]
197+
# ```
198+
def captures
199+
name_table = @regex.name_table
200+
201+
caps = [] of String?
202+
(1..size).each do |i|
203+
caps << self[i]? unless name_table.has_key? i
204+
end
205+
206+
caps
207+
end
208+
209+
# Returns a hash of named capture groups.
210+
#
211+
# ```
212+
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
213+
# match.named_captures # => {"name1" => "y", "name2" => "al"}
214+
#
215+
# # When this regex has an optional group, result hash may contain
216+
# # a `nil` if this group is not matched.
217+
# match = "Crystal".match(/(?<name1>Cr)(?<name2>stal)?/).not_nil!
218+
# match.named_captures # => {"name1" => "Cr", "name2" => nil}
219+
# ```
220+
def named_captures
221+
name_table = @regex.name_table
222+
223+
caps = {} of String => String?
224+
(1..size).each do |i|
225+
if name = name_table[i]?
226+
caps[name] = self[i]?
227+
end
228+
end
229+
230+
caps
231+
end
232+
233+
# Convert this match data into an array.
234+
#
235+
# ```
236+
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
237+
# match.to_a # => ["Crystal", "Cr", "y", "st", "al"]
238+
#
239+
# # When this regex has an optional group, result array may contain
240+
# # a `nil` if this group is not matched.
241+
# match = "Crystal".match(/(Cr)(?<name1>stal)?/).not_nil!
242+
# match.to_a # => ["Cr", "Cr", nil]
243+
# ```
244+
def to_a
245+
(0..size).map { |i| self[i]? }
246+
end
247+
248+
# Convert this match data into a hash.
249+
#
250+
# ```
251+
# match = "Crystal".match(/(Cr)(?<name1>y)(st)(?<name2>al)/).not_nil!
252+
# match.to_h # => {0 => "Crystal", 1 => "Cr", "name1" => "y", 3 => "st", "name2" => "al"}
253+
#
254+
# # When this regex has an optional group, result array may contain
255+
# # a `nil` if this group is not matched.
256+
# match = "Crystal".match(/(Cr)(?<name1>stal)?/).not_nil!
257+
# match.to_h # => {0 => "Cr", 1 => "Cr", "name1" => nil}
258+
# ```
259+
def to_h
260+
name_table = @regex.name_table
261+
262+
hash = {} of (String | Int32) => String?
263+
(0..size).each do |i|
264+
hash[name_table.fetch(i) { i }] = self[i]?
265+
end
266+
267+
hash
268+
end
269+
185270
def inspect(io : IO)
186271
to_s(io)
187272
end

0 commit comments

Comments
 (0)
Please sign in to comment.