hash_remapper 0.3.0 → 0.4.2

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: bd1eb9b9c2a9a1ed5379213e72f50a3dc5dcbf93d4fd6deedb4e5a90c9cb4e7d
4
- data.tar.gz: c565d0549390a2d4264e170aa74ef1d7e106201f8f38674a627d1c6aa4b3e6d1
3
+ metadata.gz: c683439dd0fa1c9ed4829b996ef023248412faa7c40062da5195e6c7f639b610
4
+ data.tar.gz: 169160ed106c2da575ebbd2e8ee3a09432fdecfcb100af63f899ac08ccafd689
5
5
  SHA512:
6
- metadata.gz: 9ac29a074171eac2b77c34b00a703b800bfaa9e0ceb3a8d1c498a09d1c8db4ecd616cc298ee2cc4e5b52f01183c4d697be4857fd2e3fcd25580eeb6cd822576c
7
- data.tar.gz: b8d7a86a12c5c375031bb3a9a42bc972f94f6d46bb4c0690f52177b1bf2215a496479783a877e069d9522f72346adde9827336fea3001714e69e4f0dbce8f5fc
6
+ metadata.gz: 10b4f4686588c177567f5f7a0653970b71e18f7bd2919166b9bca568c4ab80a020b69b7ecdd96cce2d3291359c02e7ba6ad43447053f577a768ea41c31360ed0
7
+ data.tar.gz: 78fdc8e7fba05285373ac1dd8395b6d5aeec76b892ddffb1508cd79c1a34dda19283da5e7014065e30f96ce7eccbba6758231fb93f0868534f584509da600c8a
data/.travis.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4.1
5
- before_install: gem install bundler -v 1.16
4
+ - 2.6.0
5
+ before_install: gem install bundler -v 2.0.1
data/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  </p>
6
6
 
