deep_hash_transformer 2.0.0 → 2.2.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
  SHA256:
3
- metadata.gz: a7f171270350f94fdce54754d2cd72e7832b50d96fd92255e7c0ccf2b4a4460b
4
- data.tar.gz: bfbe82ac975f7a38f21a26ffd969fb226592ec67e4d54c6009213ad56883e146
3
+ metadata.gz: 7e757436383ce13e17ee8e2b68e48d419e2f9f3c2e57fcfa76503e723ecde5e0
4
+ data.tar.gz: 5508770653b53402c6be53f9a3b1c0ea42a899b42760c2844565c3befa4bc54c
5
5
  SHA512:
6
- metadata.gz: 3c2b9b9c03df98e7407c1ff2e365b2b49a5e200380b1a3747be28d3bcdb8832be8c59c5b94db2753b93809549d7841da2bf1e0a9d6bb425c1c9f545802bea830
7
- data.tar.gz: cab7709f4bf6dbb2a3c9d692c0d3bb4af83b4ce318a020624ad7151c7e1d71f40dde2dec2df51326d79e0f9ae7f2a97c72ba11c65c38467e3e617d26bb572d1f
6
+ metadata.gz: 02c165dda7044c595af36f476fe4234dfca724654e4384d971c183f14dadab6a1f9782f58dd5736f2b82962b7811a41fd51350b2a755ef62cfe684b259dd705d
7
+ data.tar.gz: 41ed65816b738a755fdfbf9e97c0c6ef408fa022a16230d504b0acb4f744ef10279f98954b3eb1c22eaf7af552a9ef5a40c61b75e68cfe4f723f444ebd7e9987
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DeepHashTransformer
4
+ class Blank
5
+ BLANK_STRING = /\A[[:space:]]*\z/.freeze
6
+
7
+ def self.call(value)
8
+ new(value).blank?
9
+ end
10
+
11
+ def initialize(value)
12
+ @value = value
13
+ end
14
+
15
+ def blank?
16
+ return true unless value
17
+ return value.blank? if value.respond_to?(:blank?)
18
+ return BLANK_STRING.match?(value) if value.is_a?(String)
19
+ return value.empty? if value.respond_to?(:empty?)
20
+
21
+ false
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :value
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './blank'
4
+
5
+ class DeepHashTransformer
6
+ module CollectionOperation
7
+ class << self
8
+ def compact(val)
9
+ case val
10
+ when Array, Hash
11
+ val.compact
12
+ else
13
+ val
14
+ end
15
+ end
16
+
17
+ def compact_blank(val)
18
+ case val
19
+ when Array
20
+ val.reject { |elem| Blank.call(elem) }
21
+ when Hash
22
+ val.reject { |_, v| Blank.call(v) }
23
+ else
24
+ val
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DeepHashTransformer
4
+ module ElementOperation
5
+ class << self
6
+ def camel_case(val)
7
+ pascal_case(val)
8
+ .sub(/^[A-Z]/, &:downcase)
9
+ end
10
+
11
+ def dasherize(val)
12
+ stringify(val)
13
+ .tr('_', '-')
14
+ end
15
+
16
+ def identity(val)
17
+ val
18
+ end
19
+
20
+ def pascal_case(val)
21
+ stringify(val)
22
+ .split(/(?=[A-Z])|[-_]/)
23
+ .map(&:capitalize)
24
+ .join
25
+ end
26
+
27
+ def stringify(val)
28
+ val.to_s
29
+ end
30
+
31
+ def symbolize(val)
32
+ val.to_sym
33
+ end
34
+
35
+ def underscore(val)
36
+ stringify(val)
37
+ .tr('-', '_')
38
+ end
39
+
40
+ def snake_case(val)
41
+ stringify(val)
42
+ .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
43
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
44
+ .tr('-', '_')
45
+ .downcase
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class DeepHashTransformer
4
- VERSION = '2.0.0'
4
+ VERSION = '2.2.0'
5
5
  end
@@ -1,25 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'deep_hash_transformer/collection_operation'
4
+ require 'deep_hash_transformer/element_operation'
3
5
  require 'deep_hash_transformer/version'
