light_mapper 0.0.2 → 1.0.4

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
- SHA1:
3
- metadata.gz: 739e3317c888c4ead5db06449dee80bf264cff23
4
- data.tar.gz: 353f10793c1cd990659934465b064fe035771f55
2
+ SHA256:
3
+ metadata.gz: 309f037da546bc788d238d3fd43c7f92f1d117a1b885a30b7eaf65dc2947a445
4
+ data.tar.gz: f7a0c2c83eb49ce56ccbf1da8b102bf4325f868d9c35dee3a2dc36377f6c96f2
5
5
  SHA512:
6
- metadata.gz: 6748d296cb4d18fa6c2f49e6777ec4e269e5eb19c961baa6e57677d8014958aa59c8464cbda208da8ff8b5b5a975974bdfffd86eaf9e6ae3ad00a68f619c8f68
7
- data.tar.gz: b52174375c53a98b10c09664096fa01ee0a6ec18e841c06a0f12571cd704580c48935c907faec8b32dd77aa6e60a05b518a969ff986f9e3d5e55a62db339131c
6
+ metadata.gz: 9c306788435e091828219174645e0034c4bdfa8a096643d506e34258831bbefbbccf5dca2b0595720207796c301b01dc988abbd312d04391a9c0a5fc2b2d5dc2
7
+ data.tar.gz: 0fbd144a196b3edc3110c81e945d1d7ed6880f7d5cd8b82d34d1754bbdb3a32d7069254b03d5f8d977118c182fe8446734f555da92a144f130029c55c391ecce
data/.rubocop.yml CHANGED
@@ -15,10 +15,6 @@ Style/Documentation:
15
15
  Style/PercentLiteralDelimiters:
16
16
  Enabled: false
17
17
 
18
- # Offense count: 2
19
- Style/RegexpLiteral:
20
- MaxSlashes: 0
21
-
22
18
  # Offense count: 1
23
19
  # Cop supports --auto-correct.
24
20
  # Configuration parameters: EnforcedStyle, SupportedStyles.
@@ -45,4 +41,4 @@ Metrics/LineLength:
45
41
  AllCops:
46
42
  Exclude:
47
43
  - '**/Guardfile'
48
- - '**/light_parmas.gemspec'
44
+ - '**/light_mapper.gemspec'
data/.travis.yml CHANGED
@@ -1,6 +1,4 @@
1
1
  language: ruby
2
2
  script: bundle exec rake
3
3
  rvm:
4
- - 2.0.0
5
- - 2.1.0
6
- - 2.1.4
4
+ - 3.1.2
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in light_mapper.gemspec
4
4
  gemspec
