1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
# frozen_string_literal: true
# Digs into a value with dot notation to get a value from within a structure.
#
# **To dig into a given value**, call the function with (at least) two arguments:
#
# * The **first** argument must be an Array, or Hash. Value can also be `undef`
# (which also makes the result `undef` unless a _default value_ is given).
# * The **second** argument must be a _dot notation navigation string_.
# * The **optional third** argument can be any type of value and it is used
# as the _default value_ if the function would otherwise return `undef`.
# * An **optional lambda** for error handling taking one `Error` argument.
#
# **Dot notation navigation string** -
# The dot string consists of period `.` separated segments where each
# segment is either the index into an array or the value of a hash key.
# If a wanted key contains a period it must be quoted to avoid it being
# taken as a segment separator. Quoting can be done with either
# single quotes `'` or double quotes `"`. If a segment is
# a decimal number it is converted to an Integer index. This conversion
# can be prevented by quoting the value.
#
# @example Navigating into a value
# ```puppet
# #get($facts, 'os.family')
# $facts.get('os.family')
# ```
# Would both result in the value of `$facts['os']['family']`
#
# @example Getting the value from an expression
# ```puppet
# get([1,2,[{'name' =>'waldo'}]], '2.0.name')
# ```
# Would result in `'waldo'`
#
# @example Using a default value
# ```puppet
# get([1,2,[{'name' =>'waldo'}]], '2.1.name', 'not waldo')
#
# ```
# Would result in `'not waldo'`
#
# @example Quoting a key with period
# ```puppet
# $x = [1, 2, { 'readme.md' => "This is a readme."}]
# $x.get('2."readme.md"')
# ```
#
# @example Quoting a numeric string
# ```puppet
# $x = [1, 2, { '10' => "ten"}]
# $x.get('2."0"')
# ```
#
# **Error Handling** - There are two types of common errors that can
# be handled by giving the function a code block to execute.
# (A third kind or error; when the navigation string has syntax errors
# (for example an empty segment or unbalanced quotes) will always raise
# an error).
#
# The given block will be given an instance of the `Error` data type,
# and it has methods to extract `msg`, `issue_code`, `kind`, and
# `details`.
#
# The `msg` will be a preformatted message describing the error.
# This is the error message that would have surfaced if there was
# no block to handle the error.
#
# The `kind` is the string `'SLICE_ERROR'` for both kinds of errors,
# and the `issue_code` is either the string `'EXPECTED_INTEGER_INDEX'`
# for an attempt to index into an array with a String,
# or `'EXPECTED_COLLECTION'` for an attempt to index into something that
# is not a Collection.
#
# The `details` is a Hash that for both issue codes contain the
# entry `'walked_path'` which is an Array with each key in the
# progression of the dig up to the place where the error occurred.
#
# For an `EXPECTED_INTEGER_INDEX`-issue the detail `'index_type'` is
# set to the data type of the index value and for an
# `'EXPECTED_COLLECTION'`-issue the detail `'value_type'` is set
# to the type of the value.
#
# The logic in the error handling block can inspect the details,
# and either call `fail()` with a custom error message or produce
# the wanted value.
#
# If the block produces `undef` it will not be replaced with a
# given default value.
#
# @example Ensure `undef` result on error
# ```puppet
# $x = 'blue'
# $x.get('0.color', 'green') |$error| { undef } # result is undef
#
# $y = ['blue']
# $y.get('color', 'green') |$error| { undef } # result is undef
# ```
#
# @example Accessing information in the Error
# ```puppet
# $x = [1, 2, ['blue']]
# $x.get('2.color') |$error| {
# notice("Walked path is ${error.details['walked_path']}")
# }
# ```
# Would notice `Walked path is [2, color]`
#
# Also see:
# * `getvar()` that takes the first segment to be the name of a variable
# and then delegates to this function.
# * `dig()` function which is similar but uses an
# array of navigation values instead of a dot notation string.
#
# @since 6.0.0
#
Puppet::Functions.create_function(:get, Puppet::Functions::InternalFunction) do
dispatch :get_from_value do
param 'Any', :value
param 'String', :dotted_string
optional_param 'Any', :default_value
optional_block_param 'Callable[1,1]', :block
end
# Gets a result from given value and a navigation string
#
def get_from_value(value, navigation, default_value = nil, &block)
return default_value if value.nil?
return value if navigation.empty?
# Note: split_key always processes the initial segment as a string even if it could be an integer.
# This since it is designed for lookup keys. For a numeric first segment
# like '0.1' the wanted result is `[0,1]`, not `["0", 1]`. The workaround here is to
# prefix the navigation with `"x."` thus giving split_key a first segment that is a string.
# The fake segment is then dropped.
segments = split_key("x." + navigation) { |_err| _("Syntax error in dotted-navigation string") }
segments.shift
begin
result = call_function('dig', value, *segments)
result.nil? ? default_value : result
rescue Puppet::ErrorWithData => e
if block_given?
yield(e.error_data)
else
raise e
end
end
end
# reuse the split_key parser used also by lookup
include Puppet::Pops::Lookup::SubLookup
end
|