4
6
 
5
7
  class DeepHashTransformer
6
- OPS = {
7
- dasherize: ->(val) { val.to_s.tr('_', '-') },
8
- identity: ->(val) { val },
9
- stringify: ->(val) { val.to_s },
10
- symbolize: ->(val) { val.to_sym },
11
- underscore: ->(val) { val.to_s.tr('-', '_') }
12
- }.freeze
8
+ ELEMENT_OPS = %i[
9
+ camel_case
10
+ dasherize
11
+ identity
12
+ pascal_case
13
+ snake_case
14
+ stringify
15
+ symbolize
16
+ underscore
17
+ ].freeze
18
+
19
+ COLLECTION_OPS = %i[
20
+ compact
21
+ compact_blank
22
+ ].freeze
23
+
24
+ OPS = ELEMENT_OPS + COLLECTION_OPS
13
25
 
14
26
  def initialize(hash)
15
27
  @hash = hash
16
28
  end
17
29
 
18
30
  def tr(*ops)
31
+ unknown_transformations = ops.map(&:to_s) - OPS.map(&:to_s)
32
+ if unknown_transformations.any?
33
+ raise(
34
+ ArgumentError, "unknown transformation(s): #{unknown_transformations.join(',')}"
35
+ )
36
+ end
37
+
19
38
  transform_value(hash, ops)
20
39
  end
21
40
 
22
- OPS.each_key do |operation|
41
+ OPS.each do |operation|
23
42
  define_method(operation) { tr(operation) }
24
43
  end
25
44
 
@@ -27,20 +46,30 @@ class DeepHashTransformer
27
46
 
28
47
  attr_reader :hash
29
48
 
30
- def transform_value(value, ops)
31
- case value
32
- when Array
33
- value.map { |e| transform_value(e, ops) }
34
- when Hash
35
- value.map { |k, v| [transform_key(k, ops), transform_value(v, ops)] }.to_h
36
- else
37
- value
49
+ def transform_collection(collection, ops)
50
+ ops.inject(collection) do |c, op|
51
+ COLLECTION_OPS.include?(op) ? CollectionOperation.public_send(op, c) : c
38
52
  end
39
53
  end
40
54
 
55
+ def transform_value(value, ops)
56
+ collection = case value
57
+ when Array
58
+ value.map { |e| transform_value(e, ops) }
59
+ when Hash
60
+ value.map { |k, v| [transform_key(k, ops), transform_value(v, ops)] }.to_h
61
+ else
62
+ value
63
+ end
64
+
65
+ transform_collection(collection, ops)
66
+ end
67
+
41
68
  def transform_key(key, ops)
42
69
  return key unless [String, Symbol].include?(key.class)
43
70
 
44
- ops.inject(key) { |k, op| OPS.fetch(op).call(k) }
71
+ ops.inject(key) do |k, op|
72
+ ELEMENT_OPS.include?(op) ? ElementOperation.public_send(op, k) : k
73
+ end
45
74
  end
