classThorclassOptions<Arguments#:nodoc: # rubocop:disable ClassLengthLONG_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 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)@stop_on_unknown=stop_on_unknown@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=niloptions.eachdo|option|@switches[option.switch_name]=optionoption.aliases.eachdo|short|name=short.to_s.sub(/^(?!\-)/,"-")@shorts[name]||=option.switch_nameendendenddefremaining@extraenddefpeekreturnsuperunless@parsing_optionsresult=superifresult==OPTS_ENDshift@parsing_options=false@stopped_parsing_after_extra_index||=@extra.sizesuperelseresultendenddefparse(args)# rubocop:disable MethodLength@pile=args.dup@parsing_options=truewhilepeekifparsing_options?match,is_switch=current_is_switch?shifted=shiftifis_switchcaseshiftedwhenSHORT_SQ_REunshift($1.split("").map{|f|"-#{f}"})nextwhenEQ_RE,SHORT_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_checkassigns=Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)assigns.freezeassignsenddefcheck_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?endprotecteddefassign_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?casepeekwhenLONG_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?casepeekwhenLONG_RE,SHORT_RE,EQ_RE,SHORT_NUM,SHORT_SQ_REtrueelsefalseendenddefcurrent_is_value?peek&&(!parsing_options?||super)enddefswitch?(arg)!switch_option(normalize_switch(arg)).nil?enddefswitch_option(arg)ifmatch=no_or_skip?(arg)# rubocop:disable 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