ronin-support 0.5.0.rc1 → 0.5.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog.md +6 -1
- data/lib/ronin/fuzzing/extensions/string.rb +21 -208
- data/lib/ronin/fuzzing/fuzzer.rb +118 -0
- data/lib/ronin/fuzzing/fuzzing.rb +7 -2
- data/lib/ronin/fuzzing/mutator.rb +161 -0
- data/lib/ronin/fuzzing/repeater.rb +81 -0
- data/lib/ronin/fuzzing/template.rb +133 -0
- data/lib/ronin/network/mixins/http.rb +51 -580
- data/lib/ronin/support/version.rb +1 -1
- data/lib/ronin/wordlist.rb +9 -4
- data/spec/fuzzing/extensions/string_spec.rb +87 -0
- data/spec/fuzzing/fuzzer_spec.rb +109 -0
- data/spec/fuzzing/fuzzing_spec.rb +24 -0
- data/spec/fuzzing/mutator_spec.rb +112 -0
- data/spec/fuzzing/repeater_spec.rb +57 -0
- data/spec/fuzzing/template_spec.rb +54 -0
- data/spec/spec_helper.rb +3 -3
- metadata +12 -3
- data/spec/fuzzing/string_spec.rb +0 -158
data/ChangeLog.md
CHANGED
@@ -6,6 +6,10 @@
|
|
6
6
|
* Added {Ronin::Binary::Template}.
|
7
7
|
* Added {Ronin::Binary::Struct}.
|
8
8
|
* Added {Ronin::Binary::Hexdump::Parser}.
|
9
|
+
* Added {Ronin::Fuzzing::Template}.
|
10
|
+
* Added {Ronin::Fuzzing::Repeater}.
|
11
|
+
* Added {Ronin::Fuzzing::Fuzzer}.
|
12
|
+
* Added {Ronin::Fuzzing::Mutator}.
|
9
13
|
* Added {Ronin::Wordlist.create}.
|
10
14
|
* Added {Ronin::Wordlist#path} and {Ronin::Wordlist#words}.
|
11
15
|
* Added {Ronin::Wordlist#save}.
|
@@ -44,6 +48,7 @@
|
|
44
48
|
* Support unhexdumping floats / doubles.
|
45
49
|
* Allow {String#mutate} to accept Symbols that map to {Ronin::Fuzzing}
|
46
50
|
generator methods.
|
51
|
+
* {Ronin::Fuzzing.[]} now raises a `NoMethodError` for unknown fuzzing methods.
|
47
52
|
* Use `module_function` in {Ronin::Fuzzing}, so the generator methods can be
|
48
53
|
included into other Classes/Modules.
|
49
54
|
* Require uri-query_params ~> 0.6.
|
@@ -127,7 +132,7 @@
|
|
127
132
|
was not being escaped.
|
128
133
|
* Allow {Ronin::Network::HTTP.request} to accept `:query` and `:query_params`
|
129
134
|
options.
|
130
|
-
* Fixed a bug in
|
135
|
+
* Fixed a bug in `Ronin::Network::Mixins::HTTP#http_session`, where
|
131
136
|
normalized options were not being yielded.
|
132
137
|
* {Ronin::Network::HTTP#http_get_headers} and
|
133
138
|
{Ronin::Network::HTTP#http_post_headers} now return a Hash of Capitalized
|
@@ -17,21 +17,20 @@
|
|
17
17
|
# along with Ronin Support. If not, see <http://www.gnu.org/licenses/>.
|
18
18
|
#
|
19
19
|
|
20
|
-
require 'ronin/
|
20
|
+
require 'ronin/fuzzing/template'
|
21
|
+
require 'ronin/fuzzing/repeater'
|
22
|
+
require 'ronin/fuzzing/fuzzer'
|
23
|
+
require 'ronin/fuzzing/mutator'
|
21
24
|
require 'ronin/fuzzing/fuzzing'
|
22
|
-
|
23
|
-
require 'combinatorics/generator'
|
24
|
-
require 'combinatorics/list_comprehension'
|
25
|
-
require 'combinatorics/power_set'
|
26
|
-
require 'chars'
|
25
|
+
require 'ronin/extensions/regexp'
|
27
26
|
|
28
27
|
class String
|
29
28
|
|
30
29
|
#
|
31
30
|
# Generate permutations of Strings from a format template.
|
32
31
|
#
|
33
|
-
# @param [Array(<String,Symbol,Enumerable>, <Integer,Array,Range>)]
|
34
|
-
# The
|
32
|
+
# @param [Array(<String,Symbol,Enumerable>, <Integer,Array,Range>)] fields
|
33
|
+
# The fields which defines the string or character sets which will
|
35
34
|
# make up parts of the String.
|
36
35
|
#
|
37
36
|
# @yield [string]
|
@@ -86,69 +85,14 @@ class String
|
|
86
85
|
#
|
87
86
|
# @api public
|
88
87
|
#
|
89
|
-
def self.generate(*
|
90
|
-
|
91
|
-
|
92
|
-
sets = []
|
93
|
-
|
94
|
-
template.each do |pattern|
|
95
|
-
set, length = pattern
|
96
|
-
set = case set
|
97
|
-
when String
|
98
|
-
[set].each
|
99
|
-
when Symbol
|
100
|
-
name = set.to_s.upcase
|
101
|
-
|
102
|
-
unless Chars.const_defined?(name)
|
103
|
-
raise(ArgumentError,"unknown charset #{set.inspect}")
|
104
|
-
end
|
105
|
-
|
106
|
-
Chars.const_get(name).each_char
|
107
|
-
when Enumerable
|
108
|
-
set
|
109
|
-
else
|
110
|
-
raise(TypeError,"set must be a String, Symbol or Enumerable")
|
111
|
-
end
|
112
|
-
|
113
|
-
case length
|
114
|
-
when Integer
|
115
|
-
length.times { sets << set.dup }
|
116
|
-
when Array, Range
|
117
|
-
sets << Combinatorics::Generator.new do |g|
|
118
|
-
length.each do |sublength|
|
119
|
-
superset = Array.new(sublength) { set.dup }
|
120
|
-
|
121
|
-
superset.comprehension { |strings| g.yield strings.join }
|
122
|
-
end
|
123
|
-
end
|
124
|
-
when nil
|
125
|
-
sets << set
|
126
|
-
else
|
127
|
-
raise(TypeError,"length must be an Integer, Range or Array")
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
sets.comprehension do |strings|
|
132
|
-
new_string = ''
|
133
|
-
|
134
|
-
strings.each do |string|
|
135
|
-
new_string << case string
|
136
|
-
when Integer
|
137
|
-
string.chr
|
138
|
-
else
|
139
|
-
string.to_s
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
yield new_string
|
144
|
-
end
|
145
|
-
return nil
|
88
|
+
def self.generate(*fields,&block)
|
89
|
+
Ronin::Fuzzing::Template.new(fields).each(&block)
|
146
90
|
end
|
147
91
|
|
148
92
|
#
|
149
93
|
# Repeats the String.
|
150
94
|
#
|
151
|
-
# @param [Enumerable, Integer]
|
95
|
+
# @param [Enumerable, Integer] lengths
|
152
96
|
# The number of times to repeat the String.
|
153
97
|
#
|
154
98
|
# @yield [repeated]
|
@@ -181,26 +125,17 @@ class String
|
|
181
125
|
#
|
182
126
|
# @since 0.4.0
|
183
127
|
#
|
184
|
-
def repeating(
|
185
|
-
|
186
|
-
|
187
|
-
|
128
|
+
def repeating(lengths,&block)
|
129
|
+
case lengths
|
130
|
+
when Integer
|
131
|
+
# if lengths is an Integer, simply multiply the String and return
|
132
|
+
repeated = (self * lengths)
|
188
133
|
|
189
134
|
yield repeated if block_given?
|
190
135
|
return repeated
|
136
|
+
else
|
137
|
+
return Ronin::Fuzzing::Repeater.new(lengths).each(self,&block)
|
191
138
|
end
|
192
|
-
|
193
|
-
return enum_for(:repeating,n) unless block_given?
|
194
|
-
|
195
|
-
unless n.kind_of?(Enumerable)
|
196
|
-
raise(TypeError,"argument must be Enumerable or an Integer")
|
197
|
-
end
|
198
|
-
|
199
|
-
n.each do |length|
|
200
|
-
yield(self * length)
|
201
|
-
end
|
202
|
-
|
203
|
-
return self
|
204
139
|
end
|
205
140
|
|
206
141
|
#
|
@@ -232,54 +167,8 @@ class String
|
|
232
167
|
#
|
233
168
|
# @api public
|
234
169
|
#
|
235
|
-
def fuzz(substitutions={})
|
236
|
-
|
237
|
-
|
238
|
-
substitutions.each do |pattern,substitution|
|
239
|
-
pattern = case pattern
|
240
|
-
when Regexp
|
241
|
-
pattern
|
242
|
-
when String
|
243
|
-
Regexp.new(Regexp.escape(pattern))
|
244
|
-
when Symbol
|
245
|
-
Regexp.const_get(pattern.to_s.upcase)
|
246
|
-
else
|
247
|
-
raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp")
|
248
|
-
end
|
249
|
-
|
250
|
-
substitution = case substitution
|
251
|
-
when Enumerable
|
252
|
-
substitution
|
253
|
-
when Symbol
|
254
|
-
Ronin::Fuzzing[substitution]
|
255
|
-
else
|
256
|
-
raise(TypeError,"substitutions must be Enumerable or a Symbol")
|
257
|
-
end
|
258
|
-
|
259
|
-
scanner = StringScanner.new(self)
|
260
|
-
indices = []
|
261
|
-
|
262
|
-
while scanner.scan_until(pattern)
|
263
|
-
indices << [scanner.pos - scanner.matched_size, scanner.matched_size]
|
264
|
-
end
|
265
|
-
|
266
|
-
indices.each do |index,length|
|
267
|
-
substitution.each do |substitute|
|
268
|
-
substitute = case substitute
|
269
|
-
when Proc
|
270
|
-
substitute.call(self[index,length])
|
271
|
-
when Integer
|
272
|
-
substitute.chr
|
273
|
-
else
|
274
|
-
substitute.to_s
|
275
|
-
end
|
276
|
-
|
277
|
-
fuzzed = dup
|
278
|
-
fuzzed[index,length] = substitute
|
279
|
-
yield fuzzed
|
280
|
-
end
|
281
|
-
end
|
282
|
-
end
|
170
|
+
def fuzz(substitutions={},&block)
|
171
|
+
Ronin::Fuzzing::Fuzzer.new(substitutions).each(self,&block)
|
283
172
|
end
|
284
173
|
|
285
174
|
#
|
@@ -310,84 +199,8 @@ class String
|
|
310
199
|
#
|
311
200
|
# @api public
|
312
201
|
#
|
313
|
-
def mutate(mutations={})
|
314
|
-
|
315
|
-
|
316
|
-
matches = Set[]
|
317
|
-
|
318
|
-
mutations.each do |pattern,mutation|
|
319
|
-
pattern = case pattern
|
320
|
-
when Regexp
|
321
|
-
pattern
|
322
|
-
when String
|
323
|
-
Regexp.new(Regexp.escape(pattern))
|
324
|
-
when Symbol
|
325
|
-
Regexp.const_get(pattern.to_s.upcase)
|
326
|
-
else
|
327
|
-
raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp")
|
328
|
-
end
|
329
|
-
|
330
|
-
mutation = case mutation
|
331
|
-
when Symbol
|
332
|
-
Ronin::Fuzzing[mutation]
|
333
|
-
when Enumerable
|
334
|
-
mutation
|
335
|
-
else
|
336
|
-
raise(TypeError,"mutation #{mutation.inspect} must be a Symbol or Enumerable")
|
337
|
-
end
|
338
|
-
|
339
|
-
scanner = StringScanner.new(self)
|
340
|
-
|
341
|
-
while scanner.scan_until(pattern)
|
342
|
-
length = scanner.matched_size
|
343
|
-
index = scanner.pos - length
|
344
|
-
original = scanner.matched
|
345
|
-
|
346
|
-
mutator = Combinatorics::Generator.new do |g|
|
347
|
-
mutation.each do |mutate|
|
348
|
-
g.yield case mutate
|
349
|
-
when Proc
|
350
|
-
mutate.call(original)
|
351
|
-
when Integer
|
352
|
-
mutate.chr
|
353
|
-
else
|
354
|
-
mutate.to_s
|
355
|
-
end
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
matches << [index, length, mutator]
|
360
|
-
end
|
361
|
-
end
|
362
|
-
|
363
|
-
matches.powerset do |submatches|
|
364
|
-
# ignore the empty Set
|
365
|
-
next if submatches.empty?
|
366
|
-
|
367
|
-
# sort the submatches by index
|
368
|
-
submatches = submatches.sort_by { |index,length,mutator| index }
|
369
|
-
sets = []
|
370
|
-
prev_index = 0
|
371
|
-
|
372
|
-
submatches.each do |index,length,mutator|
|
373
|
-
# add the previous substring to the set of Strings
|
374
|
-
if index > prev_index
|
375
|
-
sets << [self[prev_index,index - prev_index]]
|
376
|
-
end
|
377
|
-
|
378
|
-
# add the mutator to the set of Strings
|
379
|
-
sets << mutator
|
380
|
-
|
381
|
-
prev_index = index + length
|
382
|
-
end
|
383
|
-
|
384
|
-
# add the remaining substring to the set of Strings
|
385
|
-
if prev_index < self.length
|
386
|
-
sets << [self[prev_index..-1]]
|
387
|
-
end
|
388
|
-
|
389
|
-
sets.comprehension { |strings| yield strings.join }
|
390
|
-
end
|
202
|
+
def mutate(mutations={},&block)
|
203
|
+
Ronin::Fuzzing::Mutator.new(mutations).each(self,&block)
|
391
204
|
end
|
392
205
|
|
393
206
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2006-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
|
3
|
+
#
|
4
|
+
# This file is part of Ronin Support.
|
5
|
+
#
|
6
|
+
# Ronin Support is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published
|
8
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Ronin Support is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
17
|
+
# along with Ronin Support. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'ronin/fuzzing/fuzzing'
|
21
|
+
require 'ronin/extensions/regexp'
|
22
|
+
|
23
|
+
require 'strscan'
|
24
|
+
|
25
|
+
module Ronin
|
26
|
+
module Fuzzing
|
27
|
+
#
|
28
|
+
# Fuzzing class that incrementally fuzzes a String, given substitution
|
29
|
+
# rules.
|
30
|
+
#
|
31
|
+
# @api semipublic
|
32
|
+
#
|
33
|
+
# @since 0.5.0
|
34
|
+
#
|
35
|
+
class Fuzzer
|
36
|
+
|
37
|
+
# Patterns and their substitutions
|
38
|
+
attr_reader :rules
|
39
|
+
|
40
|
+
#
|
41
|
+
# Initializes a new Fuzzer.
|
42
|
+
#
|
43
|
+
# @param [Hash{Regexp,String => #each}] rules
|
44
|
+
# Patterns and their substitutions.
|
45
|
+
#
|
46
|
+
def initialize(rules)
|
47
|
+
@rules = {}
|
48
|
+
|
49
|
+
rules.each do |pattern,substitution|
|
50
|
+
pattern = case pattern
|
51
|
+
when Regexp
|
52
|
+
pattern
|
53
|
+
when String
|
54
|
+
Regexp.new(Regexp.escape(pattern))
|
55
|
+
when Symbol
|
56
|
+
Regexp.const_get(pattern.to_s.upcase)
|
57
|
+
else
|
58
|
+
raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp")
|
59
|
+
end
|
60
|
+
|
61
|
+
substitution = case substitution
|
62
|
+
when Enumerable
|
63
|
+
substitution
|
64
|
+
when Symbol
|
65
|
+
Fuzzing[substitution]
|
66
|
+
else
|
67
|
+
raise(TypeError,"substitutions must be Enumerable or a Symbol")
|
68
|
+
end
|
69
|
+
|
70
|
+
@rules[pattern] = substitution
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Incrementally fuzzes the String.
|
76
|
+
#
|
77
|
+
# @yield [fuzz]
|
78
|
+
# The given block will be passed every fuzzed String.
|
79
|
+
#
|
80
|
+
# @yieldparam [String] fuzz
|
81
|
+
# A fuzzed String.
|
82
|
+
#
|
83
|
+
# @return [Enumerator]
|
84
|
+
# If no block is given, an Enumerator will be returned.
|
85
|
+
#
|
86
|
+
def each(string)
|
87
|
+
return enum_for(__method__,string) unless block_given?
|
88
|
+
|
89
|
+
@rules.each do |pattern,substitution|
|
90
|
+
scanner = StringScanner.new(string)
|
91
|
+
indices = []
|
92
|
+
|
93
|
+
while scanner.scan_until(pattern)
|
94
|
+
indices << [scanner.pos - scanner.matched_size, scanner.matched_size]
|
95
|
+
end
|
96
|
+
|
97
|
+
indices.each do |index,length|
|
98
|
+
substitution.each do |substitute|
|
99
|
+
substitute = case substitute
|
100
|
+
when Proc
|
101
|
+
substitute.call(string[index,length])
|
102
|
+
when Integer
|
103
|
+
substitute.chr
|
104
|
+
else
|
105
|
+
substitute.to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
fuzzed = string.dup
|
109
|
+
fuzzed[index,length] = substitute
|
110
|
+
yield fuzzed
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -58,12 +58,17 @@ module Ronin
|
|
58
58
|
# @return [Enumerator]
|
59
59
|
# An Enumerator for the fuzzer method.
|
60
60
|
#
|
61
|
+
# @raise [NoMethodError]
|
62
|
+
# The fuzzing method could not be found.
|
63
|
+
#
|
61
64
|
# @api semipublic
|
62
65
|
#
|
63
66
|
def self.[](name)
|
64
|
-
if (!
|
65
|
-
|
67
|
+
if (!respond_to?(name) || Module.respond_to?(name))
|
68
|
+
raise(NoMethodError,"no such fuzzing method: #{name}")
|
66
69
|
end
|
70
|
+
|
71
|
+
return enum_for(name)
|
67
72
|
end
|
68
73
|
|
69
74
|
module_function
|
@@ -0,0 +1,161 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2006-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
|
3
|
+
#
|
4
|
+
# This file is part of Ronin Support.
|
5
|
+
#
|
6
|
+
# Ronin Support is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published
|
8
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Ronin Support is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
17
|
+
# along with Ronin Support. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'ronin/fuzzing/fuzzing'
|
21
|
+
require 'ronin/extensions/regexp'
|
22
|
+
|
23
|
+
require 'combinatorics/list_comprehension'
|
24
|
+
require 'combinatorics/power_set'
|
25
|
+
require 'combinatorics/generator'
|
26
|
+
require 'strscan'
|
27
|
+
require 'set'
|
28
|
+
|
29
|
+
module Ronin
|
30
|
+
module Fuzzing
|
31
|
+
#
|
32
|
+
# Fuzzer class that permutates over every mutation of a String, given
|
33
|
+
# mutation rules.
|
34
|
+
#
|
35
|
+
# @api semipublic
|
36
|
+
#
|
37
|
+
# @since 0.5.0
|
38
|
+
#
|
39
|
+
class Mutator
|
40
|
+
|
41
|
+
# Mutation rules
|
42
|
+
attr_reader :rules
|
43
|
+
|
44
|
+
#
|
45
|
+
# Initialize the Mutator.
|
46
|
+
#
|
47
|
+
# @param [Hash{Regexp,String,Symbol => Symbol,Enumerable}] rules
|
48
|
+
# The patterns and substitutions to mutate the String with.
|
49
|
+
#
|
50
|
+
# @raise [TypeError]
|
51
|
+
# A mutation pattern was not a Regexp, String or Symbol.
|
52
|
+
# A mutation substitution was not a Symbol or Enumerable.
|
53
|
+
#
|
54
|
+
def initialize(rules)
|
55
|
+
@rules = {}
|
56
|
+
|
57
|
+
rules.each do |pattern,mutation|
|
58
|
+
pattern = case pattern
|
59
|
+
when Regexp
|
60
|
+
pattern
|
61
|
+
when String
|
62
|
+
Regexp.new(Regexp.escape(pattern))
|
63
|
+
when Symbol
|
64
|
+
Regexp.const_get(pattern.to_s.upcase)
|
65
|
+
else
|
66
|
+
raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp")
|
67
|
+
end
|
68
|
+
|
69
|
+
mutation = case mutation
|
70
|
+
when Enumerable
|
71
|
+
mutation
|
72
|
+
when Symbol
|
73
|
+
Ronin::Fuzzing[mutation]
|
74
|
+
else
|
75
|
+
raise(TypeError,"mutation #{mutation.inspect} must be a Symbol or Enumerable")
|
76
|
+
end
|
77
|
+
|
78
|
+
@rules[pattern] = mutation
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Permutes over every possible mutation of the String.
|
84
|
+
#
|
85
|
+
# @param [String] string
|
86
|
+
# The String to be mutated.
|
87
|
+
#
|
88
|
+
# @yield [mutant]
|
89
|
+
# The given block will be yielded every possible mutant String.
|
90
|
+
#
|
91
|
+
# @yieldparam [String] mutant
|
92
|
+
# A mutated String.
|
93
|
+
#
|
94
|
+
# @return [Enumerator]
|
95
|
+
# If no block is given, an Enumerator will be returned.
|
96
|
+
#
|
97
|
+
def each(string)
|
98
|
+
return enum_for(__method__,string) unless block_given?
|
99
|
+
|
100
|
+
matches = Set[]
|
101
|
+
|
102
|
+
@rules.each do |pattern,mutation|
|
103
|
+
scanner = StringScanner.new(string)
|
104
|
+
|
105
|
+
while scanner.scan_until(pattern)
|
106
|
+
length = scanner.matched_size
|
107
|
+
index = scanner.pos - length
|
108
|
+
original = scanner.matched
|
109
|
+
|
110
|
+
mutator = Combinatorics::Generator.new do |g|
|
111
|
+
mutation.each do |mutate|
|
112
|
+
g.yield case mutate
|
113
|
+
when Proc
|
114
|
+
mutate.call(original)
|
115
|
+
when Integer
|
116
|
+
mutate.chr
|
117
|
+
else
|
118
|
+
mutate.to_s
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
matches << [index, length, mutator]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
matches.powerset do |submatches|
|
128
|
+
# ignore the empty Set
|
129
|
+
next if submatches.empty?
|
130
|
+
|
131
|
+
# sort the submatches by index
|
132
|
+
submatches = submatches.sort_by { |index,length,mutator| index }
|
133
|
+
sets = []
|
134
|
+
prev_index = 0
|
135
|
+
|
136
|
+
submatches.each do |index,length,mutator|
|
137
|
+
# add the previous substring to the set of Strings
|
138
|
+
if index > prev_index
|
139
|
+
sets << [string[prev_index,index - prev_index]]
|
140
|
+
end
|
141
|
+
|
142
|
+
# add the mutator to the set of Strings
|
143
|
+
sets << mutator
|
144
|
+
|
145
|
+
prev_index = index + length
|
146
|
+
end
|
147
|
+
|
148
|
+
# add the remaining substring to the set of Strings
|
149
|
+
if prev_index < string.length
|
150
|
+
sets << [string[prev_index..-1]]
|
151
|
+
end
|
152
|
+
|
153
|
+
sets.comprehension { |strings| yield strings.join }
|
154
|
+
end
|
155
|
+
|
156
|
+
return nil
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|