File: max.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 (250 lines) | stat: -rw-r--r-- 7,583 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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# frozen_string_literal: true

# Returns the highest value among a variable number of arguments.
# Takes at least one argument.
#
# This function is (with one exception) compatible with the stdlib function
# with the same name and performs deprecated type conversion before
# comparison as follows:
#
# * If a value converted to String is an optionally '-' prefixed,
#   string of digits, one optional decimal point, followed by optional
#   decimal digits - then the comparison is performed on the values
#   converted to floating point.
# * If a value is not considered convertible to float, it is converted
#   to a `String` and the comparison is a lexical compare where min is
#   the lexicographical later value.
# * A lexicographical compare is performed in a system locale - international
#   characters may therefore not appear in what a user thinks is the correct order.
# * The conversion rules apply to values in pairs - the rule must hold for both
#   values - a value may therefore be compared using different rules depending
#   on the "other value".
# * The returned result found to be the "highest" is the original unconverted value.
#
# The above rules have been deprecated in Puppet 6.0.0 as they produce strange results when
# given values of mixed data types. In general, either convert values to be
# all `String` or all `Numeric` values before calling the function, or call the
# function with a lambda that performs type conversion and comparison. This because one
# simply cannot compare `Boolean` with `Regexp` and with any arbitrary `Array`, `Hash` or
# `Object` and getting a meaningful result.
#
# The one change in the function's behavior is when the function is given a single
# array argument. The stdlib implementation would return that array as the result where
# it now instead returns the max value from that array.
#
# @example 'max of values - stdlib compatible'
#
# ```puppet
# notice(max(1)) # would notice 1
# notice(max(1,2)) # would notice 2
# notice(max("1", 2)) # would notice 2
# notice(max("0777", 512)) # would notice "0777", since "0777" is not converted from octal form
# notice(max(0777, 512)) # would notice 512, since 0777 is decimal 511
# notice(max('aa', 'ab')) # would notice 'ab'
# notice(max(['a'], ['b'])) # would notice ['b'], since "['b']" is after "['a']"
# ```
#
# @example find 'max' value in an array - stdlib compatible
#
# ```puppet
# $x = [1,2,3,4]
# notice(max(*$x)) # would notice 4
# ```
#
# @example find 'max' value in an array directly - since Puppet 6.0.0
#
# ```puppet
# $x = [1,2,3,4]
# notice(max($x)) # would notice 4
# notice($x.max) # would notice 4
# ```
# This example shows that a single array argument is used as the set of values
# as opposed to being a single returned value.
#
# When calling with a lambda, it must accept two variables and it must return
# one of -1, 0, or 1 depending on if first argument is before/lower than, equal to,
# or higher/after the second argument.
#
# @example 'max of values using a lambda - since Puppet 6.0.0'
#
# ```puppet
# notice(max("2", "10", "100") |$a, $b| { compare($a, $b) })
# ```
#
# Would notice "2" as higher since it is lexicographically higher/after the other values. Without the
# lambda the stdlib compatible (deprecated) behavior would have been to return "100" since number conversion
# kicks in.
#
Puppet::Functions.create_function(:max) do
  dispatch :on_numeric do
    repeated_param 'Numeric', :values
  end

  dispatch :on_string do
    repeated_param 'String', :values
  end

  dispatch :on_semver do
    repeated_param 'Semver', :values
  end

  dispatch :on_timespan do
    repeated_param 'Timespan', :values
  end

  dispatch :on_timestamp do
    repeated_param 'Timestamp', :values
  end

  dispatch :on_single_numeric_array do
    param 'Array[Numeric]', :values
    optional_block_param 'Callable[2,2]', :block
  end

  dispatch :on_single_string_array do
    param 'Array[String]', :values
    optional_block_param 'Callable[2,2]', :block
  end

  dispatch :on_single_semver_array do
    param 'Array[Semver]', :values
    optional_block_param 'Callable[2,2]', :block
  end

  dispatch :on_single_timespan_array do
    param 'Array[Timespan]', :values
    optional_block_param 'Callable[2,2]', :block
  end

  dispatch :on_single_timestamp_array do
    param 'Array[Timestamp]', :values
    optional_block_param 'Callable[2,2]', :block
  end

  dispatch :on_single_any_array do
    param 'Array', :values
    optional_block_param 'Callable[2,2]', :block
  end

  dispatch :on_any_with_block do
    repeated_param 'Any', :values
    block_param 'Callable[2,2]', :block
  end

  dispatch :on_any do
    repeated_param 'Any', :values
  end

  # All are Numeric - ok now, will be ok later
  def on_numeric(*args)
    assert_arg_count(args)
    args.max
  end

  # All are String, may convert to numeric (which is deprecated)
  def on_string(*args)
    assert_arg_count(args)

    args.max do |a, b|
      if a.to_s =~ /\A^-?\d+([._eE]\d+)?\z/ && b.to_s =~ /\A-?\d+([._eE]\d+)?\z/
        Puppet.warn_once('deprecations', 'max_function_numeric_coerce_string',
                         _("The max() function's auto conversion of String to Numeric is deprecated - change to convert input before calling, or use lambda"))
        a.to_f <=> b.to_f
      else
        # case sensitive as in the stdlib function
        a <=> b
      end
    end
  end

  def on_semver(*args)
    assert_arg_count(args)
    args.max
  end

  def on_timespan(*args)
    assert_arg_count(args)
    args.max
  end

  def on_timestamp(*args)
    assert_arg_count(args)
    args.max
  end

  def on_any_with_block(*args, &block)
    args.max { |x, y| block.call(x, y) }
  end

  def on_single_numeric_array(array, &block)
    if block_given?
      on_any_with_block(*array, &block)
    else
      on_numeric(*array)
    end
  end

  def on_single_string_array(array, &block)
    if block_given?
      on_any_with_block(*array, &block)
    else
      on_string(*array)
    end
  end

  def on_single_semver_array(array, &block)
    if block_given?
      on_any_with_block(*array, &block)
    else
      on_semver(*array)
    end
  end

  def on_single_timespan_array(array, &block)
    if block_given?
      on_any_with_block(*array, &block)
    else
      on_timespan(*array)
    end
  end

  def on_single_timestamp_array(array, &block)
    if block_given?
      on_any_with_block(*array, &block)
    else
      on_timestamp(*array)
    end
  end

  def on_single_any_array(array, &block)
    if block_given?
      on_any_with_block(*array, &block)
    else
      on_any(*array)
    end
  end

  # Mix of data types - while only some compares are actually bad it will deprecate
  # the entire call
  #
  def on_any(*args)
    assert_arg_count(args)
    args.max do |a, b|
      as = a.to_s
      bs = b.to_s
      if as =~ /\A^-?\d+([._eE]\d+)?\z/ && bs =~ /\A-?\d+([._eE]\d+)?\z/
        Puppet.warn_once('deprecations', 'max_function_numeric_coerce_string',
                         _("The max() function's auto conversion of String to Numeric is deprecated - change to convert input before calling, or use lambda"))
        a.to_f <=> b.to_f
      else
        Puppet.warn_once('deprecations', 'max_function_string_coerce_any',
                         _("The max() function's auto conversion of Any to String is deprecated - change to convert input before calling, or use lambda"))
        as <=> bs
      end
    end
  end

  def assert_arg_count(args)
    raise(ArgumentError, 'max(): Wrong number of arguments need at least one') if args.empty?
  end
end