# frozen_string_literal: true require_relative '../../puppet/parameter/value' # A collection of values and regular expressions, used for specifying allowed values # in a given parameter. # @note This class is considered part of the internal implementation of {Puppet::Parameter}, and # {Puppet::Property} and the functionality provided by this class should be used via their interfaces. # @comment This class probably have several problems when trying to use it with a combination of # regular expressions and aliases as it finds an acceptable value holder vi "name" which may be # a regular expression... # # @api private # class Puppet::Parameter::ValueCollection # Aliases the given existing _other_ value with the additional given _name_. # @return [void] # @api private # def aliasvalue(name, other) other = other.to_sym value = match?(other) raise Puppet::DevError, _("Cannot alias nonexistent value %{value}") % { value: other } unless value value.alias(name) end # Returns a doc string (enumerating the acceptable values) for all of the values in this parameter/property. # @return [String] a documentation string. # @api private # def doc unless defined?(@doc) @doc = ''.dup unless values.empty? @doc << "Valid values are " @doc << @strings.collect do |value| aliases = value.aliases if aliases && !aliases.empty? "`#{value.name}` (also called `#{aliases.join(', ')}`)" else "`#{value.name}`" end end.join(", ") << ". " end unless regexes.empty? @doc << "Values can match `#{regexes.join('`, `')}`." end end @doc end # @return [Boolean] Returns whether the set of allowed values is empty or not. # @api private # def empty? @values.empty? end # @api private # def initialize # We often look values up by name, so a hash makes more sense. @values = {} # However, we want to retain the ability to match values in order, # but we always prefer directly equality (i.e., strings) over regex matches. @regexes = [] @strings = [] end # Checks if the given value is acceptable (matches one of the literal values or patterns) and returns # the "matcher" that matched. # Literal string matchers are tested first, if both a literal and a regexp match would match, the literal # match wins. # # @param test_value [Object] the value to test if it complies with the configured rules # @return [Puppet::Parameter::Value, nil] The instance of Puppet::Parameter::Value that matched the given value, or nil if there was no match. # @api private # def match?(test_value) # First look for normal values value = @strings.find { |v| v.match?(test_value) } return value if value # Then look for a regex match @regexes.find { |v| v.match?(test_value) } end # Munges the value if it is valid, else produces the same value. # @param value [Object] the value to munge # @return [Object] the munged value, or the given value # @todo This method does not seem to do any munging. It just returns the value if it matches the # regexp, or the (most likely Symbolic) allowed value if it matches (which is more of a replacement # of one instance with an equal one. Is the intent that this method should be specialized? # @api private # def munge(value) return value if empty? instance = match?(value) if instance if instance.regex? value else instance.name end else value end end # Defines a new valid value for a {Puppet::Property}. # A valid value is specified as a literal (typically a Symbol), but can also be # specified with a regexp. # # @param name [Symbol, Regexp] a valid literal value, or a regexp that matches a value # @param options [Hash] a hash with options # @option options [Symbol] :event The event that should be emitted when this value is set. # @todo Option :event original comment says "event should be returned...", is "returned" the correct word # to use? # @option options [Symbol] :invalidate_refreshes True if a change on this property should invalidate and # remove any scheduled refreshes (from notify or subscribe) targeted at the same resource. For example, if # a change in this property takes into account any changes that a scheduled refresh would have performed, # then the scheduled refresh would be deleted. # @option options [Object] _any_ Any other option is treated as a call to a setter having the given # option name (e.g. `:required_features` calls `required_features=` with the option's value as an # argument). # @api private # def newvalue(name, options = {}, &block) call_opt = options[:call] unless call_opt.nil? devfail "Cannot use obsolete :call value '#{call_opt}' for property '#{self.class.name}'" unless call_opt == :none || call_opt == :instead # TRANSLATORS ':call' is a property and should not be translated message = _("Property option :call is deprecated and no longer used.") message += ' ' + _("Please remove it.") Puppet.deprecation_warning(message) options = options.reject { |k, _v| k == :call } end value = Puppet::Parameter::Value.new(name) @values[value.name] = value if value.regex? @regexes << value else @strings << value end options.each { |opt, arg| value.send(opt.to_s + "=", arg) } if block_given? devfail "Cannot use :call value ':none' in combination with a block for property '#{self.class.name}'" if call_opt == :none value.block = block value.method ||= "set_#{value.name}" unless value.regex? elsif call_opt == :instead devfail "Cannot use :call value ':instead' without a block for property '#{self.class.name}'" end value end # Defines one or more valid values (literal or regexp) for a parameter or property. # @return [void] # @dsl type # @api private # def newvalues(*names) names.each { |name| newvalue(name) } end # @return [Array] An array of the regular expressions in string form, configured as matching valid values. # @api private # def regexes @regexes.collect { |r| r.name.inspect } end # Validates the given value against the set of valid literal values and regular expressions. # @raise [ArgumentError] if the value is not accepted # @return [void] # @api private # def validate(value) return if empty? unless @values.detect { |_name, v| v.match?(value) } str = _("Invalid value %{value}.") % { value: value.inspect } str += " " + _("Valid values are %{value_list}.") % { value_list: values.join(", ") } unless values.empty? str += " " + _("Valid values match %{pattern}.") % { pattern: regexes.join(", ") } unless regexes.empty? raise ArgumentError, str end end # Returns a valid value matcher (a literal or regular expression) # @todo This looks odd, asking for an instance that matches a symbol, or an instance that has # a regexp. What is the intention here? Marking as api private... # # @return [Puppet::Parameter::Value] a valid value matcher # @api private # def value(name) @values[name] end # @return [Array] Returns a list of valid literal values. # @see regexes # @api private # def values @strings.collect(&:name) end end