File: get.rb

package info (click to toggle)
puppet-agent 8.10.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,404 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (152 lines) | stat: -rw-r--r-- 5,450 bytes parent folder | download | duplicates (2)
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