7
7
  > A little lib which maps original keys to the new ones <sup>[*](#features)</sup>
8
+ > [![Build Status](https://travis-ci.org/smileart/hash_remapper.svg?branch=master)](https://travis-ci.org/smileart/hash_remapper)
8
9
 
9
10
  ## Installation
10
11
 
@@ -29,13 +30,13 @@ Or install it yourself as:
29
30
  * preprocess a value with a lambda [3](#lambda)
30
31
  * allows to remap the keys within preprocessing [4](#preprocessing)
31
32
  * allows to keep data subsets only [5](#subset)
32
- * allows to include data with the original keynames [6](#originals)
33
+ * allows to include data with the original key names [6](#originals)
33
34
  * allows to use global context to create composite fields [7](#composite)
34
35
  * merges values if the key already exists and supports #merge [8](#merge)
35
36
  * replaces values if the key already exists and doesn't support #merge [9](#replace)
36
37
  * allows to assign static defaults through lambdas [10](#defaults)
37
38
  * allows to remap to the deep values within the context [11](#deep)
38
- * allows to create completely new keys [12](#new_keys)
39
+ * allows to create completely new keys (including nested ones) [12](#new_keys)
39
40
 
40
41
  ## Usage
41
42
 
@@ -167,7 +168,7 @@ HashRemapper.remap(
167
168
  # }
168
169
  ```
169
170
 
170
- ## <a name="originals">6</a>: Include data with the original keyname
171
+ ## <a name="originals">6</a>: Include data with the original key name
171
172
 
172
173
  ```rb
173
174
  HashRemapper.remap(
@@ -266,7 +267,7 @@ HashRemapper.remap(
266
267
  # }
267
268
  ```
268
269
 
269
- ## <a name="new_keys">12</a>: Create completely new keys
270
+ ## <a name="new_keys">12</a>: Create completely new keys (including nested ones)
270
271
 
271
272
  ```rb
272
273
  HashRemapper.remap(
@@ -280,6 +281,54 @@ HashRemapper.remap(
280
281
  # magic_number: 42,
281
282
  # absolutely_new_key: 'shiny new value'
282
283
  # }
284
+
285
+ HashRemapper.remap(
286
+ original_hash,
287
+ _: [[:nested, :new, :key], :test]
288
+ )
289
+
290
+ # =>
291
+ # {
292
+ # nested: {
293
+ # new: {
294
+ # key: 42
295
+ # }
296
+ # }
297
+ # }
298
+
299
+
300
+ # mapping a deep target from a deep source (BEWARE an old digging API <= v0.1.0)
301
+ HashRemapper.remap(
302
+ original_hash,
303
+ _: [[:nested, :new, :key], [:nested, :really, :deep]]
304
+ )
305
+
306
+ # =>
307
+ # {
308
+ # nested: {
309
+ # new: {
310
+ # key: true
311
+ # }
312
+ # }
313
+ # }
314
+
315
+
316
+ # mapping a deep target from a deep source (new digging API >= v0.2.0)
317
+ HashRemapper.remap(
318
+ original_hash,
319
+ _: [[:new, :deeply, :nested, :value], {path: 'recursive.*.number', strict: false, default: 3.14}]
320
+ )
321
+
322
+ # =>
323
+ # {
324
+ # new: {
325
+ # deeply: {
326
+ # nested: {
327
+ # value: [21, 42, 3.14]
328
+ # }
329
+ # }
330
+ # }
331
+ # }
283
332
  ```
284
333
 
285
334
  ## Development
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+ require_relative '../lib/hash_remapper'
3
+
4
+ # https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22
5
+ weather = JSON.parse('{"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":300,"main":"Drizzle","description":"light intensity drizzle","icon":"09d"}],"base":"stations","main":{"temp":280.32,"pressure":1012,"humidity":81,"temp_min":279.15,"temp_max":281.15},"visibility":10000,"wind":{"speed":4.1,"deg":80},"clouds":{"all":90},"dt":1485789600,"sys":{"type":1,"id":5091,"message":0.0103,"country":"GB","sunrise":1485762037,"sunset":1485794875},"id":2643743,"name":"London","cod":200}')
6
+
7
+ k_to_c = ->(kelvin) { "#{(kelvin - 273.15).floor}˚C" } # notice the difference with the next example
8
+
9
+ remapped_weather = HashRemapper.remap(
10
+ weather,
11
+ # deep keys creation instead of automerge
12
+ _conditions: [[:current_conditions, :description], {path: 'weather.0', lambda: ->(res){ "#{res[:description]}".capitalize }} ],
13
+ _temperature: [[:current_conditions, :temperature], {path: 'main.temp', lambda: k_to_c}],
14
+
15
+ _clouds: [[:clouds_coverage, :percentage], {path: 'clouds.all'}],
16
+ 'visibility' => ->(vis, _) { [:visibility, "#{vis / 1000}km"] },
17
+ ['sys', 'country'] => ->(country, _) { [[:place, :country], country] },
18
+ 'name' => ->(city, _) { [[:place, :city], city] },
19
+ 'coord' => :geo_coordinates
20
+ )
21
+
22
+ p remapped_weather
23
+
24
+ # =================================================================================================================================
25
+
26
+ k_to_c = ->(kelvin) { { temperature: "#{(kelvin - 273.15).floor}˚C" } } # notice the difference with the previous example
27
+
28
+ remapped_weather = HashRemapper.remap(
29
+ weather,
30
+ # automerge on "conflict"
31
+ _conditions: [:current_conditions, {path: 'weather.0', lambda: ->(res){ { description: "#{res[:description]}".capitalize }}} ],
32
+ _temperature: [:current_conditions, {path: 'main.temp', lambda: k_to_c}], # automerge
33
+ _clouds: [[:clouds_coverage, :percentage], {path: 'clouds.all'}],
34
+ 'visibility' => ->(vis, _) { [:visibility, "#{vis / 1000}km"] },
35
+ ['sys', 'country'] => ->(country, _) { [[:place, :country], country] },
36
+ 'name' => ->(city, _) { [[:place, :city], city] },
37
+
38
+ # notice the nested HashRemapper (alternatively https://devdocs.io/rails~5.0/hash#method-i-deep_symbolize_keys could be used on overall result)
39
+ 'coord' => ->(coord, _) { [:geo_coordinates, HashRemapper.remap(coord, 'lon' => :lon, 'lat' => :lat)] },
40
+ )
41
+
42
+ p remapped_weather
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  # coding: utf-8
3
3
 
4
- lib = File.expand_path('./lib', __FILE__)
4
+ lib = File.expand_path('../lib', __FILE__)
5
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
 
7
- # to avoid double const init warning
8
- require_relative './lib/hash_remapper'
7
+ require 'hash_remapper/version'
9
8
 
10
- Gem::Specification.new do |spec|
9
+ HashRemapper::GEMSPEC = Gem::Specification.new do |spec|
11
10
  spec.name = 'hash_remapper'
12
11
  spec.version = HashRemapper::VERSION
13
12
  spec.authors = ['Serge Bedzhyk']
@@ -26,6 +25,8 @@ Gem::Specification.new do |spec|
26
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
26
  spec.require_paths = ['lib']
28
27
 
28
+ spec.add_dependency 'hash_digger', '~> 0.0', '>= 0.0.4'
29
+
29
30
  spec.add_development_dependency 'bundler', '~> 2.0'
30
31
  spec.add_development_dependency 'byebug', '~> 11.0'
31
32
  spec.add_development_dependency 'inch', '>= 0.9.0.rc1'
@@ -36,6 +37,4 @@ Gem::Specification.new do |spec|
36
37
  spec.add_development_dependency 'rubygems-tasks', '~> 0.2'
37
38
  spec.add_development_dependency 'simplecov', '~> 0.15'
38
39
  spec.add_development_dependency 'yard', '~> 0.9'
39
-
40
- spec.add_runtime_dependency 'hash_digger', '~> 0.0'
41
40
  end
data/lib/hash_remapper.rb CHANGED
@@ -3,11 +3,11 @@
3
3
  require 'set'
4
4
  require 'hash_digger'
5
5
 
6
+ require 'letters' if ENV['DEBUG']
7
+ require 'byebug' if ENV['DEBUG']
8
+
6
9
  # Utility class to map original Hash keys to the new ones
7
10
  class HashRemapper
8
- # Current Library Version
9
- VERSION = '0.3.0'
10
-
11
11
  class << self
12
12
  # Remaps `data` Hash by renaming keys, creating new ones and
13
13
  # optionally aggregating values
@@ -22,19 +22,20 @@ class HashRemapper
22
22
  #
23
23
  # @param [Hash] data the original Hash to remap
24
24
  # @param [Boolean] pass_trough the flag to pass the original key/value pairs (default: false)
25
- # @param [Hash] mapping the Hash which in the simplest case tells how to rename keys
25
+ # @param [Hash] mapping the Hash which in the simplest case tells how to rename keys (source: :target)
26
26
  #
27
27
  # @return [Hash] remapped version of the original Hash
28
28
  # (selected keys only or all the keys if we passed originals)
29
29
  def remap(data, pass_trough = false, mapping)
30
30
  mapping = pass_trough_mapping(data, mapping) if pass_trough
31
31
 
32
- mapping.each_with_object({}) do |(from, to), acc|
32
+ mapping.each_with_object(Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }) do |(from, to), acc|
33
33
  key, value = try_callable(from, to, data, acc) ||
34
- try_digging(to, data) ||
34
+ try_digging(from, to, data, acc) ||
35
+ try_nesting(from, to, data, acc) ||
35
36
  [to, data[from]]
36
37
 
37
- acc[key] = value
38
+ set_value(acc, key, value)
38
39
  acc
39
40
  end
40
41
  end
@@ -53,11 +54,11 @@ class HashRemapper
53
54
  def try_callable(from, to, data, acc)
54
55
  return unless to.respond_to?(:call)
55
56
 
56
- target_name, target_data = to.call(data[from], data)
57
+ target_name, target_data = to.call(data.dig(*from), data)
57
58
 
58
- if acc.key?(target_name) && acc[target_name].respond_to?(:merge)
59
- target_data = acc[target_name].merge(target_data)
60
- end
59
+ target_data = acc.dig(*target_name).respond_to?(:merge) && target_data.respond_to?(:merge) ?
60
+ acc.dig(*target_name).merge(target_data) :
61
+ target_data
61
62
 
62
63
  [target_name, target_data]
63
64
  end
@@ -66,34 +67,45 @@ class HashRemapper
66
67
  # (if the mapping value is enumerable)
67
68
  # @see https://github.com/smileart/hash_digger
68
69
  #
69
- # @param [Array] to the target key to map to ([new_key, digging_path, strict_flag])
70
+ # @param [Array] to the target key to map to ([new_key, {path: 'a.*.b', strict: false, lambda: ->(res){ … }}])
70
71
  # @param [Hash] data the whole original Hash to use as the digging target
71
72
  #
72
73
  # @return [Array(Object,Object)] key and its value to put to the resulting Hash
73
- def try_digging(to, data)
74
+ def try_digging(_from, to, data, acc)
74
75
  return unless to.respond_to?(:each)
75
76
 
76
77
  digger_args = to.fetch(1)
77
78
 
78
79
  # v0.1.0 backward compartability layer ([new_key, [:digg, :path, :keys]])
79
80
  return [to.first, data.dig(*to.last)] if digger_args.kind_of?(Array)
81
+ return unless digger_args.respond_to?(:fetch)
80
82
 
81
83
  lambda = digger_args.fetch(:lambda) { nil }
82
84
 
83
85
  # @see https://github.com/DamirSvrtan/fasterer — fetch_with_argument_vs_block
84
86
  # @see https://github.com/smileart/hash_digger — digger args
85
- [
86
- to.fetch(0),
87
- HashDigger::Digger.dig(
88
- data: data,
89
- path: digger_args.fetch(:path) { '*' },
90
- strict: digger_args.fetch(:strict) { true },
91
- default: digger_args.fetch(:default) { nil },
92
- &lambda
93
- )
87
+ target_name = to.fetch(0)
88
+
89
+ source_data = HashDigger::Digger.dig(
90
+ data: data,
91
+ path: digger_args.fetch(:path) { '*' },
92
+ strict: digger_args.fetch(:strict) { true },
93
+ default: digger_args.fetch(:default) { nil },
94
+ &lambda
95
+ )
96
+
97
+ return [
98
+ target_name,
99
+ acc.dig(*target_name).respond_to?(:merge) && source_data.respond_to?(:merge) ?
100
+ acc.dig(*target_name).merge(source_data) :
101
+ source_data
94
102
  ]
95
103
  end
96
104
 
105
+ def try_nesting(_from, to, data, _acc)
106
+ [to.fetch(0), data[to.fetch(1)]] if to.respond_to?(:fetch)
107
+ end
108
+
97
109
  # Method which automatically prepares direct mapping (e.g. { :a => :a })
98
110
  # for the keys that weren't used in the mapping Hash (to pass them through "as is")
99
111
  #
@@ -110,5 +122,16 @@ class HashRemapper
110
122
 
111
123
  mapping.merge(pass_trough_mapping)
112
124
  end
125
+
126
+ def set_value(data, key, value)
127
+ unless key.respond_to?(:each)
128
+ data[key] = value
129
+ return data
130
+ end
131
+
132
+ last_key = key.pop
133
+
134
+ data.dig(*key)[last_key] = value
135
+ end
113
136
  end
114
137
  end
@@ -0,0 +1,4 @@
1
+ class HashRemapper
2
+ # Current Library Version
3
+ VERSION = '0.4.2'
4
+ end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hash_remapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Serge Bedzhyk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-09 00:00:00.000000000 Z
11
+ date: 2019-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hash_digger
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.0.4
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: bundler
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -150,20 +170,6 @@ dependencies:
150
170
  - - "~>"
151
171
  - !ruby/object:Gem::Version
152
172
  version: '0.9'
153
- - !ruby/object:Gem::Dependency
154
- name: hash_digger
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '0.0'
160
- type: :runtime
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '0.0'
167
173
  description: A little lib which maps original keys to the new ones and more
168
174
  email: smileart21@gmail.com
169
175
  executables: []
@@ -182,9 +188,11 @@ files:
182
188
  - Rakefile
183
189
  - bin/console
184
190
  - bin/setup
191
+ - exmaples/weather.rb
185
192
  - hash_remapper.gemspec
186
193
  - img/hash_remapper.png
187
194
  - lib/hash_remapper.rb
195
+ - lib/hash_remapper/version.rb
188
196
  homepage: https://github.com/smileart/hash_remapper
189
197
  licenses:
190
198
  - MIT