# frozen_string_literal: true class Thor class Options < Arguments # rubocop:disable ClassLength # Constants # ======================================================================== LONG_RE = /^(--\w+(?:-\w+)*)$/ SHORT_RE = /^(-[a-z])$/i EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i # Matches "multiple short switches", like `-xv`. # # @return [Regexp] # SHORT_SQ_RE = /^-([a-z]{2,})$/i # Matches things like `'-x123'`. # # @return [Regexp] # SHORT_NUM = /^(-[a-z])#{ Thor::Arguments::NUMERIC }$/i # The "bare double-dash" used to indicate that following arguments # should not be parsed for options. # # @return [String] # OPTS_END = "--" # Class Methods # ======================================================================== # Receives a hash and makes it switches. def self.to_switches(options) options.map do |key, value| case value when true "--#{key}" when Array "--#{key} #{value.map(&:inspect).join(' ')}" when Hash "--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}" when nil, false nil else "--#{key} #{value.inspect}" end end.compact.join(" ") end # Constructor # ======================================================================== # Takes a hash of Thor::Option and a hash with defaults. # # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters # an unknown option or a regular argument. # # @param [Hash] hash_options # # def initialize options_to_parse_by_name = {}, option_default_values = {}, stop_on_unknown = false, disable_required_check = false @stop_on_unknown = stop_on_unknown @disable_required_check = disable_required_check options = options_to_parse_by_name.values super( options ) # Add defaults option_default_values.each do |option_name, value| @assigns[ option_name.to_s ] = value @non_assigned_required.delete \ options_to_parse_by_name[ option_name ] end @shorts = {} @switches = {} @extra = [] options.each do |option| @switches[option.switch_name] = option option.aliases.each do |short| name = short.to_s.sub(/^(?!\-)/, "-") @shorts[name] ||= option.switch_name end end end # Instance Methods # ======================================================================== def remaining @extra end # What's next?! I think. # # @note # This *used to* remove `--` separators (what {OPTS_END} is), # but that was problematic with multiple nested subcommands # 'cause Thor classes further down the chain wouldn't know that # it was there and would parse options that had been after it. # # Maybe that's how Thor was supposed to work (???), but it didn't really # jive with me... I've always felt like stuff after `--` meant # **_stop parsing options - these are always args_** since I usually # see it used when passing shell commands to other shell commands - # which is how I was using it when I came across the issues. # # And it ain't like Thor has any documentation to straiten it out. Hell, # this method had no doc when I showed up. The line that dropped the `--` # has no comment. The {Thor::Options} class itself had no doc. # # So, now it *does* mean that... `--` means "no option parsing after # here". For real. # def peek return super unless @parsing_options result = super if result == OPTS_END # Removed, see note above: # shift @parsing_options = false super else result end end def parse args # rubocop:disable MethodLength logger.debug __method__.to_s, args: args @pile = args.dup @parsing_options = true while peek if parsing_options? match, is_switch = current_is_switch? shifted = shift if is_switch case shifted when SHORT_SQ_RE unshift($1.split("").map { |f| "-#{f}" }) next when EQ_RE, SHORT_NUM unshift $2 raw_switch_arg = $1 when LONG_RE, SHORT_RE raw_switch_arg = $1 end switch = normalize_switch raw_switch_arg option = switch_option switch @assigns[option.human_name] = parse_peek switch, option elsif @stop_on_unknown @parsing_options = false @extra << shifted @extra << shift while peek break elsif match @extra << shifted @extra << shift while peek && peek !~ /^-/ else @extra << shifted end else @extra << shift end end check_requirement! unless @disable_required_check assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns) assigns.freeze logger.debug "#{ __method__ } done", assigns: assigns, remaining: remaining assigns end def check_unknown! # an unknown option starts with - or -- and has no more --'s afterward. unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ } unless unknown.empty? raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" end end protected # Instance Methods # ========================================================================== def last? super() || peek == OPTS_END end # Check if the current value in peek is a registered switch. # # Two booleans are returned. The first is true if the current value # starts with a hyphen; the second is true if it is a registered switch. def current_is_switch? case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM [true, switch?($1)] when SHORT_SQ_RE [true, $1.split("").any? { |f| switch?("-#{f}") }] else [false, false] end end def current_is_switch_formatted? case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE true else false end end def switch?(arg) switch_option(normalize_switch(arg)) end # Get the option for a switch arg. # # Handles parsing `--no-