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 +4 -4
- data/README.md +20 -6
- data/lib/nice/hash/generate.rb +49 -25
- data/lib/nice/hash/validate.rb +8 -4
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 718327558f69c77e13b7d19355f990e171eaf199676e8a42ced4bda0c1063827
|
|
4
|
+
data.tar.gz: d3a086c1262af567acc060fbe6cfb18a6bfbd134b4a1df807e9ee52666f0d4d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a29e7907b0feca5db0253faa574a1fba185e37ea654881b999ef3f7ea48da52c42294963ad40f19fee657f121dde0ac11f8e41881d1115a7ad89b505d19afc24
|
|
7
|
+
data.tar.gz: b1eaa9bd0c1f6baf034aaf524222bb584265495c4bb8bf014ceeb5f6404bbe6364db7ce8cf786c4cece3daf670aa2751ca65eea6986c4145930cf5091d065ae3
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# NiceHash
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/nice_hash)
|
|
4
|
-
[](https://github.com/MarioRuiz/nice_hash/actions/workflows/ci.yml)
|
|
5
5
|
[](https://coveralls.io/github/MarioRuiz/nice_hash?branch=master)
|
|
6
6
|

|
|
7
7
|

|
|
@@ -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
|
|
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,
|
|
97
|
-
# id
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
data/lib/nice/hash/generate.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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/"
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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
|
data/lib/nice/hash/validate.rb
CHANGED
|
@@ -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
|
-
|
|
123
|
-
|
|
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.
|
|
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.
|
|
18
|
+
version: '2.5'
|
|
19
19
|
- - ">="
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: 2.
|
|
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.
|
|
28
|
+
version: '2.5'
|
|
29
29
|
- - ">="
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
|
-
version: 2.
|
|
31
|
+
version: 2.5.0
|
|
32
32
|
- !ruby/object:Gem::Dependency
|
|
33
33
|
name: rspec
|
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|