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 and Range acceptors. @return [Proc]

INTEGER_CONVERTER

A converter proc that handles integers. Useful in Simple and Range acceptors. @return [Proc]

NUMERIC_CONVERTER

A converter proc that handles any numeric value. Useful in Simple and Range acceptors. @return [Proc]

RATIONAL_CONVERTER

A converter proc that handles rationals. Useful in Simple and Range 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(spec = nil, **options, &block) click to toggle source

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_well_known(spec) click to toggle source

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
scalarize_spec(spec, options, block) click to toggle source

@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

build_array() click to toggle source
# 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
build_boolean(spec, default) click to toggle source
# 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
build_decimal_integer() click to toggle source
# 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
build_decimal_numeric() click to toggle source
# 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
build_float() click to toggle source
# File lib/toys/acceptor.rb, line 585
def build_float
  Simple.new(FLOAT_CONVERTER, type_desc: "floating point number", well_known_spec: ::Float)
end
build_integer() click to toggle source
# File lib/toys/acceptor.rb, line 581
def build_integer
  Simple.new(INTEGER_CONVERTER, type_desc: "integer", well_known_spec: ::Integer)
end
build_nil() click to toggle source
# File lib/toys/acceptor.rb, line 573
def build_nil
  Simple.new(type_desc: "string", well_known_spec: ::NilClass) { |s| s }
end
build_numeric() click to toggle source
# File lib/toys/acceptor.rb, line 593
def build_numeric
  Simple.new(NUMERIC_CONVERTER, type_desc: "number", well_known_spec: ::Numeric)
end
build_octal_integer() click to toggle source
# 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
build_rational() click to toggle source
# File lib/toys/acceptor.rb, line 589
def build_rational
  Simple.new(RATIONAL_CONVERTER, type_desc: "rational number", well_known_spec: ::Rational)
end
build_regexp() click to toggle source
# 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
build_string() click to toggle source
# File lib/toys/acceptor.rb, line 577
def build_string
  Pattern.new(/.+/m, type_desc: "nonempty string", well_known_spec: ::String)
end
internal_create(spec, options, block) click to toggle source
# 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
optparse_well_knowns() click to toggle source
# 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
standard_well_knowns() click to toggle source
# 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