Skip to content

Commit 64267eb

Browse files
committed
Merge pull request #513 from dmitryilyin/fetch
Add a new function "try_get_value"
2 parents 9352db7 + 823a352 commit 64267eb

File tree

4 files changed

+258
-0
lines changed

4 files changed

+258
-0
lines changed

README.markdown

+34
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,40 @@ Returns the current Unix epoch time as an integer. For example, `time()` returns
702702

703703
Converts the argument into bytes, for example "4 kB" becomes "4096". Takes a single string value as an argument. *Type*: rvalue.
704704

705+
#### `try_get_value`
706+
707+
*Type*: rvalue.
708+
709+
Looks up into a complex structure of arrays and hashes and returns a value
710+
or the default value if nothing was found.
711+
712+
Key can contain slashes to describe path components. The function will go down
713+
the structure and try to extract the required value.
714+
715+
$data = {
716+
'a' => {
717+
'b' => [
718+
'b1',
719+
'b2',
720+
'b3',
721+
]
722+
}
723+
}
724+
725+
$value = try_get_value($data, 'a/b/2', 'not_found', '/')
726+
=> $value = 'b3'
727+
728+
a -> first hash key
729+
b -> second hash key
730+
2 -> array index starting with 0
731+
732+
not_found -> (optional) will be returned if there is no value or the path did not match. Defaults to nil.
733+
/ -> (optional) path delimiter. Defaults to '/'.
734+
735+
In addition to the required "key" argument, "try_get_value" accepts default
736+
argument. It will be returned if no value was found or a path component is
737+
missing. And the fourth argument can set a variable path separator.
738+
705739
#### `type3x`
706740

707741
Returns a string description of the type when passed a value. Type can be a string, array, hash, float, integer, or boolean. This function will be removed when Puppet 3 support is dropped and the new type system can be used. *Type*: rvalue.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
module Puppet::Parser::Functions
2+
newfunction(
3+
:try_get_value,
4+
:type => :rvalue,
5+
:arity => -2,
6+
:doc => <<-eos
7+
Looks up into a complex structure of arrays and hashes and returns a value
8+
or the default value if nothing was found.
9+
10+
Key can contain slashes to describe path components. The function will go down
11+
the structure and try to extract the required value.
12+
13+
$data = {
14+
'a' => {
15+
'b' => [
16+
'b1',
17+
'b2',
18+
'b3',
19+
]
20+
}
21+
}
22+
23+
$value = try_get_value($data, 'a/b/2', 'not_found', '/')
24+
=> $value = 'b3'
25+
26+
a -> first hash key
27+
b -> second hash key
28+
2 -> array index starting with 0
29+
30+
not_found -> (optional) will be returned if there is no value or the path did not match. Defaults to nil.
31+
/ -> (optional) path delimiter. Defaults to '/'.
32+
33+
In addition to the required "key" argument, "try_get_value" accepts default
34+
argument. It will be returned if no value was found or a path component is
35+
missing. And the fourth argument can set a variable path separator.
36+
eos
37+
) do |args|
38+
path_lookup = lambda do |data, path, default|
39+
debug "Try_get_value: #{path.inspect} from: #{data.inspect}"
40+
if data.nil?
41+
debug "Try_get_value: no data, return default: #{default.inspect}"
42+
break default
43+
end
44+
unless path.is_a? Array
45+
debug "Try_get_value: wrong path, return default: #{default.inspect}"
46+
break default
47+
end
48+
unless path.any?
49+
debug "Try_get_value: value found, return data: #{data.inspect}"
50+
break data
51+
end
52+
unless data.is_a? Hash or data.is_a? Array
53+
debug "Try_get_value: incorrect data, return default: #{default.inspect}"
54+
break default
55+
end
56+
57+
key = path.shift
58+
if data.is_a? Array
59+
begin
60+
key = Integer key
61+
rescue ArgumentError
62+
debug "Try_get_value: non-numeric path for an array, return default: #{default.inspect}"
63+
break default
64+
end
65+
end
66+
path_lookup.call data[key], path, default
67+
end
68+
69+
data = args[0]
70+
path = args[1] || ''
71+
default = args[2]
72+
separator = args[3] || '/'
73+
74+
path = path.split separator
75+
path_lookup.call data, path, default
76+
end
77+
end

spec/acceptance/try_get_value_spec.rb

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#! /usr/bin/env ruby -S rspec
2+
require 'spec_helper_acceptance'
3+
4+
describe 'try_get_value function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
5+
describe 'success' do
6+
it 'try_get_valuees a value' do
7+
pp = <<-EOS
8+
$data = {
9+
'a' => { 'b' => 'passing'}
10+
}
11+
12+
$tests = try_get_value($a, 'a/b')
13+
notice(inline_template('tests are <%= @tests.inspect %>'))
14+
EOS
15+
16+
apply_manifest(pp, :catch_failures => true) do |r|
17+
expect(r.stdout).to match(/tests are "passing"/)
18+
end
19+
end
20+
end
21+
describe 'failure' do
22+
it 'uses a default value' do
23+
pp = <<-EOS
24+
$data = {
25+
'a' => { 'b' => 'passing'}
26+
}
27+
28+
$tests = try_get_value($a, 'c/d', 'using the default value')
29+
notice(inline_template('tests are <%= @tests.inspect %>'))
30+
EOS
31+
32+
apply_manifest(pp, :expect_failures => true) do |r|
33+
expect(r.stdout).to match(/using the default value/)
34+
end
35+
end
36+
37+
it 'raises error on incorrect number of arguments' do
38+
pp = <<-EOS
39+
$o = try_get_value()
40+
EOS
41+
42+
apply_manifest(pp, :expect_failures => true) do |r|
43+
expect(r.stderr).to match(/wrong number of arguments/i)
44+
end
45+
end
46+
end
47+
end

spec/functions/try_get_value_spec.rb

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
require 'spec_helper'
2+
3+
describe 'try_get_value' do
4+
5+
let(:data) do
6+
{
7+
'a' => {
8+
'g' => '2',
9+
'e' => [
10+
'f0',
11+
'f1',
12+
{
13+
'x' => {
14+
'y' => 'z'
15+
}
16+
},
17+
'f3',
18+
]
19+
},
20+
'b' => true,
21+
'c' => false,
22+
'd' => '1',
23+
}
24+
end
25+
26+
context 'single values' do
27+
it 'should exist' do
28+
is_expected.not_to eq(nil)
29+
end
30+
31+
it 'should be able to return a single value' do
32+
is_expected.to run.with_params('test').and_return('test')
33+
end
34+
35+
it 'should use the default value if data is a single value and path is present' do
36+
is_expected.to run.with_params('test', 'path', 'default').and_return('default')
37+
end
38+
39+
it 'should return default if there is no data' do
40+
is_expected.to run.with_params(nil, nil, 'default').and_return('default')
41+
end
42+
43+
it 'should be able to use data structures as default values' do
44+
is_expected.to run.with_params('test', 'path', data).and_return(data)
45+
end
46+
end
47+
48+
context 'structure values' do
49+
it 'should be able to extracts a single hash value' do
50+
is_expected.to run.with_params(data, 'd', 'default').and_return('1')
51+
end
52+
53+
it 'should be able to extract a deeply nested hash value' do
54+
is_expected.to run.with_params(data, 'a/g', 'default').and_return('2')
55+
end
56+
57+
it 'should return the default value if the path is not found' do
58+
is_expected.to run.with_params(data, 'missing', 'default').and_return('default')
59+
end
60+
61+
it 'should return the default value if the path is too long' do
62+
is_expected.to run.with_params(data, 'a/g/c/d', 'default').and_return('default')
63+
end
64+
65+
it 'should support an array index in the path' do
66+
is_expected.to run.with_params(data, 'a/e/1', 'default').and_return('f1')
67+
end
68+
69+
it 'should return the default value if an array index is not a number' do
70+
is_expected.to run.with_params(data, 'a/b/c', 'default').and_return('default')
71+
end
72+
73+
it 'should return the default value if and index is out of array length' do
74+
is_expected.to run.with_params(data, 'a/e/5', 'default').and_return('default')
75+
end
76+
77+
it 'should be able to path though both arrays and hashes' do
78+
is_expected.to run.with_params(data, 'a/e/2/x/y', 'default').and_return('z')
79+
end
80+
81+
it 'should be able to return "true" value' do
82+
is_expected.to run.with_params(data, 'b', 'default').and_return(true)
83+
is_expected.to run.with_params(data, 'm', true).and_return(true)
84+
end
85+
86+
it 'should be able to return "false" value' do
87+
is_expected.to run.with_params(data, 'c', 'default').and_return(false)
88+
is_expected.to run.with_params(data, 'm', false).and_return(false)
89+
end
90+
91+
it 'should return "nil" if value is not found and no default value is provided' do
92+
is_expected.to run.with_params(data, 'a/1').and_return(nil)
93+
end
94+
95+
it 'should be able to use a custom path separator' do
96+
is_expected.to run.with_params(data, 'a::g', 'default', '::').and_return('2')
97+
is_expected.to run.with_params(data, 'a::c', 'default', '::').and_return('default')
98+
end
99+
end
100+
end

0 commit comments

Comments
 (0)