lib/rake-commander/option.rb in rake-commander-0.1.4 vs lib/rake-commander/option.rb in rake-commander-0.2.0
- old
+ new
@@ -1,39 +1,61 @@
class RakeCommander
@option_struct ||= Struct.new(:short, :name)
class Option < @option_struct
extend RakeCommander::Base::ClassHelpers
extend RakeCommander::Options::Name
+ include RakeCommander::Options::Description
- attr_accessor :desc, :default
- attr_writer :type_coertion, :required
- attr_reader :name_full
+ attr_reader :name_full, :desc, :default
- def initialize(short, name, *args, **kargs, &block)
- raise ArgumentError, "A short of one letter should be provided. Given: #{short}" unless self.class.valid_short?(short)
- raise ArgumentError, "A name should be provided. Given: #{name}" unless self.class.valid_name?(name)
+ # @param sample [Boolean] allows to skip the `short` and `name` validations
+ def initialize(*args, sample: false, **kargs, &block)
+ short, name = capture_arguments_short_n_name!(args, kargs, sample: sample)
- @name_full = name
- super(short, name)
+ @name_full = name.freeze
+ super(short.freeze, @name_full)
@default = kargs[:default] if kargs.key?(:default)
@desc = kargs[:desc] if kargs.key?(:desc)
@required = kargs[:required] if kargs.key?(:required)
+ @type_coertion = kargs[:type] if kargs.key?(:type)
@other_args = args
@original_block = block
- yield(self) if block_given?
configure_other
end
+ # Makes a copy of this option
+ # @return [RakeCommander::Option]
+ def dup(**kargs, &block)
+ block ||= original_block
+ self.class.new(**dup_key_arguments.merge(kargs), &block)
+ end
+ alias_method :deep_dup, :dup
+
+ # Creates a new option, result of merging this `opt` with this option,
+ # @return [RakeCommander::Option] where opt has been merged
+ def merge(opt)
+ raise "Expecting RakeCommander::Option. Given: #{opt.class}" unless opt.is_a?(RakeCommander::Option)
+ dup(**opt.dup_key_arguments, &opt.original_block)
+ end
+
+ # @return [Boolean] whether this option is required.
def required?
!!@required
end
# @return [Symbol]
def short
self.class.short_sym(super)
end
+ # `OptionParser` interprets free shorts that match the first letter of an option name
+ # as an invocation of that option. This method allows to identify this.
+ # return [Symbol]
+ def short_implicit
+ self.class.short_sym(@name_full)
+ end
+
# @return [String]
def short_hyphen
self.class.short_hyphen(short)
end
@@ -45,10 +67,15 @@
# @return [String]
def name_hyphen
self.class.name_hyphen(name_full)
end
+ # @return [Boolean]
+ def boolean_name?
+ self.class.boolean_name?(name_full)
+ end
+
# @param [Boolean] whether this option allows an argument
def argument?
self.class.name_argument?(name_full)
end
@@ -73,30 +100,47 @@
instance_variable_defined?(:@default)
end
# Adds this option's switch to the `OptionParser`
# @note it allows to add a `middleware` block that will be called at `parse` runtime
- def add_switch(opts_parser, where: :base, &middleware)
+ # @param opt_parser [OptionParser] the option parser to add this option's switch.
+ # @param implicit_short [Boolean] whether the implicit short of this option is active in the opts_parser.
+ def add_switch(opts_parser, where: :base, implicit_short: false, &middleware)
raise "Expecting OptionParser. Given: #{opts_parser.class}" unless opts_parser.is_a?(OptionParser)
+ args = switch_args(implicit_short: implicit_short)
+ block = option_block(&middleware)
case where
when :head, :top
- opts_parser.on_head(*switch_args, &option_block(&middleware))
+ opts_parser.on_head(*args, &block)
when :tail, :end
- opts_parser.on_tail(*switch_args, &option_block(&middleware))
+ opts_parser.on_tail(*args, &block)
else # :base
- opts_parser.on(*switch_args, &option_block(&middleware))
+ opts_parser.on(*args, &block)
end
opts_parser
end
+ protected
+
+ attr_reader :original_block
+
+ # @return [Hash] keyed arguments to create a new object
+ def dup_key_arguments
+ {}.tap do |kargs|
+ kargs.merge!(short: short.dup.freeze) if short
+ kargs.merge!(name: name_full.dup.freeze) if name_full
+ kargs.merge!(desc: desc.dup) if desc
+ kargs.merge!(default: default.dup) if default?
+ kargs.merge!(required: required?)
+ end
+ end
+
# @return [Array<Variant>]
- def switch_args
+ def switch_args(implicit_short: false)
configure_other
args = [short_hyphen, name_hyphen]
- if str = switch_desc
- args << str
- end
+ args.push(*switch_desc(implicit_short: implicit_short))
args << type_coertion if type_coertion
args
end
private
@@ -104,30 +148,76 @@
# Called on parse runtime
def option_block(&middleware)
block_extra_args = [default, short, name]
proc do |value|
args = block_extra_args.dup.unshift(value)
- @original_block&.call(*args)
+ original_block&.call(*args)
middleware&.call(*args)
end
end
- def switch_desc
- val = "#{desc}#{default_desc}"
- return nil if val.empty?
- val
+ # @note in `OptionParser` you can multiline the description with alignment
+ # by providing multiple strings.
+ # @return [Array<String>]
+ def switch_desc(implicit_short: false, line_width: DESC_MAX_LENGTH)
+ ishort = implicit_short ? "( -#{short_implicit} ) " : ''
+ str = "#{required_desc}#{ishort}#{desc}#{default_desc}"
+ return [] if str.empty?
+ string_to_lines(str, max: line_width)
end
+ def required_desc
+ required?? "< REQ > " : "[ opt ] "
+ end
+
def default_desc
return nil unless default?
- str = "Default: '#{default}'"
+ str = "{ Default: '#{default}' }"
if desc && !desc.downcase.include?('default')
str = desc.end_with?('.') ? " #{str}" : ". #{str}"
end
str
end
+ # Helper to simplify `short` and `name` capture from arguments and keyed arguments.
+ # @return [Array<Symbol, String>] the pair `[short, name]`
+ def capture_arguments_short_n_name!(args, kargs, sample: false)
+ name, short = kargs.values_at(:name, :short)
+ short ||= capture_arguments_short!(args)
+ name ||= capture_arguments_name!(args, sample_n_short: sample && short)
+
+ unless sample
+ raise ArgumentError, "A short of one letter should be provided. Given: #{short}" unless self.class.valid_short?(short)
+ raise ArgumentError, "A name should be provided. Given: #{name}" unless self.class.valid_name?(name)
+ end
+
+ [short, name]
+ end
+
+ # Helper to figure out the option short from args
+ # @note if found it removes it from args.
+ # @return [String, Symbol, NilClass]
+ def capture_arguments_short!(args)
+ short = nil
+ short ||= self.class.capture_arguments_short!(args, symbol: true)
+ short ||= self.class.capture_arguments_short!(args, symbol: true, strict: false)
+ short ||= self.class.capture_arguments_short!(args)
+ short || self.class.capture_arguments_short!(args, strict: false)
+ end
+
+ # Helper to figure out the option name from args
+ # @note if found it removes it from args.
+ # @return [String, Symbol, NilClass]
+ def capture_arguments_name!(args, sample_n_short: false)
+ name = nil
+ name ||= self.class.capture_arguments_name!(args, symbol: true)
+ name ||= self.class.capture_arguments_name!(args, symbol: true, strict: false)
+ name ||= self.class.capture_arguments_name!(args)
+ name || self.class.capture_arguments_name!(args, strict: false) unless sample_n_short
+ end
+
+ # The remaining `args` received in the initialization
def other_args(*args)
@other_args ||= []
if args.empty?
@other_args
else
@@ -136,14 +226,14 @@
end
# It consumes `other_args`, to prevent direct overrides to be overriden by it.
def configure_other
if type = other_args.find {|arg| arg.is_a?(Class)}
- self.type_coertion = type
+ @type_coertion = type
other_args.delete(type)
end
if value = other_args.find {|arg| arg.is_a?(String)}
- self.desc = value
+ @desc = value
other_args.dup.each do |val|
other_args.delete(val) if val.is_a?(String)
end
end
nil