Add a new function "try_get_value"
* Extracts a value from a deeply-nested data structure * Returns default if a value could not be extracted
This commit is contained in:
parent
f820bb1560
commit
823a352f0f
4 changed files with 258 additions and 0 deletions
|
@ -669,6 +669,40 @@ Returns the current Unix epoch time as an integer. For example, `time()` returns
|
|||
|
||||
Converts the argument into bytes, for example "4 kB" becomes "4096". Takes a single string value as an argument. *Type*: rvalue.
|
||||
|
||||
#### `try_get_value`
|
||||
|
||||
*Type*: rvalue.
|
||||
|
||||
Looks up into a complex structure of arrays and hashes and returns a value
|
||||
or the default value if nothing was found.
|
||||
|
||||
Key can contain slashes to describe path components. The function will go down
|
||||
the structure and try to extract the required value.
|
||||
|
||||
$data = {
|
||||
'a' => {
|
||||
'b' => [
|
||||
'b1',
|
||||
'b2',
|
||||
'b3',
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
$value = try_get_value($data, 'a/b/2', 'not_found', '/')
|
||||
=> $value = 'b3'
|
||||
|
||||
a -> first hash key
|
||||
b -> second hash key
|
||||
2 -> array index starting with 0
|
||||
|
||||
not_found -> (optional) will be returned if there is no value or the path did not match. Defaults to nil.
|
||||
/ -> (optional) path delimiter. Defaults to '/'.
|
||||
|
||||
In addition to the required "key" argument, "try_get_value" accepts default
|
||||
argument. It will be returned if no value was found or a path component is
|
||||
missing. And the fourth argument can set a variable path separator.
|
||||
|
||||
#### `type3x`
|
||||
|
||||
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.
|
||||
|
|
77
lib/puppet/parser/functions/try_get_value.rb
Normal file
77
lib/puppet/parser/functions/try_get_value.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
module Puppet::Parser::Functions
|
||||
newfunction(
|
||||
:try_get_value,
|
||||
:type => :rvalue,
|
||||
:arity => -2,
|
||||
:doc => <<-eos
|
||||
Looks up into a complex structure of arrays and hashes and returns a value
|
||||
or the default value if nothing was found.
|
||||
|
||||
Key can contain slashes to describe path components. The function will go down
|
||||
the structure and try to extract the required value.
|
||||
|
||||
$data = {
|
||||
'a' => {
|
||||
'b' => [
|
||||
'b1',
|
||||
'b2',
|
||||
'b3',
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
$value = try_get_value($data, 'a/b/2', 'not_found', '/')
|
||||
=> $value = 'b3'
|
||||
|
||||
a -> first hash key
|
||||
b -> second hash key
|
||||
2 -> array index starting with 0
|
||||
|
||||
not_found -> (optional) will be returned if there is no value or the path did not match. Defaults to nil.
|
||||
/ -> (optional) path delimiter. Defaults to '/'.
|
||||
|
||||
In addition to the required "key" argument, "try_get_value" accepts default
|
||||
argument. It will be returned if no value was found or a path component is
|
||||
missing. And the fourth argument can set a variable path separator.
|
||||
eos
|
||||
) do |args|
|
||||
path_lookup = lambda do |data, path, default|
|
||||
debug "Try_get_value: #{path.inspect} from: #{data.inspect}"
|
||||
if data.nil?
|
||||
debug "Try_get_value: no data, return default: #{default.inspect}"
|
||||
break default
|
||||
end
|
||||
unless path.is_a? Array
|
||||
debug "Try_get_value: wrong path, return default: #{default.inspect}"
|
||||
break default
|
||||
end
|
||||
unless path.any?
|
||||
debug "Try_get_value: value found, return data: #{data.inspect}"
|
||||
break data
|
||||
end
|
||||
unless data.is_a? Hash or data.is_a? Array
|
||||
debug "Try_get_value: incorrect data, return default: #{default.inspect}"
|
||||
break default
|
||||
end
|
||||
|
||||
key = path.shift
|
||||
if data.is_a? Array
|
||||
begin
|
||||
key = Integer key
|
||||
rescue ArgumentError
|
||||
debug "Try_get_value: non-numeric path for an array, return default: #{default.inspect}"
|
||||
break default
|
||||
end
|
||||
end
|
||||
path_lookup.call data[key], path, default
|
||||
end
|
||||
|
||||
data = args[0]
|
||||
path = args[1] || ''
|
||||
default = args[2]
|
||||
separator = args[3] || '/'
|
||||
|
||||
path = path.split separator
|
||||
path_lookup.call data, path, default
|
||||
end
|
||||
end
|
47
spec/acceptance/try_get_value_spec.rb
Executable file
47
spec/acceptance/try_get_value_spec.rb
Executable file
|
@ -0,0 +1,47 @@
|
|||
#! /usr/bin/env ruby -S rspec
|
||||
require 'spec_helper_acceptance'
|
||||
|
||||
describe 'try_get_value function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
|
||||
describe 'success' do
|
||||
it 'try_get_valuees a value' do
|
||||
pp = <<-EOS
|
||||
$data = {
|
||||
'a' => { 'b' => 'passing'}
|
||||
}
|
||||
|
||||
$tests = try_get_value($a, 'a/b')
|
||||
notice(inline_template('tests are <%= @tests.inspect %>'))
|
||||
EOS
|
||||
|
||||
apply_manifest(pp, :catch_failures => true) do |r|
|
||||
expect(r.stdout).to match(/tests are "passing"/)
|
||||
end
|
||||
end
|
||||
end
|
||||
describe 'failure' do
|
||||
it 'uses a default value' do
|
||||
pp = <<-EOS
|
||||
$data = {
|
||||
'a' => { 'b' => 'passing'}
|
||||
}
|
||||
|
||||
$tests = try_get_value($a, 'c/d', 'using the default value')
|
||||
notice(inline_template('tests are <%= @tests.inspect %>'))
|
||||
EOS
|
||||
|
||||
apply_manifest(pp, :expect_failures => true) do |r|
|
||||
expect(r.stdout).to match(/using the default value/)
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises error on incorrect number of arguments' do
|
||||
pp = <<-EOS
|
||||
$o = try_get_value()
|
||||
EOS
|
||||
|
||||
apply_manifest(pp, :expect_failures => true) do |r|
|
||||
expect(r.stderr).to match(/wrong number of arguments/i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
100
spec/functions/try_get_value_spec.rb
Normal file
100
spec/functions/try_get_value_spec.rb
Normal file
|
@ -0,0 +1,100 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'try_get_value' do
|
||||
|
||||
let(:data) do
|
||||
{
|
||||
'a' => {
|
||||
'g' => '2',
|
||||
'e' => [
|
||||
'f0',
|
||||
'f1',
|
||||
{
|
||||
'x' => {
|
||||
'y' => 'z'
|
||||
}
|
||||
},
|
||||
'f3',
|
||||
]
|
||||
},
|
||||
'b' => true,
|
||||
'c' => false,
|
||||
'd' => '1',
|
||||
}
|
||||
end
|
||||
|
||||
context 'single values' do
|
||||
it 'should exist' do
|
||||
is_expected.not_to eq(nil)
|
||||
end
|
||||
|
||||
it 'should be able to return a single value' do
|
||||
is_expected.to run.with_params('test').and_return('test')
|
||||
end
|
||||
|
||||
it 'should use the default value if data is a single value and path is present' do
|
||||
is_expected.to run.with_params('test', 'path', 'default').and_return('default')
|
||||
end
|
||||
|
||||
it 'should return default if there is no data' do
|
||||
is_expected.to run.with_params(nil, nil, 'default').and_return('default')
|
||||
end
|
||||
|
||||
it 'should be able to use data structures as default values' do
|
||||
is_expected.to run.with_params('test', 'path', data).and_return(data)
|
||||
end
|
||||
end
|
||||
|
||||
context 'structure values' do
|
||||
it 'should be able to extracts a single hash value' do
|
||||
is_expected.to run.with_params(data, 'd', 'default').and_return('1')
|
||||
end
|
||||
|
||||
it 'should be able to extract a deeply nested hash value' do
|
||||
is_expected.to run.with_params(data, 'a/g', 'default').and_return('2')
|
||||
end
|
||||
|
||||
it 'should return the default value if the path is not found' do
|
||||
is_expected.to run.with_params(data, 'missing', 'default').and_return('default')
|
||||
end
|
||||
|
||||
it 'should return the default value if the path is too long' do
|
||||
is_expected.to run.with_params(data, 'a/g/c/d', 'default').and_return('default')
|
||||
end
|
||||
|
||||
it 'should support an array index in the path' do
|
||||
is_expected.to run.with_params(data, 'a/e/1', 'default').and_return('f1')
|
||||
end
|
||||
|
||||
it 'should return the default value if an array index is not a number' do
|
||||
is_expected.to run.with_params(data, 'a/b/c', 'default').and_return('default')
|
||||
end
|
||||
|
||||
it 'should return the default value if and index is out of array length' do
|
||||
is_expected.to run.with_params(data, 'a/e/5', 'default').and_return('default')
|
||||
end
|
||||
|
||||
it 'should be able to path though both arrays and hashes' do
|
||||
is_expected.to run.with_params(data, 'a/e/2/x/y', 'default').and_return('z')
|
||||
end
|
||||
|
||||
it 'should be able to return "true" value' do
|
||||
is_expected.to run.with_params(data, 'b', 'default').and_return(true)
|
||||
is_expected.to run.with_params(data, 'm', true).and_return(true)
|
||||
end
|
||||
|
||||
it 'should be able to return "false" value' do
|
||||
is_expected.to run.with_params(data, 'c', 'default').and_return(false)
|
||||
is_expected.to run.with_params(data, 'm', false).and_return(false)
|
||||
end
|
||||
|
||||
it 'should return "nil" if value is not found and no default value is provided' do
|
||||
is_expected.to run.with_params(data, 'a/1').and_return(nil)
|
||||
end
|
||||
|
||||
it 'should be able to use a custom path separator' do
|
||||
is_expected.to run.with_params(data, 'a::g', 'default', '::').and_return('2')
|
||||
is_expected.to run.with_params(data, 'a::c', 'default', '::').and_return('default')
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue