Skip to content

Add a new function "try_get_value" #513

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

Merged
merged 1 commit into from
Sep 2, 2015
Merged
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
34 changes: 34 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
77 changes: 77 additions & 0 deletions lib/puppet/parser/functions/try_get_value.rb
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions spec/acceptance/try_get_value_spec.rb
Original file line number Diff line number Diff line change
@@ -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 changes: 100 additions & 0 deletions spec/functions/try_get_value_spec.rb
Original file line number Diff line number Diff line change
@@ -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