5
+
6
+ ruby '2.7.4'
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # LightMapper
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/light_mapper.svg)](http://badge.fury.io/rb/light_mapper)
4
+ [![Build Status](https://travis-ci.org/pniemczyk/light_mapper.svg?branch=0.2.0)](https://travis-ci.org/pniemczyk/light_mapper)
5
+ [![Dependency Status](https://gemnasium.com/pniemczyk/light_mapper.svg)](https://gemnasium.com/pniemczyk/light_mapper)
6
+
3
7
  This is simple mapper for hash.
4
8
 
5
9
  ## Installation
@@ -31,6 +35,16 @@ Or install it yourself as:
31
35
  'LastName' => :last_name
32
36
  )
33
37
  ```
38
+
39
+ or
40
+
41
+ ```ruby
42
+ LightMapper.mapping(
43
+ { 'FirstName' => 'Pawel', 'LastName' => 'Niemczyk' },
44
+ { 'FirstName' => :first_name, 'LastName' => :last_name }
45
+ )
46
+ ```
47
+
34
48
  result is obvious:
35
49
 
36
50
  ```ruby
@@ -53,20 +67,16 @@ data = {
53
67
  }
54
68
 
55
69
  data.extend(LightMapper).mapping(PersonMapper)
70
+ # {first_name: 'Pawel', last_name: 'Niemczyk', 'age': 5}
56
71
  ```
57
72
 
58
73
  ### When you require all keys
59
74
 
60
75
  ```ruby
61
- {
62
- 'FirstName' => 'Pawel'
63
- }.extend(LightMapper, require_keys: true).mapping(
64
- 'FirstName' => :first_name,
65
- 'LastName' => :last_name
66
- )
76
+ { 'FirstName' => 'Pawel' }.extend(LightMapper).mapping({'FirstName' => :first_name, 'LastName' => :last_name}, strict: true)
67
77
  ```
68
78
 
69
- it will raise KeyError
79
+ it will raise LightMapper::KeyMissing: LastName key not found; Full path LastName
70
80
 
71
81
  ### When you want to pass string or symbol keys
72
82
 
@@ -74,10 +84,10 @@ it will raise KeyError
74
84
  {
75
85
  'FirstName' => 'Pawel',
76
86
  second_name: 'Niemczyk'
77
- }.extend(LightMapper, any_keys_kind: true).mapping(
78
- 'FirstName' => :first_name,
79
- 'second_name' => :last_name
80
- )
87
+ }.extend(LightMapper).mapping({
88
+ 'FirstName' => :first_name,
89
+ 'second_name' => :last_name
90
+ }, any_keys: true)
81
91
  ```
82
92
 
83
93
  result will be:
@@ -86,6 +96,81 @@ result will be:
86
96
  { first_name: 'Pawel', last_name: 'Niemczyk' }
87
97
  ```
88
98
 
99
+ ### Support for nested hashes, arrays and objects (now we talking what it is capable of)
100
+
101
+ ```ruby
102
+ {
103
+ 'source' => { 'google' => { 'search_word' => 'ruby' } },
104
+ 'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
105
+ 'roles' => %w[admin manager user],
106
+ 'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
107
+ 'scores' => [ 10, 2, 5, 1000],
108
+ 'last_4_payments' => [
109
+ { 'amount' => 100, 'currency' => 'USD' },
110
+ { 'amount' => 200, 'currency' => 'USD' },
111
+ { 'amount' => 300, 'currency' => 'USD' },
112
+ { 'amount' => 400, 'currency' => 'USD' }
113
+ ],
114
+ 'array' => [
115
+ [1,2,3],
116
+ [4,5,6],
117
+ [
118
+ 7,
119
+ 8,
120
+ [':D']
121
+ ],
122
+ ]
123
+ }.extend(LightMapper).mapping(
124
+ 'source.google.search_word' => :word,
125
+ 'user.email' => :email,
126
+ 'user.as_json.name' => :name,
127
+ 'roles.0' => :first_role,
128
+ ['roles', 1] => :middle_role,
129
+ 'roles.last' => :last_role,
130
+ (->(source) { source[:mixed][:users].find { |user| user.manager }.email }) => :manager_email,
131
+ (->(source) { source[:mixed][:users].find { |user| user.manager }.name }) => :manager_name,
132
+ 'mixed.users.last.name' => :last_user_name,
133
+ (->(source) { source[:last_4_payments].map(&:values).map(&:first).max }) => :quarterly_payment_amount,
134
+ 'scores.sum' => :final_score,
135
+ 'array.2.2.first' => :smile
136
+ )
137
+ ```
138
+
139
+ result will be:
140
+
141
+ ```ruby
142
+ {
143
+ word: 'ruby',
144
+ email: 'pawel@example.com',
145
+ name: 'Pawel',
146
+ first_role: 'admin',
147
+ last_role: 'user',
148
+ manager_email: 'max@example.com',
149
+ manager_name: 'Max',
150
+ last_user_name: 'Pawel',
151
+ quarterly_payment_amount: 1000,
152
+ final_score: 1017
153
+ }
154
+ ```
155
+
156
+ ### Mappers selection via pattern matching
157
+
158
+ ```ruby
159
+ GOOGLE_MAPPER = { 'result.user.name' => :name }
160
+ LINKEDIN_MAPPER = { 'result.client.display_name' => :word }
161
+
162
+ data = { source: 'google', user: { name: 'test'}, 'result' => { 'user' => { 'name' => 'Pawel'} } }
163
+ mapper = case data
164
+ in source: 'google', user: {name:} then GOOGLE_MAPPER
165
+ in source: 'linkedin', client: {display_name:} then LINKEDIN_MAPPER
166
+ else
167
+ raise 'Unknown mapper'
168
+ end
169
+
170
+ data.extend(LightMapper).mapping(mapper)
171
+ # result { name: 'Pawel' }
172
+ ```
173
+
89
174
  ## Contributing
90
175
 
91
176
  1. Fork it ( https://github.com/[my-github-username]/light_mapper/fork )
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
2
3
 
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -1,3 +1,3 @@
1
1
  module LightMapper
2
- VERSION = '0.0.2'
2
+ VERSION = '1.0.4'
3
3
  end
data/lib/light_mapper.rb CHANGED
@@ -1,29 +1,78 @@
1
1
  require 'light_mapper/version'
2
2
 
3
3
  module LightMapper
4
- def mapping(mappings, opts = {})
5
- LightMapper.mapping(clone, mappings, opts)
6
- end
4
+ InvalidKey = Class.new(StandardError)
5
+ KeyMissing = Class.new(StandardError)
7
6
 
8
- def self.mapping(hash, mappings, opts = {})
9
- require_keys = opts[:require_keys] == true
10
- any_keys_kind = opts[:any_keys_kind] == true
11
- fetch_method = if any_keys_kind
12
- if require_keys
13
- -> (hash, key) { hash.fetch(key.to_s, hash.fetch(key.to_sym)) }
7
+ module Helper
8
+ def self.raise_key_missing(current, full_path, additional_message = nil)
9
+
10
+ raise KeyMissing, ["#{current} key not found; Full path #{full_path.map(&:to_s).join('.')}", additional_message].compact.join('; ')
11
+ end
12
+
13
+ def self.key_destructor(value)
14
+ case value
15
+ in String
16
+ value.split('.')
17
+ in Symbol
18
+ [value]
19
+ in Array
20
+ value
14
21
  else
15
- -> (hash, key) { hash[key.to_s] || hash[key.to_sym] }
22
+ raise InvalidKey, "Invalid key type: #{value.class}"
16
23
  end
17
- else
18
- if require_keys
19
- -> (hash, key) { hash.fetch(key) }
24
+ end
25
+
26
+ def self.value_extractor(object, current, path, full_path, strict = false, any_keys = false)
27
+ result = case object
28
+ in Hash
29
+ hash_key_extractor(object, current, full_path, strict, any_keys)
30
+ in Array
31
+ array_key_extractor(object, current, full_path, strict, any_keys)
32
+ in NilClass
33
+ nil
34
+ else
35
+ method_name = current.to_s.to_sym
36
+ object.respond_to?(method_name) ? object.send(method_name) : !strict ? nil : raise_key_missing(current, full_path)
37
+ end
38
+
39
+ path.compact.empty? ? result : value_extractor(result, path.first, path[1..-1], full_path, strict, any_keys)
40
+ end
41
+
42
+ def self.hash_key_extractor(object, current, full_path, strict, any_keys)
43
+ keys = any_keys ? [current, current.to_s, current.to_s.to_sym] : [current]
44
+ raise_key_missing(current, full_path) if strict && !keys.any? { |k| object.key?(k) }
45
+
46
+ object.values_at(*keys).compact.first
47
+ end
48
+
49
+ def self.array_key_extractor(object, current, full_path, strict, _any_keys)
50
+ index = current.to_s.match(/^(\d)+$/) ? current.to_i : nil
51
+
52
+ if index
53
+ raise_key_missing(current, full_path) if strict && index && object.size < index.next
54
+
55
+ object[index]
20
56
  else
21
- -> (hash, key) { hash[key] }
57
+ method_name = current.to_s.to_sym
58
+ raise_key_missing(current, full_path, "Array do not respond on #{method_name}") if strict && !object.respond_to?(method_name)
59
+
60
+ object.public_send(method_name)
22
61
  end
23
62
  end
63
+ end
64
+
65
+ def mapping(mappings, opts = {})
66
+ LightMapper.mapping(clone, mappings, opts)
67
+ end
68
+
69
+ def self.mapping(hash, mappings, opts = {})
70
+ strict, any_keys = opts.values_at(:strict, :any_keys)
71
+ mappings.each_with_object({}) do |(k, v), h|
72
+ next h[v] = k.call(hash) if k.is_a?(Proc)
24
73
 
25
- {}.tap do |h|
26
- mappings.each { |k, v| h[v] = fetch_method.call(hash, k) }
74
+ key_path = Helper.key_destructor(k)
75
+ h[v] = Helper.value_extractor(hash, key_path.first, key_path[1..-1], key_path, strict, any_keys)
27
76
  end
28
77
  end
29
78
  end
data/light_mapper.gemspec CHANGED
@@ -17,8 +17,9 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = %w(lib)
20
+ spec.required_ruby_version = '>= 2.7'
20
21
 
21
- spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'bundler', '~> 2.3'
22
23
  spec.add_development_dependency 'rake', '~> 10.4'
23
24
  spec.add_development_dependency 'rspec', '~> 3.0'
24
25
  spec.add_development_dependency 'guard', '~> 2.12'
@@ -1,4 +1,17 @@
1
1
  require 'spec_helper'
2
+ class User
3
+ attr_accessor :name, :email, :manager
4
+
5
+ def initialize(name:, email:, manager: false)
6
+ @name = name
7
+ @email = email
8
+ @manager = manager
9
+ end
10
+
11
+ def as_json
12
+ { 'name' => name, 'email' => email, 'manager' => manager }
13
+ end
14
+ end
2
15
 
3
16
  describe LightMapper do
4
17
 
@@ -12,7 +25,7 @@ describe LightMapper do
12
25
  }
13
26
  end
14
27
 
15
- let(:maper) do
28
+ let(:mapper) do
16
29
  {
17
30
  'A' => :a,
18
31
  'b' => 'z'
@@ -20,7 +33,7 @@ describe LightMapper do
20
33
  end
21
34
 
22
35
  it 'return correct hash' do
23
- expect(subject.mapping(maper)).to eq(a: original_hash['A'], 'z' => original_hash['b'])
36
+ expect(subject.mapping(mapper)).to eq(a: original_hash['A'], 'z' => original_hash['b'])
24
37
  end
25
38
  end
26
39
 
@@ -32,7 +45,7 @@ describe LightMapper do
32
45
  }
33
46
  end
34
47
 
35
- let(:maper) do
48
+ let(:mapper) do
36
49
  {
37
50
  'A' => :a,
38
51
  'c' => 'z'
@@ -40,11 +53,11 @@ describe LightMapper do
40
53
  end
41
54
 
42
55
  it 'raise KeyError when required key missing' do
43
- expect { subject.mapping(maper, require_keys: true) }.to raise_error(KeyError)
56
+ expect { subject.mapping(mapper, strict: true) }.to raise_error(LightMapper::KeyMissing)
44
57
  end
45
58
  end
46
59
 
47
- describe 'basic data mapping when any keys kind is allowed' do
60
+ describe 'basic data mapping when any keys kind is allowed' do
48
61
  let(:original_hash) do
49
62
  {
50
63
  'A' => 'test',
@@ -54,7 +67,7 @@ describe LightMapper do
54
67
  }
55
68
  end
56
69
 
57
- let(:maper) do
70
+ let(:mapper) do
58
71
  {
59
72
  'A' => :a,
60
73
  'c' => :c,
@@ -63,7 +76,7 @@ describe LightMapper do
63
76
  end
64
77
 
65
78
  it 'return correct hash' do
66
- expect(subject.mapping(maper, any_keys_kind: true)).to eq(
79
+ expect(subject.mapping(mapper, any_keys: true)).to eq(
67
80
  a: original_hash['A'],
68
81
  c: original_hash[:c],
69
82
  'z' => original_hash['b']
@@ -71,7 +84,7 @@ describe LightMapper do
71
84
  end
72
85
 
73
86
  describe 'raise KeyError' do
74
- let(:maper) do
87
+ let(:mapper) do
75
88
  {
76
89
  'A' => :a,
77
90
  'c' => :c,
@@ -80,11 +93,126 @@ describe LightMapper do
80
93
  end
81
94
 
82
95
  it ' when required key missing' do
83
- expect { subject.mapping(maper, require_keys: true, any_keys_kind: true) }.to raise_error(KeyError)
96
+ expect { subject.mapping(mapper, strict: true, any_keys: true) }.to raise_error(LightMapper::KeyMissing)
84
97
  end
85
98
  end
86
- end
99
+ end
100
+
101
+ describe 'more advanced mapping' do
102
+ let(:source) do
103
+ {
104
+ 'source' => { 'google' => { 'search_word' => 'ruby' } },
105
+ 'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
106
+ 'roles' => %w[admin manager user],
107
+ 'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
108
+ 'scores' => [ 10, 2, 5, 1000],
109
+ 'last_4_payments' => [
110
+ { 'amount' => 100, 'currency' => 'USD' },
111
+ { 'amount' => 200, 'currency' => 'USD' },
112
+ { 'amount' => 300, 'currency' => 'USD' },
113
+ { 'amount' => 400, 'currency' => 'USD' }
114
+ ],
115
+ 'array' => [
116
+ [1,2,3],
117
+ [4,5,6],
118
+ [
119
+ 7,
120
+ 8,
121
+ [':D']
122
+ ],
123
+ ]
124
+ }
125
+ end
126
+
127
+ context 'with nested hash mapping' do
128
+ let(:mapping) { {'source.google.search_word' => :word} }
129
+
130
+ it 'return correct result' do
131
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(word: 'ruby')
132
+ end
133
+ end
134
+
135
+ context 'with nested array mapping' do
136
+ let(:mapping) { {'array.2.2.first' => :result} }
137
+
138
+ it 'return correct result' do
139
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(result: ':D')
140
+ end
141
+ end
142
+
143
+ context 'with nested object mapping' do
144
+ let(:mapping) { {'user.email' => :result} }
87
145
 
88
- it 'nested keys'
89
- it 'value conversion'
146
+ it 'return correct result' do
147
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(result: 'pawel@example.com')
148
+ end
149
+ end
150
+
151
+ context 'with mapping proc' do
152
+ let(:mapping) { { (->(source) { source['last_4_payments'].last['amount'].to_s }) => :result } }
153
+
154
+ it 'return correct result' do
155
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(result: '400')
156
+ end
157
+ end
158
+
159
+ context 'with mix of nested object types' do
160
+ let(:mapping) do
161
+ {
162
+ 'source.google.search_word' => :word,
163
+ 'user.email' => :email,
164
+ 'user.as_json.name' => :name,
165
+ 'roles.0' => :first_role,
166
+ ['roles', 1] => :middle_role,
167
+ 'roles.last' => :last_role,
168
+ (->(source) { source['mixed'][:users].find { |user| user.manager }.email }) => :manager_email,
169
+ (->(source) { source['mixed'][:users].find { |user| user.manager }.name }) => :manager_name,
170
+ 'mixed.users.last.name' => :last_user_name,
171
+ (->(source) { source['last_4_payments'].map(&:values).map(&:first).max }) => :quarterly_payment_amount,
172
+ 'scores.sum' => :final_score,
173
+ 'array.2.2.first' => :smile
174
+ }
175
+ end
176
+
177
+ it 'return correct result' do
178
+ expect(source.extend(LightMapper).mapping(mapping, any_keys: true)).to match({
179
+ word: 'ruby',
180
+ email: 'pawel@example.com',
181
+ name: 'Pawel',
182
+ final_score: 1017,
183
+ first_role: 'admin',
184
+ last_role: 'user',
185
+ manager_email: 'max@example.com',
186
+ manager_name: 'Max',
187
+ last_user_name: 'Pawel',
188
+ middle_role: 'manager',
189
+ quarterly_payment_amount: 400,
190
+ smile: ':D'
191
+ })
192
+ end
193
+
194
+ it 'return correct result base on proper key types' do
195
+ expect(source.extend(LightMapper).mapping(mapping)).to match({
196
+ word: 'ruby',
197
+ email: 'pawel@example.com',
198
+ name: 'Pawel',
199
+ final_score: 1017,
200
+ first_role: 'admin',
201
+ last_role: 'user',
202
+ manager_email: 'max@example.com',
203
+ manager_name: 'Max',
204
+ last_user_name: nil,
205
+ middle_role: 'manager',
206
+ quarterly_payment_amount: 400,
207
+ smile: ':D'
208
+ })
209
+ end
210
+
211
+ it 'raise KeyMissing error' do
212
+ expect { source.extend(LightMapper).mapping({'user.non_existence_method' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
213
+ expect { source.extend(LightMapper).mapping({'last_4_payments.4' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
214
+ expect { source.extend(LightMapper).mapping({'source.google.missing_key' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
215
+ end
216
+ end
217
+ end
90
218
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: light_mapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pawel Niemczyk
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-27 00:00:00.000000000 Z
11
+ date: 2022-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: '2.3'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: '2.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -147,7 +147,7 @@ homepage: https://github.com/pniemczyk/light_mapper
147
147
  licenses:
148
148
  - MIT
149
149
  metadata: {}
150
- post_install_message:
150
+ post_install_message:
151
151
  rdoc_options: []
152
152
  require_paths:
153
153
  - lib
@@ -155,16 +155,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
155
  requirements:
156
156
  - - ">="
157
157
  - !ruby/object:Gem::Version
158
- version: '0'
158
+ version: '2.7'
159
159
  required_rubygems_version: !ruby/object:Gem::Requirement
160
160
  requirements:
161
161
  - - ">="
162
162
  - !ruby/object:Gem::Version
163
163
  version: '0'
164
164
  requirements: []
165
- rubyforge_project:
166
- rubygems_version: 2.4.5
167
- signing_key:
165
+ rubygems_version: 3.1.6
166
+ signing_key:
168
167
  specification_version: 4
169
168
  summary: Very light hash mapper
170
169
  test_files: