nice_hash 1.18.7 → 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 +115 -27
- data/lib/nice/hash/add_to_ruby.rb +45 -0
- data/lib/nice/hash/deep_clone.rb +1 -2
- data/lib/nice/hash/delete_nested.rb +9 -4
- data/lib/nice/hash/diff.rb +55 -0
- data/lib/nice/hash/flatten_keys.rb +56 -0
- data/lib/nice/hash/generate.rb +74 -31
- data/lib/nice/hash/set_nested.rb +11 -19
- data/lib/nice/hash/validate.rb +15 -9
- data/lib/nice_hash.rb +2 -0
- metadata +10 -10
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
|

|
|
@@ -22,31 +22,37 @@ To generate the strings following a pattern take a look at the documentation for
|
|
|
22
22
|
To use nice_hash on Http connections take a look at nice_http gem: https://github.com/MarioRuiz/nice_http
|
|
23
23
|
|
|
24
24
|
## Table of contents
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- [
|
|
49
|
-
- [
|
|
25
|
+
- [NiceHash](#nicehash)
|
|
26
|
+
- [Table of contents](#table-of-contents)
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Configuration](#configuration)
|
|
29
|
+
- [Usage](#usage)
|
|
30
|
+
- [How to access the different keys](#how-to-access-the-different-keys)
|
|
31
|
+
- [Change all values on the keys we specified](#change-all-values-on-the-keys-we-specified)
|
|
32
|
+
- [Filtering / Selecting an specific key on the hash and subhashes](#filtering--selecting-an-specific-key-on-the-hash-and-subhashes)
|
|
33
|
+
- [How to generate the hash with the criteria we want](#how-to-generate-the-hash-with-the-criteria-we-want)
|
|
34
|
+
- [How to generate the hash with wrong values for the string patterns specified on the hash](#how-to-generate-the-hash-with-wrong-values-for-the-string-patterns-specified-on-the-hash)
|
|
35
|
+
- [Return the select\_fields or the pattern\_fields](#return-the-select_fields-or-the-pattern_fields)
|
|
36
|
+
- [dig and bury Hash methods](#dig-and-bury-hash-methods)
|
|
37
|
+
- [Validating hashes](#validating-hashes)
|
|
38
|
+
- [Change only one value at a time and return an Array of Hashes](#change-only-one-value-at-a-time-and-return-an-array-of-hashes)
|
|
39
|
+
- [Adding other values on run time when calling `generate` method](#adding-other-values-on-run-time-when-calling-generate-method)
|
|
40
|
+
- [Accessing other values of the hash on run time](#accessing-other-values-of-the-hash-on-run-time)
|
|
41
|
+
- [Compare the structure of a replica with the supplied structure](#compare-the-structure-of-a-replica-with-the-supplied-structure)
|
|
42
|
+
- [Other useful methods](#other-useful-methods)
|
|
43
|
+
- [Hash diff](#hash-diff)
|
|
44
|
+
- [Flatten / unflatten keys](#flatten--unflatten-keys)
|
|
45
|
+
- [Time stamp](#time-stamp)
|
|
46
|
+
- [Random dates](#random-dates)
|
|
47
|
+
- [Deep copy of a hash](#deep-copy-of-a-hash)
|
|
48
|
+
- [Nested deletion](#nested-deletion)
|
|
49
|
+
- [Deep merge of two hashes](#deep-merge-of-two-hashes)
|
|
50
|
+
- [Boolean class](#boolean-class)
|
|
51
|
+
- [in?(array)](#inarray)
|
|
52
|
+
- [Other tools integration](#other-tools-integration)
|
|
53
|
+
- [Tabulo](#tabulo)
|
|
54
|
+
- [Contributing](#contributing)
|
|
55
|
+
- [License](#license)
|
|
50
56
|
|
|
51
57
|
## Installation
|
|
52
58
|
|
|
@@ -64,9 +70,45 @@ Or install it yourself as:
|
|
|
64
70
|
|
|
65
71
|
$ gem install nice_hash
|
|
66
72
|
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Set these constants **before** `require 'nice_hash'` if you need to change default behavior:
|
|
76
|
+
|
|
77
|
+
- **`SP_ADD_TO_RUBY`** (default: `true`): When `true`, the gem adds methods to core classes (`Hash`, `Array`, `String`, `Object`, `Date`, `Time`, etc.). Set to `false` to avoid patching core classes and use only `NiceHash` class methods.
|
|
78
|
+
- **`SP_COMPARE_NUMBERS_AS_STRINGS`** (default: `true`): When `true`, `String#==` is overridden so that strings are compared with numbers as strings (e.g. `'300' == 300` and `'' == nil`). Set to `false` if this causes issues with other gems (e.g. some versions of `net/ldap`).
|
|
79
|
+
|
|
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
|
+
|
|
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
|
+
|
|
88
|
+
Example:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
SP_COMPARE_NUMBERS_AS_STRINGS = false
|
|
92
|
+
require 'nice_hash'
|
|
93
|
+
```
|
|
94
|
+
|
|
67
95
|
## Usage
|
|
68
96
|
|
|
69
|
-
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.
|
|
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.
|
|
98
|
+
|
|
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):
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
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
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`:uuid`, `:iban` and `:luhn` use their own random generator. `seed:` does not make those three fields repeat.
|
|
110
|
+
|
|
111
|
+
Or use a Regular expression for custom formats:
|
|
70
112
|
|
|
71
113
|
```ruby
|
|
72
114
|
my_hash = {
|
|
@@ -371,6 +413,23 @@ new_hash = my_hash.generate(:correct)
|
|
|
371
413
|
new_hash = my_hash.select_key(:correct).generate
|
|
372
414
|
```
|
|
373
415
|
|
|
416
|
+
**Reproducible generation (seed)**
|
|
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.
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
new_hash = my_hash.generate(:correct, seed: 42)
|
|
421
|
+
# Same result every time for the same seed
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Generate n hashes at once**
|
|
425
|
+
Use `generate_n` (alias `gen_n`) to build several different hashes in one call (e.g. for bulk or boundary tests):
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
hashes = NiceHash.generate_n(my_hash, 5, :correct)
|
|
429
|
+
hashes = my_hash.generate_n(5, :correct) # or my_hash.gen_n(5, :correct)
|
|
430
|
+
# With seed: hashes = my_hash.generate_n(5, :correct, seed: 123)
|
|
431
|
+
# the same seed yields the same 5 hashes, and they differ from each other
|
|
432
|
+
```
|
|
374
433
|
|
|
375
434
|
In case of filtering by :correct new_hash would have a value like this for example:
|
|
376
435
|
|
|
@@ -838,9 +897,38 @@ Valid patterns:
|
|
|
838
897
|
- 10.. (from 10 to infinite) Only from Ruby 2.6
|
|
839
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
|
|
840
899
|
- selectors, one of the values. Example: "uno|dos|tres"
|
|
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
|
|
841
904
|
|
|
842
905
|
### Other useful methods
|
|
843
906
|
|
|
907
|
+
#### Hash diff
|
|
908
|
+
Compare two hashes and get differences with dot-notation paths (useful for API or test assertions):
|
|
909
|
+
|
|
910
|
+
```ruby
|
|
911
|
+
expected = { user: { address: { city: "Madrid" } } }
|
|
912
|
+
actual = { user: { address: { city: "London" } } }
|
|
913
|
+
NiceHash.diff(expected, actual)
|
|
914
|
+
#=> { "user.address.city" => { expected: "Madrid", got: "London" } }
|
|
915
|
+
|
|
916
|
+
# Or on a hash instance:
|
|
917
|
+
expected.diff(actual)
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
#### Flatten / unflatten keys
|
|
921
|
+
Convert between nested hashes and flat hashes with dot-notation keys:
|
|
922
|
+
|
|
923
|
+
```ruby
|
|
924
|
+
h = { user: { address: { city: "Madrid" } } }
|
|
925
|
+
h.flatten_keys
|
|
926
|
+
#=> { "user.address.city" => "Madrid" }
|
|
927
|
+
|
|
928
|
+
{ "user.address.city" => "Madrid" }.unflatten_keys
|
|
929
|
+
#=> { user: { address: { city: "Madrid" } } }
|
|
930
|
+
```
|
|
931
|
+
|
|
844
932
|
#### Time stamp
|
|
845
933
|
In case you need the time stamp, we added the method `stamp` to the `Time` class
|
|
846
934
|
|
|
@@ -166,6 +166,16 @@ class Hash
|
|
|
166
166
|
# my_hash.city="Paris"
|
|
167
167
|
# my_hash.products[1].price.wrong="AAAAA"
|
|
168
168
|
###########################################################################
|
|
169
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
170
|
+
sym = (method_name.to_s[0] == "_" ? method_name.to_s[1..-1].to_sym : method_name)
|
|
171
|
+
return true if key?(sym) || key?(sym.to_s)
|
|
172
|
+
if method_name.to_s[-1] == "="
|
|
173
|
+
setter_key = method_name.to_s.chop
|
|
174
|
+
return true if key?(setter_key) || key?(setter_key.to_sym)
|
|
175
|
+
end
|
|
176
|
+
false
|
|
177
|
+
end
|
|
178
|
+
|
|
169
179
|
def method_missing(m, *arguments, &block)
|
|
170
180
|
m = m[1..-1].to_sym if m[0] == "_"
|
|
171
181
|
if key?(m)
|
|
@@ -231,6 +241,13 @@ class Hash
|
|
|
231
241
|
NiceHash.generate(self, select_hash_key, expected_errors: expected_errors, **synonyms)
|
|
232
242
|
end
|
|
233
243
|
|
|
244
|
+
###########################################################################
|
|
245
|
+
# Generates n different hashes from the same pattern. More info: NiceHash.generate_n
|
|
246
|
+
###########################################################################
|
|
247
|
+
def generate_n(n, select_hash_key = nil, expected_errors: [], **synonyms)
|
|
248
|
+
NiceHash.generate_n(self, n, select_hash_key, expected_errors: expected_errors, **synonyms)
|
|
249
|
+
end
|
|
250
|
+
|
|
234
251
|
###########################################################################
|
|
235
252
|
# Validates a given values_hash_to_validate with string patterns and select fields
|
|
236
253
|
# More info: NiceHash.validate
|
|
@@ -298,6 +315,28 @@ class Hash
|
|
|
298
315
|
NiceHash.nice_filter(self, keys)
|
|
299
316
|
end
|
|
300
317
|
|
|
318
|
+
###########################################################################
|
|
319
|
+
# Compares with another hash and returns differences with dot-notation paths.
|
|
320
|
+
# More info: NiceHash.diff
|
|
321
|
+
###########################################################################
|
|
322
|
+
def diff(other)
|
|
323
|
+
NiceHash.diff(self, other)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
###########################################################################
|
|
327
|
+
# Flattens the hash to dot-notation keys. More info: NiceHash.flatten_keys
|
|
328
|
+
###########################################################################
|
|
329
|
+
def flatten_keys
|
|
330
|
+
NiceHash.flatten_keys(self)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
###########################################################################
|
|
334
|
+
# Unflattens a hash with dot-notation keys into nested hash. More info: NiceHash.unflatten_keys
|
|
335
|
+
###########################################################################
|
|
336
|
+
def unflatten_keys
|
|
337
|
+
NiceHash.unflatten_keys(self)
|
|
338
|
+
end
|
|
339
|
+
|
|
301
340
|
###########################################################################
|
|
302
341
|
# Merging multi-dimensional hashes
|
|
303
342
|
###########################################################################
|
|
@@ -322,6 +361,7 @@ class Hash
|
|
|
322
361
|
end
|
|
323
362
|
|
|
324
363
|
alias gen generate
|
|
364
|
+
alias gen_n generate_n
|
|
325
365
|
alias val validate
|
|
326
366
|
alias patterns pattern_fields
|
|
327
367
|
alias nice_copy deep_copy
|
|
@@ -365,6 +405,11 @@ class Array
|
|
|
365
405
|
# my_array.city
|
|
366
406
|
# my_array._name
|
|
367
407
|
###########################################################################
|
|
408
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
409
|
+
sym = (method_name.to_s[0] == "_" ? method_name.to_s[1..-1].to_sym : method_name)
|
|
410
|
+
any? { |elem| elem.is_a?(Hash) && (elem.key?(sym) || elem.key?(sym.to_s)) }
|
|
411
|
+
end
|
|
412
|
+
|
|
368
413
|
def method_missing(m, *arguments, &block)
|
|
369
414
|
m = m[1..-1].to_sym if m[0] == "_"
|
|
370
415
|
array = []
|
data/lib/nice/hash/deep_clone.rb
CHANGED
|
@@ -21,6 +21,7 @@ class NiceHash
|
|
|
21
21
|
# #>{:user=>{:address=>{:city=>"Madrid", :country=>"Spain"}, :name=>"Peter", :age=>33}, :customer=>true}
|
|
22
22
|
##################################################
|
|
23
23
|
def self.deep_clone(obj)
|
|
24
|
+
return obj.clone unless obj.is_a?(Array) || obj.is_a?(Hash)
|
|
24
25
|
obj.clone.tap do |new_obj|
|
|
25
26
|
if new_obj.is_a?(Array)
|
|
26
27
|
new_obj.each_with_index do |val, i|
|
|
@@ -30,8 +31,6 @@ class NiceHash
|
|
|
30
31
|
new_obj.each do |key, val|
|
|
31
32
|
new_obj[key] = deep_clone(val)
|
|
32
33
|
end
|
|
33
|
-
else
|
|
34
|
-
new_obj = obj.clone
|
|
35
34
|
end
|
|
36
35
|
end
|
|
37
36
|
end
|
|
@@ -27,11 +27,16 @@ class NiceHash
|
|
|
27
27
|
if keys.size == 1
|
|
28
28
|
hash.delete(nested_key.to_sym)
|
|
29
29
|
else
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
parent = hash
|
|
31
|
+
keys[0..-2].each do |k|
|
|
32
|
+
sym = k.to_sym
|
|
33
|
+
return hash unless parent.is_a?(Hash) && parent.key?(sym)
|
|
34
|
+
parent = parent[sym]
|
|
35
|
+
end
|
|
36
|
+
parent.delete(keys[-1].to_sym) if parent.is_a?(Hash)
|
|
32
37
|
end
|
|
33
38
|
return hash
|
|
34
39
|
end
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
end
|
|
41
|
+
|
|
42
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class NiceHash
|
|
4
|
+
##################################################
|
|
5
|
+
# Compares two hashes and returns differences with dot-notation paths.
|
|
6
|
+
#
|
|
7
|
+
# @param expected [Hash, Array] Expected structure (hash or array of hashes)
|
|
8
|
+
# @param actual [Hash, Array] Actual structure to compare
|
|
9
|
+
# @param prefix [String] Internal: current path prefix for recursion
|
|
10
|
+
#
|
|
11
|
+
# @return [Hash] Path => { expected: value, got: value } for each difference
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# expected = { user: { address: { city: "Madrid" } } }
|
|
15
|
+
# actual = { user: { address: { city: "London" } } }
|
|
16
|
+
# NiceHash.diff(expected, actual)
|
|
17
|
+
# #=> { "user.address.city" => { expected: "Madrid", got: "London" } }
|
|
18
|
+
##################################################
|
|
19
|
+
def self.diff(expected, actual, prefix = "")
|
|
20
|
+
result = {}
|
|
21
|
+
exp_keys = expected.is_a?(Hash) ? expected.keys : (expected.is_a?(Array) ? (0...expected.size) : nil)
|
|
22
|
+
act_keys = actual.is_a?(Hash) ? actual.keys : (actual.is_a?(Array) ? (0...actual.size) : nil)
|
|
23
|
+
|
|
24
|
+
if expected.is_a?(Hash) && actual.is_a?(Hash)
|
|
25
|
+
all_keys = (expected.keys + actual.keys).uniq
|
|
26
|
+
all_keys.each do |k|
|
|
27
|
+
path = prefix.empty? ? k.to_s : "#{prefix}.#{k}"
|
|
28
|
+
exp_v = expected[k]
|
|
29
|
+
act_v = actual[k]
|
|
30
|
+
if !expected.key?(k)
|
|
31
|
+
result[path] = { expected: nil, got: act_v }
|
|
32
|
+
elsif !actual.key?(k)
|
|
33
|
+
result[path] = { expected: exp_v, got: nil }
|
|
34
|
+
elsif exp_v.is_a?(Hash) && act_v.is_a?(Hash)
|
|
35
|
+
result.merge!(diff(exp_v, act_v, path))
|
|
36
|
+
elsif exp_v.is_a?(Array) && act_v.is_a?(Array)
|
|
37
|
+
max_len = [exp_v.size, act_v.size].max
|
|
38
|
+
max_len.times do |i|
|
|
39
|
+
result.merge!(diff(exp_v[i], act_v[i], "#{path}[#{i}]"))
|
|
40
|
+
end
|
|
41
|
+
elsif exp_v != act_v
|
|
42
|
+
result[path] = { expected: exp_v, got: act_v }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
elsif expected.is_a?(Array) && actual.is_a?(Array)
|
|
46
|
+
max_len = [expected.size, actual.size].max
|
|
47
|
+
max_len.times do |i|
|
|
48
|
+
result.merge!(diff(expected[i], actual[i], prefix.empty? ? "[#{i}]" : "#{prefix}[#{i}]"))
|
|
49
|
+
end
|
|
50
|
+
elsif expected != actual
|
|
51
|
+
result[prefix] = { expected: expected, got: actual } unless prefix.empty?
|
|
52
|
+
end
|
|
53
|
+
result
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class NiceHash
|
|
4
|
+
##################################################
|
|
5
|
+
# Flattens a nested hash so keys become dot-notation strings.
|
|
6
|
+
#
|
|
7
|
+
# @param hash [Hash] The hash to flatten
|
|
8
|
+
# @param prefix [String] Internal: current path prefix for recursion
|
|
9
|
+
#
|
|
10
|
+
# @return [Hash] Flat hash with string keys like "user.address.city"
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# NiceHash.flatten_keys({ user: { address: { city: "Madrid" } } })
|
|
14
|
+
# #=> { "user.address.city" => "Madrid" }
|
|
15
|
+
##################################################
|
|
16
|
+
def self.flatten_keys(hash, prefix = "")
|
|
17
|
+
return {} unless hash.is_a?(Hash)
|
|
18
|
+
result = {}
|
|
19
|
+
hash.each do |k, v|
|
|
20
|
+
path = prefix.empty? ? k.to_s : "#{prefix}.#{k}"
|
|
21
|
+
if v.is_a?(Hash)
|
|
22
|
+
result.merge!(flatten_keys(v, path))
|
|
23
|
+
else
|
|
24
|
+
result[path] = v
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##################################################
|
|
31
|
+
# Unflattens a hash with dot-notation keys into a nested hash.
|
|
32
|
+
#
|
|
33
|
+
# @param hash [Hash] Flat hash with string keys like "user.address.city"
|
|
34
|
+
#
|
|
35
|
+
# @return [Hash] Nested hash
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# NiceHash.unflatten_keys({ "user.address.city" => "Madrid" })
|
|
39
|
+
# #=> { user: { address: { city: "Madrid" } } }
|
|
40
|
+
##################################################
|
|
41
|
+
def self.unflatten_keys(hash)
|
|
42
|
+
return {} unless hash.is_a?(Hash)
|
|
43
|
+
result = {}
|
|
44
|
+
hash.each do |key_str, value|
|
|
45
|
+
keys = key_str.to_s.split(".")
|
|
46
|
+
keys = keys.map { |k| k.match?(/\A\d+\z/) ? k.to_i : k.to_sym }
|
|
47
|
+
current = result
|
|
48
|
+
keys[0..-2].each do |k|
|
|
49
|
+
current[k] = {} unless current[k].is_a?(Hash)
|
|
50
|
+
current = current[k]
|
|
51
|
+
end
|
|
52
|
+
current[keys[-1]] = value
|
|
53
|
+
end
|
|
54
|
+
result
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/nice/hash/generate.rb
CHANGED
|
@@ -51,13 +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
|
|
72
|
+
rng = generation_rng(synonyms)
|
|
73
|
+
sp_opts = { expected_errors: expected_errors, _rng: rng }.merge(synonyms)
|
|
61
74
|
|
|
62
75
|
if pattern_hash.kind_of?(Hash) and pattern_hash.size > 0
|
|
63
76
|
pattern_hash.each { |key, value|
|
|
@@ -70,14 +83,25 @@ class NiceHash
|
|
|
70
83
|
if value.keys.include?(select_hash_key)
|
|
71
84
|
value = value[select_hash_key]
|
|
72
85
|
else
|
|
73
|
-
value = NiceHash.generate(value, select_hash_key, expected_errors: expected_errors)
|
|
86
|
+
value = NiceHash.generate(value, select_hash_key, expected_errors: expected_errors, _rng: rng, **synonyms)
|
|
74
87
|
end
|
|
75
88
|
end
|
|
76
|
-
if value
|
|
89
|
+
if value == :uuid
|
|
90
|
+
# SecureRandom is not seeded. UUID values are not reproducible via seed:.
|
|
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"
|
|
100
|
+
elsif value.kind_of?(String) or value.kind_of?(Symbol)
|
|
77
101
|
if ((StringPattern.optimistic and value.kind_of?(String)) or value.kind_of?(Symbol)) and value.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
|
|
78
|
-
hashv[key] = StringPattern.generate(value,
|
|
102
|
+
hashv[key] = StringPattern.generate(value, **sp_opts)
|
|
79
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
|
|
80
|
-
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)
|
|
81
105
|
min = value.to_s.split("|").min { |a, b| a.size <=> b.size }
|
|
82
106
|
hashv[key] = min[0..-2] unless min == ""
|
|
83
107
|
end
|
|
@@ -91,20 +115,21 @@ class NiceHash
|
|
|
91
115
|
if hashv.keys.include?(key)
|
|
92
116
|
v = hashv[key]
|
|
93
117
|
else
|
|
94
|
-
v = value.to_s.split("|").sample
|
|
118
|
+
v = value.to_s.split("|").sample(random: rng)
|
|
95
119
|
end
|
|
96
120
|
unless expected_errors.include?(:string_set_not_allowed)
|
|
97
|
-
v = StringPattern.generate(:"#{v.size}:[#{value.to_s.split("|").join.split(//).uniq.join}]")
|
|
121
|
+
v = StringPattern.generate(:"#{v.size}:[#{value.to_s.split("|").join.split(//).uniq.join}]", **sp_opts)
|
|
98
122
|
hashv[key] = v unless value.to_s.split("|").include?(v)
|
|
99
123
|
end
|
|
100
124
|
unless hashv.keys.include?(key)
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
# Need a valid 1-char not in the allowed set (do not pass expected_errors)
|
|
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
|
|
103
128
|
hashv[key] = v unless value.to_s.split("|").include?(v)
|
|
104
129
|
end
|
|
105
130
|
end
|
|
106
131
|
unless hashv.keys.include?(key)
|
|
107
|
-
hashv[key] = value.to_s.split("|").sample
|
|
132
|
+
hashv[key] = value.to_s.split("|").sample(random: rng)
|
|
108
133
|
end
|
|
109
134
|
else
|
|
110
135
|
hashv[key] = value
|
|
@@ -115,15 +140,15 @@ class NiceHash
|
|
|
115
140
|
v = value[0]
|
|
116
141
|
if (v.kind_of?(String) or v.kind_of?(Symbol)) and StringPattern.analyze(v, silent: true).kind_of?(StringPattern::Pattern)
|
|
117
142
|
hashv[key] = []
|
|
118
|
-
(rand(5)+1).times do
|
|
119
|
-
hashv[key]<<StringPattern.generate(v,
|
|
143
|
+
(rng.rand(5)+1).times do
|
|
144
|
+
hashv[key]<<StringPattern.generate(v, **sp_opts)
|
|
120
145
|
end
|
|
121
146
|
array_pattern = true
|
|
122
147
|
end
|
|
123
148
|
else
|
|
124
149
|
value.each { |v|
|
|
125
150
|
if (v.kind_of?(String) or v.kind_of?(Symbol)) and StringPattern.analyze(v, silent: true).kind_of?(StringPattern::Pattern)
|
|
126
|
-
hashv[key] = StringPattern.generate(value,
|
|
151
|
+
hashv[key] = StringPattern.generate(value, **sp_opts)
|
|
127
152
|
array_pattern = true
|
|
128
153
|
break
|
|
129
154
|
end
|
|
@@ -133,9 +158,9 @@ class NiceHash
|
|
|
133
158
|
value_ret = Array.new
|
|
134
159
|
value.each { |v|
|
|
135
160
|
if v.is_a?(Hash)
|
|
136
|
-
ret = NiceHash.generate(v, select_hash_key, expected_errors: expected_errors)
|
|
161
|
+
ret = NiceHash.generate(v, select_hash_key, expected_errors: expected_errors, _rng: rng, **synonyms)
|
|
137
162
|
else
|
|
138
|
-
ret = NiceHash.generate({ doit: v }, select_hash_key, expected_errors: expected_errors)
|
|
163
|
+
ret = NiceHash.generate({ doit: v }, select_hash_key, expected_errors: expected_errors, _rng: rng, **synonyms)
|
|
139
164
|
ret = ret[:doit] if ret.is_a?(Hash) and ret.key?(:doit)
|
|
140
165
|
end
|
|
141
166
|
ret = v if ret.kind_of?(Hash) and ret.size == 0
|
|
@@ -148,28 +173,28 @@ class NiceHash
|
|
|
148
173
|
if value.size == Float::INFINITY
|
|
149
174
|
value = (value.min..2**64)
|
|
150
175
|
end
|
|
151
|
-
hashv[key] = rand(value)
|
|
176
|
+
hashv[key] = rng.rand(value)
|
|
152
177
|
else
|
|
153
178
|
if value.size == Float::INFINITY
|
|
154
179
|
infinite = true
|
|
155
180
|
value = (value.min..2**64)
|
|
156
181
|
else
|
|
157
182
|
infinite = false
|
|
158
|
-
hashv[key] = rand(value)
|
|
183
|
+
hashv[key] = rng.rand(value)
|
|
159
184
|
end
|
|
160
185
|
expected_errors.each do |er|
|
|
161
186
|
if er == :min_length
|
|
162
|
-
hashv[key] = rand((value.first - value.last)..value.first - 1)
|
|
187
|
+
hashv[key] = rng.rand((value.first - value.last)..value.first - 1)
|
|
163
188
|
elsif er == :max_length and !infinite
|
|
164
|
-
hashv[key] = rand((value.last + 1)..(value.last * 2))
|
|
189
|
+
hashv[key] = rng.rand((value.last + 1)..(value.last * 2))
|
|
165
190
|
elsif er == :length
|
|
166
|
-
if rand.round == 1 or infinite
|
|
167
|
-
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)
|
|
168
193
|
else
|
|
169
|
-
hashv[key] = rand((value.last + 1)..(value.last * 2))
|
|
194
|
+
hashv[key] = rng.rand((value.last + 1)..(value.last * 2))
|
|
170
195
|
end
|
|
171
196
|
elsif er == :value
|
|
172
|
-
hashv[key] = :"1-10:N/L/"
|
|
197
|
+
hashv[key] = StringPattern.generate(:"1-10:N/L/", _rng: rng)
|
|
173
198
|
end
|
|
174
199
|
end
|
|
175
200
|
end
|
|
@@ -184,31 +209,31 @@ class NiceHash
|
|
|
184
209
|
elsif er == :max_length
|
|
185
210
|
hashv[key] = hashv[key] + "Z"
|
|
186
211
|
elsif er == :length
|
|
187
|
-
if rand.round == 1
|
|
212
|
+
if rng.rand.round == 1
|
|
188
213
|
hashv[key] = hashv[key].chop
|
|
189
214
|
else
|
|
190
215
|
hashv[key] = hashv[key] + "Z"
|
|
191
216
|
end
|
|
192
217
|
elsif er == :value
|
|
193
|
-
hashv[key][rand(hashv[key].size - 1)] = "1:L"
|
|
218
|
+
hashv[key][rng.rand(hashv[key].size - 1)] = StringPattern.generate("1:L", _rng: rng)
|
|
194
219
|
end
|
|
195
220
|
end
|
|
196
221
|
end
|
|
197
222
|
elsif value.kind_of?(Module) and value == Boolean
|
|
198
223
|
if expected_errors.empty?
|
|
199
|
-
hashv[key] = (rand.round == 0)
|
|
224
|
+
hashv[key] = (rng.rand.round == 0)
|
|
200
225
|
else
|
|
201
|
-
hashv[key] = (rand.round == 0)
|
|
226
|
+
hashv[key] = (rng.rand.round == 0)
|
|
202
227
|
expected_errors.each do |er|
|
|
203
228
|
if er == :value
|
|
204
|
-
hashv[key] = "1-10:L"
|
|
229
|
+
hashv[key] = StringPattern.generate("1-10:L", _rng: rng)
|
|
205
230
|
end
|
|
206
231
|
end
|
|
207
232
|
end
|
|
208
233
|
elsif value.kind_of?(Proc)
|
|
209
234
|
hashv[key] = value.call
|
|
210
235
|
elsif value.kind_of?(Regexp)
|
|
211
|
-
hashv[key] = value.generate(
|
|
236
|
+
hashv[key] = value.generate(**sp_opts)
|
|
212
237
|
else
|
|
213
238
|
hashv[key] = value
|
|
214
239
|
end
|
|
@@ -225,5 +250,23 @@ class NiceHash
|
|
|
225
250
|
|
|
226
251
|
return hashv
|
|
227
252
|
end
|
|
228
|
-
|
|
229
|
-
|
|
253
|
+
|
|
254
|
+
###########################################################################
|
|
255
|
+
# Generates n different hashes from the same pattern (useful for bulk or boundary tests).
|
|
256
|
+
# input:
|
|
257
|
+
# pattern_hash: (Hash) Hash we want to use to generate the values
|
|
258
|
+
# n: (Integer) Number of hashes to generate
|
|
259
|
+
# select_hash_key: (key value) (optional) The key we want to select on the subhashes
|
|
260
|
+
# expected_errors: (Array) (optional) (alias: errors) Same as in generate
|
|
261
|
+
# output: (Array of Hash)
|
|
262
|
+
# examples:
|
|
263
|
+
# hashes = NiceHash.generate_n(my_hash, 5, :correct)
|
|
264
|
+
# hashes = my_hash.generate_n(5, :correct)
|
|
265
|
+
###########################################################################
|
|
266
|
+
def NiceHash.generate_n(pattern_hash, n, select_hash_key = nil, 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) }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
private_class_method :generation_rng
|
|
272
|
+
end
|
data/lib/nice/hash/set_nested.rb
CHANGED
|
@@ -28,27 +28,19 @@ class NiceHash
|
|
|
28
28
|
if keys.size == 1
|
|
29
29
|
hash[nested_key.to_sym] = value unless only_if_exist and !hash.key?(nested_key.to_sym)
|
|
30
30
|
else
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
exist = false
|
|
37
|
-
break
|
|
38
|
-
end
|
|
39
|
-
ht = ht[k.to_sym]
|
|
40
|
-
end
|
|
31
|
+
parent = hash
|
|
32
|
+
keys[0..-2].each do |k|
|
|
33
|
+
sym = k.to_sym
|
|
34
|
+
return hash unless parent.is_a?(Hash) && parent.key?(sym)
|
|
35
|
+
parent = parent[sym]
|
|
41
36
|
end
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
else
|
|
46
|
-
#todo: consider other kind of objects apart of strings
|
|
47
|
-
eval("hash.#{nested_key}=#{value}")
|
|
48
|
-
end
|
|
37
|
+
return hash unless parent.is_a?(Hash)
|
|
38
|
+
if only_if_exist
|
|
39
|
+
return hash unless parent.key?(keys[-1].to_sym)
|
|
49
40
|
end
|
|
41
|
+
parent[keys[-1].to_sym] = value
|
|
50
42
|
end
|
|
51
43
|
return hash
|
|
52
44
|
end
|
|
53
|
-
|
|
54
|
-
end
|
|
45
|
+
|
|
46
|
+
end
|
data/lib/nice/hash/validate.rb
CHANGED
|
@@ -59,7 +59,7 @@ class NiceHash
|
|
|
59
59
|
end
|
|
60
60
|
values = values_hash_to_validate
|
|
61
61
|
if pattern_hash.keys.size == get_all_keys(pattern_hash).size and values.keys.size != get_all_keys(values) and
|
|
62
|
-
pattern_hash.keys.size == pattern_hash.keys.flatten.size # dont't set_values for the case of same_value
|
|
62
|
+
pattern_hash.keys.size == pattern_hash.keys.flatten.size # dont't set_values for the case of same_value
|
|
63
63
|
# all patterns on patterns_hash are described on first level, so no same structure than values
|
|
64
64
|
pattern_hash = values.set_values(pattern_hash)
|
|
65
65
|
end
|
|
@@ -83,7 +83,15 @@ class NiceHash
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
if values.keys.include?(key)
|
|
86
|
-
if value
|
|
86
|
+
if value == :uuid
|
|
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)
|
|
94
|
+
elsif value.kind_of?(String) or value.kind_of?(Symbol)
|
|
87
95
|
if ((StringPattern.optimistic and value.kind_of?(String)) or value.kind_of?(Symbol)) and value.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
|
|
88
96
|
res = StringPattern.validate(pattern: value, text: values[key])
|
|
89
97
|
results[key] = res if res.size > 0
|
|
@@ -117,10 +125,8 @@ class NiceHash
|
|
|
117
125
|
elsif value.kind_of?(Module) and value == Boolean
|
|
118
126
|
results[key] = false unless values[key].is_a?(Boolean)
|
|
119
127
|
elsif value.kind_of?(Regexp)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
results[key] = false
|
|
123
|
-
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?)
|
|
124
130
|
elsif value.kind_of?(Array)
|
|
125
131
|
array_pattern = false
|
|
126
132
|
complex_data = false
|
|
@@ -133,7 +139,7 @@ class NiceHash
|
|
|
133
139
|
results[key] << res
|
|
134
140
|
end
|
|
135
141
|
if results[key].flatten.size == 0
|
|
136
|
-
results.delete(key)
|
|
142
|
+
results.delete(key)
|
|
137
143
|
end
|
|
138
144
|
else
|
|
139
145
|
res = StringPattern.validate(pattern: value, text: values[key])
|
|
@@ -213,5 +219,5 @@ class NiceHash
|
|
|
213
219
|
return results
|
|
214
220
|
end
|
|
215
221
|
|
|
216
|
-
|
|
217
|
-
end
|
|
222
|
+
|
|
223
|
+
end
|
data/lib/nice_hash.rb
CHANGED
|
@@ -5,6 +5,8 @@ require_relative "nice/hash/add_to_ruby" if SP_ADD_TO_RUBY
|
|
|
5
5
|
require_relative "nice/hash/change_one_by_one"
|
|
6
6
|
require_relative "nice/hash/compare_structure"
|
|
7
7
|
require_relative "nice/hash/delete_nested"
|
|
8
|
+
require_relative "nice/hash/diff"
|
|
9
|
+
require_relative "nice/hash/flatten_keys"
|
|
8
10
|
require_relative "nice/hash/generate"
|
|
9
11
|
require_relative "nice/hash/get_all_keys"
|
|
10
12
|
require_relative "nice/hash/get_values"
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
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
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: string_pattern
|
|
@@ -16,20 +15,20 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '2.
|
|
18
|
+
version: '2.5'
|
|
20
19
|
- - ">="
|
|
21
20
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: 2.
|
|
21
|
+
version: 2.5.0
|
|
23
22
|
type: :runtime
|
|
24
23
|
prerelease: false
|
|
25
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
25
|
requirements:
|
|
27
26
|
- - "~>"
|
|
28
27
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '2.
|
|
28
|
+
version: '2.5'
|
|
30
29
|
- - ">="
|
|
31
30
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 2.
|
|
31
|
+
version: 2.5.0
|
|
33
32
|
- !ruby/object:Gem::Dependency
|
|
34
33
|
name: rspec
|
|
35
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,6 +70,8 @@ files:
|
|
|
71
70
|
- lib/nice/hash/compare_structure.rb
|
|
72
71
|
- lib/nice/hash/deep_clone.rb
|
|
73
72
|
- lib/nice/hash/delete_nested.rb
|
|
73
|
+
- lib/nice/hash/diff.rb
|
|
74
|
+
- lib/nice/hash/flatten_keys.rb
|
|
74
75
|
- lib/nice/hash/generate.rb
|
|
75
76
|
- lib/nice/hash/get_all_keys.rb
|
|
76
77
|
- lib/nice/hash/get_values.rb
|
|
@@ -95,15 +96,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
95
96
|
requirements:
|
|
96
97
|
- - ">="
|
|
97
98
|
- !ruby/object:Gem::Version
|
|
98
|
-
version: '0'
|
|
99
|
+
version: '3.0'
|
|
99
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
101
|
requirements:
|
|
101
102
|
- - ">="
|
|
102
103
|
- !ruby/object:Gem::Version
|
|
103
104
|
version: '0'
|
|
104
105
|
requirements: []
|
|
105
|
-
rubygems_version:
|
|
106
|
-
signing_key:
|
|
106
|
+
rubygems_version: 4.0.6
|
|
107
107
|
specification_version: 4
|
|
108
108
|
summary: NiceHash creates hashes following certain patterns so your testing will be
|
|
109
109
|
much easier. Parse and filter JSON. Perfect to be used in test data factories
|