diff options
author | Abinoam P. Marques Jr <abinoam@gmail.com> | 2023-01-02 19:24:22 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-02 19:24:22 -0300 |
commit | 7755f1f3f3b80e4c1c48446ea8234eaf954e6538 (patch) | |
tree | e0398311e72a08fc2a92ca187674c2fa03115bb4 | |
parent | ee18153d63db64242fdbf467a89774c9627d54d5 (diff) | |
parent | aa89af1a5a61cc03e1bdac7913a507b1177832aa (diff) | |
download | highline-7755f1f3f3b80e4c1c48446ea8234eaf954e6538.tar.gz |
Merge pull request #258 from abinoam/issue_246
Issue #246 - Add custom validation class option to `Question#validate`
-rw-r--r-- | Changelog.md | 6 | ||||
-rw-r--r-- | README.md | 27 | ||||
-rw-r--r-- | examples/custom_parser_custom_validator.rb | 39 | ||||
-rw-r--r-- | highline.gemspec | 1 | ||||
-rw-r--r-- | lib/highline/question.rb | 6 | ||||
-rwxr-xr-x | test/test_highline.rb | 49 |
6 files changed, 126 insertions, 2 deletions
diff --git a/Changelog.md b/Changelog.md index 9a86f8a..cafba85 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 @@ -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 0000000..0744e7b --- /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 1c92ce0..e19f557 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 ce2245d..8fcd68d 100644 --- a/lib/highline/question.rb +++ b/lib/highline/question.rb @@ -145,6 +145,9 @@ class HighLine # 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 @@ class HighLine 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 # diff --git a/test/test_highline.rb b/test/test_highline.rb index 13ff406..8de8551 100755 --- a/test/test_highline.rb +++ b/test/test_highline.rb @@ -1626,6 +1626,55 @@ class TestHighLine < Minitest::Test assert_equal("Gray, James Edward", answer) end + class ZeroToTwentyFourValidator + def self.valid?(answer) + (0..24).include? answer.to_i + end + + def self.inspect + "(0..24) rule" + end + end + + def test_validation_with_custom_validator_class + @input << "26\n25\n24\n" + @input.rewind + + answer = @terminal.ask("What hour of the day is it?: ", Integer) do |q| + q.validate = ZeroToTwentyFourValidator + end + + assert_equal(24, answer) + assert_equal("What hour of the day is it?: " \ + "Your answer isn't valid (must match (0..24) rule).\n" \ + "? Your answer isn't valid (must match (0..24) rule).\n" \ + "? ", @output.string) + end + + require 'dry/types' + + module Types + include Dry.Types + end + + def test_validation_with_dry_types + @input << "random string\nanother uncoercible string\n42\n" + @input.rewind + + answer = @terminal.ask("Type a number: ", Integer) do |q| + q.validate = Types::Coercible::Integer + end + + assert_equal(42, answer) + assert_match(Regexp.new(<<~REGEXP.chomp), + Type a number: Your answer isn't valid .must match .*Dry.*Types.*Integer.*.. + \\\? Your answer isn't valid .must match .*Dry.*Types.*Integer.*.. + \\\? + REGEXP + @output.string + ) + end + def test_validation_with_overriding_static_message @input << "Not valid answer\n" \ "42\n" |