hash_op 0.1.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +88 -0
- data/README.md +57 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +7 -0
- data/hash_op.gemspec +34 -0
- data/lib/hash_op/deep_access.rb +106 -0
- data/lib/hash_op/filter.rb +77 -0
- data/lib/hash_op/grouping.rb +36 -0
- data/lib/hash_op/mapping.rb +103 -0
- data/lib/hash_op/math.rb +99 -0
- data/lib/hash_op/merge.rb +21 -0
- data/lib/hash_op/version.rb +3 -0
- data/lib/hash_op.rb +9 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b2933aa0df964cd1340e8e6ee1ddb7d693b4ee88
|
4
|
+
data.tar.gz: 14fecfc284ef874329d595191e1e00608b36817a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: be9847903c1c4e2fb2ed8e7ce934a2b5d7ecdd6e4e45a047ae20efd7632ff8924ec3f5dde5c8c21015c7036b452f0c363decddc8b006affdf994f0bede712c77
|
7
|
+
data.tar.gz: 3d8c64ef1ab7dbc28787043071ee3e171ad1cccee96943bbafd198830661cab9c26c660c684946756d42721cb50fb2db5de2f9ccc5711ede354dd6ad064dfa37
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
hash_op (0.1.0)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (4.2.1)
|
11
|
+
i18n (~> 0.7)
|
12
|
+
json (~> 1.7, >= 1.7.7)
|
13
|
+
minitest (~> 5.1)
|
14
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
15
|
+
tzinfo (~> 1.1)
|
16
|
+
awesome_print (1.6.1)
|
17
|
+
coderay (1.1.0)
|
18
|
+
coveralls (0.8.1)
|
19
|
+
json (~> 1.8)
|
20
|
+
rest-client (>= 1.6.8, < 2)
|
21
|
+
simplecov (~> 0.10.0)
|
22
|
+
term-ansicolor (~> 1.3)
|
23
|
+
thor (~> 0.19.1)
|
24
|
+
diff-lcs (1.2.5)
|
25
|
+
docile (1.1.5)
|
26
|
+
domain_name (0.5.24)
|
27
|
+
unf (>= 0.0.5, < 1.0.0)
|
28
|
+
http-cookie (1.0.2)
|
29
|
+
domain_name (~> 0.5)
|
30
|
+
i18n (0.7.0)
|
31
|
+
json (1.8.2)
|
32
|
+
method_source (0.8.2)
|
33
|
+
mime-types (2.6.1)
|
34
|
+
minitest (5.5.1)
|
35
|
+
netrc (0.10.3)
|
36
|
+
pry (0.10.1)
|
37
|
+
coderay (~> 1.1.0)
|
38
|
+
method_source (~> 0.8.1)
|
39
|
+
slop (~> 3.4)
|
40
|
+
rake (10.4.2)
|
41
|
+
rest-client (1.8.0)
|
42
|
+
http-cookie (>= 1.0.2, < 2.0)
|
43
|
+
mime-types (>= 1.16, < 3.0)
|
44
|
+
netrc (~> 0.7)
|
45
|
+
rspec (3.3.0)
|
46
|
+
rspec-core (~> 3.3.0)
|
47
|
+
rspec-expectations (~> 3.3.0)
|
48
|
+
rspec-mocks (~> 3.3.0)
|
49
|
+
rspec-core (3.3.0)
|
50
|
+
rspec-support (~> 3.3.0)
|
51
|
+
rspec-expectations (3.3.0)
|
52
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
53
|
+
rspec-support (~> 3.3.0)
|
54
|
+
rspec-mocks (3.3.0)
|
55
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
56
|
+
rspec-support (~> 3.3.0)
|
57
|
+
rspec-support (3.3.0)
|
58
|
+
simplecov (0.10.0)
|
59
|
+
docile (~> 1.1.0)
|
60
|
+
json (~> 1.8)
|
61
|
+
simplecov-html (~> 0.10.0)
|
62
|
+
simplecov-html (0.10.0)
|
63
|
+
slop (3.6.0)
|
64
|
+
term-ansicolor (1.3.0)
|
65
|
+
tins (~> 1.0)
|
66
|
+
thor (0.19.1)
|
67
|
+
thread_safe (0.3.5)
|
68
|
+
tins (1.5.2)
|
69
|
+
tzinfo (1.2.2)
|
70
|
+
thread_safe (~> 0.1)
|
71
|
+
unf (0.1.4)
|
72
|
+
unf_ext
|
73
|
+
unf_ext (0.0.7.1)
|
74
|
+
|
75
|
+
PLATFORMS
|
76
|
+
ruby
|
77
|
+
|
78
|
+
DEPENDENCIES
|
79
|
+
awesome_print
|
80
|
+
bundler (~> 1.10)
|
81
|
+
coveralls
|
82
|
+
hash_op!
|
83
|
+
pry
|
84
|
+
rake (~> 10.0)
|
85
|
+
rspec
|
86
|
+
|
87
|
+
BUNDLED WITH
|
88
|
+
1.10.3
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# HashOp
|
2
|
+
|
3
|
+
|
4
|
+
A Ruby library of functions to access and manipulate hash data structures.
|
5
|
+
|
6
|
+
[](https://travis-ci.org/travis-ci/travis-web)
|
7
|
+
[](https://codeclimate.com/github/rchampourlier/hash_op)
|
8
|
+
[](https://coveralls.io/r/rchampourlier/hash_op)
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'hash_op'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install hash_op
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
## Available operations
|
29
|
+
|
30
|
+
_See specs for more details on each operation._
|
31
|
+
|
32
|
+
### Deep Access
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
HashOp::DeepAccess.fetch({a: {b: {c: 1}}}, :'a.b.c')
|
36
|
+
=> 1
|
37
|
+
|
38
|
+
HashOp::DeepAccess.merge({ a: { b: { c: 1 } } }, :'a.b.c', 2)
|
39
|
+
=> {
|
40
|
+
:a => {
|
41
|
+
:b => {
|
42
|
+
:c => 2
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
```
|
47
|
+
TODO: complete with other available operations
|
48
|
+
|
49
|
+
## Development
|
50
|
+
|
51
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
52
|
+
|
53
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rchampourlier/hash_op. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) [code of conduct](CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/hash_op.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hash_op/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hash_op"
|
8
|
+
spec.version = HashOp::VERSION
|
9
|
+
spec.authors = ["Romain Champourlier"]
|
10
|
+
spec.email = ["pro@rchampourlier.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A Ruby library of functions to access and manipulate hash data structures.}
|
13
|
+
spec.homepage = "https://github.com/rchampourlier/hash_op"
|
14
|
+
|
15
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
16
|
+
# delete this section to allow pushing this gem to any host.
|
17
|
+
if spec.respond_to?(:metadata)
|
18
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
19
|
+
else
|
20
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
21
|
+
end
|
22
|
+
|
23
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "activesupport"
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec"
|
32
|
+
spec.add_development_dependency "pry"
|
33
|
+
spec.add_development_dependency "awesome_print"
|
34
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
|
3
|
+
# Provides ::fetch and ::merge methods which allows
|
4
|
+
# to perform fetch/merge operations deeply in Hash
|
5
|
+
# through a path in the form of 'a.b.c' or an array
|
6
|
+
# of segments ['a', 'b', 'c'].
|
7
|
+
module HashOp
|
8
|
+
module DeepAccess
|
9
|
+
|
10
|
+
# Examples:
|
11
|
+
# h = {a: {b: {c: 1}}}
|
12
|
+
# HashOp::DeepAccess.fetch(h, :a) # => {:b=>{:c=>1}}
|
13
|
+
# HashOp::DeepAccess.fetch(h, :'a.b') # => {:c=>1}
|
14
|
+
# HashOp::DeepAccess.fetch(h, :'a.b.c') # => 1
|
15
|
+
# HashOp::DeepAccess.fetch(h, [:a]) # => {:b=>{:c=>1}}
|
16
|
+
# HashOp::DeepAccess.fetch(h, [:a, :b, :c]) # => 1
|
17
|
+
# HashOp::DeepAccess.fetch(h, :'b.c.a') # => nil
|
18
|
+
#
|
19
|
+
def fetch(hash, path)
|
20
|
+
raise 'First argument must be an Hash' unless hash.is_a? Hash
|
21
|
+
if path.class.in? [String, Symbol]
|
22
|
+
fetch_with_deep_key(hash, path)
|
23
|
+
elsif path.is_a? Array
|
24
|
+
fetch_with_segments(hash, path)
|
25
|
+
else
|
26
|
+
raise 'Invalid attribute, must be a String or an Array'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
module_function :fetch
|
30
|
+
|
31
|
+
def merge(hash, path, value)
|
32
|
+
raise 'First argument must be an Hash' unless hash.is_a? Hash
|
33
|
+
if path.class.in? [String, Symbol]
|
34
|
+
merge_with_deep_key(hash, path, value)
|
35
|
+
elsif path.is_a? Array
|
36
|
+
merge_with_segments(hash, path, value)
|
37
|
+
else
|
38
|
+
raise 'Invalid attribute, must be a String or an Array'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
module_function :merge
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def fetch_with_deep_key(hash, deep_key)
|
46
|
+
segments = deep_key.to_s.split('.')
|
47
|
+
segments.map!(&:to_sym) if deep_key.is_a? Symbol
|
48
|
+
fetch_with_segments(hash, segments)
|
49
|
+
end
|
50
|
+
module_function :fetch_with_deep_key
|
51
|
+
|
52
|
+
def fetch_with_segments(hash, segments)
|
53
|
+
return hash if segments.empty?
|
54
|
+
|
55
|
+
result = hash[segments.first]
|
56
|
+
if result.is_a? Hash
|
57
|
+
fetch_with_segments(result, segments[1..-1])
|
58
|
+
elsif result.is_a? Array
|
59
|
+
result.map do |item|
|
60
|
+
fetch_with_segments(item, segments[1..-1])
|
61
|
+
end
|
62
|
+
else
|
63
|
+
result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
module_function :fetch_with_segments
|
67
|
+
|
68
|
+
def merge_with_deep_key(hash, deep_key, value)
|
69
|
+
segments = deep_key.to_s.split('.')
|
70
|
+
segments.map!(&:to_sym) if deep_key.is_a? Symbol
|
71
|
+
merge_with_segments(hash, segments, value)
|
72
|
+
end
|
73
|
+
module_function :merge_with_deep_key
|
74
|
+
|
75
|
+
def merge_with_segments(hash, segments, value)
|
76
|
+
current_segment = segments.first
|
77
|
+
remaining_segments = segments[1..-1]
|
78
|
+
|
79
|
+
return value if segments.empty?
|
80
|
+
|
81
|
+
current_value = hash[current_segment]
|
82
|
+
new_value = (
|
83
|
+
if remaining_segments.length > 0
|
84
|
+
if current_value.is_a? Hash
|
85
|
+
merge_with_segments(current_value, remaining_segments, value)
|
86
|
+
elsif current_value.is_a? Array
|
87
|
+
current_value.map do |item|
|
88
|
+
merge_with_segments(item, remaining_segments, value)
|
89
|
+
end
|
90
|
+
else
|
91
|
+
build_with_segments remaining_segments, value
|
92
|
+
end
|
93
|
+
else value
|
94
|
+
end
|
95
|
+
)
|
96
|
+
hash.merge current_segment => new_value
|
97
|
+
end
|
98
|
+
module_function :merge_with_segments
|
99
|
+
|
100
|
+
def build_with_segments(segments, value)
|
101
|
+
return value if segments.empty?
|
102
|
+
{ segments.first => build_with_segments(segments[1..-1], value) }
|
103
|
+
end
|
104
|
+
module_function :build_with_segments
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'hash_op/deep_access'
|
2
|
+
|
3
|
+
# Performs filtering operation on hash or array of hashes
|
4
|
+
module HashOp
|
5
|
+
module Filter
|
6
|
+
|
7
|
+
# Filters an array of hashes according to criteria
|
8
|
+
# on the values of each hash.
|
9
|
+
#
|
10
|
+
# @param [Array] hashes array of hashes to be filtered
|
11
|
+
# @param [Hash] criteria the method uses ::match?, see
|
12
|
+
# definition for more details
|
13
|
+
# @return [Array]
|
14
|
+
#
|
15
|
+
def filter(hashes, criteria = {})
|
16
|
+
hashes.select do |item|
|
17
|
+
match?(item, criteria)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
module_function :filter
|
21
|
+
|
22
|
+
# Applies ::filter on the value of an hash
|
23
|
+
#
|
24
|
+
# @param [Hash] hash the hash to be filtered
|
25
|
+
# @param [String, Symbol] the path of the array of hashes
|
26
|
+
# inside hash to be filtered. Accessed through
|
27
|
+
# HashOp::DeepAccess (path like 'path.to.some.key').
|
28
|
+
# @param [Hash] criteria to filter on (performed through
|
29
|
+
# ::filter, so see the method for more details)
|
30
|
+
# @return [Hash] the hash with values in array at path
|
31
|
+
# filtered according to criteria
|
32
|
+
def filter_deep(hash, path, criteria = {})
|
33
|
+
array = HashOp::DeepAccess.fetch hash, path
|
34
|
+
raise "Can\'t filter hash at path \"#{path}\", value is not an array" unless array.is_a?(Array)
|
35
|
+
|
36
|
+
filtered_array = filter(array, criteria)
|
37
|
+
HashOp::DeepAccess.merge hash, path, filtered_array
|
38
|
+
end
|
39
|
+
module_function :filter_deep
|
40
|
+
|
41
|
+
# @param [Hash] hash to match against criteria
|
42
|
+
# @param [Hash] criteria to match the hash against
|
43
|
+
# each criteria is an hash
|
44
|
+
# { path => matching_object }, where:
|
45
|
+
# - path [String, Symbol] is used to access the value
|
46
|
+
# in the filtered object (through HashOp::DeepAccess::fetch)
|
47
|
+
# - matching_object [Object] the object defining the
|
48
|
+
# match:
|
49
|
+
# * a Proc which will be called with the value and
|
50
|
+
# which should return true to indicate a match, else
|
51
|
+
# false,
|
52
|
+
# * a Regexp will be matched using the regexp match
|
53
|
+
# operator,
|
54
|
+
# * any other value will be matched against the
|
55
|
+
# equality operator.
|
56
|
+
def match?(hash, criteria)
|
57
|
+
raise ArgumentError.new('First argument must be an Hash') unless hash.is_a?(Hash)
|
58
|
+
return true if criteria.blank?
|
59
|
+
|
60
|
+
criteria.map do |path, matching_object|
|
61
|
+
value = HashOp::DeepAccess.fetch(hash, path)
|
62
|
+
|
63
|
+
case
|
64
|
+
|
65
|
+
when matching_object.is_a?(Proc)
|
66
|
+
matching_object.call(value)
|
67
|
+
|
68
|
+
when matching_object.is_a?(Regexp)
|
69
|
+
!!(value =~ matching_object)
|
70
|
+
|
71
|
+
else value == matching_object
|
72
|
+
end
|
73
|
+
end.uniq == [true]
|
74
|
+
end
|
75
|
+
module_function :match?
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'hash_op/deep_access'
|
2
|
+
|
3
|
+
# A module to perform group operations on hashes.
|
4
|
+
module HashOp
|
5
|
+
module Grouping
|
6
|
+
|
7
|
+
# @param hashes [Array] hashes to be grouped
|
8
|
+
# @param paths [Object] path on which the hashes must be
|
9
|
+
# grouped
|
10
|
+
# @return [Hash]
|
11
|
+
def group_on_path(hashes, path)
|
12
|
+
hashes.inject({}) do |result, hash|
|
13
|
+
value_at_path = HashOp::DeepAccess.fetch(hash, path)
|
14
|
+
result[value_at_path] ||= []
|
15
|
+
result[value_at_path] << hash
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
module_function :group_on_path
|
20
|
+
|
21
|
+
# @param hashes [Array] hashes to be grouped
|
22
|
+
# @param paths [Array] paths on which the hashes must be
|
23
|
+
# grouped, by order of grouping (1st group-level first)
|
24
|
+
# @return [Hash]
|
25
|
+
def group_on_paths(hashes, paths)
|
26
|
+
return group_on_path(hashes, paths.first) if paths.length == 1
|
27
|
+
|
28
|
+
path = paths.first
|
29
|
+
path_groups = group_on_path(hashes, path)
|
30
|
+
path_groups.each do |group_key, grouped_hashes|
|
31
|
+
path_groups[group_key] = group_on_paths(grouped_hashes, paths[1..-1])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
module_function :group_on_paths
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'hash_op/deep_access'
|
2
|
+
|
3
|
+
# A module to perform mapping from hash to hash.
|
4
|
+
module HashOp
|
5
|
+
module Mapping
|
6
|
+
|
7
|
+
# @param hash [Hash] the hash to map
|
8
|
+
# @param mapping [Hash] the mapping to use to perform the
|
9
|
+
# mapping (see example below)
|
10
|
+
# A mapping hash is:
|
11
|
+
# a_key [String or Symbol] => a_mapping_item [Hash]
|
12
|
+
# A mapping item is:
|
13
|
+
# path: [String or Symbol] the path to the value
|
14
|
+
# type: [:time or :array]
|
15
|
+
# item_mapping: [Hash] for a mapping item whose type
|
16
|
+
# is :array when the array items are hashes to be
|
17
|
+
# mapped again. This mapping can be done with any
|
18
|
+
# level of recursion.
|
19
|
+
#
|
20
|
+
# Example of a mapping hash:
|
21
|
+
# {
|
22
|
+
# int: { path: :'root.integer' },
|
23
|
+
# str: { path: :'root.string' },
|
24
|
+
# time: { path: :'root.deep_1.deep_2.time', type: :time },
|
25
|
+
# strings: { path: :'root.array_of_strings' },
|
26
|
+
# mapped_hashes: {
|
27
|
+
# path: :'root.array_of_mapped_hashes',
|
28
|
+
# type: :array,
|
29
|
+
# item_mapping: {
|
30
|
+
# str: { path: :'root.string' },
|
31
|
+
# time: { path: :'root.deep_1.time', type: :time }
|
32
|
+
# }
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
def apply_mapping(hash, mapping)
|
37
|
+
mapping.keys.inject({}) do |mapped_hash, key|
|
38
|
+
mapping_item = mapping[key]
|
39
|
+
path = mapping_item[:path]
|
40
|
+
raise "path not found in mapping item #{mapping_item}" if path.nil?
|
41
|
+
raw = HashOp::DeepAccess.fetch(hash, path)
|
42
|
+
processed = process_with_mapping_item(raw, mapping_item)
|
43
|
+
mapped_hash[key] = processed
|
44
|
+
mapped_hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
module_function :apply_mapping
|
48
|
+
|
49
|
+
def process_with_mapping_item(raw_value, mapping_item)
|
50
|
+
type = mapping_item[:type] || :raw
|
51
|
+
send :"process_with_mapping_item_#{type}", raw_value, mapping_item
|
52
|
+
end
|
53
|
+
module_function :process_with_mapping_item
|
54
|
+
|
55
|
+
def process_with_mapping_item_raw(value, mapping_item)
|
56
|
+
value
|
57
|
+
end
|
58
|
+
module_function :process_with_mapping_item_raw
|
59
|
+
|
60
|
+
def process_with_mapping_item_time(value, mapping_item)
|
61
|
+
return nil if value.nil?
|
62
|
+
begin
|
63
|
+
Time.parse value
|
64
|
+
rescue ArgumentError
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
module_function :process_with_mapping_item_time
|
69
|
+
|
70
|
+
def process_with_mapping_item_mapped_hash(value, mapping_item)
|
71
|
+
return {} if value.nil?
|
72
|
+
mapping_item_mapping = mapping_item[:mapping]
|
73
|
+
raise "Missing mapping for mapped_hash \"#{value}\"" if mapping_item_mapping.nil?
|
74
|
+
|
75
|
+
apply_mapping(value, mapping_item_mapping)
|
76
|
+
end
|
77
|
+
module_function :process_with_mapping_item_mapped_hash
|
78
|
+
|
79
|
+
def process_with_mapping_item_parseable_string(value, mapping_item)
|
80
|
+
return {} if value.nil?
|
81
|
+
parsing_mapping = mapping_item[:parsing_mapping]
|
82
|
+
raise "Missing parsing_mapping for mapping #{mapping_item}" if parsing_mapping.nil?
|
83
|
+
parsing_results = parsing_mapping.map do |parsing_key, parsing_options|
|
84
|
+
regexp = Regexp.new parsing_options[:regexp]
|
85
|
+
match = regexp.match(value)
|
86
|
+
result = match ? process_with_mapping_item(match[1], parsing_options) : nil
|
87
|
+
[parsing_key, result]
|
88
|
+
end
|
89
|
+
Hash[parsing_results]
|
90
|
+
end
|
91
|
+
module_function :process_with_mapping_item_parseable_string
|
92
|
+
|
93
|
+
def process_with_mapping_item_array(value, mapping_item)
|
94
|
+
return [] if value.nil?
|
95
|
+
item_mapping = mapping_item[:item_mapping]
|
96
|
+
raise "Missing item mapping for array \"#{value}\"" if item_mapping.nil?
|
97
|
+
value.map do |item|
|
98
|
+
process_with_mapping_item(item, item_mapping)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
module_function :process_with_mapping_item_array
|
102
|
+
end
|
103
|
+
end
|
data/lib/hash_op/math.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'hash_op/deep_access'
|
2
|
+
|
3
|
+
# A set of functions to perform mathematical operations
|
4
|
+
# on Hashes.
|
5
|
+
module HashOp
|
6
|
+
module Math
|
7
|
+
|
8
|
+
# @param hashes [Array] of Hash instances
|
9
|
+
# @return [Hash] summing values of the same key
|
10
|
+
def sum(*hashes)
|
11
|
+
hashes.flatten!
|
12
|
+
case hashes.length
|
13
|
+
when 0 then {}
|
14
|
+
when 1 then hashes.first
|
15
|
+
when 2 then sum_two(*hashes)
|
16
|
+
else
|
17
|
+
sum(*[sum_two(*hashes[0..1])] + hashes[2..-1])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
module_function :sum
|
21
|
+
|
22
|
+
def sum_two(hash_a, hash_b)
|
23
|
+
hash_b.each do |key, hash_b_value|
|
24
|
+
if hash_a[key]
|
25
|
+
hash_a[key] += hash_b_value
|
26
|
+
else
|
27
|
+
hash_a[key] = hash_b_value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
hash_a
|
31
|
+
end
|
32
|
+
module_function :sum_two
|
33
|
+
|
34
|
+
# Sum values in an array of hashes by grouping on a given
|
35
|
+
# key.
|
36
|
+
#
|
37
|
+
# Example:
|
38
|
+
# hashes = [
|
39
|
+
# { group: :a, value: 1 },
|
40
|
+
# { group: :a, value: 1 },
|
41
|
+
# { group: :b, value: 1 }
|
42
|
+
# ]
|
43
|
+
# HashOp::Math.sum_on_groups(hashes, :group, :value)
|
44
|
+
# # => [
|
45
|
+
# # { group: :a, value: 2 },
|
46
|
+
# # { group: :b, value: 1 }
|
47
|
+
# # ]
|
48
|
+
#
|
49
|
+
# @param hashes [Array] the hashes to be summed
|
50
|
+
# @param group_key [Object] the key to use to group items on
|
51
|
+
# @param value_key [Object] the key of the values to sum
|
52
|
+
# @return [Array]
|
53
|
+
def sum_on_groups(hashes, group_key, value_key)
|
54
|
+
work_hash = hashes.inject({}) do |work, hash|
|
55
|
+
if work[hash[group_key]].nil?
|
56
|
+
work[hash[group_key]] = {
|
57
|
+
group_key => hash[group_key],
|
58
|
+
value_key => hash[value_key]
|
59
|
+
}
|
60
|
+
else
|
61
|
+
work[hash[group_key]][value_key] += hash[value_key]
|
62
|
+
end
|
63
|
+
work
|
64
|
+
end
|
65
|
+
work_hash.values
|
66
|
+
end
|
67
|
+
module_function :sum_on_groups
|
68
|
+
|
69
|
+
# Sum values for the specified hashes at the specified path.
|
70
|
+
# The values are added to the specified zero (defaults to numeric
|
71
|
+
# 0), and nil values will be coerced to the zero too.
|
72
|
+
#
|
73
|
+
# @param hashes [Array]
|
74
|
+
# @param path [String]
|
75
|
+
# @param zero [Object] defaults to [Numeric] 0
|
76
|
+
#
|
77
|
+
def sum_at_path(hashes, path, zero = 0)
|
78
|
+
hashes.inject(zero) do |sum, hash|
|
79
|
+
value = HashOp::DeepAccess.fetch(hash, path) || zero
|
80
|
+
sum + value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
module_function :sum_at_path
|
84
|
+
|
85
|
+
# @param [Array] hashes array of Hash
|
86
|
+
# @param [String, Symbol] path to deep value in each hash
|
87
|
+
def deep_min(hashes, path)
|
88
|
+
hashes.map { |hash| HashOp::DeepAccess.fetch hash, path }.min
|
89
|
+
end
|
90
|
+
module_function :deep_min
|
91
|
+
|
92
|
+
# @param [Array] hashes array of Hash
|
93
|
+
# @param [String, Symbol] path to deep value in each hash
|
94
|
+
def deep_max(hashes, path)
|
95
|
+
hashes.map { |hash| HashOp::DeepAccess.fetch hash, path }.max
|
96
|
+
end
|
97
|
+
module_function :deep_max
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module HashOp
|
2
|
+
module Merge
|
3
|
+
|
4
|
+
# Merge all specified hashes by merging the second
|
5
|
+
# in the first, the third in the result, and so on.
|
6
|
+
def merge(hashes)
|
7
|
+
hashes.inject({}) do |result, hash|
|
8
|
+
result.merge hash
|
9
|
+
end
|
10
|
+
end
|
11
|
+
module_function :merge
|
12
|
+
|
13
|
+
# Merge hashes by grouping them on the
|
14
|
+
# specified key value and merging them all together.
|
15
|
+
def merge_by_group(hashes, key)
|
16
|
+
groups = hashes.group_by { |h| h[key] }
|
17
|
+
groups.values.map { |g| merge(g) }
|
18
|
+
end
|
19
|
+
module_function :merge_by_group
|
20
|
+
end
|
21
|
+
end
|
data/lib/hash_op.rb
ADDED
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hash_op
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Romain Champourlier
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
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: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
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: pry
|
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: awesome_print
|
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:
|
98
|
+
email:
|
99
|
+
- pro@rchampourlier.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".travis.yml"
|
107
|
+
- CODE_OF_CONDUCT.md
|
108
|
+
- Gemfile
|
109
|
+
- Gemfile.lock
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- hash_op.gemspec
|
115
|
+
- lib/hash_op.rb
|
116
|
+
- lib/hash_op/deep_access.rb
|
117
|
+
- lib/hash_op/filter.rb
|
118
|
+
- lib/hash_op/grouping.rb
|
119
|
+
- lib/hash_op/mapping.rb
|
120
|
+
- lib/hash_op/math.rb
|
121
|
+
- lib/hash_op/merge.rb
|
122
|
+
- lib/hash_op/version.rb
|
123
|
+
homepage: https://github.com/rchampourlier/hash_op
|
124
|
+
licenses: []
|
125
|
+
metadata:
|
126
|
+
allowed_push_host: https://rubygems.org
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.4.6
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: A Ruby library of functions to access and manipulate hash data structures.
|
147
|
+
test_files: []
|