<commit_before>module DataMapper
  module Validate

    ##
    #
    # @author <NAME>
    # @since  0.9
    class NumericValidator < GenericValidator

      def call(target)
        value = target.send(field_name)
        return true if allow_nil? && value.blank?

        errors = [
          integer_only? ? validate_integer(value) : validate_numeric(value)
        ].compact

        add_errors(target, errors)

        # if the number is invalid, skip further tests
        return false if errors.any?

        errors = [
          validate_gt(value),
          validate_lt(value),
          validate_gte(value),
          validate_lte(value),
          validate_eq(value),
          validate_ne(value),
        ].compact

        add_errors(target, errors)

        errors.empty?
      end

      private

      def allow_nil?
        options.fetch(:allow_nil, false)
      end

      def integer_only?
        options.fetch(:integer_only, false)
      end

      def value_as_string(value)
        case value
          when Float      then value.to_d.to_s('F')  # Avoid Scientific Notation in Float to_s
          when BigDecimal then value.to_s('F')
          else value.to_s
        end
      end

      def add_errors(target, errors)
        return if errors.empty?

        if options.key?(:message)
          add_error(target, options[:message], field_name)
        else
          errors.each do |error_message|
            add_error(target, error_message, field_name)
          end
        end
      end

      def validate_integer(value)
        unless value_as_string(value) =~ /\A[+-]?\d+\z/
          ValidationErrors.default_error_message(:not_an_integer, field_name)
        end
      end

      def validate_numeric(value)
        precision = options[:precision]
        scale     = options[:scale]

        regexp = if precision
          if scale.nil? || precision > scale && scale == 0
            /\A[+-]?(?:\d{1,#{precision}}(?:\.0)?)\z/
          elsif precision > scale
            /\A[+-]?(?:\d{1,#{precision - scale}}|\d{0,#{precision - scale}}\.\d{1,#{scale}})\z/
          elsif precision == scale
            /\A[+-]?(?:0(?:\.\d{1,#{scale}})?)\z/
          else
            raise ArgumentError, "Invalid precision #{precision.inspect} and scale #{scale.inspect} for #{field_name} (value: #{value.inspect} #{value.class})"
          end
        else
          /\A[+-]?(?:\d+|\d*\.\d+)\z/
        end

        return if value_as_string(value) =~ regexp
        ValidationErrors.default_error_message(:not_a_number, field_name)
      end

      def validate_with_comparison(value, expected, cmp, error_message_name, negated = false)
        return if expected.nil?

        comparison = value.send(cmp, expected)
        return if negated ? !comparison : comparison

        ValidationErrors.default_error_message(error_message_name, field_name, expected)
      end

      def validate_gt(value)
        validate_with_comparison(value, options[:gt], :>, :greater_than)
      end

      def validate_lt(value)
        validate_with_comparison(value, options[:lt], :<, :less_than)
      end

      def validate_gte(value)
        validate_with_comparison(value, options[:gte], :>=, :greater_than_or_equal_to)
      end

      def validate_lte(value)
        validate_with_comparison(value, options[:lte], :<=, :less_than_or_equal_to)
      end

      def validate_eq(value)
        eq = options[:eq] || options[:equal] || options[:equals] || options[:exactly]
        validate_with_comparison(value, eq, :==, :equal_to)
      end

      def validate_ne(value)
        validate_with_comparison(value, options[:ne], :==, :not_equal_to, true)
      end
    end # class NumericValidator

    module ValidatesIsNumber

      # Validate whether a field is numeric
      #
      # @details
      #
      # Options are:
      #
      # :message => "Error message for %s"
      #   Custom error message, also can be a callable object that takes
      #   an object (for pure Ruby objects) or object and property (for DM resources)
      #
      # :precision => 2
      #   Required precision of a value
      #
      # :scale => 2
      #   Required scale of a value
      #
      # :gte => 5.75
      #   'Greater than or greater' requirement
      #
      # :lte => 5.75
      #   'Less than or greater' requirement
      #
      # :lt => 5.75
      #   'Less than' requirement
      #
      # :gt => 5.75<commit_msg>[dm-validations] Further simplification of numeric validator<commit_after>module DataMapper
  module Validate

    ##
    #
    # @author <NAME>
    # @since  0.9
    class NumericValidator < GenericValidator

      def call(target)
        value = target.send(field_name)
        return true if allow_nil? && value.blank?

        errors = []

        validate_with(integer_only? ? :integer : :numeric, value, errors)

        add_errors(target, errors)

        # if the number is invalid, skip further tests
        return false if errors.any?

        [ :gt, :lt, :gte, :lte, :eq, :ne ].each do |validation_type|
          validate_with(validation_type, value, errors)
        end

        add_errors(target, errors)

        errors.empty?
      end

      private

      def allow_nil?
        options.fetch(:allow_nil, false)
      end

      def integer_only?
        options.fetch(:integer_only, false)
      end

      def value_as_string(value)
        case value
          when Float      then value.to_d.to_s('F')  # Avoid Scientific Notation in Float to_s
          when BigDecimal then value.to_s('F')
          else value.to_s
        end
      end

      def add_errors(target, errors)
        return if errors.empty?

        if options.key?(:message)
          add_error(target, options[:message], field_name)
        else
          errors.each do |error_message|
            add_error(target, error_message, field_name)
          end
        end
      end

      def validate_with(validation_type, value, errors)
        send("validate_#{validation_type}", value, errors)
      end

      def validate_with_comparison(value, cmp, expected, error_message_name, errors, negated = false)
        return if expected.nil?

        comparison = value.send(cmp, expected)
        return if negated ? !comparison : comparison

        errors << ValidationErrors.default_error_message(error_message_name, field_name, expected)
      end

      def validate_integer(value, errors)
        validate_with_comparison(value_as_string(value), :=~, /\A[+-]?\d+\z/, :not_an_integer, errors)
      end

      def validate_numeric(value, errors)
        precision = options[:precision]
        scale     = options[:scale]

        regexp = if precision
          if scale.nil? || precision > scale && scale == 0
            /\A[+-]?(?:\d{1,#{precision}}(?:\.0)?)\z/
          elsif precision > scale
            /\A[+-]?(?:\d{1,#{precision - scale}}|\d{0,#{precision - scale}}\.\d{1,#{scale}})\z/
          elsif precision == scale
            /\A[+-]?(?:0(?:\.\d{1,#{scale}})?)\z/
          else
            raise ArgumentError, "Invalid precision #{precision.inspect} and scale #{scale.inspect} for #{field_name} (value: #{value.inspect} #{value.class})"
          end
        else
          /\A[+-]?(?:\d+|\d*\.\d+)\z/
        end

        validate_with_comparison(value_as_string(value), :=~, regexp, :not_a_number, errors)
      end

      def validate_gt(value, errors)
        validate_with_comparison(value, :>, options[:gt], :greater_than, errors)
      end

      def validate_lt(value, errors)
        validate_with_comparison(value, :<, options[:lt], :less_than, errors)
      end

      def validate_gte(value, errors)
        validate_with_comparison(value, :>=, options[:gte], :greater_than_or_equal_to, errors)
      end

      def validate_lte(value, errors)
        validate_with_comparison(value, :<=, options[:lte], :less_than_or_equal_to, errors)
      end

      def validate_eq(value, errors)
        eq = options[:eq] || options[:equal] || options[:equals] || options[:exactly]
        validate_with_comparison(value, :==, eq, :equal_to, errors)
      end

      def validate_ne(value, errors)
        validate_with_comparison(value, :==, options[:ne], :not_equal_to, errors, true)
      end
    end # class NumericValidator

    module ValidatesIsNumber

      # Validate whether a field is numeric
      #
      # @details
      #
      # Options are:
      #
      # :message => "Error message for %s"
      #   Custom error message, also can be a callable object that takes
      #   an object (for pure Ruby objects) or object and property (for DM resources)
      #
      # :precision => 2
      #   Required precision of a value
      #
      # :scale => 2
      #   Required scale of a value
      #
      # :gte => 5.75
      #   'Greater than or greater' requirement
      #
      # :lte => 5.75
      #   'Less than or greater' requirement
      #
      # :lt => 5.75
      #   'Less than' requirement
      #
      # :gt => 5.75
{'fraction_non_alphanumeric': 0.11622222222222223, 'fraction_numerical': 0.005333333333333333, 'mean_word_length': 2.1582456140350876, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2007083', 'n_tokens_mistral': 2764, 'n_tokens_neox': 2634, 'n_words': 644}