Skip to content

Add Ruby function stdlib::cmp_hash #1376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions lib/puppet/functions/stdlib/cmp_hash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

# @summary Compare two Hashes to each other, optionally after applying a block.
#
# Returns -1, 0 or 1, depending on whether the first Hash is smaller, equal or bigger
# than the second Hash. If no block is supplied, both Hashes will be transformed
# into sorted Arrays first. Otherwise, the block has to yield something that Ruby
# can compare with the `<=>` operator.
#
# @example Use stdlib::cmp_hash to sort a list of Hashes (here, the local disks by their volume)
# $::disks.values.sort |$a, $b| { stdlib::cmp_hash($a, $b) |$h| { $h['size_bytes'] } }
#
Puppet::Functions.create_function(:'stdlib::cmp_hash') do
# @param hash1 The first Hash.
# @param hash2 The second Hash.
# @example Compare by default tranformation to Arrays
# $hash1 = {
# 'primary_key' => 2,
# 'key1' => ['val1', 'val2'],
# 'key2' => { 'key3' => 'val3', },
# 'key4' => true,
# 'key5' => 12345,
# }
# $hash2 = {
# 'primary_key' => 1,
# 'key6' => ['val1', 'val2'],
# 'key7' => { 'key8' => 'val9', },
# 'key10' => true,
# 'key11' => 67890,
# }
# stdlib::cmp_hash($hash1, $hash2) # => -1; 'key1' is the smallest key, thus $hash1 is smaller
# @return [Integer] Returns an integer (-1, 0, or +1) if hash1 is less than, equal to, or greater than hash2.
dispatch :cmp_hashes_as_arrays do
param 'Hash[Any, Any]', :hash1
param 'Hash[Any, Any]', :hash2
end

# @param hash1 The first Hash.
# @param hash2 The second Hash.
# @example Compare two Hashes by a specific key
# $hash1 = {
# 'primary_key' => 2,
# 'key1' => ['val1', 'val2'],
# 'key2' => { 'key3' => 'val3', },
# 'key4' => true,
# 'key5' => 12345,
# }
# $hash2 = {
# 'primary_key' => 1,
# 'key6' => ['val1', 'val2'],
# 'key7' => { 'key8' => 'val9', },
# 'key10' => true,
# 'key11' => 67890,
# }
# stdlib::cmp_hash($hash1, $hash2) |$h| { $h['primary_key'] } # => 1; $hash2 has the smaller value for 'primary_key', hence $hash1 is bigger
# @return [Integer] Returns an integer (-1, 0, or +1) if block(hash1) is less than, equal to, or greater than block(hash2).
dispatch :cmp_hashes_with_block do
param 'Hash[Any, Any]', :hash1
param 'Hash[Any, Any]', :hash2
block_param 'Callable[1,1]', :block
end

def cmp_hashes_as_arrays(hash1, hash2)
cmp_hashes_with_block(hash1, hash2) { |h| h.to_a.sort }
end

def cmp_hashes_with_block(hash1, hash2)
yield(hash1) <=> yield(hash2)
end
end
63 changes: 63 additions & 0 deletions spec/functions/cmp_hash_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'stdlib::cmp_hash' do
it { is_expected.not_to be_nil }

describe 'raise exception unless two Hashes are provided' do
it { is_expected.to run.with_params.and_raise_error(ArgumentError, %r{'stdlib::cmp_hash' expects 2 arguments, got none}) }
it { is_expected.to run.with_params({}).and_raise_error(ArgumentError, %r{'stdlib::cmp_hash' expects 2 arguments, got 1}) }
it { is_expected.to run.with_params({}, {}, {}).and_raise_error(ArgumentError, %r{'stdlib::cmp_hash' expects 2 arguments, got 3}) }
it { is_expected.to run.with_params({}, 1).and_raise_error(ArgumentError, %r{'stdlib::cmp_hash' parameter 'hash2' expects a Hash value, got Integer}) }
it { is_expected.to run.with_params({}, 'a').and_raise_error(ArgumentError, %r{'stdlib::cmp_hash' parameter 'hash2' expects a Hash value, got String}) }
it { is_expected.to run.with_params(:undef, {}).and_raise_error(ArgumentError, %r{'stdlib::cmp_hash' parameter 'hash1' expects a Hash value, got Undef}) }
end

describe 'raise exception in case lambda expects wrong number of arguments' do
it 'one too few' do
expect(subject).to run \
.with_params({}, {}) \
.with_lambda { 1 } \
.and_raise_error(ArgumentError, Regexp.new(Regexp.escape('rejected: block expects 1 argument, got none')))
end

# TODO: Does not pass Rubocop tests, because running the function with this kind of lambda does not throw an error.
# it 'one too many' do
# expect(subject).to run \
# .with_params({}, {}) \
# .with_lambda { |_, _| 1 } \
# .and_raise_error(ArgumentError, Regexp.new(Regexp.escape('rejected: block expects 1 argument, got 2')))
# end
end

hash1 = {
'primary_key' => 2,
'key1' => ['val1', 'val2'],
'key2' => { 'key3' => 'val3' },
'key4' => true,
'key5' => 123
}
hash2 = {
'primary_key' => 1,
'key6' => ['val1', 'val2'],
'key7' => { 'key8' => 'val9' },
'key10' => true,
'key11' => 456
}
describe 'compare without block' do
it { is_expected.to run.with_params(hash1, hash2).and_return(-1) }
it { is_expected.to run.with_params(hash2, hash1).and_return(1) }
it { is_expected.to run.with_params({}, hash1).and_return(-1) }
it { is_expected.to run.with_params(hash1, hash1).and_return(0) }
end

describe 'compare with block' do
it {
expect(subject).to run \
.with_params(hash1, hash2) \
.with_lambda { |hsh| hsh['primary_key'] } \
.and_return(1)
}
end
end