svenfuchs/cl

View on GitHub
lib/cl/opts/validate.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'cl/helper'

class Cl
  class Opts
    module Validate
      def validate(cmd, opts, values, orig)
        Validate.constants.each do |name|
          next if name == :Validator
          const = Validate.const_get(name)
          const.new(cmd, opts, values, orig).apply
        end
      end

      class Validator < Struct.new(:cmd, :opts, :values, :orig)
        include Regex
        def compact(hash, *keys)
          hash.reject { |_, value| value.nil? }.to_h
        end

        def invert(hash)
          hash.map { |key, obj| Array(obj).map { |obj| [obj, key] } }.flatten(1).to_h
        end

        def only(hash, *keys)
          hash.select { |key, _| keys.include?(key) }.to_h
        end
      end

      class Required < Validator
        def apply
          # make sure we do not accept unnamed required options
          raise RequiredOpts.new(missing.map(&:name)) if missing.any?
        end

        def missing
          @missing ||= opts.select(&:required?).select { |opt| !values.key?(opt.name) }
        end
      end

      class Requireds < Validator
        def apply
          raise RequiredsOpts.new(missing) if missing.any?
        end

        def missing
          @missing ||= cmd.class.required.map do |alts|
            alts if alts.none? { |alt| Array(alt).all? { |key| values.key?(key) } }
          end.compact
        end
      end

      class Requires < Validator
        def apply
          raise RequiresOpts.new(invert(missing)) if missing.any?
        end

        def missing
          @missing ||= requires.map do |opt|
            missing = opt.requires.select { |key| !values.key?(key) }
            [opt.name, missing] if missing.any?
          end.compact
        end

        def requires
          opts.select(&:requires?).select { |opt| orig.key?(opt.name) }
        end
      end

      class Format < Validator
        def apply
          raise InvalidFormat.new(invalid) if invalid.any?
        end

        def invalid
          @invalid ||= opts.select(&:format?).map do |opt|
            value = values[opt.name]
            [opt.name, opt.format] if value && !opt.formatted?(value)
          end.compact
        end
      end

      class Enum < Validator
        def apply
          raise UnknownValues.new(unknown) if unknown.any?
        end

        def unknown
          @unknown ||= opts.select(&:enum?).map do |opt|
            unknown = opt.unknown(values[opt.name])
            next if unknown.empty?
            known = opt.enum.map { |str| format_regex(str) }
            [opt.name, unknown, known]
          end.compact
        end
      end

      class Range < Validator
        def apply
          raise OutOfRange.new(invalid) if invalid.any?
        end

        def invalid
          @invalid ||= opts.map do |opt|
            next unless value = values[opt.name]
            range = only(opt.opts, :min, :max)
            [opt.name, compact(range)] if invalid?(range, value)
          end.compact
        end

        def invalid?(range, value)
          min, max = range.values_at(:min, :max)
          min && value < min || max && value > max
        end
      end
    end
  end
end