nice_hash 1.19.0 → 1.20.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
  SHA256:
3
- metadata.gz: 7eae7fae66c4ee14c40481157a8cd6e90ec43781b092779a3344c36a7d0d6308
4
- data.tar.gz: f3071e0977d3e7e0f0e7ced9699f4823535c1daaa880cbfd7d5693d8329e9513
3
+ metadata.gz: 718327558f69c77e13b7d19355f990e171eaf199676e8a42ced4bda0c1063827
4
+ data.tar.gz: d3a086c1262af567acc060fbe6cfb18a6bfbd134b4a1df807e9ee52666f0d4d4
5
5
  SHA512:
6
- metadata.gz: 72ced814e688ca65212c8812969947ce77f0496c2e7dbc12e2ab9ef9c396db77da473e5c079788206c6fcc24e05d92b1186c85bfe986c634e75e3b7894208b5a
7
- data.tar.gz: bce43cdcaa33a6e50a06a364737e3a723156e052b34ba3423574dc4f296743f47c7b089723f72ba2ad617ece70f3ec129617dcf500fc6c5d5cddd8cf8f7bba27
6
+ metadata.gz: a29e7907b0feca5db0253faa574a1fba185e37ea654881b999ef3f7ea48da52c42294963ad40f19fee657f121dde0ac11f8e41881d1115a7ad89b505d19afc24
7
+ data.tar.gz: b1eaa9bd0c1f6baf034aaf524222bb584265495c4bb8bf014ceeb5f6404bbe6364db7ce8cf786c4cece3daf670aa2751ca65eea6986c4145930cf5091d065ae3
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # NiceHash
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/nice_hash.svg)](https://rubygems.org/gems/nice_hash)
4
- [![Build Status](https://travis-ci.com/MarioRuiz/nice_hash.svg?branch=master)](https://github.com/MarioRuiz/nice_hash)
4
+ [![CI](https://github.com/MarioRuiz/nice_hash/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/MarioRuiz/nice_hash/actions/workflows/ci.yml)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/MarioRuiz/nice_hash/badge.svg?branch=master)](https://coveralls.io/github/MarioRuiz/nice_hash?branch=master)
6
6
  ![Gem](https://img.shields.io/gem/dt/nice_hash)
7
7
  ![GitHub commit activity](https://img.shields.io/github/commit-activity/y/MarioRuiz/nice_hash)
@@ -79,6 +79,12 @@ Set these constants **before** `require 'nice_hash'` if you need to change defau
79
79
 
80
80
  When using **string_pattern** 2.4+, you can also set **`StringPattern.logger`** (e.g. `Logger.new($stderr)`) to redirect generation/validation messages, and **`StringPattern.raise_on_error = true`** to raise on invalid patterns or impossible generation instead of returning empty strings.
81
81
 
82
+ **Upgrading to 1.20** (requires string_pattern 2.5):
83
+
84
+ - Update assertions that pin a value produced with `seed:` in 1.19. The same seed now walks one sequence for patterns, ranges, booleans and selectors, so those strings change. Do not expect `:uuid`, `:iban` or `:luhn` to repeat from `seed:`.
85
+ - If `StringPattern.block_list` contains regular expressions, set `StringPattern.block_list_regexp = true`. Otherwise each entry is a literal.
86
+ - A `Regexp` pattern must match the whole value. Update fixtures that used to pass because `$` matched before a newline.
87
+
82
88
  Example:
83
89
 
84
90
  ```ruby
@@ -90,13 +96,18 @@ require 'nice_hash'
90
96
 
91
97
  Remember!! To generate the strings following a pattern take a look at the documentation for string_pattern gem: https://github.com/MarioRuiz/string_pattern. You can also generate Spanish or English words. We added support for generating strings from regular expressions but it is only working for the ´generate´ method, use it with caution since it is still on an early stage of development.
92
98
 
93
- For **UUID v4** you can use the shorthand `:uuid` (requires string_pattern 2.4+):
99
+ For **UUID v4**, **email**, **Spanish IBAN** and a **16-digit Luhn** number you can use these shorthands (string_pattern 2.4+ for `:uuid`, 2.5+ for the others):
94
100
 
95
101
  ```ruby
96
- my_hash = { id: :uuid, key: "Wsdf88888", doomId: :"10:N" }
97
- # id will be like "550e8400-e29b-41d4-a716-446655440000"
102
+ my_hash = { id: :uuid, email: :email, iban: :iban, card: :luhn, doomId: :"10:N" }
103
+ # id: "550e8400-e29b-41d4-a716-446655440000"
104
+ # email: a generated address
105
+ # iban: a Spanish IBAN (country is always ES)
106
+ # card: 16 digits with a valid Luhn checksum
98
107
  ```
99
108
 
109
+ `:uuid`, `:iban` and `:luhn` use their own random generator. `seed:` does not make those three fields repeat.
110
+
100
111
  Or use a Regular expression for custom formats:
101
112
 
102
113
  ```ruby
@@ -403,7 +414,7 @@ new_hash = my_hash.select_key(:correct).generate
403
414
  ```
404
415
 
405
416
  **Reproducible generation (seed)**
406
- With **string_pattern** 2.4+, pass `seed:` to get the same hash every time (useful for tests):
417
+ Pass `seed:` to get the same hash every time, including string patterns, ranges, booleans and selectors. One seed advances through the whole hash, so two fields with the same pattern get different values. `:uuid`, `:iban` and `:luhn` stay outside that sequence.
407
418
 
408
419
  ```ruby
409
420
  new_hash = my_hash.generate(:correct, seed: 42)
@@ -417,7 +428,7 @@ Use `generate_n` (alias `gen_n`) to build several different hashes in one call (
417
428
  hashes = NiceHash.generate_n(my_hash, 5, :correct)
418
429
  hashes = my_hash.generate_n(5, :correct) # or my_hash.gen_n(5, :correct)
419
430
  # With seed: hashes = my_hash.generate_n(5, :correct, seed: 123)
420
- # => array of 5 hashes
431
+ # the same seed yields the same 5 hashes, and they differ from each other
421
432
  ```
422
433
 
423
434
  In case of filtering by :correct new_hash would have a value like this for example:
@@ -887,6 +898,9 @@ Valid patterns:
887
898
  - DateTime: it will verify if the value is following Time stamp string '2019-06-20T12:01:09.971Z' or if the object is a Time, Date or DateTime class
888
899
  - selectors, one of the values. Example: "uno|dos|tres"
889
900
  - **:uuid** (string_pattern 2.4+): UUID v4 format
901
+ - **:email** (string_pattern 2.5+): email address
902
+ - **:iban** (string_pattern 2.5+): IBAN, generated as Spanish (`ES`)
903
+ - **:luhn** (string_pattern 2.5+): 16 digits with a valid Luhn checksum
890
904
 
891
905
  ### Other useful methods
892
906
 
@@ -51,14 +51,26 @@ class NiceHash
51
51
  # new_hash = my_hash.gen(:correct)
52
52
  # new_hash = my_hash.generate(:correct, errors: [:min_length])
53
53
  ###########################################################################
54
+ # One RNG for the whole hash. string_pattern 2.5 no longer seeds Kernel#rand,
55
+ # so passing seed: on every StringPattern.generate call would restart the sequence.
56
+ def self.generation_rng(synonyms)
57
+ rng = synonyms.delete(:_rng)
58
+ return rng unless rng.nil?
59
+
60
+ seed = synonyms.delete(:seed)
61
+ base = seed.nil? ? Random.new : Random.new(seed)
62
+ StringPattern::RandomSource.new(base)
63
+ end
64
+
54
65
  def NiceHash.generate(pattern_hash, select_hash_key = nil, expected_errors: [], **synonyms)
55
66
  hashv = Hash.new()
56
67
  same_values = Hash.new()
57
- expected_errors = synonyms[:errors] if synonyms.keys.include?(:errors)
68
+ expected_errors = synonyms.delete(:errors) if synonyms.key?(:errors)
58
69
  if expected_errors.kind_of?(Symbol)
59
70
  expected_errors = [expected_errors]
60
71
  end
61
- sp_opts = { expected_errors: expected_errors }.merge(synonyms)
72
+ rng = generation_rng(synonyms)
73
+ sp_opts = { expected_errors: expected_errors, _rng: rng }.merge(synonyms)
62
74
 
63
75
  if pattern_hash.kind_of?(Hash) and pattern_hash.size > 0
64
76
  pattern_hash.each { |key, value|
@@ -71,16 +83,25 @@ class NiceHash
71
83
  if value.keys.include?(select_hash_key)
72
84
  value = value[select_hash_key]
73
85
  else
74
- value = NiceHash.generate(value, select_hash_key, expected_errors: expected_errors, **synonyms)
86
+ value = NiceHash.generate(value, select_hash_key, expected_errors: expected_errors, _rng: rng, **synonyms)
75
87
  end
76
88
  end
77
89
  if value == :uuid
90
+ # SecureRandom is not seeded. UUID values are not reproducible via seed:.
78
91
  hashv[key] = expected_errors.empty? ? StringPattern.uuid : (StringPattern.uuid[0..-2] + "X")
92
+ elsif value == :email
93
+ hashv[key] = StringPattern.generate("20-40:@", **sp_opts)
94
+ elsif value == :iban
95
+ # StringPattern.iban uses its own Random, so it is not reproducible via seed:.
96
+ hashv[key] = expected_errors.empty? ? StringPattern.iban(country: "ES") : "ES00NOTANIBAN"
97
+ elsif value == :luhn
98
+ # StringPattern.luhn uses its own Random, so it is not reproducible via seed:.
99
+ hashv[key] = expected_errors.empty? ? StringPattern.luhn(length: 16) : "1234567890123456"
79
100
  elsif value.kind_of?(String) or value.kind_of?(Symbol)
80
101
  if ((StringPattern.optimistic and value.kind_of?(String)) or value.kind_of?(Symbol)) and value.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
81
102
  hashv[key] = StringPattern.generate(value, **sp_opts)
82
103
  elsif ((StringPattern.optimistic and value.kind_of?(String)) or value.kind_of?(Symbol)) and value.to_s.scan(/^([\w\s\-]+\|)+[\w\s\-]+$/).size > 0
83
- if expected_errors.include?(:min_length) or (expected_errors.include?(:length) and rand.round == 0)
104
+ if expected_errors.include?(:min_length) or (expected_errors.include?(:length) and rng.rand.round == 0)
84
105
  min = value.to_s.split("|").min { |a, b| a.size <=> b.size }
85
106
  hashv[key] = min[0..-2] unless min == ""
86
107
  end
@@ -94,7 +115,7 @@ class NiceHash
94
115
  if hashv.keys.include?(key)
95
116
  v = hashv[key]
96
117
  else
97
- v = value.to_s.split("|").sample
118
+ v = value.to_s.split("|").sample(random: rng)
98
119
  end
99
120
  unless expected_errors.include?(:string_set_not_allowed)
100
121
  v = StringPattern.generate(:"#{v.size}:[#{value.to_s.split("|").join.split(//).uniq.join}]", **sp_opts)
@@ -102,13 +123,13 @@ class NiceHash
102
123
  end
103
124
  unless hashv.keys.include?(key)
104
125
  # Need a valid 1-char not in the allowed set (do not pass expected_errors)
105
- one_wrong_letter = StringPattern.generate(:"1:LN$[%#{value.to_s.split("|").join.split(//).uniq.join}%]")
106
- v[rand(v.size)] = one_wrong_letter
126
+ one_wrong_letter = StringPattern.generate(:"1:LN$[%#{value.to_s.split("|").join.split(//).uniq.join}%]", _rng: rng)
127
+ v[rng.rand(v.size)] = one_wrong_letter
107
128
  hashv[key] = v unless value.to_s.split("|").include?(v)
108
129
  end
109
130
  end
110
131
  unless hashv.keys.include?(key)
111
- hashv[key] = value.to_s.split("|").sample
132
+ hashv[key] = value.to_s.split("|").sample(random: rng)
112
133
  end
113
134
  else
114
135
  hashv[key] = value
@@ -119,7 +140,7 @@ class NiceHash
119
140
  v = value[0]
120
141
  if (v.kind_of?(String) or v.kind_of?(Symbol)) and StringPattern.analyze(v, silent: true).kind_of?(StringPattern::Pattern)
121
142
  hashv[key] = []
122
- (rand(5)+1).times do
143
+ (rng.rand(5)+1).times do
123
144
  hashv[key]<<StringPattern.generate(v, **sp_opts)
124
145
  end
125
146
  array_pattern = true
@@ -137,9 +158,9 @@ class NiceHash
137
158
  value_ret = Array.new
138
159
  value.each { |v|
139
160
  if v.is_a?(Hash)
140
- ret = NiceHash.generate(v, select_hash_key, expected_errors: expected_errors, **synonyms)
161
+ ret = NiceHash.generate(v, select_hash_key, expected_errors: expected_errors, _rng: rng, **synonyms)
141
162
  else
142
- ret = NiceHash.generate({ doit: v }, select_hash_key, expected_errors: expected_errors, **synonyms)
163
+ ret = NiceHash.generate({ doit: v }, select_hash_key, expected_errors: expected_errors, _rng: rng, **synonyms)
143
164
  ret = ret[:doit] if ret.is_a?(Hash) and ret.key?(:doit)
144
165
  end
145
166
  ret = v if ret.kind_of?(Hash) and ret.size == 0
@@ -152,28 +173,28 @@ class NiceHash
152
173
  if value.size == Float::INFINITY
153
174
  value = (value.min..2**64)
154
175
  end
155
- hashv[key] = rand(value)
176
+ hashv[key] = rng.rand(value)
156
177
  else
157
178
  if value.size == Float::INFINITY
158
179
  infinite = true
159
180
  value = (value.min..2**64)
160
181
  else
161
182
  infinite = false
162
- hashv[key] = rand(value)
183
+ hashv[key] = rng.rand(value)
163
184
  end
164
185
  expected_errors.each do |er|
165
186
  if er == :min_length
166
- hashv[key] = rand((value.first - value.last)..value.first - 1)
187
+ hashv[key] = rng.rand((value.first - value.last)..value.first - 1)
167
188
  elsif er == :max_length and !infinite
168
- hashv[key] = rand((value.last + 1)..(value.last * 2))
189
+ hashv[key] = rng.rand((value.last + 1)..(value.last * 2))
169
190
  elsif er == :length
170
- if rand.round == 1 or infinite
171
- hashv[key] = rand((value.first - value.last)..value.first - 1)
191
+ if rng.rand.round == 1 or infinite
192
+ hashv[key] = rng.rand((value.first - value.last)..value.first - 1)
172
193
  else
173
- hashv[key] = rand((value.last + 1)..(value.last * 2))
194
+ hashv[key] = rng.rand((value.last + 1)..(value.last * 2))
174
195
  end
175
196
  elsif er == :value
176
- hashv[key] = :"1-10:N/L/".gen
197
+ hashv[key] = StringPattern.generate(:"1-10:N/L/", _rng: rng)
177
198
  end
178
199
  end
179
200
  end
@@ -188,24 +209,24 @@ class NiceHash
188
209
  elsif er == :max_length
189
210
  hashv[key] = hashv[key] + "Z"
190
211
  elsif er == :length
191
- if rand.round == 1
212
+ if rng.rand.round == 1
192
213
  hashv[key] = hashv[key].chop
193
214
  else
194
215
  hashv[key] = hashv[key] + "Z"
195
216
  end
196
217
  elsif er == :value
197
- hashv[key][rand(hashv[key].size - 1)] = "1:L".gen
218
+ hashv[key][rng.rand(hashv[key].size - 1)] = StringPattern.generate("1:L", _rng: rng)
198
219
  end
199
220
  end
200
221
  end
201
222
  elsif value.kind_of?(Module) and value == Boolean
202
223
  if expected_errors.empty?
203
- hashv[key] = (rand.round == 0)
224
+ hashv[key] = (rng.rand.round == 0)
204
225
  else
205
- hashv[key] = (rand.round == 0)
226
+ hashv[key] = (rng.rand.round == 0)
206
227
  expected_errors.each do |er|
207
228
  if er == :value
208
- hashv[key] = "1-10:L".gen
229
+ hashv[key] = StringPattern.generate("1-10:L", _rng: rng)
209
230
  end
210
231
  end
211
232
  end
@@ -243,6 +264,9 @@ class NiceHash
243
264
  # hashes = my_hash.generate_n(5, :correct)
244
265
  ###########################################################################
245
266
  def NiceHash.generate_n(pattern_hash, n, select_hash_key = nil, expected_errors: [], **synonyms)
246
- n.times.map { NiceHash.generate(pattern_hash, select_hash_key, expected_errors: expected_errors, **synonyms) }
267
+ rng = generation_rng(synonyms)
268
+ n.times.map { NiceHash.generate(pattern_hash, select_hash_key, expected_errors: expected_errors, _rng: rng, **synonyms) }
247
269
  end
270
+
271
+ private_class_method :generation_rng
248
272
  end
@@ -85,6 +85,12 @@ class NiceHash
85
85
  if values.keys.include?(key)
86
86
  if value == :uuid
87
87
  results[key] = false unless StringPattern.valid_uuid?(values[key].to_s)
88
+ elsif value == :email
89
+ results[key] = false unless StringPattern.valid_email?(values[key].to_s)
90
+ elsif value == :iban
91
+ results[key] = false unless StringPattern.valid_iban?(values[key].to_s)
92
+ elsif value == :luhn
93
+ results[key] = false unless StringPattern.valid_luhn?(values[key].to_s)
88
94
  elsif value.kind_of?(String) or value.kind_of?(Symbol)
89
95
  if ((StringPattern.optimistic and value.kind_of?(String)) or value.kind_of?(Symbol)) and value.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
90
96
  res = StringPattern.validate(pattern: value, text: values[key])
@@ -119,10 +125,8 @@ class NiceHash
119
125
  elsif value.kind_of?(Module) and value == Boolean
120
126
  results[key] = false unless values[key].is_a?(Boolean)
121
127
  elsif value.kind_of?(Regexp)
122
- rex = Regexp.new("^#{value}$")
123
- unless values[key].to_s.match?(rex)
124
- results[key] = false
125
- end
128
+ res = StringPattern.validate(text: values[key].to_s, pattern: value)
129
+ results[key] = false unless res == true || (res.is_a?(Array) && res.empty?)
126
130
  elsif value.kind_of?(Array)
127
131
  array_pattern = false
128
132
  complex_data = false
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nice_hash
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.19.0
4
+ version: 1.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Ruiz
@@ -15,20 +15,20 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '2.4'
18
+ version: '2.5'
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: 2.4.0
21
+ version: 2.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: '2.4'
28
+ version: '2.5'
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: 2.4.0
31
+ version: 2.5.0
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: rspec
34
34
  requirement: !ruby/object:Gem::Requirement