summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml4
-rw-r--r--CHANGELOG.md17
-rw-r--r--README.md37
-rw-r--r--lib/slop.rb2
-rw-r--r--lib/slop/error.rb12
-rw-r--r--lib/slop/option.rb20
-rwxr-xr-xlib/slop/options.rb4
-rw-r--r--lib/slop/parser.rb2
-rw-r--r--lib/slop/types.rb25
-rw-r--r--test/error_test.rb15
-rw-r--r--test/parser_test.rb27
-rw-r--r--test/types_test.rb32
12 files changed, 173 insertions, 24 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9bac1d4..5b69200 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,10 +22,10 @@ jobs:
strategy:
fail-fast: false
matrix:
- ruby: [ "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "3.0", "3.1", head, jruby, truffleruby ]
+ ruby: [ "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "3.0", "3.1", "3.2", head, jruby, truffleruby ]
name: Ruby ${{ matrix.ruby }}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fdd1e96..9af3f7f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,23 @@
Changelog
=========
+v4.10.0 (2023-02-15)
+
+Features:
+ * Add support for optional type validation [#278](https://github.com/leejarvis/slop/pull/278) (Victor Gama)
+
+v4.9.3 (2022-09-30)
+-------------------
+
+Bug fixes:
+ * Fix explicitly false boolean options and allow for additional false arguments [#276](https://github.com/leejarvis/slop/pull/276) (Eugene Otto)
+
+v4.9.2 (2022-03-26)
+-------------------
+
+Bug fixes:
+ * Handle flag arguments that contain equals character [#275](https://github.com/leejarvis/slop/pull/275) (ConnorWGarvey)
+
v4.9.1 (2021-05-28)
-------------------
diff --git a/README.md b/README.md
index 5d806f1..f15fbc3 100644
--- a/README.md
+++ b/README.md
@@ -22,17 +22,19 @@ opts = Slop.parse do |o|
o.bool '-v', '--verbose', 'enable verbose mode'
o.bool '-q', '--quiet', 'suppress output (quiet mode)'
o.bool '-c', '--check-ssl-certificate', 'check SSL certificate for host'
+ o.bool '-k', '--use-keychain', 'store passphrase in OS keychain'
o.on '--version', 'print the version' do
puts Slop::VERSION
exit
end
end
-ARGV #=> -v --login alice --host 192.168.0.1 -m post --check-ssl-certificate
+ARGV #=> -v --login alice --host 192.168.0.1 -m post --check-ssl-certificate --use-keychain false
opts[:host] #=> 192.168.0.1
opts[:login] #=> alice
opts[:method] #=> :post
+opts[:use_keychain] #=> false
opts.verbose? #=> true
opts.quiet? #=> false
opts.check_ssl_certificate? #=> true
@@ -53,7 +55,7 @@ Built in Option types are as follows:
```ruby
o.string #=> Slop::StringOption, expects an argument
-o.bool #=> Slop::BoolOption, no argument, aliased to BooleanOption
+o.bool #=> Slop::BoolOption, argument optional, aliased to BooleanOption
o.integer #=> Slop::IntegerOption, expects an argument, aliased to IntOption
o.float #=> Slop::FloatOption, expects an argument
o.array #=> Slop::ArrayOption, expects an argument
@@ -202,6 +204,8 @@ Slop will raise errors for the following:
* An option used without an argument when it expects one: `Slop::MissingArgument`
* An option used that Slop doesn't know about: `Slop::UnknownOption`
* An option marked as `required` when not provided: `Slop::MissingRequiredOption`
+* An option marked as `validate_types`, with an argument that does not match its
+type (i.e. `bla` for `integer`): `Slop::InvalidOptionValue`
These errors inherit from `Slop::Error`, so you can rescue them all.
Alternatively you can suppress these errors with the `suppress_errors` config
@@ -220,6 +224,33 @@ opts = Slop.parse do
end
```
+Validating Types
+----------------
+
+By default, Slop does not validate whether an argument is a valid value for a
+given option; instead, if the option has a default value, it will be used over
+the invalid argument provided.
+In order to have types (such as `integer` and `float`) validate and indicate
+that the provided value is invalid, an extra option can be either provided to
+the argument itself, or its option set:
+
+```ruby
+opts = Slop::Options.new
+opts.int "-p", "--port", "a port", default: 80, validate_types: true
+
+parser = Slop::Parser.new(opts)
+result = parser.parse(["--port", "bla"])
+# invalid value for -p, --port (Slop::InvalidOptionValue)
+
+# Or to the option set...
+opts = Slop::Options.new(validate_types: true)
+opts.int "-p", "--port", "a port", default: 80
+
+parser = Slop::Parser.new(opts)
+result = parser.parse(["--port", "bla"])
+# invalid value for -p, --port (Slop::InvalidOptionValue)
+```
+
Printing help
-------------
@@ -277,4 +308,4 @@ end
Commands
--------
-Slop not longer has built in support for git-style subcommands.
+Slop no longer has built in support for git-style subcommands.
diff --git a/lib/slop.rb b/lib/slop.rb
index 35da9cd..7b5ef54 100644
--- a/lib/slop.rb
+++ b/lib/slop.rb
@@ -6,7 +6,7 @@ require 'slop/types'
require 'slop/error'
module Slop
- VERSION = '4.9.1'
+ VERSION = '4.10.0'
# Parse an array of options (defaults to ARGV). Accepts an
# optional hash of configuration options and block.
diff --git a/lib/slop/error.rb b/lib/slop/error.rb
index ddfa1a7..b83cab3 100644
--- a/lib/slop/error.rb
+++ b/lib/slop/error.rb
@@ -38,4 +38,16 @@ module Slop
# Suppress with the `suppress_errors` config option.
class MissingRequiredOption < Error
end
+
+ # Raised when a given option is provided by the user and does not
+ # match the expected format for that type. This is only raised if
+ # validate_types is set to true.
+ class InvalidOptionValue < Error
+ attr_reader :flag
+
+ def initialize(msg, flag)
+ super(msg)
+ @flag = flag
+ end
+ end
end
diff --git a/lib/slop/option.rb b/lib/slop/option.rb
index 093b37f..655b283 100644
--- a/lib/slop/option.rb
+++ b/lib/slop/option.rb
@@ -56,7 +56,11 @@ module Slop
raise Slop::MissingArgument.new("missing argument for #{flag}", flags)
end
else
- @value = call(value)
+ if validate_type? && !valid?(value) && !suppress_errors?
+ raise Slop::InvalidOptionValue.new("invalid value for #{flag}", flags)
+ end
+
+ @value = valid?(value) && call(value)
end
block.call(@value) if block.respond_to?(:call)
@@ -107,6 +111,13 @@ module Slop
config[:required]
end
+ # Returns true if an exception should be raised when this option value can't
+ # be parsed into the desired type or does not conform to the expected type's
+ # format
+ def validate_type?
+ config[:validate_type] || config[:validate_types]
+ end
+
# Returns all flags joined by a comma. Used by the help string.
def flag
flags.join(", ")
@@ -119,6 +130,13 @@ module Slop
key.to_sym
end
+ # Override this if you want to provide a custom validator for a type. This
+ # method must return whether the provided value is valid for the current
+ # argument's type
+ def valid?(value)
+ true
+ end
+
# Returns true if this option should be displayed with dashes transformed into underscores.
def underscore_flags?
config[:underscore_flags]
diff --git a/lib/slop/options.rb b/lib/slop/options.rb
index 17e9501..5e71eac 100755
--- a/lib/slop/options.rb
+++ b/lib/slop/options.rb
@@ -7,6 +7,7 @@ module Slop
type: "null",
banner: true,
underscore_flags: true,
+ validate_types: false,
}
# The Array of Option instances we've created.
@@ -24,6 +25,9 @@ module Slop
# The String banner prefixed to the help string.
attr_accessor :banner
+ # Whether we should validate types of values provided by the user
+ attr_accessor :validate_types
+
def initialize(**config, &block)
@options = []
@separators = []
diff --git a/lib/slop/parser.rb b/lib/slop/parser.rb
index 49d0230..3c2c9c6 100644
--- a/lib/slop/parser.rb
+++ b/lib/slop/parser.rb
@@ -52,7 +52,7 @@ module Slop
# support `foo=bar`
orig_flag = flag.dup
- if match = flag.match(/([^=]+)=([^=]*)/)
+ if match = flag.match(/([^=]+)=(.*)/)
flag, arg = match.captures
end
diff --git a/lib/slop/types.rb b/lib/slop/types.rb
index 4ccd42e..a08537d 100644
--- a/lib/slop/types.rb
+++ b/lib/slop/types.rb
@@ -21,6 +21,15 @@ module Slop
class BoolOption < Option
attr_accessor :explicit_value
+ FALSE_VALUES = [false, 'false', 'no', 'off', '0'].freeze
+ TRUE_VALUES = [true, 'true', 'yes', 'on', '1'].freeze
+ VALID_VALUES = (FALSE_VALUES + TRUE_VALUES).freeze
+
+ def valid?(value)
+ return true if value.is_a?(String) && value.start_with?("--")
+ value.nil? || VALID_VALUES.include?(value)
+ end
+
def call(value)
self.explicit_value = value
!force_false?
@@ -35,7 +44,7 @@ module Slop
end
def force_false?
- explicit_value == false
+ FALSE_VALUES.include?(explicit_value)
end
def default_value
@@ -50,8 +59,14 @@ module Slop
# Cast the option argument to an Integer.
class IntegerOption < Option
+ INT_STRING_REGEXP = /\A[+-]?\d+\z/.freeze
+
+ def valid?(value)
+ value =~ INT_STRING_REGEXP
+ end
+
def call(value)
- value =~ /\A[+-]?\d+\z/ && value.to_i
+ value.to_i
end
end
IntOption = IntegerOption
@@ -60,8 +75,12 @@ module Slop
class FloatOption < Option
FLOAT_STRING_REGEXP = /\A[+-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?\z/.freeze
+ def valid?(value)
+ value =~ FLOAT_STRING_REGEXP
+ end
+
def call(value)
- value =~ FLOAT_STRING_REGEXP && value.to_f
+ value.to_f
end
end
diff --git a/test/error_test.rb b/test/error_test.rb
index d9c71ab..8ea9069 100644
--- a/test/error_test.rb
+++ b/test/error_test.rb
@@ -63,3 +63,18 @@ describe Slop::MissingRequiredOption do
opts.parse []
end
end
+
+describe Slop::InvalidOptionValue do
+ it "raises when an option has an invalid value" do
+ opts = Slop::Options.new(validate_types: true)
+ opts.integer "-n", "--number", default: 10
+ assert_raises(Slop::InvalidOptionValue) { opts.parse %w(-n foo) }
+ end
+
+ it "does not raise when errors are suppressed" do
+ opts = Slop::Options.new(suppress_errors: true)
+ opts.integer "-n", "--number", default: 10, validate_type: true
+ r = opts.parse %w(-n foo)
+ assert_equal(10, r[:n])
+ end
+end
diff --git a/test/parser_test.rb b/test/parser_test.rb
index b729ee8..bbdc4a9 100644
--- a/test/parser_test.rb
+++ b/test/parser_test.rb
@@ -17,16 +17,25 @@ describe Slop::Parser do
assert_equal ["-v", "--name", "lee"], @parser.arguments
end
- it "parses flag=argument" do
- @options.integer "-p", "--port"
- @result.parser.parse %w(--name=bob -p=123)
- assert_equal "bob", @result[:name]
- assert_equal 123, @result[:port]
+ describe "for flag=argument" do
+ it "parses names and values" do
+ @options.integer "-p", "--port"
+ @result.parser.parse %w(--name=bob -p=123)
+ assert_equal "bob", @result[:name]
+ assert_equal 123, @result[:port]
+ end
- @options.string "--foo"
- @result.parser.parse %w(--foo = =)
- assert_equal "=", @result[:foo]
- assert_equal %w(=), @result.args
+ it "treats an = separated by a space as a value" do
+ @options.string "--foo"
+ @result.parser.parse %w(--foo = =)
+ assert_equal "=", @result[:foo]
+ assert_equal %w(=), @result.args
+ end
+
+ it "includes = in strings" do
+ @result.parser.parse(%w(--name=b=b))
+ assert_equal "b=b", @result[:name]
+ end
end
it "parses flag=''" do
diff --git a/test/types_test.rb b/test/types_test.rb
index a45302a..c6f0864 100644
--- a/test/types_test.rb
+++ b/test/types_test.rb
@@ -31,12 +31,14 @@ end
describe Slop::BoolOption do
before do
@options = Slop::Options.new
- @verbose = @options.bool "--verbose"
+ @verbose = @options.bool "--verbose", validate_type: true
@quiet = @options.bool "--quiet"
@inversed = @options.bool "--inversed", default: true
+ @explicit = @options.bool "--explicit", validate_type: true
@bloc = @options.bool("--bloc"){|val| (@bloc_val ||= []) << val}
@result = @options.parse %w(--verbose --no-inversed
- --bloc --no-bloc)
+ --bloc --no-bloc
+ --explicit=false)
end
it "returns true if used" do
@@ -54,13 +56,23 @@ describe Slop::BoolOption do
it "will invert the value passed to &block via --no- prefix" do
assert_equal [true, false], @bloc_val
end
+
+ it "returns false when explicitly false" do
+ assert_equal false, @result[:explicit]
+ end
+
+ it "raises with invalid types" do
+ assert_raises(Slop::InvalidOptionValue) do
+ @result.parser.parse %w(--verbose foo)
+ end
+ end
end
describe Slop::IntegerOption do
before do
@options = Slop::Options.new
@age = @options.integer "--age"
- @minus = @options.integer "--minus"
+ @minus = @options.integer "--minus", validate_type: true
@plus = @options.integer "--plus"
@result = @options.parse %w(--age 20 --minus -10 --plus +30)
end
@@ -75,6 +87,12 @@ describe Slop::IntegerOption do
@result.parser.parse %w(--age hello)
assert_nil @result[:age]
end
+
+ it "raises with invalid types" do
+ assert_raises(Slop::InvalidOptionValue) do
+ @result.parser.parse %w(--minus foo)
+ end
+ end
end
describe Slop::FloatOption do
@@ -82,7 +100,7 @@ describe Slop::FloatOption do
@options = Slop::Options.new
@apr = @options.float "--apr"
@apr_value = 2.9
- @minus = @options.float "--minus"
+ @minus = @options.float "--minus", validate_type: true
@plus = @options.float "--plus"
@scientific_notation = @options.float "--scientific-notation"
@scientific_notation_value = 4e21
@@ -119,6 +137,12 @@ describe Slop::FloatOption do
@result.parser.parse %w(--apr hello)
assert_nil @result[:apr]
end
+
+ it "raises with invalid types" do
+ assert_raises(Slop::InvalidOptionValue) do
+ @result.parser.parse %w(--minus foo)
+ end
+ end
end
describe Slop::ArrayOption do