46
75
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe DeepHashTransformer::Blank do
4
+ describe '.call' do
5
+ subject { described_class.call(value) }
6
+
7
+ presents = [true, 1, 0, 'any', [nil], { A: nil }]
8
+ blanks = [nil, false, '', ' ', [], {}]
9
+
10
+ presents.each do |value|
11
+ context "with #{value.inspect}" do
12
+ let(:value) { value }
13
+
14
+ it { is_expected.to be false }
15
+ end
16
+ end
17
+
18
+ blanks.each do |value|
19
+ context "with #{value.inspect}" do
20
+ let(:value) { value }
21
+
22
+ it { is_expected.to be true }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe DeepHashTransformer::CollectionOperation do
4
+ examples = [
5
+ { a: 'b' }, { a: nil }, { a: ' ' }, [:foo], ['', nil]
6
+ ]
7
+
8
+ subject do
9
+ examples.map { |value| described_class.public_send(method, value) }
10
+ end
11
+
12
+ describe '.compact' do
13
+ let(:method) { :compact }
14
+
15
+ expected_output = [
16
+ { a: 'b' }, {}, { a: ' ' }, [:foo], ['']
17
+ ]
18
+
19
+ it { is_expected.to eq(expected_output) }
20
+ end
21
+
22
+ describe '.compact_blank' do
23
+ let(:method) { :compact_blank }
24
+
25
+ expected_output = [
26
+ { a: 'b' }, {}, {}, [:foo], []
27
+ ]
28
+
29
+ it { is_expected.to eq(expected_output) }
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe DeepHashTransformer::ElementOperation do
4
+ examples = [
5
+ :symbol, 'string', 'camelCase', 'dashed-key', 'PascalCase', 'under_scored'
6
+ ]
7
+
8
+ subject do
9
+ examples.map { |value| described_class.public_send(method, value) }
10
+ end
11
+
12
+ # rubocop:disable Layout/HashAlignment, Style/SymbolArray, Style/WordArray
13
+ expected_output = {
14
+ camel_case: ['symbol', 'string', 'camelCase', 'dashedKey', 'pascalCase', 'underScored'],
15
+ dasherize: ['symbol', 'string', 'camelCase', 'dashed-key', 'PascalCase', 'under-scored'],
16
+ identity: [:symbol, 'string', 'camelCase', 'dashed-key', 'PascalCase', 'under_scored'],
17
+ pascal_case: ['Symbol', 'String', 'CamelCase', 'DashedKey', 'PascalCase', 'UnderScored'],
18
+ snake_case: ['symbol', 'string', 'camel_case', 'dashed_key', 'pascal_case', 'under_scored'],
19
+ stringify: ['symbol', 'string', 'camelCase', 'dashed-key', 'PascalCase', 'under_scored'],
20
+ symbolize: [:symbol, :string, :camelCase, :'dashed-key', :PascalCase, :under_scored],
21
+ underscore: ['symbol', 'string', 'camelCase', 'dashed_key', 'PascalCase', 'under_scored']
22
+ }
23
+ # rubocop:enable Layout/HashAlignment, Style/SymbolArray, Style/WordArray
24
+
25
+ DeepHashTransformer::ELEMENT_OPS.each do |method|
26
+ describe "##{method}" do
27
+ let(:method) { method }
28
+
29
+ it { is_expected.to eq(expected_output[method]) }
30
+ end
31
+ end
32
+ end
@@ -1,87 +1,128 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe DeepHashTransformer do
4
- subject do
5
- described_class.new(
6
- Integer => 123,
7
- :foobar => { bar: 'bar' },
8
- 'aa_zz' => [{ 'bar' => :bar, 'a-z' => 'a-z' }]
9
- )
10
- end
4
+ subject(:operation) { described_class.new(example) }
5
+
6
+ let(:example) { { foo: 'bar' } }
11
7
 
12
- describe '#tr with `:underscore, :symbolize`' do
13
- subject { super().tr(:underscore, :symbolize) }
8
+ describe 'autogenerated methods' do
9
+ DeepHashTransformer::ELEMENT_OPS.each do |method|
10
+ describe ".#{method}" do
11
+ it "delegates to `Operation.#{method}`" do
12
+ expect(
13
+ DeepHashTransformer::ElementOperation
14
+ ).to receive(method).with(:foo).once # rubocop:disable RSpec/MessageSpies
14
15
 
15
- it do
16
- expect(subject).to eq( # rubocop:disable RSpec/NamedSubject
17
- Integer => 123,
18
- :foobar => { bar: 'bar' },
19
- :aa_zz => [{ bar: :bar, a_z: 'a-z' }]
20
- )
16
+ operation.public_send(method)
17
+ end
18
+ end
21
19
  end
22
20
  end
23
21
 
24
- describe '#dasherize' do
25
- subject { super().dasherize }
22
+ describe '#tr' do
23
+ context 'with `:snake_case, :symbolize`' do
24
+ subject { super().tr(:snake_case, :symbolize) }
26
25
 
