regexp-examples 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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