]> gitweb.fluxo.info Git - puppet-stdlib.git/commitdiff
Add a new function "try_get_value"
authorDmitry Ilyin <dilyin@mirantis.com>
Tue, 1 Sep 2015 18:39:16 +0000 (21:39 +0300)
committerDmitry Ilyin <dilyin@mirantis.com>
Tue, 1 Sep 2015 18:45:44 +0000 (21:45 +0300)
* Extracts a value from a deeply-nested data structure
* Returns default if a value could not be extracted

README.markdown
lib/puppet/parser/functions/try_get_value.rb [new file with mode: 0644]
spec/acceptance/try_get_value_spec.rb [new file with mode: 0755]
spec/functions/try_get_value_spec.rb [new file with mode: 0644]

index 8ed3d9b2492500ee9fc6bd74d7f0e840a100b471..22bece8711b7f987ce30ccafba2eeb32745a8177 100644 (file)
@@ -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.
diff --git a/lib/puppet/parser/functions/try_get_value.rb b/lib/puppet/parser/functions/try_get_value.rb
new file mode 100644 (file)
index 0000000..0c19fd9
--- /dev/null
@@ -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
diff --git a/spec/acceptance/try_get_value_spec.rb b/spec/acceptance/try_get_value_spec.rb
new file mode 100755 (executable)
index 0000000..46b1c4d
--- /dev/null
@@ -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
diff --git a/spec/functions/try_get_value_spec.rb b/spec/functions/try_get_value_spec.rb
new file mode 100644 (file)
index 0000000..38c0efd
--- /dev/null
@@ -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