regexp-examples 0.3.1 → 0.3.2

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.
@@ -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