module Toys::Acceptor
An Acceptor
validates and converts arguments. It is designed to be compatible with the OptionParser accept mechanism.
First, an acceptor validates the argument via its {Toys::Acceptor::Base#match} method. This method should determine whether the argument is valid, and return information that will help with conversion of the argument.
Second, an acceptor converts the argument to its final form via the {Toys::Acceptor::Base#convert} method.
Finally, an acceptor has a name that may appear in help text for flags and arguments that use it.
Constants
- DEFAULT
The default acceptor. Corresponds to the well-known acceptor for `Object`. @return [Toys::Acceptor::Base]
- DEFAULT_TYPE_DESC
The default type description. @return [String]
- FALSE_STRINGS
- FLOAT_CONVERTER
A converter proc that handles floats. Useful in
Simple
andRange
acceptors. @return [Proc]- INTEGER_CONVERTER
A converter proc that handles integers. Useful in
Simple
andRange
acceptors. @return [Proc]- NUMERIC_CONVERTER
A converter proc that handles any numeric value. Useful in
Simple
andRange
acceptors. @return [Proc]- RATIONAL_CONVERTER
A converter proc that handles rationals. Useful in
Simple
andRange
acceptors. @return [Proc]- REJECT
A sentinel that may be returned from a function-based acceptor to indicate invalid input. @return [Object]
- TRUE_STRINGS
Public Class Methods
Create an acceptor from a variety of specification formats. The acceptor is constructed from the given specification object and/or the given block. Additionally, some acceptors can take an optional type description string used to describe the type of data in online help.
Recognized specs include:
* Any well-known acceptor recognized by OptionParser, such as `Integer`, `Array`, or `OptionParser::DecimalInteger`. Any block and type description you provide are ignored. * Any **regular expression**. The returned acceptor validates only if the regex matches the *entire string parameter*. You can also provide an optional conversion function as a block. If provided, the block must take a variable number of arguments, the first being the matched string and the remainder being the captures from the regular expression. It should return the converted object that will be stored in the context data. If you do not provide a block, no conversion takes place, and the original string is used. * An **array** of possible values. The acceptor validates if the string parameter matches the *string form* of one of the array elements (i.e. the results of calling `to_s` on the element.) An array acceptor automatically converts the string parameter to the actual array element that it matched. For example, if the symbol `:foo` is in the array, it will match the string `"foo"`, and then store the symbol `:foo` in the tool data. You may not further customize the conversion function; any block is ignored. * A **range** of possible values. The acceptor validates if the string parameter, after conversion to the range type, lies within the range. The final value stored in context data is the converted value. For numeric ranges, conversion is provided, but if the range has a different type, you must provide the conversion function as a block. * A **function** as a Proc (where the block is ignored) or a block (if the spec is nil). This function performs *both* validation and conversion. It should take the string parameter as its argument, and it must either return the object that should be stored in the tool data, or raise an exception (descended from `StandardError`) to indicate that the string parameter is invalid. You may also return the sentinel value {Toys::Acceptor::REJECT} to indicate that the string is invalid. * The value `nil` or `:default` with no block, to indicate the default pass-through acceptor {Toys::Acceptor::DEFAULT}. Any type description you provide is ignored.
Additional options:
* `:type_desc` (String) The type description for interpolating into help text. Ignored if the spec indicates the default acceptor or a well-known acceptor.
@param spec [Object] See the description for recognized values. @param options [Hash] Additional options to pass to the acceptor. @param block [Proc] See the description for recognized forms. @return [Toys::Acceptor::Base,Proc]
# File lib/toys/acceptor.rb, line 505 def create(spec = nil, **options, &block) well_known = lookup_well_known(spec) return well_known if well_known if spec.is_a?(::Hash) options = options.merge(spec) spec = nil end spec ||= options.delete(:"") internal_create(spec, options, block) end
Lookup a standard acceptor name recognized by OptionParser.
@param spec [Object] A well-known acceptor specification, such as
`String`, `Integer`, `Array`, `OptionParser::DecimalInteger`, etc.
@return [Toys::Acceptor::Base] The corresponding Acceptor
object @return [nil] if the given standard acceptor was not recognized.
# File lib/toys/acceptor.rb, line 434 def lookup_well_known(spec) result = standard_well_knowns[spec] if result.nil? && defined?(::OptionParser) result = optparse_well_knowns[spec] end result end
@private
# File lib/toys/acceptor.rb, line 517 def scalarize_spec(spec, options, block) spec ||= block if options.empty? spec elsif spec options.merge({"": spec}) else options end end
Private Class Methods
# File lib/toys/acceptor.rb, line 620 def build_array Simple.new(type_desc: "string array", well_known_spec: ::Array) do |s| if s.nil? nil else s.split(",").collect { |elem| elem unless elem.empty? } end end end
# File lib/toys/acceptor.rb, line 601 def build_boolean(spec, default) Simple.new(type_desc: "boolean", well_known_spec: spec) do |s| if s.nil? default elsif s.empty? REJECT else s = s.downcase if TRUE_STRINGS.any? { |t| t.start_with?(s) } true elsif FALSE_STRINGS.any? { |f| f.start_with?(s) } false else REJECT end end end end
# File lib/toys/acceptor.rb, line 648 def build_decimal_integer Simple.new(type_desc: "decimal integer", well_known_spec: ::OptionParser::DecimalInteger) do |s| s.nil? ? nil : Integer(s, 10) end end
# File lib/toys/acceptor.rb, line 662 def build_decimal_numeric Simple.new(type_desc: "decimal number", well_known_spec: ::OptionParser::DecimalNumeric) do |s| if s.nil? nil elsif s.include?(".") || (s.include?("e") && s !~ /\A-?0x/) Float(s) else Integer(s, 10) end end end
# File lib/toys/acceptor.rb, line 585 def build_float Simple.new(FLOAT_CONVERTER, type_desc: "floating point number", well_known_spec: ::Float) end
# File lib/toys/acceptor.rb, line 581 def build_integer Simple.new(INTEGER_CONVERTER, type_desc: "integer", well_known_spec: ::Integer) end
# File lib/toys/acceptor.rb, line 573 def build_nil Simple.new(type_desc: "string", well_known_spec: ::NilClass) { |s| s } end
# File lib/toys/acceptor.rb, line 593 def build_numeric Simple.new(NUMERIC_CONVERTER, type_desc: "number", well_known_spec: ::Numeric) end
# File lib/toys/acceptor.rb, line 655 def build_octal_integer Simple.new(type_desc: "octal integer", well_known_spec: ::OptionParser::OctalInteger) do |s| s.nil? ? nil : Integer(s, 8) end end
# File lib/toys/acceptor.rb, line 589 def build_rational Simple.new(RATIONAL_CONVERTER, type_desc: "rational number", well_known_spec: ::Rational) end
# File lib/toys/acceptor.rb, line 630 def build_regexp Simple.new(type_desc: "regular expression", well_known_spec: ::Regexp) do |s| if s.nil? nil else flags = 0 if (match = %r{\A/((?:\\.|[^\\])*)/([[:alpha:]]*)\z}.match(s)) s = match[1] opts = match[2] || "" flags |= ::Regexp::IGNORECASE if opts.include?("i") flags |= ::Regexp::MULTILINE if opts.include?("m") flags |= ::Regexp::EXTENDED if opts.include?("x") end ::Regexp.new(s, flags) end end end
# File lib/toys/acceptor.rb, line 577 def build_string Pattern.new(/.+/m, type_desc: "nonempty string", well_known_spec: ::String) end
# File lib/toys/acceptor.rb, line 530 def internal_create(spec, options, block) case spec when Base spec when ::Regexp Pattern.new(spec, **options, &block) when ::Array Enum.new(spec, **options) when ::Proc Simple.new(spec, **options) when ::Range Range.new(spec, **options, &block) when nil, :default block ? Simple.new(**options, &block) : DEFAULT else raise ToolDefinitionError, "Illegal acceptor spec: #{spec.inspect}" end end
# File lib/toys/acceptor.rb, line 565 def optparse_well_knowns @optparse_well_knowns ||= { ::OptionParser::DecimalInteger => build_decimal_integer, ::OptionParser::OctalInteger => build_octal_integer, ::OptionParser::DecimalNumeric => build_decimal_numeric, } end
# File lib/toys/acceptor.rb, line 549 def standard_well_knowns @standard_well_knowns ||= { ::Object => DEFAULT, ::NilClass => build_nil, ::String => build_string, ::Integer => build_integer, ::Float => build_float, ::Rational => build_rational, ::Numeric => build_numeric, ::TrueClass => build_boolean(::TrueClass, true), ::FalseClass => build_boolean(::FalseClass, false), ::Array => build_array, ::Regexp => build_regexp, } end