classBundler::ThorclassOptions<Arguments#:nodoc:LONG_RE=/^(--\w+(?:-\w+)*)$/SHORT_RE=/^(-[a-z])$/iEQ_RE=/^(--\w+(?:-\w+)*|-[a-z])=(.*)$/iSHORT_SQ_RE=/^-([a-z]{2,})$/i# Allow either -x -v or -xv style for single char argsSHORT_NUM=/^(-[a-z])#{NUMERIC}$/iOPTS_END="--".freeze# Receives a hash and makes it switches.defself.to_switches(options)options.mapdo|key,value|casevaluewhentrue"--#{key}"whenArray"--#{key}#{value.map(&:inspect).join(' ')}"whenHash"--#{key}#{value.map{|k,v|"#{k}:#{v}"}.join(' ')}"whennil,falsenilelse"--#{key}#{value.inspect}"endend.compact.join(" ")end# Takes a hash of Bundler::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.definitialize(hash_options={},defaults={},stop_on_unknown=false,disable_required_check=false,relations={})@stop_on_unknown=stop_on_unknown@exclusives=(relations[:exclusive_option_names]||[]).select{|array|!array.empty?}@at_least_ones=(relations[:at_least_one_option_names]||[]).select{|array|!array.empty?}@disable_required_check=disable_required_checkoptions=hash_options.valuessuper(options)# Add defaultsdefaults.eachdo|key,value|@assigns[key.to_s]=value@non_assigned_required.delete(hash_options[key])end@shorts={}@switches={}@extra=[]@stopped_parsing_after_extra_index=nil@is_treated_as_value=falseoptions.eachdo|option|@switches[option.switch_name]=optionoption.aliases.eachdo|name|@shorts[name]||=option.switch_nameendendenddefremaining@extraenddefpeekreturnsuperunless@parsing_optionsresult=superifresult==OPTS_ENDshift@parsing_options=false@stopped_parsing_after_extra_index||=@extra.sizesuperelseresultendenddefshift@is_treated_as_value=falsesuperenddefunshift(arg,is_value: false)@is_treated_as_value=is_valuesuper(arg)enddefparse(args)# rubocop:disable Metrics/MethodLength@pile=args.dup@is_treated_as_value=false@parsing_options=truewhilepeekifparsing_options?match,is_switch=current_is_switch?shifted=shiftifis_switchcaseshiftedwhenSHORT_SQ_REunshift($1.split("").map{|f|"-#{f}"})nextwhenEQ_REunshift($2,is_value: true)switch=$1whenSHORT_NUMunshift($2)switch=$1whenLONG_RE,SHORT_REswitch=$1endswitch=normalize_switch(switch)option=switch_option(switch)result=parse_peek(switch,option)assign_result!(option,result)elsif@stop_on_unknown@parsing_options=false@extra<<shifted@stopped_parsing_after_extra_index||=@extra.size@extra<<shiftwhilepeekbreakelsifmatch@extra<<shifted@extra<<shiftwhilepeek&&peek!~/^-/else@extra<<shiftedendelse@extra<<shiftendendcheck_requirement!unless@disable_required_checkcheck_exclusive!check_at_least_one!assigns=Bundler::Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)assigns.freezeassignsenddefcheck_exclusive!opts=@assigns.keys# When option A and B are exclusive, if A and B are given at the same time,# the diffrence of argument array size will decrease.found=@exclusives.find{|ex|(ex-opts).size<ex.size-1}iffoundnames=names_to_switch_names(found&opts).map{|n|"'#{n}'"}class_name=self.class.name.split("::").last.downcasefailExclusiveArgumentError,"Found exclusive #{class_name}#{names.join(", ")}"endenddefcheck_at_least_one!opts=@assigns.keys# When at least one is required of the options A and B,# if the both options were not given, none? would be true.found=@at_least_ones.find{|one_reqs|one_reqs.none?{|o|opts.include?o}}iffoundnames=names_to_switch_names(found).map{|n|"'#{n}'"}class_name=self.class.name.split("::").last.downcasefailAtLeastOneRequiredArgumentError,"Not found at least one of required #{class_name}#{names.join(", ")}"endenddefcheck_unknown!to_check=@stopped_parsing_after_extra_index?@extra[0...@stopped_parsing_after_extra_index]:@extra# an unknown option starts with - or -- and has no more --'s afterward.unknown=to_check.select{|str|str=~/^--?(?:(?!--).)*$/}raiseUnknownArgumentError.new(@switches.keys,unknown)unlessunknown.empty?endprotected# Option names changes to swith name or human namedefnames_to_switch_names(names=[])@switches.mapdo|_,o|ifnames.include?o.nameo.respond_to?(:switch_name)?o.switch_name:o.human_nameelsenilendend.compactenddefassign_result!(option,result)ifoption.repeatable&&option.type==:hash(@assigns[option.human_name]||={}).merge!(result)elsifoption.repeatable(@assigns[option.human_name]||=[])<<resultelse@assigns[option.human_name]=resultendend# 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.defcurrent_is_switch?return[false,false]if@is_treated_as_valuecasepeekwhenLONG_RE,SHORT_RE,EQ_RE,SHORT_NUM[true,switch?($1)]whenSHORT_SQ_RE[true,$1.split("").any?{|f|switch?("-#{f}")}]else[false,false]endenddefcurrent_is_switch_formatted?returnfalseif@is_treated_as_valuecasepeekwhenLONG_RE,SHORT_RE,EQ_RE,SHORT_NUM,SHORT_SQ_REtrueelsefalseendenddefcurrent_is_value?returntrueif@is_treated_as_valuepeek&&(!parsing_options?||super)enddefswitch?(arg)!switch_option(normalize_switch(arg)).nil?enddefswitch_option(arg)ifmatch=no_or_skip?(arg)# rubocop:disable Lint/AssignmentInCondition@switches[arg]||@switches["--#{match}"]else@switches[arg]endend# Check if the given argument is actually a shortcut.#defnormalize_switch(arg)(@shorts[arg]||arg).tr("_","-")enddefparsing_options?peek@parsing_optionsend# Parse boolean values which can be given as --foo=true, --foo or --no-foo.#defparse_boolean(switch)ifcurrent_is_value?if["true","TRUE","t","T",true].include?(peek)shifttrueelsif["false","FALSE","f","F",false].include?(peek)shiftfalseelse@switches.key?(switch)||!no_or_skip?(switch)endelse@switches.key?(switch)||!no_or_skip?(switch)endend# Parse the value at the peek analyzing if it requires an input or not.#defparse_peek(switch,option)ifparsing_options?&&(current_is_switch_formatted?||last?)ifoption.boolean?# No problem for boolean typeselsifno_or_skip?(switch)returnnil# User set value to nilelsifoption.string?&&!option.required?# Return the default if there is one, else the human namereturnoption.lazy_default||option.default||option.human_nameelsifoption.lazy_defaultreturnoption.lazy_defaultelseraiseMalformattedArgumentError,"No value provided for option '#{switch}'"endend@non_assigned_required.delete(option)send(:"parse_#{option.type}",switch)endendend