hash-mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f1c56d04bcaafe897567fb67d702384f63ba055317e33986a9ffef7fd59039cf
4
+ data.tar.gz: c8186acb1376d11698734052311f364595fb889fe8b9cd6d327b9f17bcb60588
5
+ SHA512:
6
+ metadata.gz: 5d0eead6cfadd731678cc539f72851b015c0a66304b4a794c212611645571cedd75c8147168265bc55023655a2c1651c93ba05b5286420e89f0aa38736e6392d
7
+ data.tar.gz: 2978665cb5a9e565ae04b3135c9f4fee2dba50a9cb333c46d6723939e081a95ff808746b772c8f379e0368f55033fb51f56849c796a3487fa7ce974ac2c31b31
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_JOBS: "20"
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem 'hashie'
6
+ gem 'rspec', group: :test
7
+
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.3)
5
+ hashie (4.0.0)
6
+ rspec (3.9.0)
7
+ rspec-core (~> 3.9.0)
8
+ rspec-expectations (~> 3.9.0)
9
+ rspec-mocks (~> 3.9.0)
10
+ rspec-core (3.9.0)
11
+ rspec-support (~> 3.9.0)
12
+ rspec-expectations (3.9.0)
13
+ diff-lcs (>= 1.2.0, < 2.0)
14
+ rspec-support (~> 3.9.0)
15
+ rspec-mocks (3.9.0)
16
+ diff-lcs (>= 1.2.0, < 2.0)
17
+ rspec-support (~> 3.9.0)
18
+ rspec-support (3.9.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ hashie
25
+ rspec
26
+
27
+ BUNDLED WITH
28
+ 1.17.3
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2019 Volkan Unsal
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,30 @@
1
+ # hash-mapper
2
+
3
+ Map a Ruby hash into another Ruby hash.
4
+
5
+ ## Usage
6
+
7
+ ```ruby
8
+ m = HashMapper.new do
9
+ key :name, source: :Name
10
+ key :last_name, source: :LastName, :allow_nil, then: proc { |o| o.value + '!' }
11
+ key :full_name, source: [:Name, :LastName], then: proc { |o|
12
+ "#{o.value.first} W. #{o.value.second}"
13
+ }
14
+ key :surname, eq: 'Wheeler'
15
+ key :zip
16
+ merge { |o| { extra: o.Name + '!' } }
17
+ end
18
+ m.run(Name: 'Bob', LastName: 'Fuller', zip: 'Boo!')
19
+ # => {name: 'Bob', last_name: 'Bob', full_name: 'Bob W. Fuller', surname: 'Wheeler', extra: 'Bob!', zip: 'Boo!' }
20
+ ```
21
+
22
+ ## Installation
23
+
24
+ ```
25
+ gem install hash-mapper
26
+ ```
27
+
28
+ ## License
29
+
30
+ See LICENSE
@@ -0,0 +1,8 @@
1
+
2
+ Rake::TestTask.new do |t|
3
+ t.libs << "spec"
4
+ t.test_files = FileList["spec/*_spec.rb"]
5
+ t.verbose = true
6
+ end
7
+
8
+ task default: :spec
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "hash-mapper"
7
+ spec.version = '0.0.1'
8
+ spec.authors = ["Volkan Unsal"]
9
+ spec.email = ["spocksplanet@gmail.com"]
10
+
11
+ spec.summary = %q{Map a Ruby hash into another Ruby hash..}
12
+ spec.description = %q{Map a Ruby hash into another Ruby hash..}
13
+ spec.homepage = "https://github.com/volkanunsal/hash-mapper"
14
+ spec.license = "MIT"
15
+ spec.metadata = {
16
+ "homepage_uri" => "https://github.com/volkanunsal/hash-mapper",
17
+ "source_code_uri" => "https://github.com/volkanunsal/hash-mapper"
18
+ }
19
+
20
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
21
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
22
+ # if spec.respond_to?(:metadata)
23
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
24
+ # else
25
+ # raise "RubyGems 2.0 or newer is required to protect against " \
26
+ # "public gem pushes."
27
+ # end
28
+
29
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
30
+ f.match(%r{^(test|spec|features)/})
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.required_ruby_version = '>= 2.4.0'
37
+
38
+ spec.add_development_dependency "bundler", "~> 1.14"
39
+ spec.add_development_dependency "rspec", "~> 3.7"
40
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "hashie"
5
+ require "hash-mapper/dsl"
6
+
7
+ class HashMapper
8
+ VERSION = "0.0.1"
9
+
10
+ def initialize(opts = [], &blk)
11
+ @opts = case opts
12
+ when Array
13
+ { keep: opts }
14
+ when Hash
15
+ opts
16
+ end
17
+ @mapper = Dsl.new
18
+ @mapper.evaluate(&blk) unless blk.nil?
19
+ end
20
+
21
+ def run(object = {}, state = {})
22
+ object = object
23
+ state = state.dup
24
+ state = process_initializer(object, state)
25
+ _run(object, state)
26
+ end
27
+ alias call run
28
+
29
+ private
30
+
31
+ def process_initializer(object, state)
32
+ keep = @opts[:keep] || []
33
+ exclude = @opts[:except] || []
34
+
35
+ # Sanity check
36
+ raise ArgumentError.new('The option :keep must be an array.') unless keep.kind_of?(Array)
37
+
38
+ # Sanity check
39
+ raise ArgumentError.new('The option :except must be an array.') unless exclude.kind_of?(Array)
40
+
41
+ unless keep.empty?
42
+ # Ensure we only pick the keys that are in the input object.
43
+ keep.select { |k| object.has_key?(k) }.each do |k|
44
+ state[k] = object[k]
45
+ end
46
+ end
47
+
48
+ unless exclude.empty?
49
+ object.each do |k, v|
50
+ state[k] = k unless exclude.include?(v)
51
+ end
52
+ end
53
+
54
+ state
55
+ end
56
+
57
+ class Context
58
+ attr_reader :input, :value, :state
59
+ attr_writer :state
60
+
61
+ def initialize(input, state, value)
62
+ @input = input
63
+ @state = state
64
+ @value = value
65
+ end
66
+ end
67
+
68
+ class Input < Hashie::Mash
69
+ disable_warnings
70
+ end
71
+
72
+ def _run(input, state)
73
+ input = Input.new(input)
74
+
75
+ @mapper.each_key do |config|
76
+ name = config.name
77
+ source = config.source
78
+ then_block = config.then_block
79
+ create = config.create
80
+ allow_nil = config.allow_nil
81
+ merge = config.merge
82
+ if_key = config.if_key
83
+ value = config.value
84
+
85
+ # Skip the iteration if the if_key condition is not met.
86
+ next if if_key && if_key.all? { |k| input.key?(k) } == false
87
+
88
+ if merge
89
+ # TODO: test
90
+ next if source && !source.all? { |k| input.key?(k) }
91
+
92
+ # If the source has only one element, just take its value.
93
+ value = if source.size == 1
94
+ input.send(source.first)
95
+ elsif source.size > 1
96
+ source.map { |k| input.send(k) }
97
+ end
98
+
99
+ ctx = Context.new(input, state, value)
100
+
101
+ res = then_block.call(ctx)
102
+ state.merge!(res) if res.kind_of?(Hash)
103
+
104
+ next
105
+ # If the keys in the source are present OR key is creatable...
106
+ elsif source.any? { |k| input.key?(k) } && value.nil?
107
+ # If the source has only one element, just take its value.
108
+ value = if source.size == 1
109
+ input.send(source.first)
110
+ elsif source.size > 1
111
+ source.map { |k| input.send(k) }
112
+ end
113
+
114
+ ctx = Context.new(input, state, value)
115
+
116
+ # If there is a then_block, pass it the ctx. And assign its outcome
117
+ # to the key.
118
+ value = then_block.call(ctx) unless then_block.nil?
119
+ elsif create
120
+ ctx = Context.new(input, state, nil)
121
+
122
+ # if there is a then_block, pass it the ctx. And assign its outcome
123
+ # to the key.
124
+ value = then_block.call(ctx) unless then_block.nil?
125
+ end
126
+
127
+ # If the key is creatable or the value is non-nil,
128
+ # assign value to the key in the state.
129
+ state[name] = value if allow_nil || !value.nil?
130
+ end
131
+
132
+ state
133
+ end
134
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # see: http://djellemah.com/blog/2013/10/09/instance-eval-with-access-to-outside-scope/
4
+ class Combinder < BasicObject
5
+ def initialize(obj, saved_binding)
6
+ @obj, @saved_binding = obj, saved_binding
7
+ end
8
+
9
+ def __bound_self__
10
+ @saved_binding.eval('self')
11
+ end
12
+
13
+ def method_missing(meth, *args, &blk)
14
+ # methods in dsl object are called in preference to self outside the block
15
+ if @obj.respond_to?(meth)
16
+ # dsl method, so call it
17
+ @obj.send meth, *args, &blk
18
+ else
19
+ __bound_self__.send meth, *args, &blk
20
+ end
21
+ end
22
+
23
+ def respond_to_missing?(meth, _include_all)
24
+ __bound_self__.respond_to?(meth) || @obj.respond_to?(meth)
25
+ end
26
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './combinder'
4
+
5
+ class HashMapper
6
+ class Dsl
7
+ attr_reader :keys
8
+
9
+ def initialize(&block)
10
+ @keys = []
11
+ evaluate(&block) if block_given?
12
+ end
13
+
14
+ def each_key
15
+ @keys.each { |k| yield(k) }
16
+ end
17
+
18
+ def evaluate(&block)
19
+ case block.arity
20
+ when 0
21
+ Combinder.new(self, block.binding).instance_eval(&block)
22
+ when 1
23
+ yield self
24
+ else
25
+ raise "Too many args for block"
26
+ end
27
+ end
28
+
29
+ class Key
30
+ attr_reader *%i[name source create then_block merge allow_nil if_key value]
31
+
32
+ def initialize(name: nil, source: nil, create: nil, then_block: nil, merge: nil, allow_nil: nil, if_key: nil, value: nil)
33
+ @name = name
34
+ @source = source
35
+ @create = create
36
+ @then_block = then_block
37
+ @merge = merge
38
+ @allow_nil = allow_nil
39
+ @if_key = if_key
40
+ @value = value
41
+ end
42
+ end
43
+
44
+ def merge(*opts, &block)
45
+ hash_opts = opts.select { |v| v.kind_of?(Hash) }.inject(&:merge) || {}
46
+ source = hash_opts.fetch(:source) { [] }
47
+ then_block = block || hash_opts[:then]
48
+
49
+ # Normalize source
50
+ source = case source
51
+ when Array
52
+ source
53
+ when Symbol
54
+ [source]
55
+ end
56
+
57
+ @keys.push Key.new(merge: true, then_block: then_block, source: source)
58
+ end
59
+
60
+ # opts -
61
+ # source - Symbol | [Symbol]
62
+ # create - Symbol
63
+ # allow_nil - Symbol
64
+ #
65
+ def key(name, *opts)
66
+ hash_opts = opts.select { |v| v.kind_of?(Hash) }.inject(&:merge) || {}
67
+ source = hash_opts[:source]
68
+ then_block = hash_opts[:then]
69
+ value = hash_opts[:eq]
70
+ if_key = hash_opts.fetch(:if_key) { [] }
71
+ create = opts.empty? || opts.include?(:create) || opts.include?(:allow_nil)
72
+ allow_nil = opts.include?(:allow_nil)
73
+
74
+ # TODO: validate the option keys.
75
+
76
+ # Normalize source
77
+ source = case source
78
+ when Array
79
+ source
80
+ when Symbol
81
+ [source]
82
+ else
83
+ # Use the key as value source when source is missing
84
+ [name]
85
+ end
86
+
87
+ # Normalize if_key
88
+ if_key = case if_key
89
+ when Array
90
+ if_key
91
+ when Symbol
92
+ [if_key]
93
+ end
94
+
95
+ # -- Sanity check
96
+ raise ArgumentError.new("A :then block is required if a key has multiple sources.") if source.size > 1 && !then_block
97
+
98
+ @keys.push Key.new(
99
+ name: name,
100
+ source: source,
101
+ create: create,
102
+ allow_nil: allow_nil,
103
+ then_block: then_block,
104
+ if_key: if_key,
105
+ value: value
106
+ )
107
+ end
108
+ end
109
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hash-mapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Volkan Unsal
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-11-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.7'
41
+ description: Map a Ruby hash into another Ruby hash..
42
+ email:
43
+ - spocksplanet@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".bundle/config"
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - LICENSE
52
+ - README.md
53
+ - Rakefile
54
+ - hash-mapper.gemspec
55
+ - lib/hash-mapper.rb
56
+ - lib/hash-mapper/combinder.rb
57
+ - lib/hash-mapper/dsl.rb
58
+ homepage: https://github.com/volkanunsal/hash-mapper
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ homepage_uri: https://github.com/volkanunsal/hash-mapper
63
+ source_code_uri: https://github.com/volkanunsal/hash-mapper
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.4.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.0.3
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Map a Ruby hash into another Ruby hash..
83
+ test_files: []