regexp-examples 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,12 @@
1
1
  module RegexpExamples
2
- # Number of times to repeat for Star and Plus repeaters
3
- TIMES = 2
2
+ # The maximum variance for any given repeater, to prevent a huge/infinite number of
3
+ # examples from being listed. For example, if MaxRepeaterVariance = 2 then:
4
+ # .* is equivalent to .{0,2}
5
+ # .+ is equivalent to .{1,3}
6
+ # .{2,} is equivalent to .{2,4}
7
+ # .{,3} is equivalent to .{0,2}
8
+ # .{3,8} is equivalent to .{3,5}
9
+ MaxRepeaterVariance = 2
4
10
 
5
11
  # Maximum number of characters returned from a char set, to reduce output spam
6
12
  # For example:
@@ -9,14 +15,13 @@ module RegexpExamples
9
15
  MaxGroupResults = 5
10
16
 
11
17
  module CharSets
12
- Lower = Array('a'..'z')
13
- Upper = Array('A'..'Z')
14
- Digit = Array('0'..'9')
15
- # 45.chr = "-". Need to make sure this is at the START of the array, or things break
16
- # This is because of the /[a-z]/ regex syntax, and how it's being parsed
17
- Punct = [45..45, 33..44, 46..47, 58..64, 91..96, 123..126].map { |r| r.map { |val| val.chr } }.flatten
18
- Hex = Array('a'..'f') | Array('A'..'F') | Digit
19
- Any = Lower | Upper | Digit | Punct
18
+ Lower = Array('a'..'z')
19
+ Upper = Array('A'..'Z')
20
+ Digit = Array('0'..'9')
21
+ Punct = [33..47, 58..64, 91..96, 123..126].map { |r| r.map { |val| val.chr } }.flatten
22
+ Hex = Array('a'..'f') | Array('A'..'F') | Digit
23
+ Whitespace = [' ', "\t", "\n", "\r", "\v", "\f"]
24
+ Any = Lower | Upper | Digit | Punct
20
25
  end
21
26
 
22
27
  # Map of special regex characters, to their associated character sets
@@ -25,8 +30,8 @@ module RegexpExamples
25
30
  'D' => CharSets::Lower | CharSets::Upper | CharSets::Punct,
26
31
  'w' => CharSets::Lower | CharSets::Upper | CharSets::Digit | ['_'],
27
32
  'W' => CharSets::Punct.reject { |val| val == '_' },
28
- 's' => [' ', "\t", "\n", "\r", "\v", "\f"],
29
- 'S' => CharSets::Any - [' ', "\t", "\n", "\r", "\v", "\f"],
33
+ 's' => CharSets::Whitespace,
34
+ 'S' => CharSets::Any - CharSets::Whitespace,
30
35
  'h' => CharSets::Hex,
31
36
  'H' => CharSets::Any - CharSets::Hex,
32
37
 
@@ -53,9 +53,16 @@ module RegexpExamples
53
53
  last = nil
54
54
  first = @chars.shift if @chars.first == "-"
55
55
  last = @chars.pop if @chars.last == "-"
56
- # Replace all instances of e.g. ["a" "-" "z"] with ["a", "b", ..., "z"]
56
+ # Replace all instances of e.g. ["a", "-", "z"] with ["a", "b", ..., "z"]
57
57
  while i = @chars.index("-")
58
- @chars[i-1..i+1] = (@chars[i-1]..@chars[i+1]).to_a
58
+ # Prevent infinite loops from expanding [",", "-", "."] to itself
59
+ # (Since ",".ord = 44, "-".ord = 45, ".".ord = 46)
60
+ if (@chars[i-1] == ',' && @chars[i+1] == '.')
61
+ first = '-'
62
+ @chars.delete_at(i)
63
+ else
64
+ @chars[i-1..i+1] = (@chars[i-1]..@chars[i+1]).to_a
65
+ end
59
66
  end
60
67
  # restore them back
61
68
  @chars.unshift(first) if first
@@ -66,7 +73,7 @@ module RegexpExamples
66
73
  @chars.each_with_index do |char, i|
67
74
  if char == "\\"
68
75
  if BackslashCharMap.keys.include?(@chars[i+1])
69
- @chars[i..i+1] = BackslashCharMap[@chars[i+1]]
76
+ @chars[i..i+1] = move_backslash_to_front( BackslashCharMap[@chars[i+1]] )
70
77
  elsif @chars[i+1] == 'b'
71
78
  @chars[i..i+1] = "\b"
72
79
  elsif @chars[i+1] == "\\"
@@ -83,6 +90,14 @@ module RegexpExamples
83
90
  GroupResult.new(result)
84
91
  end
85
92
  end
93
+
94
+ private
95
+ def move_backslash_to_front(chars)
96
+ if index = chars.index { |char| char == '\\' }
97
+ chars.unshift chars.delete_at(index)
98
+ end
99
+ chars
100
+ end
86
101
  end
87
102
 
88
103
  class DotGroup
@@ -120,13 +135,10 @@ module RegexpExamples
120
135
  @right_repeaters = right_repeaters
121
136
  end
122
137
 
138
+
123
139
  def result
124
- left_result = @left_repeaters.map do |repeater|
125
- RegexpExamples::permutations_of_strings([repeater.result])
126
- end
127
- right_result = @right_repeaters.map do |repeater|
128
- RegexpExamples::permutations_of_strings([repeater.result])
129
- end
140
+ left_result = RegexpExamples::map_results(@left_repeaters)
141
+ right_result = RegexpExamples::map_results(@right_repeaters)
130
142
  left_result.concat(right_result).flatten.uniq.map do |result|
131
143
  GroupResult.new(result)
132
144
  end
@@ -5,12 +5,12 @@ module RegexpExamples
5
5
  # element from each array
6
6
  #
7
7
  # For example:
8
- # permutations_of_strings [ ['a'], ['b'], ['c', 'd', 'e'] ] #=> ['acb', 'abd', 'abe']
8
+ # permutations_of_strings [ ['a'], ['b'], ['c', 'd', 'e'] ] #=> ['abc', 'abd', 'abe']
9
9
  # permutations_of_strings [ ['a', 'b'], ['c', 'd'] ] #=> [ 'ac', 'ad', 'bc', 'bd' ]
10
- def self.permutations_of_strings(arrays_of_strings, options={})
10
+ def self.permutations_of_strings(arrays_of_strings)
11
11
  first = arrays_of_strings.shift
12
12
  return first if arrays_of_strings.empty?
13
- first.product( permutations_of_strings(arrays_of_strings, options) ).map do |result|
13
+ first.product( permutations_of_strings(arrays_of_strings) ).map do |result|
14
14
  join_preserving_capture_groups(result)
15
15
  end
16
16
  end
@@ -22,5 +22,13 @@ module RegexpExamples
22
22
  .flatten
23
23
  GroupResult.new(result.join, nil, subgroups)
24
24
  end
25
+
26
+ def self.map_results(repeaters)
27
+ repeaters
28
+ .map {|repeater| repeater.result}
29
+ .instance_eval do |partial_results|
30
+ RegexpExamples::permutations_of_strings(partial_results)
31
+ end
32
+ end
25
33
  end
26
34
 
@@ -12,7 +12,9 @@ module RegexpExamples
12
12
  while @current_position < regexp_string.length
13
13
  group = parse_group(repeaters)
14
14
  break if group.is_a? MultiGroupEnd
15
- repeaters = [] if group.is_a? OrGroup
15
+ if group.is_a? OrGroup
16
+ return [OneTimeRepeater.new(group)]
17
+ end
16
18
  @current_position += 1
17
19
  repeaters << parse_repeater(group)
18
20
  end
@@ -65,7 +67,10 @@ module RegexpExamples
65
67
  group = parse_backreference_group($1)
66
68
  when BackslashCharMap.keys.include?(regexp_string[@current_position])
67
69
  group = CharGroup.new(
68
- BackslashCharMap[regexp_string[@current_position]])
70
+ # Note: The `.dup` is important, as it prevents modifying the constant, in
71
+ # CharGroup#init_ranges (where the '-' is moved to the front)
72
+ BackslashCharMap[regexp_string[@current_position]].dup
73
+ )
69
74
  when rest_of_string =~ /\A(c|C-)(.)/ # Control character
70
75
  @current_position += $1.length
71
76
  group = parse_single_char_group( parse_control_character($2) )
@@ -98,7 +103,6 @@ module RegexpExamples
98
103
  end
99
104
  else
100
105
  group = parse_single_char_group( regexp_string[@current_position] )
101
- # TODO: What about cases like \A, \z, \Z ?
102
106
  end
103
107
  group
104
108
  end
@@ -1,11 +1,9 @@
1
1
  class Regexp
2
2
  module Examples
3
3
  def examples
