regexp-examples 1.3.2 → 1.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ce9ae460670e7c7525a38992990271751c311d4
4
- data.tar.gz: f9501da52ba2b57b6ef92324f43ba7252a8d70f8
3
+ metadata.gz: e7ea454116c6367dba39b57fb8ca5fdd6b1e5062
4
+ data.tar.gz: a41c56176c0bbf007e99780790f3e196d4605190
5
5
  SHA512:
6
- metadata.gz: 5fabfe8bf9dcb41d4e1a6ceba7b8c98278ea44f12ea657409821abd25fbf856b11effc726d67d06c0d2c67e728caf24853a89e2e73f26f1c5fba58ea397478e9
7
- data.tar.gz: 717755a835ca5f0a611c4a62ba4d8f4b90b7a422ce8d80136f79a97a71ee2f439b027c3fbd42f68ac79e65884940f0004b9f35a72197e8748cc6c7fef2680891
6
+ metadata.gz: 41f307047bbd5ca1539d6eda5d4035b91649187fdb015c449c2f56a64353c601911b1da465df0e891d01fed3344e70b3bc1efbfeaa8bfd61d371b0e1a0bf5c12
7
+ data.tar.gz: 2c126a5b52842b9c2b5b13fbf3243e09f6ae29d0d290561e050566bf6fa94401fa3d33a6d492bdec177ce1be9bacd49cee363ff0ce552b2986b1e0da22eea9ce
data/README.md CHANGED
@@ -10,9 +10,8 @@ Extends the `Regexp` class with the methods: `Regexp#examples` and `Regexp#rando
10
10
 
11
11
  `Regexp#random_example` returns one, random string (from all possible strings!!) that matches the regex.
12
12
 
13
- \* If the regex has an infinite number of possible srings that match it, such as `/a*b+c{2,}/`,
13
+ \* If the regex has an infinite number of possible strings that match it, such as `/a*b+c{2,}/`,
14
14
  or a huge number of possible matches, such as `/.\w/`, then only a subset of these will be listed.