27
- it do
28
- expect(subject).to eq( # rubocop:disable RSpec/NamedSubject
29
- Integer => 123,
30
- 'foobar' => { 'bar' => 'bar' },
31
- 'aa-zz' => [{ 'bar' => :bar, 'a-z' => 'a-z' }]
32
- )
26
+ let(:example) { { 'FooBar' => 'baz' } }
27
+
28
+ it { is_expected.to eq(foo_bar: 'baz') }
33
29
  end
34
- end
35
30
 
36
- describe '#identity' do
37
- subject { super().identity }
31
+ context 'with an unknown transformation' do
32
+ subject(:unknown_ops) { operation.tr(:unknown) }
38
33
 
39
- it do
40
- expect(subject).to eq( # rubocop:disable RSpec/NamedSubject
41
- Integer => 123,
42
- :foobar => { bar: 'bar' },
43
- 'aa_zz' => [{ 'bar' => :bar, 'a-z' => 'a-z' }]
44
- )
34
+ it 'raise an exception' do
35
+ expect { unknown_ops }.to raise_error(ArgumentError, /unknown/)
36
+ end
45
37
  end
46
- end
47
38
 
48
- describe '#stringify' do
49
- subject { super().stringify }
39
+ context 'with a complex, nested example' do
40
+ subject { super().tr(:camel_case, :symbolize) }
41
+
42
+ let(:example) do
43
+ {
44
+ Integer => 123,
45
+ :symbol => { foo_bar: 'bar' },
46
+ 'string' => { 'foo_bar' => 123 },
47
+ 'nested-array' => [
48
+ {
49
+ 'camelCased' => 'camelCased',
50
+ 'dashed-key' => 'dashed-key',
51
+ 'PascalCased' => 'PascalCased',
52
+ 'under_scored' => 'under_scored'
53
+ }
54
+ ]
55
+ }
56
+ end
50
57
 
51
- it do
52
- expect(subject).to eq( # rubocop:disable RSpec/NamedSubject
53
- Integer => 123,
54
- 'foobar' => { 'bar' => 'bar' },
55
- 'aa_zz' => [{ 'bar' => :bar, 'a-z' => 'a-z' }]
56
- )
58
+ it do # rubocop:disable RSpec/ExampleLength
59
+ is_expected.to eq( # rubocop:disable RSpec/ImplicitSubject
60
+ Integer => 123,
61
+ :symbol => { fooBar: 'bar' },
62
+ :string => { fooBar: 123 },
63
+ :nestedArray => [
64
+ {
65
+ camelCased: 'camelCased',
66
+ dashedKey: 'dashed-key',
67
+ pascalCased: 'PascalCased',
68
+ underScored: 'under_scored'
69
+ }
70
+ ]
71
+ )
72
+ end
57
73
  end
58
- end
59
74
 
60
- describe '#symbolize' do
61
- subject { super().symbolize }
75
+ context 'with nil values' do
76
+ subject { super().tr(:compact, :stringify) }
62
77
 
63
- it do
64
- expect(subject).to eq( # rubocop:disable RSpec/NamedSubject
65
- Integer => 123,
66
- :foobar => { bar: 'bar' },
67
- :aa_zz => [{ bar: :bar, 'a-z': 'a-z' }]
68
- )
78
+ let(:example) do
79
+ { a: { b: ['', nil, :value], c: '', d: nil, e: true, f: false, g: 123, h: [''] } }
80
+ end
81
+
82
+ it do # rubocop:disable RSpec/ExampleLength
83
+ is_expected.to eq( # rubocop:disable RSpec/ImplicitSubject
84
+ 'a' => {
85
+ 'b' => ['', :value],
86
+ 'c' => '',
87
+ 'e' => true,
88
+ 'f' => false,
89
+ 'g' => 123,
90
+ 'h' => ['']
91
+ }
92
+ )
93
+ end
69
94
  end
70
- end
71
95
 
