Repository: JEG2/highline (Ruby)

Issue:
Possible to accept multiple selections with `.in` and `.default`?
Currently I have 

```ruby
# trimmed: puts menu items

cli.ask("Which number? (0 or <Enter> to skip): ", Integer) { |q|
  q.in = 0..(possibleSelections.length)
  q.default = 0
}
```

This is awesome for choosing multiple items from a list, such as:
```bash

1. TEST1.png
2. TEST2.png
3. TEST3.png
4. TEST4.png
5. TEST5.png
6. TEST6.png
7. TEST7.png

Which number? (0 or <Enter> to skip): 12
Your answer isn't within the expected range (included in 0..7).
?
```

What I would like to extend to is this:
```bash
Which number(s)? (0 or <Enter> to skip): 1, 2, 5
Success
```

and 
```bash
Which number(s)? (0 or <Enter> to skip): 1, 2, 5, 12
Your answer isn't within the expected range (included in 0..7).
?
```

Thanks!


Discussion: Dear @Joshfindit,

Sorry, for the late answer.

I'm not sure I could understand the problem you have stated correctly.
Could you please clarify it? (Note that I'm not a native english speaker)

Best regards
Perhaps it would be easier for me to understand if you provide a complete code snipped that I could try to run on my machine things.
If I understand it correctly he wants to be able to do multiple input on the same line?

not sure if I understood it but the sample he showed was e. g.:

"1, 2, 5, 12"

Can't say so far as I usually have only single selections. :D But this may be
what he is aiming for. In that case probably just a convenience feature to
avoid several newlines for multiple guesses, I suppose?
Hi @Joshfindit

Are you still interested in this feature. 
I tried to accomplish it with minimum code change.
I'll open a PR tomorrow.

> Are you still interested in this feature.

I don’t personally have a need for it any more. I still do think there’s general benefit for others.



Fix (patch):
diff --git a/Changelog.md b/Changelog.md
index 9a86f8a7..cafba856 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,7 +3,11 @@
 Below is a complete listing of changes for each revision of HighLine.
 
 ### Unreleased 2.2.0.develop.1
-* I #233 Show Question#default hint for non String values (@abinoam)
+* PR #258 / I #246 - Add validation class support
+  * Make it dry-types compatible through the use of `#valid?`
+  * Solve the multiple answers in one line problem with a combination of
+    custom coercion (parser) and custom validation
+* PR #257 / I #233 - Show Question#default hint for non String values (@abinoam)
   * Add Question#default_hint_show to allow disabling it.
 
 ### 2.1.0 / 2022-12-31
diff --git a/README.md b/README.md
index c1baf49e..0d878cfd 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,33 @@ end
 cli.ask("Age?  ", Integer) { |q| q.in = 0..105 }
 cli.ask("Name?  (last, first)  ") { |q| q.validate = /\A\w+, ?\w+\Z/ }
 
+## Validation with custom class
+class ZeroToTwentyFourValidator
+  def self.valid?(answer)
+    (0..24).include? answer.to_i
+  end
+
+  def self.inspect
+    "(0..24) rule"
+  end
+end
+
+cli.ask("What hour of the day is it?:  ", Integer) do |q|
+  q.validate = ZeroToTwentyFourValidator
+end
+
+## Validation with Dry::Types
+## `Dry::Types` provides a `valid?` method so it can be used effortlessly
+
+require 'dry-type'
+
+module Types
+  include Dry.Types
+end
+
+cli.ask("Type an integer:", Integer) do |q|
+  q.validate = Types::Coercible::Integer
+end
 
 # Type conversion for answers:
 
diff --git a/examples/custom_parser_custom_validator.rb b/examples/custom_parser_custom_validator.rb
new file mode 100644
index 00000000..0744e7bd
--- /dev/null
+++ b/examples/custom_parser_custom_validator.rb
@@ -0,0 +1,39 @@
+require 'highline'
+
+cli = HighLine.new
+
+# The parser
+class ArrayOfNumbersFromString
+  def self.parse(string)
+    string.scan(/\d+/).map(&:to_i)
+  end
+end
+
+# The validator
+class ArrayOfNumbersFromStringInRange
+  def self.in?(range)
+    new(range)
+  end
+
+  attr_reader :range
+
+  def initialize(range)
+    @range = range
+  end
+
+  def valid?(answer)
+    ary = ArrayOfNumbersFromString.parse(answer)
+    ary.all? ->(number) { range.include? number }
+  end
+
+  def inspect
+    "in range #@range validator"
+  end
+end
+
+answer = cli.ask("Which number? (0 or <Enter> to skip): ", ArrayOfNumbersFromString) { |q|
+  q.validate = ArrayOfNumbersFromStringInRange.in?(0..10)
+  q.default = 0
+}
+
+puts "Your answer was: #{answer} and it was correctly validated and coerced into an #{answer.class}"
diff --git a/highline.gemspec b/highline.gemspec
index 1c92ce00..e19f557e 100644
--- a/highline.gemspec
+++ b/highline.gemspec
@@ -32,4 +32,5 @@ DESCRIPTION
   spec.add_development_dependency "bundler"
   spec.add_development_dependency "rake"
   spec.add_development_dependency "minitest"
+  spec.add_development_dependency "dry-types"
 end
diff --git a/lib/highline/question.rb b/lib/highline/question.rb
index ce2245da..8fcd68d6 100644
--- a/lib/highline/question.rb
+++ b/lib/highline/question.rb
@@ -145,6 +145,9 @@ def initialize(template, answer_type)
     # If set to a Regexp, the answer must match (before type conversion).
     # Can also be set to a Proc which will be called with the provided
     # answer to validate with a +true+ or +false+ return.
+    # It's possible to use a custom validator class. It must respond to
+    # `#valid?`. The result of `#inspect` will be used in error messages.
+    # See README.md for details.
     #
     attr_accessor :validate
     # Used to control range checks for answer.
@@ -502,7 +505,8 @@ def to_s
     def valid_answer?
       !validate ||
         (validate.is_a?(Regexp) && answer =~ validate) ||
-        (validate.is_a?(Proc)   && validate[answer])
+        (validate.is_a?(Proc)   && validate[answer]) ||
+        (validate.respond_to?(:valid?) && validate.valid?(answer))
     end
 
     #