File: tree_each.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 (198 lines) | stat: -rw-r--r-- 7,598 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# frozen_string_literal: true

# Runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html)
# recursively and repeatedly using values from a data structure, then returns the unchanged data structure, or if
# a lambda is not given, returns an `Iterator` for the tree.
#
# This function takes one mandatory argument, one optional, and an optional block in this order:
#
# 1. An `Array`, `Hash`, `Iterator`, or `Object` that the function will iterate over.
# 2. An optional hash with the options:
#    * `include_containers` => `Optional[Boolean]` # default `true` - if containers should be given to the lambda
#    * `include_values` => `Optional[Boolean]` # default `true` - if non containers should be given to the lambda
#    * `include_root` => `Optional[Boolean]` # default `true` - if the root container should be given to the lambda
#    * `container_type` => `Optional[Type[Variant[Array, Hash, Object]]]` # a type that determines what a container is - can only
#       be set to a type that matches the default `Variant[Array, Hash, Object]`.
#    * `order` => `Enum[depth_first, breadth_first]` # default ´depth_first`, the order in which elements are visited
#    * `include_refs` => `Optional[Boolean]` # default `false`, if attributes in objects marked as bing of `reference` kind
#       should be included.
# 3. An optional lambda, which the function calls for each element in the first argument. It must
#    accept one or two arguments; either `$path`, and `$value`, or just `$value`.
#
# @example Using the `tree_each` function
#
# `$data.tree_each |$path, $value| { <PUPPET CODE BLOCK> }`
# `$data.tree_each |$value| { <PUPPET CODE BLOCK> }`
#
# or
#
# `tree_each($data) |$path, $value| { <PUPPET CODE BLOCK> }`
# `tree_each($data) |$value| { <PUPPET CODE BLOCK> }`
#
# The parameter `$path` is always given as an `Array` containing the path that when applied to
# the tree as `$data.dig(*$path) yields the `$value`.
# The `$value` is the value at that path.
#
# For `Array` values, the path will contain `Integer` entries with the array index,
# and for `Hash` values, the path will contain the hash key, which may be `Any` value.
# For `Object` containers, the entry is the name of the attribute (a `String`).
#
# The tree is walked in either depth-first order, or in breadth-first order under the control of the
# `order` option, yielding each `Array`, `Hash`, `Object`, and each entry/attribute.
# The default is `depth_first` which means that children are processed before siblings.
# An order of `breadth_first` means that siblings are processed before children.
#
# @example depth- or breadth-first order
#
# ```puppet
# [1, [2, 3], 4]
# ```
#
# If containers are skipped, results in:
#
# * `depth_first` order `1`, `2`, `3`, `4`
# * `breadth_first` order `1`, `4`,`2`, `3`
#
# If containers and root are included, results in:
#
# * `depth_first` order `[1, [2, 3], 4]`, `1`, `[2, 3]`, `2`, `3`, `4`
# * `breadth_first` order `[1, [2, 3], 4]`, `1`, `[2, 3]`, `4`, `2`, `3`
#
# Typical use of the `tree_each` function include:
# * a more efficient way to iterate over a tree than first using `flatten` on an array
#   as that requires a new (potentially very large) array to be created
# * when a tree needs to be transformed and 'pretty printed' in a template
# * avoiding having to write a special recursive function when tree contains hashes (flatten does
#   not work on hashes)
#
# @example A flattened iteration over a tree excluding Collections
#
# ```puppet
# $data = [1, 2, [3, [4, 5]]]
# $data.tree_each({include_containers => false}) |$v| { notice "$v" }
# ```
#
# This would call the lambda 5 times with with the following values in sequence: `1`, `2`, `3`, `4`, `5`
#
# @example A flattened iteration over a tree (including containers by default)
#
# ```puppet
# $data = [1, 2, [3, [4, 5]]]
# $data.tree_each |$v| { notice "$v" }
# ```
#
# This would call the lambda 7 times with the following values in sequence:
# `1`, `2`, `[3, [4, 5]]`, `3`, `[4, 5]`, `4`, `5`
#
# @example A flattened iteration over a tree (including only non root containers)
#
# ```puppet
# $data = [1, 2, [3, [4, 5]]]
# $data.tree_each({include_values => false, include_root => false}) |$v| { notice "$v" }
# ```
#
# This would call the lambda 2 times with the following values in sequence:
# `[3, [4, 5]]`, `[4, 5]`
#
# Any Puppet Type system data type can be used to filter what is
# considered to be a container, but it must be a narrower type than one of
# the default `Array`, `Hash`, `Object` types - for example it is not possible to make a
# `String` be a container type.
#
# @example Only `Array` as container type
#
# ```puppet
# $data = [1, {a => 'hello', b => [100, 200]}, [3, [4, 5]]]
# $data.tree_each({container_type => Array, include_containers => false} |$v| { notice "$v" }
# ```
#
# Would call the lambda 5 times with `1`, `{a => 'hello', b => [100, 200]}`, `3`, `4`, `5`
#
# **Chaining** When calling `tree_each` without a lambda the function produces an `Iterator`
# that can be chained into another iteration. Thus it is easy to use one of:
#
# * `reverse_each` - get "leaves before root"
# * `filter` - prune the tree
# * `map` - transform each element
#
# Note than when chaining, the value passed on is a `Tuple` with `[path, value]`.
#
# @example Pruning a tree
#
# ```puppet
# # A tree of some complexity (here very simple for readability)
# $tree = [
#  { name => 'user1', status => 'inactive', id => '10'},
#  { name => 'user2', status => 'active', id => '20'}
# ]
# notice $tree.tree_each.filter |$v| {
#  $value = $v[1]
#  $value =~ Hash and $value[status] == active
# }
# ```
#
# Would notice `[[[1], {name => user2, status => active, id => 20}]]`, which can then be processed
# further as each filtered result appears as a `Tuple` with `[path, value]`.
#
#
# For general examples that demonstrates iteration see the Puppet
# [iteration](https://puppet.com/docs/puppet/latest/lang_iteration.html)
# documentation.
#
# @since 5.0.0
#
Puppet::Functions.create_function(:tree_each) do
  local_types do
    type "OptionsType  = Struct[{\
      container_type => Optional[Type],\
      include_root   => Optional[Boolean],
      include_containers => Optional[Boolean],\
      include_values => Optional[Boolean],\
      order => Optional[Enum[depth_first, breadth_first]],\
      include_refs   => Optional[Boolean]\
    }]"
  end

  dispatch :tree_Enumerable2 do
    param 'Variant[Iterator, Array, Hash, Object]', :tree
    optional_param 'OptionsType', :options
    block_param 'Callable[2,2]', :block
  end

  dispatch :tree_Enumerable1 do
    param 'Variant[Iterator, Array, Hash, Object]', :tree
    optional_param 'OptionsType', :options
    block_param 'Callable[1,1]', :block
  end

  dispatch :tree_Iterable do
    param 'Variant[Iterator, Array, Hash, Object]', :tree
    optional_param 'OptionsType', :options
  end

  def tree_Enumerable1(enum, options = {}, &block)
    iterator(enum, options).each { |_, v| yield(v) }
    enum
  end

  def tree_Enumerable2(enum, options = {}, &block)
    iterator(enum, options).each { |path, v| yield(path, v) }
    enum
  end

  def tree_Iterable(enum, options = {}, &block)
    Puppet::Pops::Types::Iterable.on(iterator(enum, options))
  end

  def iterator(enum, options)
    if depth_first?(options)
      Puppet::Pops::Types::Iterable::DepthFirstTreeIterator.new(enum, options)
    else
      Puppet::Pops::Types::Iterable::BreadthFirstTreeIterator.new(enum, options)
    end
  end

  def depth_first?(options)
    (order = options['order']).nil? ? true : order == 'depth_first'
  end
end