72
- describe '#underscore' do
73
- subject { super().underscore }
96
+ context 'with blank values' do
97
+ subject { super().tr(:compact_blank, :stringify) }
98
+
99
+ let(:example) do
100
+ { a: { b: ['', nil, :value], c: '', d: nil, e: true, f: false, g: 123, h: [''] } }
101
+ end
102
+
103
+ it do # rubocop:disable RSpec/ExampleLength
104
+ is_expected.to eq( # rubocop:disable RSpec/ImplicitSubject
105
+ 'a' => {
106
+ 'b' => [:value],
107
+ 'e' => true,
108
+ 'g' => 123
109
+ }
110
+ )
111
+ end
112
+ end
113
+
114
+ context 'with deeply nested blank values' do
115
+ subject { super().tr(:compact_blank) }
116
+
117
+ let(:example) do
118
+ { a: { b: [nil, { c: nil }] } }
119
+ end
74
120
 
75
- it do
76
- expect(subject).to eq( # rubocop:disable RSpec/NamedSubject
77
- Integer => 123,
78
- 'foobar' => { 'bar' => 'bar' },
79
- 'aa_zz' => [{ 'bar' => :bar, 'a_z' => 'a-z' }]
80
- )
121
+ it { is_expected.to eq({}) }
81
122
  end
82
123
  end
83
124
 
84
125
  it 'has a version number' do
85
- expect(DeepHashTransformer::VERSION).not_to be nil
126
+ expect(DeepHashTransformer::VERSION).not_to be_nil
86
127
  end
87
128
  end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'coveralls'
5
4
  require 'deep_hash_transformer'
5
+ require 'deep_hash_transformer/blank'
6
+ require 'deep_hash_transformer/collection_operation'
7
+ require 'deep_hash_transformer/element_operation'
8
+ require 'simplecov'
6
9
 
7
- Coveralls.wear!
10
+ SimpleCov.start do
11
+ if ENV['CI']
12
+ require 'simplecov-lcov'
13
+
14
+ SimpleCov::Formatter::LcovFormatter.config do |c|
15
+ c.report_with_single_file = true
16
+ c.single_report_path = 'coverage/lcov.info'
17
+ end
18
+
19
+ formatter SimpleCov::Formatter::LcovFormatter
20
+ end
21
+
22
+ add_filter %w[version.rb initializer.rb]
23
+ end
8
24
 
9
25
  RSpec.configure do |config|
10
26
  # Enable flags like --only-failures and --next-failure
metadata CHANGED
@@ -1,100 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deep_hash_transformer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Spickermann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-28 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: coveralls
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rubocop
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: rubocop-performance
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rubocop-rspec
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- description: " DeepHashTransformer helps to transform deeply nested hash structures\n"
11
+ date: 2023-05-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: " DeepHashTransformer helps to transform keys in deeply nested hash
14
+ structures\n"
98
15
  email:
99
16
  - spickermann@gmail.com
100
17
  executables: []
@@ -103,13 +20,20 @@ extra_rdoc_files: []
103
20
  files:
104
21
  - MIT-LICENSE
105
22
  - lib/deep_hash_transformer.rb
23
+ - lib/deep_hash_transformer/blank.rb
24
+ - lib/deep_hash_transformer/collection_operation.rb
25
+ - lib/deep_hash_transformer/element_operation.rb
106
26
  - lib/deep_hash_transformer/version.rb
27
+ - spec/deep_hash_transformer/blank_spec.rb
28
+ - spec/deep_hash_transformer/collection_operation_spec.rb
29
+ - spec/deep_hash_transformer/element_operation_spec.rb
107
30
  - spec/deep_hash_transformer_spec.rb
108
31
  - spec/spec_helper.rb
109
32
  homepage: https://github.com/spickermann/deep_hash_transformer
110
33
  licenses:
111
34
  - MIT
112
- metadata: {}
35
+ metadata:
36
+ rubygems_mfa_required: 'true'
113
37
  post_install_message:
114
38
  rdoc_options: []
115
39
  require_paths:
@@ -125,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
49
  - !ruby/object:Gem::Version
126
50
  version: '0'
127
51
  requirements: []
128
- rubygems_version: 3.2.3
52
+ rubygems_version: 3.4.10
129
53
  signing_key:
130
54
  specification_version: 4
131
55
  summary: Transforms deeply nested hash structure