15
-
16
15
  For more detail on this, see [configuration options](#configuration-options).
17
16
 
18
17
  If you'd like to understand how/why this gem works, please check out my [blog post](https://tom-lord.github.io/Reverse-Engineering-Regular-Expressions/) about it.
@@ -112,11 +111,11 @@ Long answer:
112
111
  * Non-capture groups, e.g. `/(?:foo)/`
113
112
  * Comment groups, e.g. `/foo(?#comment)bar/`
114
113
  * Control characters, e.g. `/\ca/`, `/\cZ/`, `/\C-9/`
115
- * Escape sequences, e.g. `/\x42/`, `/\x5word/`, `/#{"\x80".force\_encoding("ASCII-8BIT")}/`
114
+ * Escape sequences, e.g. `/\x42/`, `/\x5word/`, `/#{"\x80".force_encoding("ASCII-8BIT")}/`
116
115
  * Unicode characters, e.g. `/\u0123/`, `/\uabcd/`, `/\u{789}/`
117
116
  * Octal characters, e.g. `/\10/`, `/\177/`
118
117
  * Named properties, e.g. `/\p{L}/` ("Letter"), `/\p{Arabic}/` ("Arabic character")
119
- , `/\p{^Ll}/` ("Not a lowercase letter"), `/\P{^Canadian\_Aboriginal}/` ("Not not a Canadian aboriginal character")
118
+ , `/\p{^Ll}/` ("Not a lowercase letter"), `/\P{^Canadian_Aboriginal}/` ("Not not a Canadian aboriginal character")
120
119
  * ...Even between different ruby versions!! (e.g. `/\p{Arabic}/.examples(max_group_results: 999)` will give you a different answer in ruby v2.1.x and v2.2.x)
121
120
  * **Arbitrarily complex combinations of all the above!**
122
121
 
@@ -126,7 +125,7 @@ Long answer:
126
125
  * Extended form examples: `/line1 #comment \n line2/x.examples #=> ["line1line2"]`
127
126
  * Options toggling supported: `/before(?imx-imx)after/`, `/before(?imx-imx:subexpr)after/`
128
127
 
129
- ##Configuration Options
128
+ ## Configuration Options
130
129
 
131
130
  When generating examples, the gem uses 3 configurable values to limit how many examples are listed:
132
131
 
@@ -149,7 +148,9 @@ When generating examples, the gem uses 3 configurable values to limit how many e
149
148
 
150
149
  `Rexexp#examples` makes use of *all* these options; `Rexexp#random_example` only uses `max_repeater_variance`, since the other options are redundant.
151
150
 
152
- To use an alternative value, simply pass the configuration option as follows:
151
+ ### Defining custom configuration values
152
+
153
+ To use an alternative value, you can either pass the configuration option as a parameter:
153
154
 
154
155
  ```ruby
155
156
  /a*/.examples(max_repeater_variance: 5)
@@ -162,6 +163,24 @@ To use an alternative value, simply pass the configuration option as follows:
162
163
  #=> "A very unlikely result!"
163
164
  ```
164
165
 
166
+ Or, set an alternative value *within a block*:
167
+
168
+ ```ruby
169
+ RegexpExamples::Config.with_configuration(max_repeater_variance: 5) do
170
+ # ...
171
+ end
172
+ ```
173
+
174
+ Or, globally set a different default value:
175
+
176
+ ```ruby
177
+ # e.g In a rails project, you may wish to place this in
178
+ # config/initializers/regexp_examples.rb
179
+ RegexpExamples::Config.max_repeater_variance = 5
180
+ RegexpExamples::Config.max_group_results = 10
181
+ RegexpExamples::Config.max_results_limit = 20000
182
+ ```
183
+
165
184
  A sensible use case might be, for example, to generate all 1-5 digit strings:
166
185
 
167
186
  ```ruby
@@ -169,12 +188,16 @@ A sensible use case might be, for example, to generate all 1-5 digit strings:
169
188
  #=> ['0', '1', '2', ..., '99998', '99999']
170
189
  ```
171
190
 
191
+ ### Configuration Notes
192
+
172
193
  Due to code optimisation, `Regexp#random_example` runs pretty fast even on very complex patterns.
173
194
  (I.e. It's typically a _lot_ faster than using `/pattern/.examples.sample(1)`.)
174
195
  For instance, the following takes no more than ~ 1 second on my machine:
175
196
 
176
197
  `/.*\w+\d{100}/.random_example(max_repeater_variance: 1000)`
177
198
 
199
+ All forms of configuration mentioned above **are thread safe**.
200
+
178
201
  ## Bugs and TODOs
179
202
 
180
203
  There are no known major bugs with this library. However, there are a few obscure issues that you *may* encounter:
@@ -5,19 +5,15 @@ module CoreExtensions
5
5
  # No core classes are extended in any way, other than the above two methods.
6
6
  module Examples
7
7
  def examples(**config_options)
8
- RegexpExamples::ResultCountLimiters.configure!(
9
- max_repeater_variance: config_options[:max_repeater_variance],
10
- max_group_results: config_options[:max_group_results],
11
- max_results_limit: config_options[:max_results_limit]
12
- )
13
- examples_by_method(:result)
8
+ RegexpExamples::Config.with_configuration(config_options) do
9
+ examples_by_method(:result)
10
+ end
14
11
  end
15
12
 
16
13
  def random_example(**config_options)
17
- RegexpExamples::ResultCountLimiters.configure!(
18
- max_repeater_variance: config_options[:max_repeater_variance]
19
- )
20
- examples_by_method(:random_result).sample(1).first
14
+ RegexpExamples::Config.with_configuration(config_options) do
15
+ examples_by_method(:random_result).sample
16
+ end
21
17
  end
22
18
 
23
19
  private
@@ -1,9 +1,47 @@
1
1
  # :nodoc:
2
2
  module RegexpExamples
3
3
  # Configuration settings to limit the number/length of Regexp examples generated
4
- class ResultCountLimiters
4
+ class Config
5
+ class << self
6
+ def with_configuration(**new_config)
7
+ original_config = config.dup
8
+
9
+ begin
10
+ self.config = new_config
11
+ result = yield
12
+ ensure
13
+ self.config = original_config
14
+ end
15
+
16
+ result
17
+ end
18
+
19
+ # Thread-safe getters and setters
20
+ %i[max_repeater_variance max_group_results max_results_limit].each do |m|
21
+ define_method(m) do
22
+ config[m]
23
+ end
24
+ define_method("#{m}=") do |value|
25
+ config[m] = value
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def config=(**args)
32
+ Thread.current[:regexp_examples_config].merge!(args)
33
+ end
34
+
35
+ def config
36
+ Thread.current[:regexp_examples_config] ||= {
37
+ max_repeater_variance: MAX_REPEATER_VARIANCE_DEFAULT,
38
+ max_group_results: MAX_GROUP_RESULTS_DEFAULT,
39
+ max_results_limit: MAX_RESULTS_LIMIT_DEFAULT
40
+ }
41
+ end
42
+ end
5
43
  # The maximum variance for any given repeater, to prevent a huge/infinite number of
6
- # examples from being listed. For example, if @@max_repeater_variance = 2 then:
44
+ # examples from being listed. For example, if self.max_repeater_variance = 2 then:
7
45
  # .* is equivalent to .{0,2}
8
46
  # .+ is equivalent to .{1,3}
9
47
  # .{2,} is equivalent to .{2,4}
@@ -12,7 +50,7 @@ module RegexpExamples
12
50
  MAX_REPEATER_VARIANCE_DEFAULT = 2
13
51
 
14
52
  # Maximum number of characters returned from a char set, to reduce output spam
15
- # For example, if @@max_group_results = 5 then:
53
+ # For example, if self.max_group_results = 5 then:
16
54
  # \d is equivalent to [01234]
17
55
  # \w is equivalent to [abcde]
18
56
  MAX_GROUP_RESULTS_DEFAULT = 5
@@ -22,28 +60,6 @@ module RegexpExamples
22
60
  # /[ab]{30}/.examples
23
61
  # (Which would attempt to generate 2**30 == 1073741824 examples!!!)
24
62
  MAX_RESULTS_LIMIT_DEFAULT = 10_000
25
- class << self
26
- attr_reader :max_repeater_variance, :max_group_results, :max_results_limit
27
- def configure!(max_repeater_variance: nil,
28
- max_group_results: nil,
29
- max_results_limit: nil)
30
- @max_repeater_variance = (max_repeater_variance || MAX_REPEATER_VARIANCE_DEFAULT)
31
- @max_group_results = (max_group_results || MAX_GROUP_RESULTS_DEFAULT)
32
- @max_results_limit = (max_results_limit || MAX_RESULTS_LIMIT_DEFAULT)
33
- end
34
- end
35
- end
36
-
37
- def self.max_repeater_variance
38
- ResultCountLimiters.max_repeater_variance
39
- end
40
-
41
- def self.max_group_results
42
- ResultCountLimiters.max_group_results
43
- end
44
-
45
- def self.max_results_limit
46
- ResultCountLimiters.max_results_limit
47
63
  end
48
64
 
49
65
  # Definitions of various special characters, used in regular expressions.
@@ -10,10 +10,13 @@ module RegexpExamples
10
10
  # Edge case:
11
11
  # permutations_of_strings [ [] ] #=> nil
12
12
  # (For example, ths occurs during /[^\d\D]/.examples #=> [])
13
- def self.permutations_of_strings(arrays_of_strings, max_results_limiter = MaxResultsLimiterByProduct.new)
13
+ def self.permutations_of_strings(arrays_of_strings,
14
+ max_results_limiter = MaxResultsLimiterByProduct.new)
14
15
  partial_result = max_results_limiter.limit_results(arrays_of_strings.shift)
15
16
  return partial_result if arrays_of_strings.empty?
16
- partial_result.product(permutations_of_strings(arrays_of_strings, max_results_limiter)).map do |result|
17
+ partial_result.product(
18
+ permutations_of_strings(arrays_of_strings, max_results_limiter)
19
+ ).map do |result|
17
20
  join_preserving_capture_groups(result)
18
21
  end
19
22
  end
@@ -1,5 +1,6 @@
1
1
  module RegexpExamples
2
- class MaxResultsLimiter # Base class
2
+ # Abstract (base) class to assist limiting Regexp.examples max results
3
+ class MaxResultsLimiter
3
4
  def initialize(initial_results_count)
4
5
  @results_count = initial_results_count
5
6
  end
@@ -25,7 +26,8 @@ module RegexpExamples
25
26
 
26
27
  def results_allowed_from(partial_results, limiter_method)
27
28
  partial_results.first(
28
- RegexpExamples.max_results_limit.public_send(limiter_method, @results_count)
29
+ RegexpExamples::Config.max_results_limit
30
+ .public_send(limiter_method, @results_count)
29
31
  )
30
32
  end
31
33
  end
@@ -10,7 +10,7 @@ module RegexpExamples
10
10
  end
11
11
 
12
12
  def result
13
- group_results = group.result.first(RegexpExamples.max_group_results)
13
+ group_results = group.result.first(RegexpExamples::Config.max_group_results)
14
14
  results = []
15
15
  max_results_limiter = MaxResultsLimiterBySum.new
16
16
  min_repeats.upto(max_repeats) do |repeats|
@@ -51,7 +51,7 @@ module RegexpExamples
51
51
  def initialize(group)
52
52
  super
53
53
  @min_repeats = 0
54
- @max_repeats = RegexpExamples.max_repeater_variance
54
+ @max_repeats = RegexpExamples::Config.max_repeater_variance
55
55
  end
56
56
  end
57
57
 
@@ -61,7 +61,7 @@ module RegexpExamples
61
61
  def initialize(group)
62
62
  super
63
63
  @min_repeats = 1
64
- @max_repeats = RegexpExamples.max_repeater_variance + 1
64
+ @max_repeats = RegexpExamples::Config.max_repeater_variance + 1
65
65
  end
66
66
  end
67
67
 
@@ -80,19 +80,14 @@ module RegexpExamples
80
80
  def initialize(group, min, has_comma, max)
81
81
  super(group)
82
82
  @min_repeats = min || 0
83
- if max # e.g. {1,100} --> Treat as {1,3} (by default max_repeater_variance)
84
- @max_repeats = smallest(max, @min_repeats + RegexpExamples.max_repeater_variance)
85
- elsif has_comma # e.g. {2,} --> Treat as {2,4} (by default max_repeater_variance)
86
- @max_repeats = @min_repeats + RegexpExamples.max_repeater_variance
87
- else # e.g. {3} --> Treat as {3,3}
88
- @max_repeats = @min_repeats
89
- end
90
- end
91
-
92
- private
93
-
94
- def smallest(x, y)
95
- x < y ? x : y
83
+ @max_repeats = if !has_comma
84
+ @min_repeats
85
+ else
86
+ [
87
+ max,
88
+ @min_repeats + RegexpExamples::Config.max_repeater_variance
89
+ ].compact.min
90
+ end
96
91
  end
97
92
  end
98
93
  end
@@ -1,4 +1,4 @@
1
1
  # Gem version
2
2
  module RegexpExamples
3
- VERSION = '1.3.2'.freeze
3
+ VERSION = '1.4.0'.freeze
4
4
  end
@@ -0,0 +1,135 @@
1
+ RSpec.describe RegexpExamples::Config do
2
+
3
+ describe 'max_repeater_variance' do
4
+ context 'as a passed parameter' do
5
+ it 'with low limit' do
6
+ expect(/[A-Z]/.examples(max_results_limit: 5))
7
+ .to match_array %w(A B C D E)
8
+ end
9
+ it 'with (default) high limit' do
10
+ expect(/[ab]{14}/.examples.length)
11
+ .to be <= 10000 # NOT 2**14 == 16384, because it's been limited
12
+ end
13
+ it 'with (custom) high limit' do
14
+ expect(/[ab]{14}/.examples(max_results_limit: 20000).length)
15
+ .to eq 16384 # NOT 10000, because it's below the limit
16
+ end
17
+ it 'for boolean or groups' do
18
+ expect(/[ab]{3}|[cd]{3}/.examples(max_results_limit: 10).length)
19
+ .to eq 10
20
+ end
21
+ it 'for case insensitive examples' do
22
+ expect(/[ab]{3}/i.examples(max_results_limit: 10).length)
23
+ .to be <= 10
24
+ end
25
+ it 'for range repeaters' do
26
+ expect(/[ab]{2,3}/.examples(max_results_limit: 10).length)
27
+ .to be <= 10 # NOT 4 + 8 = 12
28
+ end
29
+ it 'for backreferences' do
30
+ expect(/([ab]{3})\1?/.examples(max_results_limit: 10).length)
31
+ .to be <= 10 # NOT 8 * 2 = 16
32
+ end
33
+ it 'for a complex pattern' do
34
+ expect(/(a|[bc]{2})\1{1,3}/.examples(max_results_limit: 14).length)
35
+ .to be <= 14 # NOT (1 + 4) * 3 = 15
36
+ end
37
+ end
38
+
39
+ context 'as a global setting' do
40
+ before do
41
+ @original = RegexpExamples::Config.max_results_limit
42
+ RegexpExamples::Config.max_results_limit = 5
43
+ end
44
+ after do
45
+ RegexpExamples::Config.max_results_limit = @original
46
+ end
47
+
48
+ it 'sets limit without passing explicitly' do
49
+ expect(/[A-Z]/.examples)
50
+ .to match_array %w(A B C D E)
51
+ end
52
+ end
53
+ end # describe 'max_results_limit'
54
+
55
+ describe 'max_repeater_variance' do
56
+ context 'as a passed parameter' do
57
+ it 'with a larger value' do
58
+ expect(/a+/.examples(max_repeater_variance: 5))
59
+ .to match_array %w(a aa aaa aaaa aaaaa aaaaaa)
60
+ end
61
+ it 'with a lower value' do
62
+ expect(/a{4,8}/.examples(max_repeater_variance: 0))
63
+ .to eq %w(aaaa)
64
+ end
65
+ end
66
+
67
+ context 'as a global setting' do
68
+ before do
69
+ @original = RegexpExamples::Config.max_repeater_variance
70
+ RegexpExamples::Config.max_repeater_variance = 5
71
+ end
72
+ after do
73
+ RegexpExamples::Config.max_repeater_variance = @original
74
+ end
75
+
76
+ it 'sets limit without passing explicitly' do
77
+ expect(/a+/.examples)
78
+ .to match_array %w(a aa aaa aaaa aaaaa aaaaaa)
79
+ end
80
+ end
81
+ end # describe 'max_repeater_variance'
82
+
83
+ describe 'max_group_results' do
84
+ context 'as a passed parameter' do
85
+ it 'with a larger value' do
86
+ expect(/\d/.examples(max_group_results: 10))
87
+ .to match_array %w(0 1 2 3 4 5 6 7 8 9)
88
+ end
89
+ it 'with a lower value' do
90
+ expect(/\d/.examples(max_group_results: 3))
91
+ .to match_array %w(0 1 2)
92
+ end
93
+ end
94
+
95
+ context 'as a global setting' do
96
+ before do
97
+ @original = RegexpExamples::Config.max_group_results
98
+ RegexpExamples::Config.max_group_results = 10
99
+ end
100
+ after do
101
+ RegexpExamples::Config.max_group_results = @original
102
+ end
103
+
104
+ it 'sets limit without passing explicitly' do
105
+ expect(/\d/.examples)
106
+ .to match_array %w(0 1 2 3 4 5 6 7 8 9)
107
+ end
108
+ end
109
+ end # describe 'max_group_results'
110
+
111
+ describe 'thread safety' do
112
+ it 'uses thread-local global config values' do
113
+ thread = Thread.new do
114
+ RegexpExamples::Config.max_group_results = 1
115
+ expect(/\d/.examples).to eq %w(0)
116
+ end
117
+ sleep 0.1 # Give the above thread time to run
118
+ expect(/\d/.examples).to eq %w(0 1 2 3 4)
119
+ thread.join
120
+ end
121
+
122
+ it 'uses thread-local block config values' do
123
+ thread = Thread.new do
124
+ RegexpExamples::Config.with_configuration(max_group_results: 1) do
125
+ expect(/\d/.examples).to eq %w(0)
126
+ sleep 0.2 # Give the below thread time to run while this block is open
127
+ end
128
+ end
129
+ sleep 0.1 # Give the above thread time to run
130
+ expect(/\d/.examples).to eq %w(0 1 2 3 4)
131
+ thread.join
132
+ end
133
+ end # describe 'thread safety'
134
+
135
+ end
@@ -316,24 +316,6 @@ RSpec.describe Regexp, '#examples' do
316
316
  end
317
317
  end
318
318
 
319
- context 'max_repeater_variance config option' do
320
- it do
321
- expect(/a+/.examples(max_repeater_variance: 5))
322
- .to match_array %w(a aa aaa aaaa aaaaa aaaaaa)
323
- end
324
- it do
325
- expect(/a{4,8}/.examples(max_repeater_variance: 0))
326
- .to eq %w(aaaa)
327
- end
328
- end
329
-
330
- context 'max_group_results config option' do
331
- it do
332
- expect(/\d/.examples(max_group_results: 10))
333
- .to match_array %w(0 1 2 3 4 5 6 7 8 9)
334
- end
335
- end
336
-
337
319
  context 'case insensitive' do
338
320
  it { expect(/ab/i.examples).to match_array %w(ab aB Ab AB) }
339
321
  it do
@@ -377,39 +359,4 @@ RSpec.describe Regexp, '#examples' do
377
359
  end
378
360
  end # context 'exact examples match'
379
361
  end # context 'returns matching strings'
380
-
381
- context 'max_results_limit config option' do
382
- it 'with low limit' do
383
- expect(/[A-Z]/.examples(max_results_limit: 5))
384
- .to match_array %w(A B C D E)
385
- end
386
- it 'with (default) high limit' do
387
- expect(/[ab]{14}/.examples.length)
388
- .to be <= 10000 # NOT 2**14 == 16384, because it's been limited
389
- end
390
- it 'with (custom) high limit' do
391
- expect(/[ab]{14}/.examples(max_results_limit: 20000).length)
392
- .to eq 16384 # NOT 10000, because it's below the limit
393
- end
394
- it 'for boolean or groups' do
395
- expect(/[ab]{3}|[cd]{3}/.examples(max_results_limit: 10).length)
396
- .to eq 10
397
- end
398
- it 'for case insensitive examples' do
399
- expect(/[ab]{3}/i.examples(max_results_limit: 10).length)
400
- .to be <= 10
401
- end
402
- it 'for range repeaters' do
403
- expect(/[ab]{2,3}/.examples(max_results_limit: 10).length)
404
- .to be <= 10 # NOT 4 + 8 = 12
405
- end
406
- it 'for backreferences' do
407
- expect(/([ab]{3})\1?/.examples(max_results_limit: 10).length)
408
- .to be <= 10 # NOT 8 * 2 = 16
409
- end
410
- it 'for a complex pattern' do
411
- expect(/(a|[bc]{2})\1{1,3}/.examples(max_results_limit: 14).length)
412
- .to be <= 14 # NOT (1 + 4) * 3 = 15
413
- end
414
- end
415
362
  end
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: 1.3.2
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Lord
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-06 00:00:00.000000000 Z
11
+ date: 2017-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -76,6 +76,7 @@ files:
76
76
  - lib/regexp-examples/version.rb
77
77
  - regexp-examples.gemspec
78
78
  - scripts/unicode_lister.rb
79
+ - spec/config_spec.rb
79
80
  - spec/helpers.rb
80
81
  - spec/regexp-examples_spec.rb
81
82
  - spec/regexp-random_example_spec.rb
@@ -105,6 +106,7 @@ signing_key:
105
106
  specification_version: 4
106
107
  summary: Extends the Regexp class with '#examples' and '#random_example'
107
108
  test_files:
109
+ - spec/config_spec.rb
108
110
  - spec/helpers.rb
109
111
  - spec/regexp-examples_spec.rb
110
112
  - spec/regexp-random_example_spec.rb