4
- partial_examples =
5
- RegexpExamples::Parser.new(source)
6
- .parse
7
- .map {|repeater| repeater.result}
8
- full_examples = RegexpExamples::permutations_of_strings(partial_examples)
4
+ full_examples = RegexpExamples::map_results(
5
+ RegexpExamples::Parser.new(source).parse
6
+ )
9
7
  RegexpExamples::BackReferenceReplacer.new.substitute_backreferences(full_examples)
10
8
  end
11
9
  end
@@ -33,7 +33,7 @@ module RegexpExamples
33
33
  end
34
34
 
35
35
  def result
36
- super(0, TIMES)
36
+ super(0, MaxRepeaterVariance)
37
37
  end
38
38
  end
39
39
 
@@ -43,7 +43,7 @@ module RegexpExamples
43
43
  end
44
44
 
45
45
  def result
46
- super(1, TIMES)
46
+ super(1, MaxRepeaterVariance + 1)
47
47
  end
48
48
  end
49
49
 
@@ -62,17 +62,23 @@ module RegexpExamples
62
62
  super(group)
63
63
  @min = min || 0
64
64
  if max
65
- @max = max
65
+ # Prevent huge number of results in case of e.g. /.{1,100}/.examples
66
+ @max = smallest(max, @min + MaxRepeaterVariance)
66
67
  elsif has_comma
67
- @max = min + TIMES
68
+ @max = @min + MaxRepeaterVariance
68
69
  else
69
- @max = min
70
+ @max = @min
70
71
  end
71
72
  end
72
73
 
73
74
  def result
74
75
  super(@min, @max)
75
76
  end
77
+
78
+ private
79
+ def smallest(x, y)
80
+ (x < y) ? x : y
81
+ end
76
82
  end
77
83
  end
78
84
 
@@ -1,3 +1,3 @@
1
1
  module RegexpExamples
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
@@ -29,6 +29,14 @@ RSpec.describe Regexp, "#examples" do
29
29
  end
30
30
  end
31
31
 
32
+ def self.examples_are_empty(*regexps)
33
+ regexps.each do |regexp|
34
+ it do
35
+ expect(regexp.examples).to be_empty
36
+ end
37
+ end
38
+ end
39
+
32
40
  context 'returns matching strings' do
33
41
  context "for basic repeaters" do
34
42
  examples_exist_and_match(
@@ -54,6 +62,7 @@ RSpec.describe Regexp, "#examples" do
54
62
 
55
63
  context "for complex char groups (square brackets)" do
56
64
  examples_exist_and_match(
65
+
57
66
  /[abc]/,
58
67
  /[a-c]/,
59
68
  /[abc-e]/,
@@ -65,7 +74,8 @@ RSpec.describe Regexp, "#examples" do
65
74
  /[\\\]]/,
66
75
  /[\n-\r]/,
67
76
  /[\-]/,
68
- /[%-+]/ # This regex is "supposed to" match some surprising things!!!
77
+ /[%-+]/, # This regex is "supposed to" match some surprising things!!!
78
+ /['-.]/ # Test to ensure no "infinite loop" on character set expansion
69
79
  )
70
80
  end
71
81
 
@@ -118,7 +128,8 @@ RSpec.describe Regexp, "#examples" do
118
128
  /((a?b*c+)) \1/,
119
129
  /((a?b*c+)?) \1/,
120
130
  /a|b|c|d/,
121
- /a+|b*|c?/
131
+ /a+|b*|c?/,
132
+ /one|two|three/
122
133
  )
123
134
  end
124
135
 
@@ -189,5 +200,20 @@ RSpec.describe Regexp, "#examples" do
189
200
  )
190
201
  end
191
202
 
203
+ context "for empty character sets" do
204
+ examples_are_empty(
205
+ /[^\d\D]/,
206
+ /[^\w\W]/,
207
+ /[^\s\S]/,
208
+ /[^\h\H]/,
209
+ /[^\D0-9]/,
210
+ /[^\Wa-zA-Z0-9_]/,
211
+ /[^\d\D]*/,
212
+ /[^\d\D]+/,
213
+ /[^\d\D]{2}/,
214
+ /[^\d\D]word/
215
+ )
216
+ end
217
+
192
218
  end
193
219
  end
@@ -33,7 +33,6 @@ RSpec.configure do |config|
33
33
  end
34
34
 
35
35
  config.disable_monkey_patching!
36
- config.warnings = true
37
36
 
38
37
  # Print the 10 slowest examples and example groups at the
39
38
  # end of the spec run, to help surface which specs are running
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: regexp-examples
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Lord
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-17 00:00:00.000000000 Z
11
+ date: 2015-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler