summaryrefslogtreecommitdiff
path: root/lib/bundler/vendor/thor/lib/thor/parser
diff options
context:
space:
mode:
authorhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-11-02 23:07:56 +0000
committerhsbt <hsbt@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-11-02 23:07:56 +0000
commit59c8d50653480bef3f24517296e6ddf937fdf6bc (patch)
treedf10aaf4f3307837fe3d1d129d66f6c0c7586bc5 /lib/bundler/vendor/thor/lib/thor/parser
parent7deb37777a230837e865e0a11fb8d7c1dc6d03ce (diff)
Added bundler as default gems. Revisit [Feature #12733]
* bin/*, lib/bundler/*, lib/bundler.rb, spec/bundler, man/*: Merge from latest stable branch of bundler/bundler repository and added workaround patches. I will backport them into upstream. * common.mk, defs/gmake.mk: Added `test-bundler` task for test suite of bundler. * tool/sync_default_gems.rb: Added sync task for bundler. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65509 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/bundler/vendor/thor/lib/thor/parser')
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/argument.rb70
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/arguments.rb175
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/option.rb146
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/options.rb221
4 files changed, 612 insertions, 0 deletions
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb
new file mode 100644
index 0000000000..dfe7398583
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb
@@ -0,0 +1,70 @@
+class Bundler::Thor
+ class Argument #:nodoc:
+ VALID_TYPES = [:numeric, :hash, :array, :string]
+
+ attr_reader :name, :description, :enum, :required, :type, :default, :banner
+ alias_method :human_name, :name
+
+ def initialize(name, options = {})
+ class_name = self.class.name.split("::").last
+
+ type = options[:type]
+
+ raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
+ raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
+
+ @name = name.to_s
+ @description = options[:desc]
+ @required = options.key?(:required) ? options[:required] : true
+ @type = (type || :string).to_sym
+ @default = options[:default]
+ @banner = options[:banner] || default_banner
+ @enum = options[:enum]
+
+ validate! # Trigger specific validations
+ end
+
+ def usage
+ required? ? banner : "[#{banner}]"
+ end
+
+ def required?
+ required
+ end
+
+ def show_default?
+ case default
+ when Array, String, Hash
+ !default.empty?
+ else
+ default
+ end
+ end
+
+ protected
+
+ def validate!
+ raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
+ raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array)
+ end
+
+ def valid_type?(type)
+ self.class::VALID_TYPES.include?(type.to_sym)
+ end
+
+ def default_banner
+ case type
+ when :boolean
+ nil
+ when :string, :default
+ human_name.upcase
+ when :numeric
+ "N"
+ when :hash
+ "key:value"
+ when :array
+ "one two three"
+ end
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb
new file mode 100644
index 0000000000..1fd790f4b7
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb
@@ -0,0 +1,175 @@
+class Bundler::Thor
+ class Arguments #:nodoc: # rubocop:disable ClassLength
+ NUMERIC = /[-+]?(\d*\.\d+|\d+)/
+
+ # Receives an array of args and returns two arrays, one with arguments
+ # and one with switches.
+ #
+ def self.split(args)
+ arguments = []
+
+ args.each do |item|
+ break if item =~ /^-/
+ arguments << item
+ end
+
+ [arguments, args[Range.new(arguments.size, -1)]]
+ end
+
+ def self.parse(*args)
+ to_parse = args.pop
+ new(*args).parse(to_parse)
+ end
+
+ # Takes an array of Bundler::Thor::Argument objects.
+ #
+ def initialize(arguments = [])
+ @assigns = {}
+ @non_assigned_required = []
+ @switches = arguments
+
+ arguments.each do |argument|
+ if !argument.default.nil?
+ @assigns[argument.human_name] = argument.default
+ elsif argument.required?
+ @non_assigned_required << argument
+ end
+ end
+ end
+
+ def parse(args)
+ @pile = args.dup
+
+ @switches.each do |argument|
+ break unless peek
+ @non_assigned_required.delete(argument)
+ @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
+ end
+
+ check_requirement!
+ @assigns
+ end
+
+ def remaining
+ @pile
+ end
+
+ private
+
+ def no_or_skip?(arg)
+ arg =~ /^--(no|skip)-([-\w]+)$/
+ $2
+ end
+
+ def last?
+ @pile.empty?
+ end
+
+ def peek
+ @pile.first
+ end
+
+ def shift
+ @pile.shift
+ end
+
+ def unshift(arg)
+ if arg.is_a?(Array)
+ @pile = arg + @pile
+ else
+ @pile.unshift(arg)
+ end
+ end
+
+ def current_is_value?
+ peek && peek.to_s !~ /^-/
+ end
+
+ # Runs through the argument array getting strings that contains ":" and
+ # mark it as a hash:
+ #
+ # [ "name:string", "age:integer" ]
+ #
+ # Becomes:
+ #
+ # { "name" => "string", "age" => "integer" }
+ #
+ def parse_hash(name)
+ return shift if peek.is_a?(Hash)
+ hash = {}
+
+ while current_is_value? && peek.include?(":")
+ key, value = shift.split(":", 2)
+ raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key
+ hash[key] = value
+ end
+ hash
+ end
+
+ # Runs through the argument array getting all strings until no string is
+ # found or a switch is found.
+ #
+ # ["a", "b", "c"]
+ #
+ # And returns it as an array:
+ #
+ # ["a", "b", "c"]
+ #
+ def parse_array(name)
+ return shift if peek.is_a?(Array)
+ array = []
+ array << shift while current_is_value?
+ array
+ end
+
+ # Check if the peek is numeric format and return a Float or Integer.
+ # Check if the peek is included in enum if enum is provided.
+ # Otherwise raises an error.
+ #
+ def parse_numeric(name)
+ return shift if peek.is_a?(Numeric)
+
+ unless peek =~ NUMERIC && $& == peek
+ raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
+ end
+
+ value = $&.index(".") ? shift.to_f : shift.to_i
+ if @switches.is_a?(Hash) && switch = @switches[name]
+ if switch.enum && !switch.enum.include?(value)
+ raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
+ end
+ end
+ value
+ end
+
+ # Parse string:
+ # for --string-arg, just return the current value in the pile
+ # for --no-string-arg, nil
+ # Check if the peek is included in enum if enum is provided. Otherwise raises an error.
+ #
+ def parse_string(name)
+ if no_or_skip?(name)
+ nil
+ else
+ value = shift
+ if @switches.is_a?(Hash) && switch = @switches[name]
+ if switch.enum && !switch.enum.include?(value)
+ raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
+ end
+ end
+ value
+ end
+ end
+
+ # Raises an error if @non_assigned_required array is not empty.
+ #
+ def check_requirement!
+ return if @non_assigned_required.empty?
+ names = @non_assigned_required.map do |o|
+ o.respond_to?(:switch_name) ? o.switch_name : o.human_name
+ end.join("', '")
+ class_name = self.class.name.split("::").last.downcase
+ raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/option.rb b/lib/bundler/vendor/thor/lib/thor/parser/option.rb
new file mode 100644
index 0000000000..85169b56c8
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/option.rb
@@ -0,0 +1,146 @@
+class Bundler::Thor
+ class Option < Argument #:nodoc:
+ attr_reader :aliases, :group, :lazy_default, :hide
+
+ VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
+
+ def initialize(name, options = {})
+ @check_default_type = options[:check_default_type]
+ options[:required] = false unless options.key?(:required)
+ super
+ @lazy_default = options[:lazy_default]
+ @group = options[:group].to_s.capitalize if options[:group]
+ @aliases = Array(options[:aliases])
+ @hide = options[:hide]
+ end
+
+ # This parse quick options given as method_options. It makes several
+ # assumptions, but you can be more specific using the option method.
+ #
+ # parse :foo => "bar"
+ # #=> Option foo with default value bar
+ #
+ # parse [:foo, :baz] => "bar"
+ # #=> Option foo with default value bar and alias :baz
+ #
+ # parse :foo => :required
+ # #=> Required option foo without default value
+ #
+ # parse :foo => 2
+ # #=> Option foo with default value 2 and type numeric
+ #
+ # parse :foo => :numeric
+ # #=> Option foo without default value and type numeric
+ #
+ # parse :foo => true
+ # #=> Option foo with default value true and type boolean
+ #
+ # The valid types are :boolean, :numeric, :hash, :array and :string. If none
+ # is given a default type is assumed. This default type accepts arguments as
+ # string (--foo=value) or booleans (just --foo).
+ #
+ # By default all options are optional, unless :required is given.
+ #
+ def self.parse(key, value)
+ if key.is_a?(Array)
+ name, *aliases = key
+ else
+ name = key
+ aliases = []
+ end
+
+ name = name.to_s
+ default = value
+
+ type = case value
+ when Symbol
+ default = nil
+ if VALID_TYPES.include?(value)
+ value
+ elsif required = (value == :required) # rubocop:disable AssignmentInCondition
+ :string
+ end
+ when TrueClass, FalseClass
+ :boolean
+ when Numeric
+ :numeric
+ when Hash, Array, String
+ value.class.name.downcase.to_sym
+ end
+
+ new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases)
+ end
+
+ def switch_name
+ @switch_name ||= dasherized? ? name : dasherize(name)
+ end
+
+ def human_name
+ @human_name ||= dasherized? ? undasherize(name) : name
+ end
+
+ def usage(padding = 0)
+ sample = if banner && !banner.to_s.empty?
+ "#{switch_name}=#{banner}".dup
+ else
+ switch_name
+ end
+
+ sample = "[#{sample}]".dup unless required?
+
+ if boolean?
+ sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
+ end
+
+ if aliases.empty?
+ (" " * padding) << sample
+ else
+ "#{aliases.join(', ')}, #{sample}"
+ end
+ end
+
+ VALID_TYPES.each do |type|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{type}?
+ self.type == #{type.inspect}
+ end
+ RUBY
+ end
+
+ protected
+
+ def validate!
+ raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
+ validate_default_type! if @check_default_type
+ end
+
+ def validate_default_type!
+ default_type = case @default
+ when nil
+ return
+ when TrueClass, FalseClass
+ required? ? :string : :boolean
+ when Numeric
+ :numeric
+ when Symbol
+ :string
+ when Hash, Array, String
+ @default.class.name.downcase.to_sym
+ end
+
+ raise ArgumentError, "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
+ end
+
+ def dasherized?
+ name.index("-") == 0
+ end
+
+ def undasherize(str)
+ str.sub(/^-{1,2}/, "")
+ end
+
+ def dasherize(str)
+ (str.length > 1 ? "--" : "-") + str.tr("_", "-")
+ end
+ end
+end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
new file mode 100644
index 0000000000..70f6366842
--- /dev/null
+++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
@@ -0,0 +1,221 @@
+class Bundler::Thor
+ class Options < Arguments #:nodoc: # rubocop:disable ClassLength
+ LONG_RE = /^(--\w+(?:-\w+)*)$/
+ SHORT_RE = /^(-[a-z])$/i
+ EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
+ SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
+ SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
+ OPTS_END = "--".freeze
+
+ # 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
+
+ # 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.
+ def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false)
+ @stop_on_unknown = stop_on_unknown
+ @disable_required_check = disable_required_check
+ options = hash_options.values
+ super(options)
+
+ # Add defaults
+ defaults.each do |key, value|
+ @assigns[key.to_s] = value
+ @non_assigned_required.delete(hash_options[key])
+ 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
+
+ def remaining
+ @extra
+ end
+
+ def peek
+ return super unless @parsing_options
+
+ result = super
+ if result == OPTS_END
+ shift
+ @parsing_options = false
+ super
+ else
+ result
+ end
+ end
+
+ def parse(args) # rubocop:disable MethodLength
+ @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)
+ switch = $1
+ when LONG_RE, SHORT_RE
+ switch = $1
+ end
+
+ switch = normalize_switch(switch)
+ 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 = Bundler::Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
+ assigns.freeze
+ assigns
+ end
+
+ def check_unknown!
+ # an unknown option starts with - or -- and has no more --'s afterward.
+ unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
+ raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
+ end
+
+ protected
+
+ # 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 current_is_value?
+ peek && (!parsing_options? || super)
+ end
+
+ def switch?(arg)
+ switch_option(normalize_switch(arg))
+ end
+
+ def switch_option(arg)
+ if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition
+ @switches[arg] || @switches["--#{match}"]
+ else
+ @switches[arg]
+ end
+ end
+
+ # Check if the given argument is actually a shortcut.
+ #
+ def normalize_switch(arg)
+ (@shorts[arg] || arg).tr("_", "-")
+ end
+
+ def parsing_options?
+ peek
+ @parsing_options
+ end
+
+ # Parse boolean values which can be given as --foo=true, --foo or --no-foo.
+ #
+ def parse_boolean(switch)
+ if current_is_value?
+ if ["true", "TRUE", "t", "T", true].include?(peek)
+ shift
+ true
+ elsif ["false", "FALSE", "f", "F", false].include?(peek)
+ shift
+ false
+ else
+ !no_or_skip?(switch)
+ end
+ else
+ @switches.key?(switch) || !no_or_skip?(switch)
+ end
+ end
+
+ # Parse the value at the peek analyzing if it requires an input or not.
+ #
+ def parse_peek(switch, option)
+ if parsing_options? && (current_is_switch_formatted? || last?)
+ if option.boolean?
+ # No problem for boolean types
+ elsif no_or_skip?(switch)
+ return nil # User set value to nil
+ elsif option.string? && !option.required?
+ # Return the default if there is one, else the human name
+ return option.lazy_default || option.default || option.human_name
+ elsif option.lazy_default
+ return option.lazy_default
+ else
+ raise MalformattedArgumentError, "No value provided for option '#{switch}'"
+ end
+ end
+
+ @non_assigned_required.delete(option)
+ send(:"parse_#{option.type}", switch)
+ end
+ end
+end