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
|