diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 000000000..236922881 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_PATH: "vendor/bundle" diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml new file mode 100644 index 000000000..20865d57a --- /dev/null +++ b/.github/workflows/visual-regression.yml @@ -0,0 +1,78 @@ +name: Visual Regression + +on: + pull_request: + branches: [ main, master ] + paths: + - 'assets/**' + - '_includes/**' + - '_layouts/**' + - 'categories/**/*.md' + - 'pages/**/*.md' + - 'index.md' + - '_config.yml' + - '_config.dev.yml' + - 'backstop.config.js' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/visual-regression.yml' + +permissions: + contents: read + +jobs: + backstop: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + + - name: Install Node dependencies + run: npm ci + + - name: Install Playwright Chromium + run: npx playwright install --with-deps chromium + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2.2' + bundler-cache: true + + - name: Start Jekyll preview + env: + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + run: | + bundle exec jekyll serve --config _config.yml,_config.dev.yml --host 127.0.0.1 --port 4000 > jekyll.log 2>&1 & + for attempt in {1..60}; do + if curl --fail --silent --show-error http://127.0.0.1:4000/ > /dev/null; then + exit 0 + fi + sleep 2 + done + cat jekyll.log + exit 1 + + - name: Run BackstopJS + env: + BACKSTOP_BASE_URL: http://127.0.0.1:4000 + run: npm run backstop:test + + - name: Upload Backstop report + if: always() + uses: actions/upload-artifact@v4 + with: + name: backstop-report + path: | + test/backstop/report + test/backstop/test + test/backstop/ci + jekyll.log diff --git a/.gitignore b/.gitignore index 36fbca2f2..33ce67faf 100755 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,9 @@ templates /public/generated/placeholders/*.svg _site.backup/ */__pycache__/* +build/ + +# BackstopJS visual regression reports and generated test screenshots. +/test/backstop/ci +/test/backstop/test +/test/backstop/report diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6621bbf0b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "markdown.copyFiles.destination": { + "/**/*.md": "/public/images/${documentBaseName}/${fileName}" + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 657d733b5..34b9b4c56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,14 @@ This can include: * **Tutorials** - on reverse engineering or writing emulators * **Research material related to the game industry** - e.g. content of game industry conferences, programming/software books, game industry magazines, or even just game magazines from the past that contain interviews with game developers +### Let categories incubate smaller topics +
{{ include.description }}
+ {%- endif -%} +
-
-
-
-
+You can open any Pokemon Mini rom file (`*.min`) in a tool called `Tile Molester` with the Codec setup to be `1BPP`, as Pokemon Mini is only back and white all images are stored as 1 bit per pixed, with black as 0 and white as 1.
- In the screenshot on the left you can see what initially looks like lots of duplicate tiles, these are infact the Masks used for transparency and you can see the tile they apply to to the right of the mask.
- true if the Template objects are equal. This method
+ # does NOT normalize either Template before doing the comparison.
+ #
+ # @param [Object] template The Template to compare.
+ #
+ # @return [TrueClass, FalseClass]
+ # true if the Templates are equivalent, false
+ # otherwise.
+ def ==(template)
+ return false unless template.kind_of?(Template)
+ return self.pattern == template.pattern
+ end
+
+ ##
+ # Addressable::Template makes no distinction between `==` and `eql?`.
+ #
+ # @see #==
+ alias_method :eql?, :==
+
+ ##
+ # Extracts a mapping from the URI using a URI Template pattern.
+ #
+ # @param [Addressable::URI, #to_str] uri
+ # The URI to extract from.
+ #
+ # @param [#restore, #match] processor
+ # A template processor object may optionally be supplied.
+ #
+ # The object should respond to either the restore or
+ # match messages or both. The restore method should
+ # take two parameters: `[String] name` and `[String] value`.
+ # The restore method should reverse any transformations that
+ # have been performed on the value to ensure a valid URI.
+ # The match method should take a single
+ # parameter: `[String] name`. The match method should return
+ # a String containing a regular expression capture group for
+ # matching on that particular variable. The default value is `".*?"`.
+ # The match method has no effect on multivariate operator
+ # expansions.
+ #
+ # @return [Hash, NilClass]
+ # The Hash mapping that was extracted from the URI, or
+ # nil if the URI didn't match the template.
+ #
+ # @example
+ # class ExampleProcessor
+ # def self.restore(name, value)
+ # return value.gsub(/\+/, " ") if name == "query"
+ # return value
+ # end
+ #
+ # def self.match(name)
+ # return ".*?" if name == "first"
+ # return ".*"
+ # end
+ # end
+ #
+ # uri = Addressable::URI.parse(
+ # "http://example.com/search/an+example+search+query/"
+ # )
+ # Addressable::Template.new(
+ # "http://example.com/search/{query}/"
+ # ).extract(uri, ExampleProcessor)
+ # #=> {"query" => "an example search query"}
+ #
+ # uri = Addressable::URI.parse("http://example.com/a/b/c/")
+ # Addressable::Template.new(
+ # "http://example.com/{first}/{second}/"
+ # ).extract(uri, ExampleProcessor)
+ # #=> {"first" => "a", "second" => "b/c"}
+ #
+ # uri = Addressable::URI.parse("http://example.com/a/b/c/")
+ # Addressable::Template.new(
+ # "http://example.com/{first}/{-list|/|second}/"
+ # ).extract(uri)
+ # #=> {"first" => "a", "second" => ["b", "c"]}
+ def extract(uri, processor=nil)
+ match_data = self.match(uri, processor)
+ return (match_data ? match_data.mapping : nil)
+ end
+
+ ##
+ # Extracts match data from the URI using a URI Template pattern.
+ #
+ # @param [Addressable::URI, #to_str] uri
+ # The URI to extract from.
+ #
+ # @param [#restore, #match] processor
+ # A template processor object may optionally be supplied.
+ #
+ # The object should respond to either the restore or
+ # match messages or both. The restore method should
+ # take two parameters: `[String] name` and `[String] value`.
+ # The restore method should reverse any transformations that
+ # have been performed on the value to ensure a valid URI.
+ # The match method should take a single
+ # parameter: `[String] name`. The match method should return
+ # a String containing a regular expression capture group for
+ # matching on that particular variable. The default value is `".*?"`.
+ # The match method has no effect on multivariate operator
+ # expansions.
+ #
+ # @return [Hash, NilClass]
+ # The Hash mapping that was extracted from the URI, or
+ # nil if the URI didn't match the template.
+ #
+ # @example
+ # class ExampleProcessor
+ # def self.restore(name, value)
+ # return value.gsub(/\+/, " ") if name == "query"
+ # return value
+ # end
+ #
+ # def self.match(name)
+ # return ".*?" if name == "first"
+ # return ".*"
+ # end
+ # end
+ #
+ # uri = Addressable::URI.parse(
+ # "http://example.com/search/an+example+search+query/"
+ # )
+ # match = Addressable::Template.new(
+ # "http://example.com/search/{query}/"
+ # ).match(uri, ExampleProcessor)
+ # match.variables
+ # #=> ["query"]
+ # match.captures
+ # #=> ["an example search query"]
+ #
+ # uri = Addressable::URI.parse("http://example.com/a/b/c/")
+ # match = Addressable::Template.new(
+ # "http://example.com/{first}/{+second}/"
+ # ).match(uri, ExampleProcessor)
+ # match.variables
+ # #=> ["first", "second"]
+ # match.captures
+ # #=> ["a", "b/c"]
+ #
+ # uri = Addressable::URI.parse("http://example.com/a/b/c/")
+ # match = Addressable::Template.new(
+ # "http://example.com/{first}{/second*}/"
+ # ).match(uri)
+ # match.variables
+ # #=> ["first", "second"]
+ # match.captures
+ # #=> ["a", ["b", "c"]]
+ def match(uri, processor=nil)
+ uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
+ mapping = {}
+
+ # First, we need to process the pattern, and extract the values.
+ expansions, expansion_regexp =
+ parse_template_pattern(pattern, processor)
+
+ return nil unless uri.to_str.match(expansion_regexp)
+ unparsed_values = uri.to_str.scan(expansion_regexp).flatten
+
+ if uri.to_str == pattern
+ return Addressable::Template::MatchData.new(uri, self, mapping)
+ elsif expansions.size > 0
+ index = 0
+ expansions.each do |expansion|
+ _, operator, varlist = *expansion.match(EXPRESSION)
+ varlist.split(',').each do |varspec|
+ _, name, modifier = *varspec.match(VARSPEC)
+ mapping[name] ||= nil
+ case operator
+ when nil, '+', '#', '/', '.'
+ unparsed_value = unparsed_values[index]
+ name = varspec[VARSPEC, 1]
+ value = unparsed_value
+ value = value.split(JOINERS[operator]) if value && modifier == '*'
+ when ';', '?', '&'
+ if modifier == '*'
+ if unparsed_values[index]
+ value = unparsed_values[index].split(JOINERS[operator])
+ value = value.inject({}) do |acc, v|
+ key, val = v.split('=')
+ val = "" if val.nil?
+ acc[key] = val
+ acc
+ end
+ end
+ else
+ if (unparsed_values[index])
+ name, value = unparsed_values[index].split('=')
+ value = "" if value.nil?
+ end
+ end
+ end
+ if processor != nil && processor.respond_to?(:restore)
+ value = processor.restore(name, value)
+ end
+ if processor == nil
+ if value.is_a?(Hash)
+ value = value.inject({}){|acc, (k, v)|
+ acc[Addressable::URI.unencode_component(k)] =
+ Addressable::URI.unencode_component(v)
+ acc
+ }
+ elsif value.is_a?(Array)
+ value = value.map{|v| Addressable::URI.unencode_component(v) }
+ else
+ value = Addressable::URI.unencode_component(value)
+ end
+ end
+ if !mapping.has_key?(name) || mapping[name].nil?
+ # Doesn't exist, set to value (even if value is nil)
+ mapping[name] = value
+ end
+ index = index + 1
+ end
+ end
+ return Addressable::Template::MatchData.new(uri, self, mapping)
+ else
+ return nil
+ end
+ end
+
+ ##
+ # Expands a URI template into another URI template.
+ #
+ # @param [Hash] mapping The mapping that corresponds to the pattern.
+ # @param [#validate, #transform] processor
+ # An optional processor object may be supplied.
+ # @param [Boolean] normalize_values
+ # Optional flag to enable/disable unicode normalization. Default: true
+ #
+ # The object should respond to either the validate or
+ # transform messages or both. Both the validate and
+ # transform methods should take two parameters: name and
+ # value. The validate method should return true
+ # or false; true if the value of the variable is valid,
+ # false otherwise. An InvalidTemplateValueError
+ # exception will be raised if the value is invalid. The transform
+ # method should return the transformed variable value as a String.
+ # If a transform method is used, the value will not be percent
+ # encoded automatically. Unicode normalization will be performed both
+ # before and after sending the value to the transform method.
+ #
+ # @return [Addressable::Template] The partially expanded URI template.
+ #
+ # @example
+ # Addressable::Template.new(
+ # "http://example.com/{one}/{two}/"
+ # ).partial_expand({"one" => "1"}).pattern
+ # #=> "http://example.com/1/{two}/"
+ #
+ # Addressable::Template.new(
+ # "http://example.com/{?one,two}/"
+ # ).partial_expand({"one" => "1"}).pattern
+ # #=> "http://example.com/?one=1{&two}/"
+ #
+ # Addressable::Template.new(
+ # "http://example.com/{?one,two,three}/"
+ # ).partial_expand({"one" => "1", "three" => 3}).pattern
+ # #=> "http://example.com/?one=1{&two}&three=3"
+ def partial_expand(mapping, processor=nil, normalize_values=true)
+ result = self.pattern.dup
+ mapping = normalize_keys(mapping)
+ result.gsub!( EXPRESSION ) do |capture|
+ transform_partial_capture(mapping, capture, processor, normalize_values)
+ end
+ return Addressable::Template.new(result)
+ end
+
+ ##
+ # Expands a URI template into a full URI.
+ #
+ # @param [Hash] mapping The mapping that corresponds to the pattern.
+ # @param [#validate, #transform] processor
+ # An optional processor object may be supplied.
+ # @param [Boolean] normalize_values
+ # Optional flag to enable/disable unicode normalization. Default: true
+ #
+ # The object should respond to either the validate or
+ # transform messages or both. Both the validate and
+ # transform methods should take two parameters: name and
+ # value. The validate method should return true
+ # or false; true if the value of the variable is valid,
+ # false otherwise. An InvalidTemplateValueError
+ # exception will be raised if the value is invalid. The transform
+ # method should return the transformed variable value as a String.
+ # If a transform method is used, the value will not be percent
+ # encoded automatically. Unicode normalization will be performed both
+ # before and after sending the value to the transform method.
+ #
+ # @return [Addressable::URI] The expanded URI template.
+ #
+ # @example
+ # class ExampleProcessor
+ # def self.validate(name, value)
+ # return !!(value =~ /^[\w ]+$/) if name == "query"
+ # return true
+ # end
+ #
+ # def self.transform(name, value)
+ # return value.gsub(/ /, "+") if name == "query"
+ # return value
+ # end
+ # end
+ #
+ # Addressable::Template.new(
+ # "http://example.com/search/{query}/"
+ # ).expand(
+ # {"query" => "an example search query"},
+ # ExampleProcessor
+ # ).to_str
+ # #=> "http://example.com/search/an+example+search+query/"
+ #
+ # Addressable::Template.new(
+ # "http://example.com/search/{query}/"
+ # ).expand(
+ # {"query" => "an example search query"}
+ # ).to_str
+ # #=> "http://example.com/search/an%20example%20search%20query/"
+ #
+ # Addressable::Template.new(
+ # "http://example.com/search/{query}/"
+ # ).expand(
+ # {"query" => "bogus!"},
+ # ExampleProcessor
+ # ).to_str
+ # #=> Addressable::Template::InvalidTemplateValueError
+ def expand(mapping, processor=nil, normalize_values=true)
+ result = self.pattern.dup
+ mapping = normalize_keys(mapping)
+ result.gsub!( EXPRESSION ) do |capture|
+ transform_capture(mapping, capture, processor, normalize_values)
+ end
+ return Addressable::URI.parse(result)
+ end
+
+ ##
+ # Returns an Array of variables used within the template pattern.
+ # The variables are listed in the Array in the order they appear within
+ # the pattern. Multiple occurrences of a variable within a pattern are
+ # not represented in this Array.
+ #
+ # @return [Array] The variables present in the template's pattern.
+ def variables
+ @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
+ end
+ alias_method :keys, :variables
+ alias_method :names, :variables
+
+ ##
+ # Returns a mapping of variables to their default values specified
+ # in the template. Variables without defaults are not returned.
+ #
+ # @return [Hash] Mapping of template variables to their defaults
+ def variable_defaults
+ @variable_defaults ||=
+ Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
+ end
+
+ ##
+ # Coerces a template into a `Regexp` object. This regular expression will
+ # behave very similarly to the actual template, and should match the same
+ # URI values, but it cannot fully handle, for example, values that would
+ # extract to an `Array`.
+ #
+ # @return [Regexp] A regular expression which should match the template.
+ def to_regexp
+ _, source = parse_template_pattern(pattern)
+ Regexp.new(source)
+ end
+
+ ##
+ # Returns the source of the coerced `Regexp`.
+ #
+ # @return [String] The source of the `Regexp` given by {#to_regexp}.
+ #
+ # @api private
+ def source
+ self.to_regexp.source
+ end
+
+ ##
+ # Returns the named captures of the coerced `Regexp`.
+ #
+ # @return [Hash] The named captures of the `Regexp` given by {#to_regexp}.
+ #
+ # @api private
+ def named_captures
+ self.to_regexp.named_captures
+ end
+
+ private
+ def ordered_variable_defaults
+ @ordered_variable_defaults ||= begin
+ expansions, _ = parse_template_pattern(pattern)
+ expansions.flat_map do |capture|
+ _, _, varlist = *capture.match(EXPRESSION)
+ varlist.split(',').map do |varspec|
+ varspec[VARSPEC, 1]
+ end
+ end
+ end
+ end
+
+
+ ##
+ # Loops through each capture and expands any values available in mapping
+ #
+ # @param [Hash] mapping
+ # Set of keys to expand
+ # @param [String] capture
+ # The expression to expand
+ # @param [#validate, #transform] processor
+ # An optional processor object may be supplied.
+ # @param [Boolean] normalize_values
+ # Optional flag to enable/disable unicode normalization. Default: true
+ #
+ # The object should respond to either the validate or
+ # transform messages or both. Both the validate and
+ # transform methods should take two parameters: name and
+ # value. The validate method should return true
+ # or false; true if the value of the variable is valid,
+ # false otherwise. An InvalidTemplateValueError exception
+ # will be raised if the value is invalid. The transform method
+ # should return the transformed variable value as a String. If a
+ # transform method is used, the value will not be percent encoded
+ # automatically. Unicode normalization will be performed both before and
+ # after sending the value to the transform method.
+ #
+ # @return [String] The expanded expression
+ def transform_partial_capture(mapping, capture, processor = nil,
+ normalize_values = true)
+ _, operator, varlist = *capture.match(EXPRESSION)
+
+ vars = varlist.split(",")
+
+ if operator == "?"
+ # partial expansion of form style query variables sometimes requires a
+ # slight reordering of the variables to produce a valid url.
+ first_to_expand = vars.find { |varspec|
+ _, name, _ = *varspec.match(VARSPEC)
+ mapping.key?(name) && !mapping[name].nil?
+ }
+
+ vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
+ end
+
+ vars.
+ inject("".dup) do |acc, varspec|
+ _, name, _ = *varspec.match(VARSPEC)
+ next_val = if mapping.key? name
+ transform_capture(mapping, "{#{operator}#{varspec}}",
+ processor, normalize_values)
+ else
+ "{#{operator}#{varspec}}"
+ end
+ # If we've already expanded at least one '?' operator with non-empty
+ # value, change to '&'
+ operator = "&" if (operator == "?") && (next_val != "")
+ acc << next_val
+ end
+ end
+
+ ##
+ # Transforms a mapped value so that values can be substituted into the
+ # template.
+ #
+ # @param [Hash] mapping The mapping to replace captures
+ # @param [String] capture
+ # The expression to replace
+ # @param [#validate, #transform] processor
+ # An optional processor object may be supplied.
+ # @param [Boolean] normalize_values
+ # Optional flag to enable/disable unicode normalization. Default: true
+ #
+ #
+ # The object should respond to either the validate or
+ # transform messages or both. Both the validate and
+ # transform methods should take two parameters: name and
+ # value. The validate method should return true
+ # or false; true if the value of the variable is valid,
+ # false otherwise. An InvalidTemplateValueError exception
+ # will be raised if the value is invalid. The transform method
+ # should return the transformed variable value as a String. If a
+ # transform method is used, the value will not be percent encoded
+ # automatically. Unicode normalization will be performed both before and
+ # after sending the value to the transform method.
+ #
+ # @return [String] The expanded expression
+ def transform_capture(mapping, capture, processor=nil,
+ normalize_values=true)
+ _, operator, varlist = *capture.match(EXPRESSION)
+ return_value = varlist.split(',').inject([]) do |acc, varspec|
+ _, name, modifier = *varspec.match(VARSPEC)
+ value = mapping[name]
+ unless value == nil || value == {}
+ allow_reserved = %w(+ #).include?(operator)
+ # Common primitives where the .to_s output is well-defined
+ if Numeric === value || Symbol === value ||
+ value == true || value == false
+ value = value.to_s
+ end
+ length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
+
+ unless (Hash === value) ||
+ value.respond_to?(:to_ary) || value.respond_to?(:to_str)
+ raise TypeError,
+ "Can't convert #{value.class} into String or Array."
+ end
+
+ value = normalize_value(value) if normalize_values
+
+ if processor == nil || !processor.respond_to?(:transform)
+ # Handle percent escaping
+ if allow_reserved
+ encode_map =
+ Addressable::URI::CharacterClasses::RESERVED +
+ Addressable::URI::CharacterClasses::UNRESERVED
+ else
+ encode_map = Addressable::URI::CharacterClasses::UNRESERVED
+ end
+ if value.kind_of?(Array)
+ transformed_value = value.map do |val|
+ if length
+ Addressable::URI.encode_component(val[0...length], encode_map)
+ else
+ Addressable::URI.encode_component(val, encode_map)
+ end
+ end
+ unless modifier == "*"
+ transformed_value = transformed_value.join(',')
+ end
+ elsif value.kind_of?(Hash)
+ transformed_value = value.map do |key, val|
+ if modifier == "*"
+ "#{
+ Addressable::URI.encode_component( key, encode_map)
+ }=#{
+ Addressable::URI.encode_component( val, encode_map)
+ }"
+ else
+ "#{
+ Addressable::URI.encode_component( key, encode_map)
+ },#{
+ Addressable::URI.encode_component( val, encode_map)
+ }"
+ end
+ end
+ unless modifier == "*"
+ transformed_value = transformed_value.join(',')
+ end
+ else
+ if length
+ transformed_value = Addressable::URI.encode_component(
+ value[0...length], encode_map)
+ else
+ transformed_value = Addressable::URI.encode_component(
+ value, encode_map)
+ end
+ end
+ end
+
+ # Process, if we've got a processor
+ if processor != nil
+ if processor.respond_to?(:validate)
+ if !processor.validate(name, value)
+ display_value = value.kind_of?(Array) ? value.inspect : value
+ raise InvalidTemplateValueError,
+ "#{name}=#{display_value} is an invalid template value."
+ end
+ end
+ if processor.respond_to?(:transform)
+ transformed_value = processor.transform(name, value)
+ if normalize_values
+ transformed_value = normalize_value(transformed_value)
+ end
+ end
+ end
+ acc << [name, transformed_value]
+ end
+ acc
+ end
+ return "" if return_value.empty?
+ join_values(operator, return_value)
+ end
+
+ ##
+ # Takes a set of values, and joins them together based on the
+ # operator.
+ #
+ # @param [String, Nil] operator One of the operators from the set
+ # (?,&,+,#,;,/,.), or nil if there wasn't one.
+ # @param [Array] return_value
+ # The set of return values (as [variable_name, value] tuples) that will
+ # be joined together.
+ #
+ # @return [String] The transformed mapped value
+ def join_values(operator, return_value)
+ leader = LEADERS.fetch(operator, '')
+ joiner = JOINERS.fetch(operator, ',')
+ case operator
+ when '&', '?'
+ leader + return_value.map{|k,v|
+ if v.is_a?(Array) && v.first =~ /=/
+ v.join(joiner)
+ elsif v.is_a?(Array)
+ v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner)
+ else
+ "#{k}=#{v}"
+ end
+ }.join(joiner)
+ when ';'
+ return_value.map{|k,v|
+ if v.is_a?(Array) && v.first =~ /=/
+ ';' + v.join(";")
+ elsif v.is_a?(Array)
+ ';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";")
+ else
+ v && v != '' ? ";#{k}=#{v}" : ";#{k}"
+ end
+ }.join
+ else
+ leader + return_value.map{|k,v| v}.join(joiner)
+ end
+ end
+
+ ##
+ # Takes a set of values, and joins them together based on the
+ # operator.
+ #
+ # @param [Hash, Array, String] value
+ # Normalizes unicode keys and values with String#unicode_normalize (NFC)
+ #
+ # @return [Hash, Array, String] The normalized values
+ def normalize_value(value)
+ # Handle unicode normalization
+ if value.respond_to?(:to_ary)
+ value.to_ary.map! { |val| normalize_value(val) }
+ elsif value.kind_of?(Hash)
+ value = value.inject({}) { |acc, (k, v)|
+ acc[normalize_value(k)] = normalize_value(v)
+ acc
+ }
+ else
+ value = value.to_s if !value.kind_of?(String)
+ if value.encoding != Encoding::UTF_8
+ value = value.dup.force_encoding(Encoding::UTF_8)
+ end
+ value = value.unicode_normalize(:nfc)
+ end
+ value
+ end
+
+ ##
+ # Generates a hash with string keys
+ #
+ # @param [Hash] mapping A mapping hash to normalize
+ #
+ # @return [Hash]
+ # A hash with stringified keys
+ def normalize_keys(mapping)
+ return mapping.inject({}) do |accu, pair|
+ name, value = pair
+ if Symbol === name
+ name = name.to_s
+ elsif name.respond_to?(:to_str)
+ name = name.to_str
+ else
+ raise TypeError,
+ "Can't convert #{name.class} into String."
+ end
+ accu[name] = value
+ accu
+ end
+ end
+
+ ##
+ # Generates the Regexp that parses a template pattern. Memoizes the
+ # value if template processor not set (processors may not be deterministic)
+ #
+ # @param [String] pattern The URI template pattern.
+ # @param [#match] processor The template processor to use.
+ #
+ # @return [Array, Regexp]
+ # An array of expansion variables nad a regular expression which may be
+ # used to parse a template pattern
+ def parse_template_pattern(pattern, processor = nil)
+ if processor.nil? && pattern == @pattern
+ @cached_template_parse ||=
+ parse_new_template_pattern(pattern, processor)
+ else
+ parse_new_template_pattern(pattern, processor)
+ end
+ end
+
+ ##
+ # Generates the Regexp that parses a template pattern.
+ #
+ # @param [String] pattern The URI template pattern.
+ # @param [#match] processor The template processor to use.
+ #
+ # @return [Array, Regexp]
+ # An array of expansion variables nad a regular expression which may be
+ # used to parse a template pattern
+ def parse_new_template_pattern(pattern, processor = nil)
+ # Escape the pattern. The two gsubs restore the escaped curly braces
+ # back to their original form. Basically, escape everything that isn't
+ # within an expansion.
+ escaped_pattern = Regexp.escape(
+ pattern
+ ).gsub(/\\\{(.*?)\\\}/) do |escaped|
+ escaped.gsub(/\\(.)/, "\\1")
+ end
+
+ expansions = []
+
+ # Create a regular expression that captures the values of the
+ # variables in the URI.
+ regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
+
+ expansions << expansion
+ _, operator, varlist = *expansion.match(EXPRESSION)
+ leader = Regexp.escape(LEADERS.fetch(operator, ''))
+ joiner = Regexp.escape(JOINERS.fetch(operator, ','))
+ varspecs = varlist.split(',')
+ combined = varspecs.map do |varspec|
+ _, name, modifier = *varspec.match(VARSPEC)
+
+ result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
+ if result
+ "(?<#{name}>#{ result })"
+ else
+ group = case operator
+ when '+'
+ "#{ RESERVED }*?"
+ when '#'
+ "#{ RESERVED }*?"
+ when '/'
+ "#{ UNRESERVED }*?"
+ when '.'
+ "#{ UNRESERVED.gsub('\.', '') }*?"
+ when ';'
+ "#{ UNRESERVED }*=?#{ UNRESERVED }*?"
+ when '?'
+ "#{ UNRESERVED }*=#{ UNRESERVED }*?"
+ when '&'
+ "#{ UNRESERVED }*=#{ UNRESERVED }*?"
+ else
+ "#{ UNRESERVED }*?"
+ end
+ if modifier == '*'
+ seg = case operator
+ when '+', '#' then "#{RESERVED_NO_COMMA}*+"
+ else group
+ end
+ joiner_pattern = operator.nil? ? joiner : "#{joiner}?"
+ "(?<#{name}>#{seg}(?:#{joiner_pattern}#{seg})*)?"
+ elsif varspecs.size > 1 && (operator == '+' || operator == '#') &&
+ varspec != varspecs.last
+ "(?<#{name}>#{RESERVED_NO_COMMA}*+)?"
+ else
+ "(?<#{name}>#{group})?"
+ end
+ end
+ end.join("#{joiner}?")
+ "(?:|#{leader}#{combined})"
+ end
+
+ # Ensure that the regular expression matches the whole URI.
+ regexp_string = "\\A#{regexp_string}\\z"
+ return expansions, Regexp.new(regexp_string)
+ end
+
+ end
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/uri.rb b/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/uri.rb
new file mode 100644
index 000000000..07c9aeb58
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/addressable-2.9.0/lib/addressable/uri.rb
@@ -0,0 +1,2602 @@
+# frozen_string_literal: true
+
+#--
+# Copyright (C) Bob Aman
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#++
+
+
+require "addressable/version"
+require "addressable/idna"
+require "public_suffix"
+
+##
+# Addressable is a library for processing links and URIs.
+module Addressable
+ ##
+ # This is an implementation of a URI parser based on
+ # RFC 3986,
+ # RFC 3987.
+ class URI
+ ##
+ # Raised if something other than a uri is supplied.
+ class InvalidURIError < StandardError
+ end
+
+ ##
+ # Container for the character classes specified in
+ # RFC 3986.
+ #
+ # Note: Concatenated and interpolated `String`s are not affected by the
+ # `frozen_string_literal` directive and must be frozen explicitly.
+ #
+ # Interpolated `String`s *were* frozen this way before Ruby 3.0:
+ # https://bugs.ruby-lang.org/issues/17104
+ module CharacterClasses
+ ALPHA = "a-zA-Z"
+ DIGIT = "0-9"
+ GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
+ SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
+ RESERVED = (GEN_DELIMS + SUB_DELIMS).freeze
+ UNRESERVED = (ALPHA + DIGIT + "\\-\\.\\_\\~").freeze
+ RESERVED_AND_UNRESERVED = RESERVED + UNRESERVED
+ PCHAR = (UNRESERVED + SUB_DELIMS + "\\:\\@").freeze
+ SCHEME = (ALPHA + DIGIT + "\\-\\+\\.").freeze
+ HOST = (UNRESERVED + SUB_DELIMS + "\\[\\:\\]").freeze
+ AUTHORITY = (PCHAR + "\\[\\]").freeze
+ PATH = (PCHAR + "\\/").freeze
+ QUERY = (PCHAR + "\\/\\?").freeze
+ FRAGMENT = (PCHAR + "\\/\\?").freeze
+ end
+
+ module NormalizeCharacterClasses
+ HOST = /[^#{CharacterClasses::HOST}]/
+ UNRESERVED = /[^#{CharacterClasses::UNRESERVED}]/
+ PCHAR = /[^#{CharacterClasses::PCHAR}]/
+ SCHEME = /[^#{CharacterClasses::SCHEME}]/
+ FRAGMENT = /[^#{CharacterClasses::FRAGMENT}]/
+ QUERY = %r{[^a-zA-Z0-9\-\.\_\~\!\$\'\(\)\*\+\,\=\:\@\/\?%]|%(?!2B|2b)}
+ end
+
+ module CharacterClassesRegexps
+ AUTHORITY = /[^#{CharacterClasses::AUTHORITY}]/
+ FRAGMENT = /[^#{CharacterClasses::FRAGMENT}]/
+ HOST = /[^#{CharacterClasses::HOST}]/
+ PATH = /[^#{CharacterClasses::PATH}]/
+ QUERY = /[^#{CharacterClasses::QUERY}]/
+ RESERVED = /[^#{CharacterClasses::RESERVED}]/
+ RESERVED_AND_UNRESERVED = /[^#{CharacterClasses::RESERVED_AND_UNRESERVED}]/
+ SCHEME = /[^#{CharacterClasses::SCHEME}]/
+ UNRESERVED = /[^#{CharacterClasses::UNRESERVED}]/
+ end
+
+ SLASH = '/'
+ EMPTY_STR = ''
+
+ URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
+
+ PORT_MAPPING = {
+ "http" => 80,
+ "https" => 443,
+ "ftp" => 21,
+ "tftp" => 69,
+ "sftp" => 22,
+ "ssh" => 22,
+ "svn+ssh" => 22,
+ "telnet" => 23,
+ "nntp" => 119,
+ "gopher" => 70,
+ "wais" => 210,
+ "ldap" => 389,
+ "prospero" => 1525
+ }.freeze
+
+ ##
+ # Returns a URI object based on the parsed string.
+ #
+ # @param [String, Addressable::URI, #to_str] uri
+ # The URI string to parse.
+ # No parsing is performed if the object is already an
+ # Addressable::URI.
+ #
+ # @return [Addressable::URI] The parsed URI.
+ def self.parse(uri)
+ # If we were given nil, return nil.
+ return nil unless uri
+ # If a URI object is passed, just return itself.
+ return uri.dup if uri.kind_of?(self)
+
+ # If a URI object of the Ruby standard library variety is passed,
+ # convert it to a string, then parse the string.
+ # We do the check this way because we don't want to accidentally
+ # cause a missing constant exception to be thrown.
+ if uri.class.name =~ /^URI\b/
+ uri = uri.to_s
+ end
+
+ # Otherwise, convert to a String
+ begin
+ uri = uri.to_str
+ rescue TypeError, NoMethodError
+ raise TypeError, "Can't convert #{uri.class} into String."
+ end unless uri.is_a?(String)
+
+ # This Regexp supplied as an example in RFC 3986, and it works great.
+ scan = uri.scan(URIREGEX)
+ fragments = scan[0]
+ scheme = fragments[1]
+ authority = fragments[3]
+ path = fragments[4]
+ query = fragments[6]
+ fragment = fragments[8]
+ user = nil
+ password = nil
+ host = nil
+ port = nil
+ if authority != nil
+ # The Regexp above doesn't split apart the authority.
+ userinfo = authority[/^([^\[\]]*)@/, 1]
+ if userinfo != nil
+ user = userinfo.strip[/^([^:]*):?/, 1]
+ password = userinfo.strip[/:(.*)$/, 1]
+ end
+
+ host = authority.sub(
+ /^([^\[\]]*)@/, EMPTY_STR
+ ).sub(
+ /:([^:@\[\]]*?)$/, EMPTY_STR
+ )
+
+ port = authority[/:([^:@\[\]]*?)$/, 1]
+ port = nil if port == EMPTY_STR
+ end
+
+ return new(
+ :scheme => scheme,
+ :user => user,
+ :password => password,
+ :host => host,
+ :port => port,
+ :path => path,
+ :query => query,
+ :fragment => fragment
+ )
+ end
+
+ ##
+ # Converts an input to a URI. The input does not have to be a valid
+ # URI — the method will use heuristics to guess what URI was intended.
+ # This is not standards-compliant, merely user-friendly.
+ #
+ # @param [String, Addressable::URI, #to_str] uri
+ # The URI string to parse.
+ # No parsing is performed if the object is already an
+ # Addressable::URI.
+ # @param [Hash] hints
+ # A Hash of hints to the heuristic parser.
+ # Defaults to {:scheme => "http"}.
+ #
+ # @return [Addressable::URI] The parsed URI.
+ def self.heuristic_parse(uri, hints={})
+ # If we were given nil, return nil.
+ return nil unless uri
+ # If a URI object is passed, just return itself.
+ return uri.dup if uri.kind_of?(self)
+
+ # If a URI object of the Ruby standard library variety is passed,
+ # convert it to a string, then parse the string.
+ # We do the check this way because we don't want to accidentally
+ # cause a missing constant exception to be thrown.
+ if uri.class.name =~ /^URI\b/
+ uri = uri.to_s
+ end
+
+ unless uri.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{uri.class} into String."
+ end
+ # Otherwise, convert to a String
+ uri = uri.to_str.dup.strip
+ hints = {
+ :scheme => "http"
+ }.merge(hints)
+ case uri
+ when /^http:\//i
+ uri.sub!(/^http:\/+/i, "http://")
+ when /^https:\//i
+ uri.sub!(/^https:\/+/i, "https://")
+ when /^feed:\/+http:\//i
+ uri.sub!(/^feed:\/+http:\/+/i, "feed:http://")
+ when /^feed:\//i
+ uri.sub!(/^feed:\/+/i, "feed://")
+ when %r[^file:/{4}]i
+ uri.sub!(%r[^file:/+]i, "file:////")
+ when %r[^file://localhost/]i
+ uri.sub!(%r[^file://localhost/+]i, "file:///")
+ when %r[^file:/+]i
+ uri.sub!(%r[^file:/+]i, "file:///")
+ when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
+ uri.sub!(/^/, hints[:scheme] + "://")
+ when /\A\d+\..*:\d+\z/
+ uri = "#{hints[:scheme]}://#{uri}"
+ end
+ match = uri.match(URIREGEX)
+ fragments = match.captures
+ authority = fragments[3]
+ if authority && authority.length > 0
+ new_authority = authority.tr("\\", "/").gsub(" ", "%20")
+ # NOTE: We want offset 4, not 3!
+ offset = match.offset(4)
+ uri = uri.dup
+ uri[offset[0]...offset[1]] = new_authority
+ end
+ parsed = self.parse(uri)
+ if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
+ parsed = self.parse(hints[:scheme] + "://" + uri)
+ end
+ if parsed.path.include?(".")
+ if parsed.path[/\b@\b/]
+ parsed.scheme = "mailto" unless parsed.scheme
+ elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
+ parsed.defer_validation do
+ new_path = parsed.path.sub(
+ Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
+ parsed.host = new_host
+ parsed.path = new_path
+ parsed.scheme = hints[:scheme] unless parsed.scheme
+ end
+ end
+ end
+ return parsed
+ end
+
+ ##
+ # Converts a path to a file scheme URI. If the path supplied is
+ # relative, it will be returned as a relative URI. If the path supplied
+ # is actually a non-file URI, it will parse the URI as if it had been
+ # parsed with Addressable::URI.parse. Handles all of the
+ # various Microsoft-specific formats for specifying paths.
+ #
+ # @param [String, Addressable::URI, #to_str] path
+ # Typically a String path to a file or directory, but
+ # will return a sensible return value if an absolute URI is supplied
+ # instead.
+ #
+ # @return [Addressable::URI]
+ # The parsed file scheme URI or the original URI if some other URI
+ # scheme was provided.
+ #
+ # @example
+ # base = Addressable::URI.convert_path("/absolute/path/")
+ # uri = Addressable::URI.convert_path("relative/path")
+ # (base + uri).to_s
+ # #=> "file:///absolute/path/relative/path"
+ #
+ # Addressable::URI.convert_path(
+ # "c:\\windows\\My Documents 100%20\\foo.txt"
+ # ).to_s
+ # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
+ #
+ # Addressable::URI.convert_path("http://example.com/").to_s
+ # #=> "http://example.com/"
+ def self.convert_path(path)
+ # If we were given nil, return nil.
+ return nil unless path
+ # If a URI object is passed, just return itself.
+ return path if path.kind_of?(self)
+ unless path.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{path.class} into String."
+ end
+ # Otherwise, convert to a String
+ path = path.to_str.strip
+
+ path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
+ path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
+ uri = self.parse(path)
+
+ if uri.scheme == nil
+ # Adjust windows-style uris
+ uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
+ "/#{$1.downcase}:/"
+ end
+ uri.path.tr!("\\", SLASH)
+ if File.exist?(uri.path) &&
+ File.stat(uri.path).directory?
+ uri.path.chomp!(SLASH)
+ uri.path = uri.path + '/'
+ end
+
+ # If the path is absolute, set the scheme and host.
+ if uri.path.start_with?(SLASH)
+ uri.scheme = "file"
+ uri.host = EMPTY_STR
+ end
+ uri.normalize!
+ end
+
+ return uri
+ end
+
+ ##
+ # Joins several URIs together.
+ #
+ # @param [String, Addressable::URI, #to_str] *uris
+ # The URIs to join.
+ #
+ # @return [Addressable::URI] The joined URI.
+ #
+ # @example
+ # base = "http://example.com/"
+ # uri = Addressable::URI.parse("relative/path")
+ # Addressable::URI.join(base, uri)
+ # #=> #String
+ # is passed, the String must be formatted as a regular
+ # expression character class. (Do not include the surrounding square
+ # brackets.) For example, "b-zB-Z0-9" would cause
+ # everything but the letters 'b' through 'z' and the numbers '0' through
+ # '9' to be percent encoded. If a Regexp is passed, the
+ # value /[^b-zB-Z0-9]/ would have the same effect. A set of
+ # useful String values may be found in the
+ # Addressable::URI::CharacterClasses module. The default
+ # value is the reserved plus unreserved character classes specified in
+ # RFC 3986.
+ #
+ # @param [Regexp] upcase_encoded
+ # A string of characters that may already be percent encoded, and whose
+ # encodings should be upcased. This allows normalization of percent
+ # encodings for characters not included in the
+ # character_class.
+ #
+ # @return [String] The encoded component.
+ #
+ # @example
+ # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
+ # => "simple%2Fex%61mple"
+ # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
+ # => "simple%2Fex%61mple"
+ # Addressable::URI.encode_component(
+ # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
+ # )
+ # => "simple%2Fexample"
+ def self.encode_component(component, character_class=CharacterClassesRegexps::RESERVED_AND_UNRESERVED, upcase_encoded='')
+ return nil if component.nil?
+
+ begin
+ if component.kind_of?(Symbol) ||
+ component.kind_of?(Numeric) ||
+ component.kind_of?(TrueClass) ||
+ component.kind_of?(FalseClass)
+ component = component.to_s
+ else
+ component = component.to_str
+ end
+ rescue TypeError, NoMethodError
+ raise TypeError, "Can't convert #{component.class} into String."
+ end if !component.is_a? String
+
+ if ![String, Regexp].include?(character_class.class)
+ raise TypeError,
+ "Expected String or Regexp, got #{character_class.inspect}"
+ end
+ if character_class.kind_of?(String)
+ character_class = /[^#{character_class}]/
+ end
+ # We can't perform regexps on invalid UTF sequences, but
+ # here we need to, so switch to ASCII.
+ component = component.dup
+ component.force_encoding(Encoding::ASCII_8BIT)
+ # Avoiding gsub! because there are edge cases with frozen strings
+ component = component.gsub(character_class) do |char|
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[char.ord]
+ end
+ if upcase_encoded.length > 0
+ upcase_encoded_chars = upcase_encoded.bytes.map do |byte|
+ SEQUENCE_ENCODING_TABLE[byte]
+ end
+ component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/,
+ &:upcase)
+ end
+
+ return component
+ end
+
+ class << self
+ alias_method :escape_component, :encode_component
+ end
+
+ ##
+ # Unencodes any percent encoded characters within a URI component.
+ # This method may be used for unencoding either components or full URIs,
+ # however, it is recommended to use the unencode_component
+ # alias when unencoding components.
+ #
+ # @param [String, Addressable::URI, #to_str] uri
+ # The URI or component to unencode.
+ #
+ # @param [Class] return_type
+ # The type of object to return.
+ # This value may only be set to String or
+ # Addressable::URI. All other values are invalid. Defaults
+ # to String.
+ #
+ # @param [String] leave_encoded
+ # A string of characters to leave encoded. If a percent encoded character
+ # in this list is encountered then it will remain percent encoded.
+ #
+ # @return [String, Addressable::URI]
+ # The unencoded component or URI.
+ # The return type is determined by the return_type
+ # parameter.
+ def self.unencode(uri, return_type=String, leave_encoded='')
+ return nil if uri.nil?
+
+ begin
+ uri = uri.to_str
+ rescue NoMethodError, TypeError
+ raise TypeError, "Can't convert #{uri.class} into String."
+ end if !uri.is_a? String
+ if ![String, ::Addressable::URI].include?(return_type)
+ raise TypeError,
+ "Expected Class (String or Addressable::URI), " +
+ "got #{return_type.inspect}"
+ end
+
+ result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
+ c = sequence[1..3].to_i(16).chr
+ c.force_encoding(sequence.encoding)
+ leave_encoded.include?(c) ? sequence : c
+ end
+
+ result.force_encoding(Encoding::UTF_8)
+ if return_type == String
+ return result
+ elsif return_type == ::Addressable::URI
+ return ::Addressable::URI.parse(result)
+ end
+ end
+
+ class << self
+ alias_method :unescape, :unencode
+ alias_method :unencode_component, :unencode
+ alias_method :unescape_component, :unencode
+ end
+
+
+ ##
+ # Normalizes the encoding of a URI component.
+ #
+ # @param [String, #to_str] component The URI component to encode.
+ #
+ # @param [String, Regexp] character_class
+ # The characters which are not percent encoded. If a String
+ # is passed, the String must be formatted as a regular
+ # expression character class. (Do not include the surrounding square
+ # brackets.) For example, "b-zB-Z0-9" would cause
+ # everything but the letters 'b' through 'z' and the numbers '0'
+ # through '9' to be percent encoded. If a Regexp is passed,
+ # the value /[^b-zB-Z0-9]/ would have the same effect. A
+ # set of useful String values may be found in the
+ # Addressable::URI::CharacterClasses module. The default
+ # value is the reserved plus unreserved character classes specified in
+ # RFC 3986.
+ #
+ # @param [String] leave_encoded
+ # When character_class is a String then
+ # leave_encoded is a string of characters that should remain
+ # percent encoded while normalizing the component; if they appear percent
+ # encoded in the original component, then they will be upcased ("%2f"
+ # normalized to "%2F") but otherwise left alone.
+ #
+ # @return [String] The normalized component.
+ #
+ # @example
+ # Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z")
+ # => "simple%2Fex%61mple"
+ # Addressable::URI.normalize_component(
+ # "simpl%65/%65xampl%65", /[^b-zB-Z]/
+ # )
+ # => "simple%2Fex%61mple"
+ # Addressable::URI.normalize_component(
+ # "simpl%65/%65xampl%65",
+ # Addressable::URI::CharacterClasses::UNRESERVED
+ # )
+ # => "simple%2Fexample"
+ # Addressable::URI.normalize_component(
+ # "one%20two%2fthree%26four",
+ # "0-9a-zA-Z &/",
+ # "/"
+ # )
+ # => "one two%2Fthree&four"
+ def self.normalize_component(component, character_class=
+ CharacterClassesRegexps::RESERVED_AND_UNRESERVED,
+ leave_encoded='')
+ return nil if component.nil?
+
+ begin
+ component = component.to_str
+ rescue NoMethodError, TypeError
+ raise TypeError, "Can't convert #{component.class} into String."
+ end if !component.is_a? String
+
+ if ![String, Regexp].include?(character_class.class)
+ raise TypeError,
+ "Expected String or Regexp, got #{character_class.inspect}"
+ end
+ if character_class.kind_of?(String)
+ leave_re = if leave_encoded.length > 0
+ character_class = "#{character_class}%" unless character_class.include?('%')
+
+ bytes = leave_encoded.bytes
+ leave_encoded_pattern = bytes.map { |b| SEQUENCE_ENCODING_TABLE[b] }.join('|')
+ "|%(?!#{leave_encoded_pattern}|#{leave_encoded_pattern.upcase})"
+ end
+
+ character_class = if leave_re
+ /[^#{character_class}]#{leave_re}/
+ else
+ /[^#{character_class}]/
+ end
+ end
+ # We can't perform regexps on invalid UTF sequences, but
+ # here we need to, so switch to ASCII.
+ component = component.dup
+ component.force_encoding(Encoding::ASCII_8BIT)
+ unencoded = self.unencode_component(component, String, leave_encoded)
+ begin
+ encoded = self.encode_component(
+ unencoded.unicode_normalize(:nfc),
+ character_class,
+ leave_encoded
+ )
+ rescue ArgumentError
+ encoded = self.encode_component(unencoded)
+ end
+ encoded.force_encoding(Encoding::UTF_8)
+ return encoded
+ end
+
+ ##
+ # Percent encodes any special characters in the URI.
+ #
+ # @param [String, Addressable::URI, #to_str] uri
+ # The URI to encode.
+ #
+ # @param [Class] return_type
+ # The type of object to return.
+ # This value may only be set to String or
+ # Addressable::URI. All other values are invalid. Defaults
+ # to String.
+ #
+ # @return [String, Addressable::URI]
+ # The encoded URI.
+ # The return type is determined by the return_type
+ # parameter.
+ def self.encode(uri, return_type=String)
+ return nil if uri.nil?
+
+ begin
+ uri = uri.to_str
+ rescue NoMethodError, TypeError
+ raise TypeError, "Can't convert #{uri.class} into String."
+ end if !uri.is_a? String
+
+ if ![String, ::Addressable::URI].include?(return_type)
+ raise TypeError,
+ "Expected Class (String or Addressable::URI), " +
+ "got #{return_type.inspect}"
+ end
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
+ encoded_uri = Addressable::URI.new(
+ :scheme => self.encode_component(uri_object.scheme,
+ Addressable::URI::CharacterClassesRegexps::SCHEME),
+ :authority => self.encode_component(uri_object.authority,
+ Addressable::URI::CharacterClassesRegexps::AUTHORITY),
+ :path => self.encode_component(uri_object.path,
+ Addressable::URI::CharacterClassesRegexps::PATH),
+ :query => self.encode_component(uri_object.query,
+ Addressable::URI::CharacterClassesRegexps::QUERY),
+ :fragment => self.encode_component(uri_object.fragment,
+ Addressable::URI::CharacterClassesRegexps::FRAGMENT)
+ )
+ if return_type == String
+ return encoded_uri.to_s
+ elsif return_type == ::Addressable::URI
+ return encoded_uri
+ end
+ end
+
+ class << self
+ alias_method :escape, :encode
+ end
+
+ ##
+ # Normalizes the encoding of a URI. Characters within a hostname are
+ # not percent encoded to allow for internationalized domain names.
+ #
+ # @param [String, Addressable::URI, #to_str] uri
+ # The URI to encode.
+ #
+ # @param [Class] return_type
+ # The type of object to return.
+ # This value may only be set to String or
+ # Addressable::URI. All other values are invalid. Defaults
+ # to String.
+ #
+ # @return [String, Addressable::URI]
+ # The encoded URI.
+ # The return type is determined by the return_type
+ # parameter.
+ def self.normalized_encode(uri, return_type=String)
+ begin
+ uri = uri.to_str
+ rescue NoMethodError, TypeError
+ raise TypeError, "Can't convert #{uri.class} into String."
+ end if !uri.is_a? String
+
+ if ![String, ::Addressable::URI].include?(return_type)
+ raise TypeError,
+ "Expected Class (String or Addressable::URI), " +
+ "got #{return_type.inspect}"
+ end
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
+ components = {
+ :scheme => self.unencode_component(uri_object.scheme),
+ :user => self.unencode_component(uri_object.user),
+ :password => self.unencode_component(uri_object.password),
+ :host => self.unencode_component(uri_object.host),
+ :port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
+ :path => self.unencode_component(uri_object.path),
+ :query => self.unencode_component(uri_object.query),
+ :fragment => self.unencode_component(uri_object.fragment)
+ }
+ components.each do |key, value|
+ if value != nil
+ begin
+ components[key] = value.to_str.unicode_normalize(:nfc)
+ rescue ArgumentError
+ # Likely a malformed UTF-8 character, skip unicode normalization
+ components[key] = value.to_str
+ end
+ end
+ end
+ encoded_uri = Addressable::URI.new(
+ :scheme => self.encode_component(components[:scheme],
+ Addressable::URI::CharacterClassesRegexps::SCHEME),
+ :user => self.encode_component(components[:user],
+ Addressable::URI::CharacterClassesRegexps::UNRESERVED),
+ :password => self.encode_component(components[:password],
+ Addressable::URI::CharacterClassesRegexps::UNRESERVED),
+ :host => components[:host],
+ :port => components[:port],
+ :path => self.encode_component(components[:path],
+ Addressable::URI::CharacterClassesRegexps::PATH),
+ :query => self.encode_component(components[:query],
+ Addressable::URI::CharacterClassesRegexps::QUERY),
+ :fragment => self.encode_component(components[:fragment],
+ Addressable::URI::CharacterClassesRegexps::FRAGMENT)
+ )
+ if return_type == String
+ return encoded_uri.to_s
+ elsif return_type == ::Addressable::URI
+ return encoded_uri
+ end
+ end
+
+ ##
+ # Encodes a set of key/value pairs according to the rules for the
+ # application/x-www-form-urlencoded MIME type.
+ #
+ # @param [#to_hash, #to_ary] form_values
+ # The form values to encode.
+ #
+ # @param [TrueClass, FalseClass] sort
+ # Sort the key/value pairs prior to encoding.
+ # Defaults to false.
+ #
+ # @return [String]
+ # The encoded value.
+ def self.form_encode(form_values, sort=false)
+ if form_values.respond_to?(:to_hash)
+ form_values = form_values.to_hash.to_a
+ elsif form_values.respond_to?(:to_ary)
+ form_values = form_values.to_ary
+ else
+ raise TypeError, "Can't convert #{form_values.class} into Array."
+ end
+
+ form_values = form_values.inject([]) do |accu, (key, value)|
+ if value.kind_of?(Array)
+ value.each do |v|
+ accu << [key.to_s, v.to_s]
+ end
+ else
+ accu << [key.to_s, value.to_s]
+ end
+ accu
+ end
+
+ if sort
+ # Useful for OAuth and optimizing caching systems
+ form_values = form_values.sort
+ end
+ escaped_form_values = form_values.map do |(key, value)|
+ # Line breaks are CRLF pairs
+ [
+ self.encode_component(
+ key.gsub(/(\r\n|\n|\r)/, "\r\n"),
+ CharacterClassesRegexps::UNRESERVED
+ ).gsub("%20", "+"),
+ self.encode_component(
+ value.gsub(/(\r\n|\n|\r)/, "\r\n"),
+ CharacterClassesRegexps::UNRESERVED
+ ).gsub("%20", "+")
+ ]
+ end
+ return escaped_form_values.map do |(key, value)|
+ "#{key}=#{value}"
+ end.join("&")
+ end
+
+ ##
+ # Decodes a String according to the rules for the
+ # application/x-www-form-urlencoded MIME type.
+ #
+ # @param [String, #to_str] encoded_value
+ # The form values to decode.
+ #
+ # @return [Array]
+ # The decoded values.
+ # This is not a Hash because of the possibility for
+ # duplicate keys.
+ def self.form_unencode(encoded_value)
+ if !encoded_value.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{encoded_value.class} into String."
+ end
+ encoded_value = encoded_value.to_str
+ split_values = encoded_value.split("&").map do |pair|
+ pair.split("=", 2)
+ end
+ return split_values.map do |(key, value)|
+ [
+ key ? self.unencode_component(
+ key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil,
+ value ? (self.unencode_component(
+ value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil
+ ]
+ end
+ end
+
+ ##
+ # Creates a new uri object from component parts.
+ #
+ # @option [String, #to_str] scheme The scheme component.
+ # @option [String, #to_str] user The user component.
+ # @option [String, #to_str] password The password component.
+ # @option [String, #to_str] userinfo
+ # The userinfo component. If this is supplied, the user and password
+ # components must be omitted.
+ # @option [String, #to_str] host The host component.
+ # @option [String, #to_str] port The port component.
+ # @option [String, #to_str] authority
+ # The authority component. If this is supplied, the user, password,
+ # userinfo, host, and port components must be omitted.
+ # @option [String, #to_str] path The path component.
+ # @option [String, #to_str] query The query component.
+ # @option [String, #to_str] fragment The fragment component.
+ #
+ # @return [Addressable::URI] The constructed URI object.
+ def initialize(options={})
+ if options.has_key?(:authority)
+ if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
+ raise ArgumentError,
+ "Cannot specify both an authority and any of the components " +
+ "within the authority."
+ end
+ end
+ if options.has_key?(:userinfo)
+ if (options.keys & [:user, :password]).any?
+ raise ArgumentError,
+ "Cannot specify both a userinfo and either the user or password."
+ end
+ end
+
+ reset_ivs
+
+ defer_validation do
+ # Bunch of crazy logic required because of the composite components
+ # like userinfo and authority.
+ self.scheme = options[:scheme] if options[:scheme]
+ self.user = options[:user] if options[:user]
+ self.password = options[:password] if options[:password]
+ self.userinfo = options[:userinfo] if options[:userinfo]
+ self.host = options[:host] if options[:host]
+ self.port = options[:port] if options[:port]
+ self.authority = options[:authority] if options[:authority]
+ self.path = options[:path] if options[:path]
+ self.query = options[:query] if options[:query]
+ self.query_values = options[:query_values] if options[:query_values]
+ self.fragment = options[:fragment] if options[:fragment]
+ end
+
+ to_s # force path validation
+ end
+
+ ##
+ # Freeze URI, initializing instance variables.
+ #
+ # @return [Addressable::URI] The frozen URI object.
+ def freeze
+ self.normalized_scheme
+ self.normalized_user
+ self.normalized_password
+ self.normalized_userinfo
+ self.normalized_host
+ self.normalized_port
+ self.normalized_authority
+ self.normalized_site
+ self.normalized_path
+ self.normalized_query
+ self.normalized_fragment
+ self.hash
+ super
+ end
+
+ ##
+ # The scheme component for this URI.
+ #
+ # @return [String] The scheme component.
+ attr_reader :scheme
+
+ ##
+ # The scheme component for this URI, normalized.
+ #
+ # @return [String] The scheme component, normalized.
+ def normalized_scheme
+ return nil unless self.scheme
+ if @normalized_scheme == NONE
+ @normalized_scheme = if self.scheme =~ /^\s*ssh\+svn\s*$/i
+ "svn+ssh".dup
+ else
+ Addressable::URI.normalize_component(
+ self.scheme.strip.downcase,
+ Addressable::URI::NormalizeCharacterClasses::SCHEME
+ )
+ end
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_scheme)
+ @normalized_scheme
+ end
+
+ ##
+ # Sets the scheme component for this URI.
+ #
+ # @param [String, #to_str] new_scheme The new scheme component.
+ def scheme=(new_scheme)
+ if new_scheme && !new_scheme.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_scheme.class} into String."
+ elsif new_scheme
+ new_scheme = new_scheme.to_str
+ end
+ if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
+ raise InvalidURIError, "Invalid scheme format: '#{new_scheme}'"
+ end
+ @scheme = new_scheme
+ @scheme = nil if @scheme.to_s.strip.empty?
+
+ # Reset dependent values
+ @normalized_scheme = NONE
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # The user component for this URI.
+ #
+ # @return [String] The user component.
+ attr_reader :user
+
+ ##
+ # The user component for this URI, normalized.
+ #
+ # @return [String] The user component, normalized.
+ def normalized_user
+ return nil unless self.user
+ return @normalized_user unless @normalized_user == NONE
+ @normalized_user = begin
+ if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
+ (!self.password || self.password.strip.empty?)
+ nil
+ else
+ Addressable::URI.normalize_component(
+ self.user.strip,
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
+ )
+ end
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_user)
+ @normalized_user
+ end
+
+ ##
+ # Sets the user component for this URI.
+ #
+ # @param [String, #to_str] new_user The new user component.
+ def user=(new_user)
+ if new_user && !new_user.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_user.class} into String."
+ end
+ @user = new_user ? new_user.to_str : nil
+
+ # You can't have a nil user with a non-nil password
+ if password != nil
+ @user = EMPTY_STR unless user
+ end
+
+ # Reset dependent values
+ @userinfo = nil
+ @normalized_userinfo = NONE
+ @authority = nil
+ @normalized_user = NONE
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # The password component for this URI.
+ #
+ # @return [String] The password component.
+ attr_reader :password
+
+ ##
+ # The password component for this URI, normalized.
+ #
+ # @return [String] The password component, normalized.
+ def normalized_password
+ return nil unless self.password
+ return @normalized_password unless @normalized_password == NONE
+ @normalized_password = begin
+ if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
+ (!self.user || self.user.strip.empty?)
+ nil
+ else
+ Addressable::URI.normalize_component(
+ self.password.strip,
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
+ )
+ end
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_password)
+ @normalized_password
+ end
+
+ ##
+ # Sets the password component for this URI.
+ #
+ # @param [String, #to_str] new_password The new password component.
+ def password=(new_password)
+ if new_password && !new_password.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_password.class} into String."
+ end
+ @password = new_password ? new_password.to_str : nil
+
+ # You can't have a nil user with a non-nil password
+ if @password != nil
+ self.user = EMPTY_STR if user.nil?
+ end
+
+ # Reset dependent values
+ @userinfo = nil
+ @normalized_userinfo = NONE
+ @authority = nil
+ @normalized_password = NONE
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # The userinfo component for this URI.
+ # Combines the user and password components.
+ #
+ # @return [String] The userinfo component.
+ def userinfo
+ current_user = self.user
+ current_password = self.password
+ (current_user || current_password) && @userinfo ||= begin
+ if current_user && current_password
+ "#{current_user}:#{current_password}"
+ elsif current_user && !current_password
+ "#{current_user}"
+ end
+ end
+ end
+
+ ##
+ # The userinfo component for this URI, normalized.
+ #
+ # @return [String] The userinfo component, normalized.
+ def normalized_userinfo
+ return nil unless self.userinfo
+ return @normalized_userinfo unless @normalized_userinfo == NONE
+ @normalized_userinfo = begin
+ current_user = self.normalized_user
+ current_password = self.normalized_password
+ if !current_user && !current_password
+ nil
+ elsif current_user && current_password
+ "#{current_user}:#{current_password}".dup
+ elsif current_user && !current_password
+ "#{current_user}".dup
+ end
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_userinfo)
+ @normalized_userinfo
+ end
+
+ ##
+ # Sets the userinfo component for this URI.
+ #
+ # @param [String, #to_str] new_userinfo The new userinfo component.
+ def userinfo=(new_userinfo)
+ if new_userinfo && !new_userinfo.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_userinfo.class} into String."
+ end
+ new_user, new_password = if new_userinfo
+ [
+ new_userinfo.to_str.strip[/^(.*):/, 1],
+ new_userinfo.to_str.strip[/:(.*)$/, 1]
+ ]
+ else
+ [nil, nil]
+ end
+
+ # Password assigned first to ensure validity in case of nil
+ self.password = new_password
+ self.user = new_user
+
+ # Reset dependent values
+ @authority = nil
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # The host component for this URI.
+ #
+ # @return [String] The host component.
+ attr_reader :host
+
+ ##
+ # The host component for this URI, normalized.
+ #
+ # @return [String] The host component, normalized.
+ def normalized_host
+ return nil unless self.host
+
+ @normalized_host ||= begin
+ if !self.host.strip.empty?
+ result = ::Addressable::IDNA.to_ascii(
+ URI.unencode_component(self.host.strip.downcase)
+ )
+ if result =~ /[^\.]\.$/
+ # Single trailing dots are unnecessary.
+ result = result[0...-1]
+ end
+ result = Addressable::URI.normalize_component(
+ result,
+ NormalizeCharacterClasses::HOST
+ )
+ result
+ else
+ EMPTY_STR.dup
+ end
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_host)
+ @normalized_host
+ end
+
+ ##
+ # Sets the host component for this URI.
+ #
+ # @param [String, #to_str] new_host The new host component.
+ def host=(new_host)
+ if new_host && !new_host.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_host.class} into String."
+ end
+ @host = new_host ? new_host.to_str : nil
+
+ # Reset dependent values
+ @authority = nil
+ @normalized_host = nil
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # This method is same as URI::Generic#host except
+ # brackets for IPv6 (and 'IPvFuture') addresses are removed.
+ #
+ # @see Addressable::URI#host
+ #
+ # @return [String] The hostname for this URI.
+ def hostname
+ v = self.host
+ /\A\[(.*)\]\z/ =~ v ? $1 : v
+ end
+
+ ##
+ # This method is same as URI::Generic#host= except
+ # the argument can be a bare IPv6 address (or 'IPvFuture').
+ #
+ # @see Addressable::URI#host=
+ #
+ # @param [String, #to_str] new_hostname The new hostname for this URI.
+ def hostname=(new_hostname)
+ if new_hostname &&
+ (new_hostname.respond_to?(:ipv4?) || new_hostname.respond_to?(:ipv6?))
+ new_hostname = new_hostname.to_s
+ elsif new_hostname && !new_hostname.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_hostname.class} into String."
+ end
+ v = new_hostname ? new_hostname.to_str : nil
+ v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
+ self.host = v
+ end
+
+ ##
+ # Returns the top-level domain for this host.
+ #
+ # @example
+ # Addressable::URI.parse("http://www.example.co.uk").tld # => "co.uk"
+ def tld
+ PublicSuffix.parse(self.host, ignore_private: true).tld
+ end
+
+ ##
+ # Sets the top-level domain for this URI.
+ #
+ # @param [String, #to_str] new_tld The new top-level domain.
+ def tld=(new_tld)
+ replaced_tld = host.sub(/#{tld}\z/, new_tld)
+ self.host = PublicSuffix::Domain.new(replaced_tld).to_s
+ end
+
+ ##
+ # Returns the public suffix domain for this host.
+ #
+ # @example
+ # Addressable::URI.parse("http://www.example.co.uk").domain # => "example.co.uk"
+ def domain
+ PublicSuffix.domain(self.host, ignore_private: true)
+ end
+
+ ##
+ # The authority component for this URI.
+ # Combines the user, password, host, and port components.
+ #
+ # @return [String] The authority component.
+ def authority
+ self.host && @authority ||= begin
+ authority = String.new
+ if self.userinfo != nil
+ authority << "#{self.userinfo}@"
+ end
+ authority << self.host
+ if self.port != nil
+ authority << ":#{self.port}"
+ end
+ authority
+ end
+ end
+
+ ##
+ # The authority component for this URI, normalized.
+ #
+ # @return [String] The authority component, normalized.
+ def normalized_authority
+ return nil unless self.authority
+ @normalized_authority ||= begin
+ authority = String.new
+ if self.normalized_userinfo != nil
+ authority << "#{self.normalized_userinfo}@"
+ end
+ authority << self.normalized_host
+ if self.normalized_port != nil
+ authority << ":#{self.normalized_port}"
+ end
+ authority
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_authority)
+ @normalized_authority
+ end
+
+ ##
+ # Sets the authority component for this URI.
+ #
+ # @param [String, #to_str] new_authority The new authority component.
+ def authority=(new_authority)
+ if new_authority
+ if !new_authority.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_authority.class} into String."
+ end
+ new_authority = new_authority.to_str
+ new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
+ if new_userinfo
+ new_user = new_userinfo.strip[/^([^:]*):?/, 1]
+ new_password = new_userinfo.strip[/:(.*)$/, 1]
+ end
+ new_host = new_authority.sub(
+ /^([^\[\]]*)@/, EMPTY_STR
+ ).sub(
+ /:([^:@\[\]]*?)$/, EMPTY_STR
+ )
+ new_port =
+ new_authority[/:([^:@\[\]]*?)$/, 1]
+ end
+
+ # Password assigned first to ensure validity in case of nil
+ self.password = new_password
+ self.user = new_user
+ self.host = new_host
+ self.port = new_port
+
+ # Reset dependent values
+ @userinfo = nil
+ @normalized_userinfo = NONE
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # The origin for this URI, serialized to ASCII, as per
+ # RFC 6454, section 6.2.
+ #
+ # @return [String] The serialized origin.
+ def origin
+ if self.scheme && self.authority
+ if self.normalized_port
+ "#{self.normalized_scheme}://#{self.normalized_host}" +
+ ":#{self.normalized_port}"
+ else
+ "#{self.normalized_scheme}://#{self.normalized_host}"
+ end
+ else
+ "null"
+ end
+ end
+
+ ##
+ # Sets the origin for this URI, serialized to ASCII, as per
+ # RFC 6454, section 6.2. This assignment will reset the `userinfo`
+ # component.
+ #
+ # @param [String, #to_str] new_origin The new origin component.
+ def origin=(new_origin)
+ if new_origin
+ if !new_origin.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_origin.class} into String."
+ end
+ new_origin = new_origin.to_str
+ new_scheme = new_origin[/^([^:\/?#]+):\/\//, 1]
+ unless new_scheme
+ raise InvalidURIError, 'An origin cannot omit the scheme.'
+ end
+ new_host = new_origin[/:\/\/([^\/?#:]+)/, 1]
+ unless new_host
+ raise InvalidURIError, 'An origin cannot omit the host.'
+ end
+ new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
+ end
+
+ self.scheme = new_scheme
+ self.host = new_host
+ self.port = new_port
+ self.userinfo = nil
+
+ # Reset dependent values
+ @userinfo = nil
+ @normalized_userinfo = NONE
+ @authority = nil
+ @normalized_authority = nil
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ # Returns an array of known ip-based schemes. These schemes typically
+ # use a similar URI form:
+ # //:@:/
+ def self.ip_based_schemes
+ return self.port_mapping.keys
+ end
+
+ # Returns a hash of common IP-based schemes and their default port
+ # numbers. Adding new schemes to this hash, as necessary, will allow
+ # for better URI normalization.
+ def self.port_mapping
+ PORT_MAPPING
+ end
+
+ ##
+ # The port component for this URI.
+ # This is the port number actually given in the URI. This does not
+ # infer port numbers from default values.
+ #
+ # @return [Integer] The port component.
+ attr_reader :port
+
+ ##
+ # The port component for this URI, normalized.
+ #
+ # @return [Integer] The port component, normalized.
+ def normalized_port
+ return nil unless self.port
+ return @normalized_port unless @normalized_port == NONE
+ @normalized_port = begin
+ if URI.port_mapping[self.normalized_scheme] == self.port
+ nil
+ else
+ self.port
+ end
+ end
+ end
+
+ ##
+ # Sets the port component for this URI.
+ #
+ # @param [String, Integer, #to_s] new_port The new port component.
+ def port=(new_port)
+ if new_port != nil && new_port.respond_to?(:to_str)
+ new_port = Addressable::URI.unencode_component(new_port.to_str)
+ end
+
+ if new_port.respond_to?(:valid_encoding?) && !new_port.valid_encoding?
+ raise InvalidURIError, "Invalid encoding in port"
+ end
+
+ if new_port != nil && !(new_port.to_s =~ /^\d+$/)
+ raise InvalidURIError,
+ "Invalid port number: #{new_port.inspect}"
+ end
+
+ @port = new_port.to_s.to_i
+ @port = nil if @port == 0
+
+ # Reset dependent values
+ @authority = nil
+ @normalized_port = NONE
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # The inferred port component for this URI.
+ # This method will normalize to the default port for the URI's scheme if
+ # the port isn't explicitly specified in the URI.
+ #
+ # @return [Integer] The inferred port component.
+ def inferred_port
+ if self.port.to_i == 0
+ self.default_port
+ else
+ self.port.to_i
+ end
+ end
+
+ ##
+ # The default port for this URI's scheme.
+ # This method will always returns the default port for the URI's scheme
+ # regardless of the presence of an explicit port in the URI.
+ #
+ # @return [Integer] The default port.
+ def default_port
+ URI.port_mapping[self.scheme.strip.downcase] if self.scheme
+ end
+
+ ##
+ # The combination of components that represent a site.
+ # Combines the scheme, user, password, host, and port components.
+ # Primarily useful for HTTP and HTTPS.
+ #
+ # For example, "http://example.com/path?query" would have a
+ # site value of "http://example.com".
+ #
+ # @return [String] The components that identify a site.
+ def site
+ (self.scheme || self.authority) && @site ||= begin
+ site_string = "".dup
+ site_string << "#{self.scheme}:" if self.scheme != nil
+ site_string << "//#{self.authority}" if self.authority != nil
+ site_string
+ end
+ end
+
+ ##
+ # The normalized combination of components that represent a site.
+ # Combines the scheme, user, password, host, and port components.
+ # Primarily useful for HTTP and HTTPS.
+ #
+ # For example, "http://example.com/path?query" would have a
+ # site value of "http://example.com".
+ #
+ # @return [String] The normalized components that identify a site.
+ def normalized_site
+ return nil unless self.site
+ @normalized_site ||= begin
+ site_string = "".dup
+ if self.normalized_scheme != nil
+ site_string << "#{self.normalized_scheme}:"
+ end
+ if self.normalized_authority != nil
+ site_string << "//#{self.normalized_authority}"
+ end
+ site_string
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_site)
+ @normalized_site
+ end
+
+ ##
+ # Sets the site value for this URI.
+ #
+ # @param [String, #to_str] new_site The new site value.
+ def site=(new_site)
+ if new_site
+ if !new_site.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_site.class} into String."
+ end
+ new_site = new_site.to_str
+ # These two regular expressions derived from the primary parsing
+ # expression
+ self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1]
+ self.authority = new_site[
+ /^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1
+ ]
+ else
+ self.scheme = nil
+ self.authority = nil
+ end
+ end
+
+ ##
+ # The path component for this URI.
+ #
+ # @return [String] The path component.
+ attr_reader :path
+
+ NORMPATH = /^(?!\/)[^\/:]*:.*$/
+ ##
+ # The path component for this URI, normalized.
+ #
+ # @return [String] The path component, normalized.
+ def normalized_path
+ @normalized_path ||= begin
+ path = self.path.to_s
+ if self.scheme == nil && path =~ NORMPATH
+ # Relative paths with colons in the first segment are ambiguous.
+ path = path.sub(":", "%2F")
+ end
+ # String#split(delimiter, -1) uses the more strict splitting behavior
+ # found by default in Python.
+ result = path.strip.split(SLASH, -1).map do |segment|
+ Addressable::URI.normalize_component(
+ segment,
+ Addressable::URI::NormalizeCharacterClasses::PCHAR
+ )
+ end.join(SLASH)
+
+ result = URI.normalize_path(result)
+ if result.empty? &&
+ ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
+ result = SLASH.dup
+ end
+ result
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_path)
+ @normalized_path
+ end
+
+ ##
+ # Sets the path component for this URI.
+ #
+ # @param [String, #to_str] new_path The new path component.
+ def path=(new_path)
+ if new_path && !new_path.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_path.class} into String."
+ end
+ @path = (new_path || EMPTY_STR).to_str
+ if !@path.empty? && @path[0..0] != SLASH && host != nil
+ @path = "/#{@path}"
+ end
+
+ # Reset dependent values
+ @normalized_path = nil
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # The basename, if any, of the file in the path component.
+ #
+ # @return [String] The path's basename.
+ def basename
+ # Path cannot be nil
+ return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR)
+ end
+
+ ##
+ # The extname, if any, of the file in the path component.
+ # Empty string if there is no extension.
+ #
+ # @return [String] The path's extname.
+ def extname
+ return nil unless self.path
+ return File.extname(self.basename)
+ end
+
+ ##
+ # The query component for this URI.
+ #
+ # @return [String] The query component.
+ attr_reader :query
+
+ ##
+ # The query component for this URI, normalized.
+ #
+ # @return [String] The query component, normalized.
+ def normalized_query(*flags)
+ return nil unless self.query
+ return @normalized_query unless @normalized_query == NONE
+ @normalized_query = begin
+ modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
+ # Make sure possible key-value pair delimiters are escaped.
+ modified_query_class.sub!("\\&", "").sub!("\\;", "")
+ pairs = (query || "").split("&", -1)
+ pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
+ pairs.sort! if flags.include?(:sorted)
+ component = pairs.map do |pair|
+ Addressable::URI.normalize_component(
+ pair,
+ Addressable::URI::NormalizeCharacterClasses::QUERY,
+ "+"
+ )
+ end.join("&")
+ component == "" ? nil : component
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_query)
+ @normalized_query
+ end
+
+ ##
+ # Sets the query component for this URI.
+ #
+ # @param [String, #to_str] new_query The new query component.
+ def query=(new_query)
+ if new_query && !new_query.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_query.class} into String."
+ end
+ @query = new_query ? new_query.to_str : nil
+
+ # Reset dependent values
+ @normalized_query = NONE
+ remove_composite_values
+ end
+
+ ##
+ # Converts the query component to a Hash value.
+ #
+ # @param [Class] return_type The return type desired. Value must be either
+ # `Hash` or `Array`.
+ #
+ # @return [Hash, Array, nil] The query string parsed as a Hash or Array
+ # or nil if the query string is blank.
+ #
+ # @example
+ # Addressable::URI.parse("?one=1&two=2&three=3").query_values
+ # #=> {"one" => "1", "two" => "2", "three" => "3"}
+ # Addressable::URI.parse("?one=two&one=three").query_values(Array)
+ # #=> [["one", "two"], ["one", "three"]]
+ # Addressable::URI.parse("?one=two&one=three").query_values(Hash)
+ # #=> {"one" => "three"}
+ # Addressable::URI.parse("?").query_values
+ # #=> {}
+ # Addressable::URI.parse("").query_values
+ # #=> nil
+ def query_values(return_type=Hash)
+ empty_accumulator = Array == return_type ? [] : {}
+ if return_type != Hash && return_type != Array
+ raise ArgumentError, "Invalid return type. Must be Hash or Array."
+ end
+ return nil if self.query == nil
+ split_query = self.query.split("&").map do |pair|
+ pair.split("=", 2) if pair && !pair.empty?
+ end.compact
+ return split_query.inject(empty_accumulator.dup) do |accu, pair|
+ # I'd rather use key/value identifiers instead of array lookups,
+ # but in this case I really want to maintain the exact pair structure,
+ # so it's best to make all changes in-place.
+ pair[0] = URI.unencode_component(pair[0])
+ if pair[1].respond_to?(:to_str)
+ value = pair[1].to_str
+ # I loathe the fact that I have to do this. Stupid HTML 4.01.
+ # Treating '+' as a space was just an unbelievably bad idea.
+ # There was nothing wrong with '%20'!
+ # If it ain't broke, don't fix it!
+ value = value.tr("+", " ") if ["http", "https", nil].include?(scheme)
+ pair[1] = URI.unencode_component(value)
+ end
+ if return_type == Hash
+ accu[pair[0]] = pair[1]
+ else
+ accu << pair
+ end
+ accu
+ end
+ end
+
+ ##
+ # Sets the query component for this URI from a Hash object.
+ # An empty Hash or Array will result in an empty query string.
+ #
+ # @param [Hash, #to_hash, Array] new_query_values The new query values.
+ #
+ # @example
+ # uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
+ # uri.query
+ # # => "a=a&b=c&b=d&b=e"
+ # uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
+ # uri.query
+ # # => "a=a&b=c&b=d&b=e"
+ # uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
+ # uri.query
+ # # => "a=a&b=c&b=d&b=e"
+ # uri.query_values = [['flag'], ['key', 'value']]
+ # uri.query
+ # # => "flag&key=value"
+ def query_values=(new_query_values)
+ if new_query_values == nil
+ self.query = nil
+ return nil
+ end
+
+ if !new_query_values.is_a?(Array)
+ if !new_query_values.respond_to?(:to_hash)
+ raise TypeError,
+ "Can't convert #{new_query_values.class} into Hash."
+ end
+ new_query_values = new_query_values.to_hash
+ new_query_values = new_query_values.map do |key, value|
+ key = key.to_s if key.kind_of?(Symbol)
+ [key, value]
+ end
+ # Useful default for OAuth and caching.
+ # Only to be used for non-Array inputs. Arrays should preserve order.
+ new_query_values.sort!
+ end
+
+ # new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
+ buffer = "".dup
+ new_query_values.each do |key, value|
+ encoded_key = URI.encode_component(
+ key, CharacterClassesRegexps::UNRESERVED
+ )
+ if value == nil
+ buffer << "#{encoded_key}&"
+ elsif value.kind_of?(Array)
+ value.each do |sub_value|
+ encoded_value = URI.encode_component(
+ sub_value, CharacterClassesRegexps::UNRESERVED
+ )
+ buffer << "#{encoded_key}=#{encoded_value}&"
+ end
+ else
+ encoded_value = URI.encode_component(
+ value, CharacterClassesRegexps::UNRESERVED
+ )
+ buffer << "#{encoded_key}=#{encoded_value}&"
+ end
+ end
+ self.query = buffer.chop
+ end
+
+ ##
+ # The HTTP request URI for this URI. This is the path and the
+ # query string.
+ #
+ # @return [String] The request URI required for an HTTP request.
+ def request_uri
+ return nil if self.absolute? && self.scheme !~ /^https?$/i
+ return (
+ (!self.path.empty? ? self.path : SLASH) +
+ (self.query ? "?#{self.query}" : EMPTY_STR)
+ )
+ end
+
+ ##
+ # Sets the HTTP request URI for this URI.
+ #
+ # @param [String, #to_str] new_request_uri The new HTTP request URI.
+ def request_uri=(new_request_uri)
+ if !new_request_uri.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_request_uri.class} into String."
+ end
+ if self.absolute? && self.scheme !~ /^https?$/i
+ raise InvalidURIError,
+ "Cannot set an HTTP request URI for a non-HTTP URI."
+ end
+ new_request_uri = new_request_uri.to_str
+ path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1]
+ query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
+ path_component = path_component.to_s
+ path_component = (!path_component.empty? ? path_component : SLASH)
+ self.path = path_component
+ self.query = query_component
+
+ # Reset dependent values
+ remove_composite_values
+ end
+
+ ##
+ # The fragment component for this URI.
+ #
+ # @return [String] The fragment component.
+ attr_reader :fragment
+
+ ##
+ # The fragment component for this URI, normalized.
+ #
+ # @return [String] The fragment component, normalized.
+ def normalized_fragment
+ return nil unless self.fragment
+ return @normalized_fragment unless @normalized_fragment == NONE
+ @normalized_fragment = begin
+ component = Addressable::URI.normalize_component(
+ self.fragment,
+ Addressable::URI::NormalizeCharacterClasses::FRAGMENT
+ )
+ component == "" ? nil : component
+ end
+ # All normalized values should be UTF-8
+ force_utf8_encoding_if_needed(@normalized_fragment)
+ @normalized_fragment
+ end
+
+ ##
+ # Sets the fragment component for this URI.
+ #
+ # @param [String, #to_str] new_fragment The new fragment component.
+ def fragment=(new_fragment)
+ if new_fragment && !new_fragment.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{new_fragment.class} into String."
+ end
+ @fragment = new_fragment ? new_fragment.to_str : nil
+
+ # Reset dependent values
+ @normalized_fragment = NONE
+ remove_composite_values
+
+ # Ensure we haven't created an invalid URI
+ validate()
+ end
+
+ ##
+ # Determines if the scheme indicates an IP-based protocol.
+ #
+ # @return [TrueClass, FalseClass]
+ # true if the scheme indicates an IP-based protocol.
+ # false otherwise.
+ def ip_based?
+ if self.scheme
+ return URI.ip_based_schemes.include?(
+ self.scheme.strip.downcase)
+ end
+ return false
+ end
+
+ ##
+ # Determines if the URI is relative.
+ #
+ # @return [TrueClass, FalseClass]
+ # true if the URI is relative. false
+ # otherwise.
+ def relative?
+ return self.scheme.nil?
+ end
+
+ ##
+ # Determines if the URI is absolute.
+ #
+ # @return [TrueClass, FalseClass]
+ # true if the URI is absolute. false
+ # otherwise.
+ def absolute?
+ return !relative?
+ end
+
+ ##
+ # Joins two URIs together.
+ #
+ # @param [String, Addressable::URI, #to_str] The URI to join with.
+ #
+ # @return [Addressable::URI] The joined URI.
+ def join(uri)
+ if !uri.respond_to?(:to_str)
+ raise TypeError, "Can't convert #{uri.class} into String."
+ end
+ if !uri.kind_of?(URI)
+ # Otherwise, convert to a String, then parse.
+ uri = URI.parse(uri.to_str)
+ end
+ if uri.to_s.empty?
+ return self.dup
+ end
+
+ joined_scheme = nil
+ joined_user = nil
+ joined_password = nil
+ joined_host = nil
+ joined_port = nil
+ joined_path = nil
+ joined_query = nil
+ joined_fragment = nil
+
+ # Section 5.2.2 of RFC 3986
+ if uri.scheme != nil
+ joined_scheme = uri.scheme
+ joined_user = uri.user
+ joined_password = uri.password
+ joined_host = uri.host
+ joined_port = uri.port
+ joined_path = URI.normalize_path(uri.path)
+ joined_query = uri.query
+ else
+ if uri.authority != nil
+ joined_user = uri.user
+ joined_password = uri.password
+ joined_host = uri.host
+ joined_port = uri.port
+ joined_path = URI.normalize_path(uri.path)
+ joined_query = uri.query
+ else
+ if uri.path == nil || uri.path.empty?
+ joined_path = self.path
+ if uri.query != nil
+ joined_query = uri.query
+ else
+ joined_query = self.query
+ end
+ else
+ if uri.path[0..0] == SLASH
+ joined_path = URI.normalize_path(uri.path)
+ else
+ base_path = self.path.dup
+ base_path = EMPTY_STR if base_path == nil
+ base_path = URI.normalize_path(base_path)
+
+ # Section 5.2.3 of RFC 3986
+ #
+ # Removes the right-most path segment from the base path.
+ if base_path.include?(SLASH)
+ base_path.sub!(/\/[^\/]+$/, SLASH)
+ else
+ base_path = EMPTY_STR
+ end
+
+ # If the base path is empty and an authority segment has been
+ # defined, use a base path of SLASH
+ if base_path.empty? && self.authority != nil
+ base_path = SLASH
+ end
+
+ joined_path = URI.normalize_path(base_path + uri.path)
+ end
+ joined_query = uri.query
+ end
+ joined_user = self.user
+ joined_password = self.password
+ joined_host = self.host
+ joined_port = self.port
+ end
+ joined_scheme = self.scheme
+ end
+ joined_fragment = uri.fragment
+
+ return self.class.new(
+ :scheme => joined_scheme,
+ :user => joined_user,
+ :password => joined_password,
+ :host => joined_host,
+ :port => joined_port,
+ :path => joined_path,
+ :query => joined_query,
+ :fragment => joined_fragment
+ )
+ end
+ alias_method :+, :join
+
+ ##
+ # Destructive form of join.
+ #
+ # @param [String, Addressable::URI, #to_str] The URI to join with.
+ #
+ # @return [Addressable::URI] The joined URI.
+ #
+ # @see Addressable::URI#join
+ def join!(uri)
+ replace_self(self.join(uri))
+ end
+
+ ##
+ # Merges a URI with a Hash of components.
+ # This method has different behavior from join. Any
+ # components present in the hash parameter will override the
+ # original components. The path component is not treated specially.
+ #
+ # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
+ #
+ # @return [Addressable::URI] The merged URI.
+ #
+ # @see Hash#merge
+ def merge(hash)
+ unless hash.respond_to?(:to_hash)
+ raise TypeError, "Can't convert #{hash.class} into Hash."
+ end
+ hash = hash.to_hash
+
+ if hash.has_key?(:authority)
+ if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
+ raise ArgumentError,
+ "Cannot specify both an authority and any of the components " +
+ "within the authority."
+ end
+ end
+ if hash.has_key?(:userinfo)
+ if (hash.keys & [:user, :password]).any?
+ raise ArgumentError,
+ "Cannot specify both a userinfo and either the user or password."
+ end
+ end
+
+ uri = self.class.new
+ uri.defer_validation do
+ # Bunch of crazy logic required because of the composite components
+ # like userinfo and authority.
+ uri.scheme =
+ hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
+ if hash.has_key?(:authority)
+ uri.authority =
+ hash.has_key?(:authority) ? hash[:authority] : self.authority
+ end
+ if hash.has_key?(:userinfo)
+ uri.userinfo =
+ hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
+ end
+ if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
+ uri.user =
+ hash.has_key?(:user) ? hash[:user] : self.user
+ uri.password =
+ hash.has_key?(:password) ? hash[:password] : self.password
+ end
+ if !hash.has_key?(:authority)
+ uri.host =
+ hash.has_key?(:host) ? hash[:host] : self.host
+ uri.port =
+ hash.has_key?(:port) ? hash[:port] : self.port
+ end
+ uri.path =
+ hash.has_key?(:path) ? hash[:path] : self.path
+ uri.query =
+ hash.has_key?(:query) ? hash[:query] : self.query
+ uri.fragment =
+ hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
+ end
+
+ return uri
+ end
+
+ ##
+ # Destructive form of merge.
+ #
+ # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
+ #
+ # @return [Addressable::URI] The merged URI.
+ #
+ # @see Addressable::URI#merge
+ def merge!(uri)
+ replace_self(self.merge(uri))
+ end
+
+ ##
+ # Returns the shortest normalized relative form of this URI that uses the
+ # supplied URI as a base for resolution. Returns an absolute URI if
+ # necessary. This is effectively the opposite of route_to.
+ #
+ # @param [String, Addressable::URI, #to_str] uri The URI to route from.
+ #
+ # @return [Addressable::URI]
+ # The normalized relative URI that is equivalent to the original URI.
+ def route_from(uri)
+ uri = URI.parse(uri).normalize
+ normalized_self = self.normalize
+ if normalized_self.relative?
+ raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
+ end
+ if uri.relative?
+ raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
+ end
+ if normalized_self == uri
+ return Addressable::URI.parse("##{normalized_self.fragment}")
+ end
+ components = normalized_self.to_hash
+ if normalized_self.scheme == uri.scheme
+ components[:scheme] = nil
+ if normalized_self.authority == uri.authority
+ components[:user] = nil
+ components[:password] = nil
+ components[:host] = nil
+ components[:port] = nil
+ if normalized_self.path == uri.path
+ components[:path] = nil
+ if normalized_self.query == uri.query
+ components[:query] = nil
+ end
+ else
+ if uri.path != SLASH and components[:path]
+ self_splitted_path = split_path(components[:path])
+ uri_splitted_path = split_path(uri.path)
+ self_dir = self_splitted_path.shift
+ uri_dir = uri_splitted_path.shift
+ while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
+ self_dir = self_splitted_path.shift
+ uri_dir = uri_splitted_path.shift
+ end
+ components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
+ end
+ end
+ end
+ end
+ # Avoid network-path references.
+ if components[:host] != nil
+ components[:scheme] = normalized_self.scheme
+ end
+ return Addressable::URI.new(
+ :scheme => components[:scheme],
+ :user => components[:user],
+ :password => components[:password],
+ :host => components[:host],
+ :port => components[:port],
+ :path => components[:path],
+ :query => components[:query],
+ :fragment => components[:fragment]
+ )
+ end
+
+ ##
+ # Returns the shortest normalized relative form of the supplied URI that
+ # uses this URI as a base for resolution. Returns an absolute URI if
+ # necessary. This is effectively the opposite of route_from.
+ #
+ # @param [String, Addressable::URI, #to_str] uri The URI to route to.
+ #
+ # @return [Addressable::URI]
+ # The normalized relative URI that is equivalent to the supplied URI.
+ def route_to(uri)
+ return URI.parse(uri).route_from(self)
+ end
+
+ ##
+ # Returns a normalized URI object.
+ #
+ # NOTE: This method does not attempt to fully conform to specifications.
+ # It exists largely to correct other people's failures to read the
+ # specifications, and also to deal with caching issues since several
+ # different URIs may represent the same resource and should not be
+ # cached multiple times.
+ #
+ # @return [Addressable::URI] The normalized URI.
+ def normalize
+ # This is a special exception for the frequently misused feed
+ # URI scheme.
+ if normalized_scheme == "feed"
+ if self.to_s =~ /^feed:\/*http:\/*/
+ return URI.parse(
+ self.to_s[/^feed:\/*(http:\/*.*)/, 1]
+ ).normalize
+ end
+ end
+
+ return self.class.new(
+ :scheme => normalized_scheme,
+ :authority => normalized_authority,
+ :path => normalized_path,
+ :query => normalized_query,
+ :fragment => normalized_fragment
+ )
+ end
+
+ ##
+ # Destructively normalizes this URI object.
+ #
+ # @return [Addressable::URI] The normalized URI.
+ #
+ # @see Addressable::URI#normalize
+ def normalize!
+ replace_self(self.normalize)
+ end
+
+ ##
+ # Creates a URI suitable for display to users. If semantic attacks are
+ # likely, the application should try to detect these and warn the user.
+ # See RFC 3986,
+ # section 7.6 for more information.
+ #
+ # @return [Addressable::URI] A URI suitable for display purposes.
+ def display_uri
+ display_uri = self.normalize
+ display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
+ return display_uri
+ end
+
+ ##
+ # Returns true if the URI objects are equal. This method
+ # normalizes both URIs before doing the comparison, and allows comparison
+ # against Strings.
+ #
+ # @param [Object] uri The URI to compare.
+ #
+ # @return [TrueClass, FalseClass]
+ # true if the URIs are equivalent, false
+ # otherwise.
+ def ===(uri)
+ if uri.respond_to?(:normalize)
+ uri_string = uri.normalize.to_s
+ else
+ begin
+ uri_string = ::Addressable::URI.parse(uri).normalize.to_s
+ rescue InvalidURIError, TypeError
+ return false
+ end
+ end
+ return self.normalize.to_s == uri_string
+ end
+
+ ##
+ # Returns true if the URI objects are equal. This method
+ # normalizes both URIs before doing the comparison.
+ #
+ # @param [Object] uri The URI to compare.
+ #
+ # @return [TrueClass, FalseClass]
+ # true if the URIs are equivalent, false
+ # otherwise.
+ def ==(uri)
+ return false unless uri.kind_of?(URI)
+ return self.normalize.to_s == uri.normalize.to_s
+ end
+
+ ##
+ # Returns true if the URI objects are equal. This method
+ # does NOT normalize either URI before doing the comparison.
+ #
+ # @param [Object] uri The URI to compare.
+ #
+ # @return [TrueClass, FalseClass]
+ # true if the URIs are equivalent, false
+ # otherwise.
+ def eql?(uri)
+ return false unless uri.kind_of?(URI)
+ return self.to_s == uri.to_s
+ end
+
+ ##
+ # A hash value that will make a URI equivalent to its normalized
+ # form.
+ #
+ # @return [Integer] A hash of the URI.
+ def hash
+ @hash ||= self.to_s.hash * -1
+ end
+
+ ##
+ # Clones the URI object.
+ #
+ # @return [Addressable::URI] The cloned URI.
+ def dup
+ duplicated_uri = self.class.new(
+ :scheme => self.scheme ? self.scheme.dup : nil,
+ :user => self.user ? self.user.dup : nil,
+ :password => self.password ? self.password.dup : nil,
+ :host => self.host ? self.host.dup : nil,
+ :port => self.port,
+ :path => self.path ? self.path.dup : nil,
+ :query => self.query ? self.query.dup : nil,
+ :fragment => self.fragment ? self.fragment.dup : nil
+ )
+ return duplicated_uri
+ end
+
+ ##
+ # Omits components from a URI.
+ #
+ # @param [Symbol] *components The components to be omitted.
+ #
+ # @return [Addressable::URI] The URI with components omitted.
+ #
+ # @example
+ # uri = Addressable::URI.parse("http://example.com/path?query")
+ # #=> #true if empty, false otherwise.
+ def empty?
+ return self.to_s.empty?
+ end
+
+ ##
+ # Converts the URI to a String.
+ #
+ # @return [String] The URI's String representation.
+ def to_s
+ if self.scheme == nil && self.path != nil && !self.path.empty? &&
+ self.path =~ NORMPATH
+ raise InvalidURIError,
+ "Cannot assemble URI string with ambiguous path: '#{self.path}'"
+ end
+ @uri_string ||= begin
+ uri_string = String.new
+ uri_string << "#{self.scheme}:" if self.scheme != nil
+ uri_string << "//#{self.authority}" if self.authority != nil
+ uri_string << self.path.to_s
+ uri_string << "?#{self.query}" if self.query != nil
+ uri_string << "##{self.fragment}" if self.fragment != nil
+ uri_string.force_encoding(Encoding::UTF_8)
+ uri_string
+ end
+ end
+
+ ##
+ # URI's are glorified Strings. Allow implicit conversion.
+ alias_method :to_str, :to_s
+
+ ##
+ # Returns a Hash of the URI components.
+ #
+ # @return [Hash] The URI as a Hash of components.
+ def to_hash
+ return {
+ :scheme => self.scheme,
+ :user => self.user,
+ :password => self.password,
+ :host => self.host,
+ :port => self.port,
+ :path => self.path,
+ :query => self.query,
+ :fragment => self.fragment
+ }
+ end
+
+ ##
+ # Returns a String representation of the URI object's state.
+ #
+ # @return [String] The URI object's state, as a String.
+ def inspect
+ sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
+ end
+
+ ##
+ # This method allows you to make several changes to a URI simultaneously,
+ # which separately would cause validation errors, but in conjunction,
+ # are valid. The URI will be revalidated as soon as the entire block has
+ # been executed.
+ #
+ # @param [Proc] block
+ # A set of operations to perform on a given URI.
+ def defer_validation
+ raise LocalJumpError, "No block given." unless block_given?
+ @validation_deferred = true
+ yield
+ @validation_deferred = false
+ validate
+ ensure
+ @validation_deferred = false
+ end
+
+ def encode_with(coder)
+ instance_variables.each do |ivar|
+ value = instance_variable_get(ivar)
+ if value != NONE
+ key = ivar.to_s.slice(1..-1)
+ coder[key] = value
+ end
+ end
+ nil
+ end
+
+ def init_with(coder)
+ reset_ivs
+ coder.map.each do |key, value|
+ instance_variable_set("@#{key}", value)
+ end
+ nil
+ end
+
+ protected
+ SELF_REF = '.'
+ PARENT = '..'
+
+ RULE_2A = /\/\.\/|\/\.$/
+ RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
+ RULE_2D = /^\.\.?\/?/
+ RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
+
+ ##
+ # Resolves paths to their simplest form.
+ #
+ # @param [String] path The path to normalize.
+ #
+ # @return [String] The normalized path.
+ def self.normalize_path(path)
+ # Section 5.2.4 of RFC 3986
+
+ return if path.nil?
+ normalized_path = path.dup
+ loop do
+ mod ||= normalized_path.gsub!(RULE_2A, SLASH)
+
+ pair = normalized_path.match(RULE_2B_2C)
+ if pair
+ parent = pair[1]
+ current = pair[2]
+ else
+ parent = nil
+ current = nil
+ end
+
+ regexp = "/#{Regexp.escape(parent.to_s)}/\\.\\./|"
+ regexp += "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
+
+ if pair && ((parent != SELF_REF && parent != PARENT) ||
+ (current != SELF_REF && current != PARENT))
+ mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
+ end
+
+ mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
+ # Non-standard, removes prefixed dotted segments from path.
+ mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
+ break if mod.nil?
+ end
+
+ normalized_path
+ end
+
+ ##
+ # Ensures that the URI is valid.
+ def validate
+ return if !!@validation_deferred
+ if self.scheme != nil && self.ip_based? &&
+ (self.host == nil || self.host.empty?) &&
+ (self.path == nil || self.path.empty?)
+ raise InvalidURIError,
+ "Absolute URI missing hierarchical segment: '#{self.to_s}'"
+ end
+ if self.host == nil
+ if self.port != nil ||
+ self.user != nil ||
+ self.password != nil
+ raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
+ end
+ end
+ if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH &&
+ self.authority != nil
+ raise InvalidURIError,
+ "Cannot have a relative path with an authority set: '#{self.to_s}'"
+ end
+ if self.path != nil && !self.path.empty? &&
+ self.path[0..1] == SLASH + SLASH && self.authority == nil
+ raise InvalidURIError,
+ "Cannot have a path with two leading slashes " +
+ "without an authority set: '#{self.to_s}'"
+ end
+ unreserved = CharacterClasses::UNRESERVED
+ sub_delims = CharacterClasses::SUB_DELIMS
+ if !self.host.nil? && (self.host =~ /[<>{}\/\\\?\#\@"[[:space:]]]/ ||
+ (self.host[/^\[(.*)\]$/, 1] != nil && self.host[/^\[(.*)\]$/, 1] !~
+ Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
+ raise InvalidURIError, "Invalid character in host: '#{self.host.to_s}'"
+ end
+ return nil
+ end
+
+ ##
+ # Replaces the internal state of self with the specified URI's state.
+ # Used in destructive operations to avoid massive code repetition.
+ #
+ # @param [Addressable::URI] uri The URI to replace self with.
+ #
+ # @return [Addressable::URI] self.
+ def replace_self(uri)
+ # Reset dependent values
+ reset_ivs
+
+ @scheme = uri.scheme
+ @user = uri.user
+ @password = uri.password
+ @host = uri.host
+ @port = uri.port
+ @path = uri.path
+ @query = uri.query
+ @fragment = uri.fragment
+ return self
+ end
+
+ ##
+ # Splits path string with "/" (slash).
+ # It is considered that there is empty string after last slash when
+ # path ends with slash.
+ #
+ # @param [String] path The path to split.
+ #
+ # @return [Array"a"*1_000_000_000:
+
+```ruby
+require 'benchmark'
+puts Benchmark.measure { "a"*1_000_000_000 }
+```
+
+On my machine (OSX 10.8.3 on i5 1.7 GHz) this generates:
+
+```
+0.350000 0.400000 0.750000 ( 0.835234)
+```
+
+This report shows the user CPU time, system CPU time, the total time (sum of user CPU time, system CPU time, children's user CPU time, and children's system CPU time), and the elapsed real time. The unit of time is seconds.
+
+Do some experiments sequentially using the #bm method:
+
+```ruby
+require 'benchmark'
+n = 5000000
+Benchmark.bm do |x|
+ x.report { for i in 1..n; a = "1"; end }
+ x.report { n.times do ; a = "1"; end }
+ x.report { 1.upto(n) do ; a = "1"; end }
+end
+```
+
+The result:
+
+```
+ user system total real
+1.010000 0.000000 1.010000 ( 1.014479)
+1.000000 0.000000 1.000000 ( 0.998261)
+0.980000 0.000000 0.980000 ( 0.981335)
+```
+
+Continuing the previous example, put a label in each report:
+
+```ruby
+require 'benchmark'
+n = 5000000
+Benchmark.bm(7) do |x|
+ x.report("for:") { for i in 1..n; a = "1"; end }
+ x.report("times:") { n.times do ; a = "1"; end }
+ x.report("upto:") { 1.upto(n) do ; a = "1"; end }
+end
+```
+
+The result:
+
+```
+ user system total real
+for: 1.010000 0.000000 1.010000 ( 1.015688)
+times: 1.000000 0.000000 1.000000 ( 1.003611)
+upto: 1.030000 0.000000 1.030000 ( 1.028098)
+```
+
+The times for some benchmarks depend on the order in which items are run. These differences are due to the cost of memory allocation and garbage collection. To avoid these discrepancies, the #bmbm method is provided. For example, to compare ways to sort an array of floats:
+
+```ruby
+require 'benchmark'
+array = (1..1000000).map { rand }
+Benchmark.bmbm do |x|
+ x.report("sort!") { array.dup.sort! }
+ x.report("sort") { array.dup.sort }
+end
+```
+
+The result:
+
+```
+Rehearsal -----------------------------------------
+sort! 1.490000 0.010000 1.500000 ( 1.490520)
+sort 1.460000 0.000000 1.460000 ( 1.463025)
+-------------------------------- total: 2.960000sec
+ user system total real
+sort! 1.460000 0.000000 1.460000 ( 1.460465)
+sort 1.450000 0.010000 1.460000 ( 1.448327)
+```
+
+Report statistics of sequential experiments with unique labels, using the #benchmark method:
+
+```ruby
+require 'benchmark'
+include Benchmark # we need the CAPTION and FORMAT constants
+n = 5000000
+Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
+ tf = x.report("for:") { for i in 1..n; a = "1"; end }
+ tt = x.report("times:") { n.times do ; a = "1"; end }
+ tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
+ [tf+tt+tu, (tf+tt+tu)/3]
+end
+```
+
+The result:
+
+```
+ user system total real
+for: 0.950000 0.000000 0.950000 ( 0.952039)
+times: 0.980000 0.000000 0.980000 ( 0.984938)
+upto: 0.950000 0.000000 0.950000 ( 0.946787)
+>total: 2.880000 0.000000 2.880000 ( 2.883764)
+>avg: 0.960000 0.000000 0.960000 ( 0.961255)
+```
+
+## Development
+
+After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
+
+To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
+
+## Contributing
+
+Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/benchmark.
diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/Rakefile b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/Rakefile
new file mode 100644
index 000000000..8830e057a
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/Rakefile
@@ -0,0 +1,8 @@
+require "bundler/gem_tasks"
+require "rake/testtask"
+
+Rake::TestTask.new(:test) do |t|
+ t.test_files = FileList["test/**/test_*.rb"]
+end
+
+task :default => :test
diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/benchmark.gemspec b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/benchmark.gemspec
new file mode 100644
index 000000000..35deff8d1
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/benchmark.gemspec
@@ -0,0 +1,32 @@
+name = File.basename(__FILE__, ".gemspec")
+version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir|
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
+ /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
+ end rescue nil
+end
+
+Gem::Specification.new do |spec|
+ spec.name = name
+ spec.version = version
+ spec.authors = ["Yukihiro Matsumoto"]
+ spec.email = ["matz@ruby-lang.org"]
+
+ spec.summary = %q{a performance benchmarking library}
+ spec.description = spec.summary
+ spec.homepage = "https://github.com/ruby/benchmark"
+ spec.licenses = ["Ruby", "BSD-2-Clause"]
+
+ spec.required_ruby_version = ">= 2.1.0"
+
+ spec.metadata["homepage_uri"] = spec.homepage
+ spec.metadata["source_code_uri"] = spec.homepage
+
+ # Specify which files should be added to the gem when it is released.
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
+ `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
+ end
+ spec.bindir = "exe"
+ spec.executables = []
+ spec.require_paths = ["lib"]
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/bin/console b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/bin/console
new file mode 100755
index 000000000..87e7b95e1
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/bin/console
@@ -0,0 +1,14 @@
+#!/usr/bin/env ruby
+
+require "bundler/setup"
+require "benchmark"
+
+# You can add fixtures and/or initialization code here to make experimenting
+# with your gem easier. You can also use a different console, if you like.
+
+# (If you use this, don't forget to add pry to your Gemfile!)
+# require "pry"
+# Pry.start
+
+require "irb"
+IRB.start(__FILE__)
diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/bin/setup b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/bin/setup
new file mode 100755
index 000000000..dce67d860
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/bin/setup
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+set -vx
+
+bundle install
+
+# Do any other automated setup that you need to do here
diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/lib/benchmark.rb b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/lib/benchmark.rb
new file mode 100644
index 000000000..b73fd33f6
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.5.0/lib/benchmark.rb
@@ -0,0 +1,608 @@
+# frozen_string_literal: true
+#--
+# benchmark.rb - a performance benchmarking library
+#
+# $Id$
+#
+# Created by Gotoken (gotoken@notwork.org).
+#
+# Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and
+# Gavin Sinclair (editing).
+#++
+#
+# == Overview
+#
+# The Benchmark module provides methods for benchmarking Ruby code, giving
+# detailed reports on the time taken for each task.
+#
+
+# The Benchmark module provides methods to measure and report the time
+# used to execute Ruby code.
+#
+# * Measure the time to construct the string given by the expression
+# "a"*1_000_000_000:
+#
+# require 'benchmark'
+#
+# puts Benchmark.measure { "a"*1_000_000_000 }
+#
+# On my machine (OSX 10.8.3 on i5 1.7 GHz) this generates:
+#
+# 0.350000 0.400000 0.750000 ( 0.835234)
+#
+# This report shows the user CPU time, system CPU time, the total time
+# (sum of user CPU time, system CPU time, children's user CPU time,
+# and children's system CPU time), and the elapsed real time. The unit
+# of time is seconds.
+#
+# * Do some experiments sequentially using the #bm method:
+#
+# require 'benchmark'
+#
+# n = 5000000
+# Benchmark.bm do |x|
+# x.report { for i in 1..n; a = "1"; end }
+# x.report { n.times do ; a = "1"; end }
+# x.report { 1.upto(n) do ; a = "1"; end }
+# end
+#
+# The result:
+#
+# user system total real
+# 1.010000 0.000000 1.010000 ( 1.014479)
+# 1.000000 0.000000 1.000000 ( 0.998261)
+# 0.980000 0.000000 0.980000 ( 0.981335)
+#
+# * Continuing the previous example, put a label in each report:
+#
+# require 'benchmark'
+#
+# n = 5000000
+# Benchmark.bm(7) do |x|
+# x.report("for:") { for i in 1..n; a = "1"; end }
+# x.report("times:") { n.times do ; a = "1"; end }
+# x.report("upto:") { 1.upto(n) do ; a = "1"; end }
+# end
+#
+# The result:
+#
+# user system total real
+# for: 1.010000 0.000000 1.010000 ( 1.015688)
+# times: 1.000000 0.000000 1.000000 ( 1.003611)
+# upto: 1.030000 0.000000 1.030000 ( 1.028098)
+#
+# * The times for some benchmarks depend on the order in which items
+# are run. These differences are due to the cost of memory
+# allocation and garbage collection. To avoid these discrepancies,
+# the #bmbm method is provided. For example, to compare ways to
+# sort an array of floats:
+#
+# require 'benchmark'
+#
+# array = (1..1000000).map { rand }
+#
+# Benchmark.bmbm do |x|
+# x.report("sort!") { array.dup.sort! }
+# x.report("sort") { array.dup.sort }
+# end
+#
+# The result:
+#
+# Rehearsal -----------------------------------------
+# sort! 1.490000 0.010000 1.500000 ( 1.490520)
+# sort 1.460000 0.000000 1.460000 ( 1.463025)
+# -------------------------------- total: 2.960000sec
+#
+# user system total real
+# sort! 1.460000 0.000000 1.460000 ( 1.460465)
+# sort 1.450000 0.010000 1.460000 ( 1.448327)
+#
+# * Report statistics of sequential experiments with unique labels,
+# using the #benchmark method:
+#
+# require 'benchmark'
+# include Benchmark # we need the CAPTION and FORMAT constants
+#
+# n = 5000000
+# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
+# tf = x.report("for:") { for i in 1..n; a = "1"; end }
+# tt = x.report("times:") { n.times do ; a = "1"; end }
+# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
+# [tf+tt+tu, (tf+tt+tu)/3]
+# end
+#
+# The result:
+#
+# user system total real
+# for: 0.950000 0.000000 0.950000 ( 0.952039)
+# times: 0.980000 0.000000 0.980000 ( 0.984938)
+# upto: 0.950000 0.000000 0.950000 ( 0.946787)
+# >total: 2.880000 0.000000 2.880000 ( 2.883764)
+# >avg: 0.960000 0.000000 0.960000 ( 0.961255)
+
+module Benchmark
+
+ VERSION = "0.5.0"
+
+ BENCHMARK_VERSION = "2002-04-25" # :nodoc:
+
+ # Invokes the block with a Benchmark::Report object, which
+ # may be used to collect and report on the results of individual
+ # benchmark tests. Reserves +label_width+ leading spaces for
+ # labels on each line. Prints +caption+ at the top of the
+ # report, and uses +format+ to format each line.
+ # (Note: +caption+ must contain a terminating newline character,
+ # see the default Benchmark::Tms::CAPTION for an example.)
+ #
+ # Returns an array of Benchmark::Tms objects.
+ #
+ # If the block returns an array of
+ # Benchmark::Tms objects, these will be used to format
+ # additional lines of output. If +labels+ parameter are
+ # given, these are used to label these extra lines.
+ #
+ # _Note_: Other methods provide a simpler interface to this one, and are
+ # suitable for nearly all benchmarking requirements. See the examples in
+ # Benchmark, and the #bm and #bmbm methods.
+ #
+ # Example:
+ #
+ # require 'benchmark'
+ # include Benchmark # we need the CAPTION and FORMAT constants
+ #
+ # n = 5000000
+ # Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
+ # tf = x.report("for:") { for i in 1..n; a = "1"; end }
+ # tt = x.report("times:") { n.times do ; a = "1"; end }
+ # tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
+ # [tf+tt+tu, (tf+tt+tu)/3]
+ # end
+ #
+ # Generates:
+ #
+ # user system total real
+ # for: 0.970000 0.000000 0.970000 ( 0.970493)
+ # times: 0.990000 0.000000 0.990000 ( 0.989542)
+ # upto: 0.970000 0.000000 0.970000 ( 0.972854)
+ # >total: 2.930000 0.000000 2.930000 ( 2.932889)
+ # >avg: 0.976667 0.000000 0.976667 ( 0.977630)
+ #
+
+ def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report
+ sync = $stdout.sync
+ $stdout.sync = true
+ label_width ||= 0
+ label_width += 1
+ format ||= FORMAT
+ report = Report.new(label_width, format)
+ results = yield(report)
+
+ print " " * report.width + caption unless caption.empty?
+ report.list.each { |i|
+ print i.label.to_s.ljust(report.width)
+ print i.format(report.format, *format)
+ }
+
+ Array === results and results.grep(Tms).each {|t|
+ print((labels.shift || t.label || "").ljust(label_width), t.format(format))
+ }
+ report.list
+ ensure
+ $stdout.sync = sync unless sync.nil?
+ end
+
+
+ # A simple interface to the #benchmark method, #bm generates sequential
+ # reports with labels. +label_width+ and +labels+ parameters have the same
+ # meaning as for #benchmark.
+ #
+ # require 'benchmark'
+ #
+ # n = 5000000
+ # Benchmark.bm(7) do |x|
+ # x.report("for:") { for i in 1..n; a = "1"; end }
+ # x.report("times:") { n.times do ; a = "1"; end }
+ # x.report("upto:") { 1.upto(n) do ; a = "1"; end }
+ # end
+ #
+ # Generates:
+ #
+ # user system total real
+ # for: 0.960000 0.000000 0.960000 ( 0.957966)
+ # times: 0.960000 0.000000 0.960000 ( 0.960423)
+ # upto: 0.950000 0.000000 0.950000 ( 0.954864)
+ #
+
+ def bm(label_width = 0, *labels, &blk) # :yield: report
+ benchmark(CAPTION, label_width, FORMAT, *labels, &blk)
+ end
+
+
+ # Sometimes benchmark results are skewed because code executed
+ # earlier encounters different garbage collection overheads than
+ # that run later. #bmbm attempts to minimize this effect by running
+ # the tests twice, the first time as a rehearsal in order to get the
+ # runtime environment stable, the second time for
+ # real. GC.start is executed before the start of each of
+ # the real timings; the cost of this is not included in the
+ # timings. In reality, though, there's only so much that #bmbm can
+ # do, and the results are not guaranteed to be isolated from garbage
+ # collection and other effects.
+ #
+ # Because #bmbm takes two passes through the tests, it can
+ # calculate the required label width.
+ #
+ # require 'benchmark'
+ #
+ # array = (1..1000000).map { rand }
+ #
+ # Benchmark.bmbm do |x|
+ # x.report("sort!") { array.dup.sort! }
+ # x.report("sort") { array.dup.sort }
+ # end
+ #
+ # Generates:
+ #
+ # Rehearsal -----------------------------------------
+ # sort! 1.440000 0.010000 1.450000 ( 1.446833)
+ # sort 1.440000 0.000000 1.440000 ( 1.448257)
+ # -------------------------------- total: 2.890000sec
+ #
+ # user system total real
+ # sort! 1.460000 0.000000 1.460000 ( 1.458065)
+ # sort 1.450000 0.000000 1.450000 ( 1.455963)
+ #
+ # #bmbm yields a Benchmark::Job object and returns an array of
+ # Benchmark::Tms objects.
+ #
+ def bmbm(width = 0) # :yield: job
+ job = Job.new(width)
+ yield(job)
+ width = job.width + 1
+ sync = $stdout.sync
+ $stdout.sync = true
+
+ # rehearsal
+ puts 'Rehearsal '.ljust(width+CAPTION.length,'-')
+ ets = job.list.inject(Tms.new) { |sum,(label,item)|
+ print label.ljust(width)
+ res = Benchmark.measure(&item)
+ print res.format
+ sum + res
+ }.format("total: %tsec")
+ print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-')
+
+ # take
+ print ' '*width + CAPTION
+ job.list.map { |label,item|
+ GC.start
+ print label.ljust(width)
+ Benchmark.measure(label, &item).tap { |res| print res }
+ }
+ ensure
+ $stdout.sync = sync unless sync.nil?
+ end
+
+ #
+ # Returns the time used to execute the given block as a
+ # Benchmark::Tms object. Takes +label+ option.
+ #
+ # require 'benchmark'
+ #
+ # n = 1000000
+ #
+ # time = Benchmark.measure do
+ # n.times { a = "1" }
+ # end
+ # puts time
+ #
+ # Generates:
+ #
+ # 0.220000 0.000000 0.220000 ( 0.227313)
+ #
+ def measure(label = "") # :yield:
+ t0, r0 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ yield
+ t1, r1 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ Benchmark::Tms.new(t1.utime - t0.utime,
+ t1.stime - t0.stime,
+ t1.cutime - t0.cutime,
+ t1.cstime - t0.cstime,
+ r1 - r0,
+ label)
+ end
+
+ #
+ # Returns the elapsed real time used to execute the given block.
+ # The unit of time is seconds.
+ #
+ # Benchmark.realtime { "a" * 1_000_000_000 }
+ # #=> 0.5098029999935534
+ #
+ def realtime # :yield:
+ r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ yield
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
+ end
+
+ #
+ # Returns the elapsed real time used to execute the given block.
+ # The unit of time is milliseconds.
+ #
+ # Benchmark.ms { "a" * 1_000_000_000 }
+ # #=> 509.8029999935534
+ #
+ def ms # :yield:
+ r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
+ yield
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - r0
+ end
+
+ module_function :benchmark, :measure, :realtime, :ms, :bm, :bmbm
+
+ #
+ # A Job is a sequence of labelled blocks to be processed by the
+ # Benchmark.bmbm method. It is of little direct interest to the user.
+ #
+ class Job # :nodoc:
+ #
+ # Returns an initialized Job instance.
+ # Usually, one doesn't call this method directly, as new
+ # Job objects are created by the #bmbm method.
+ # +width+ is a initial value for the label offset used in formatting;
+ # the #bmbm method passes its +width+ argument to this constructor.
+ #
+ def initialize(width)
+ @width = width
+ @list = []
+ end
+
+ #
+ # Registers the given label and block pair in the job list.
+ #
+ def item(label = "", &blk) # :yield:
+ raise ArgumentError, "no block" unless block_given?
+ label = label.to_s
+ w = label.length
+ @width = w if @width < w
+ @list << [label, blk]
+ self
+ end
+
+ alias report item
+
+ # An array of 2-element arrays, consisting of label and block pairs.
+ attr_reader :list
+
+ # Length of the widest label in the #list.
+ attr_reader :width
+ end
+
+ #
+ # This class is used by the Benchmark.benchmark and Benchmark.bm methods.
+ # It is of little direct interest to the user.
+ #
+ class Report # :nodoc:
+ #
+ # Returns an initialized Report instance.
+ # Usually, one doesn't call this method directly, as new
+ # Report objects are created by the #benchmark and #bm methods.
+ # +width+ and +format+ are the label offset and
+ # format string used by Tms#format.
+ #
+ def initialize(width = 0, format = nil)
+ @width, @format, @list = width, format, []
+ end
+
+ #
+ # Prints the +label+ and measured time for the block,
+ # formatted by +format+. See Tms#format for the
+ # formatting rules.
+ #
+ def item(label = "", *format, &blk) # :yield:
+ w = label.to_s.length
+ @width = w if @width < w
+ @list << res = Benchmark.measure(label, &blk)
+ res
+ end
+
+ alias report item
+
+ # An array of Benchmark::Tms objects representing each item.
+ attr_reader :width, :format, :list
+ end
+
+
+
+ #
+ # A data object, representing the times associated with a benchmark
+ # measurement.
+ #
+ class Tms
+
+ # Default caption, see also Benchmark::CAPTION
+ CAPTION = " user system total real\n"
+
+ # Default format string, see also Benchmark::FORMAT
+ FORMAT = "%10.6u %10.6y %10.6t %10.6r\n"
+
+ # User CPU time
+ attr_reader :utime
+
+ # System CPU time
+ attr_reader :stime
+
+ # User CPU time of children
+ attr_reader :cutime
+
+ # System CPU time of children
+ attr_reader :cstime
+
+ # Elapsed real time
+ attr_reader :real
+
+ # Total time, that is +utime+ + +stime+ + +cutime+ + +cstime+
+ attr_reader :total
+
+ # Label
+ attr_reader :label
+
+ #
+ # Returns an initialized Tms object which has
+ # +utime+ as the user CPU time, +stime+ as the system CPU time,
+ # +cutime+ as the children's user CPU time, +cstime+ as the children's
+ # system CPU time, +real+ as the elapsed real time and +label+ as the label.
+ #
+ def initialize(utime = 0.0, stime = 0.0, cutime = 0.0, cstime = 0.0, real = 0.0, label = nil)
+ @utime, @stime, @cutime, @cstime, @real, @label = utime, stime, cutime, cstime, real, label.to_s
+ @total = @utime + @stime + @cutime + @cstime
+ end
+
+ #
+ # Returns a new Tms object whose times are the sum of the times for this
+ # Tms object, plus the time required to execute the code block (+blk+).
+ #
+ def add(&blk) # :yield:
+ self + Benchmark.measure(&blk)
+ end
+
+ #
+ # An in-place version of #add.
+ # Changes the times of this Tms object by making it the sum of the times
+ # for this Tms object, plus the time required to execute
+ # the code block (+blk+).
+ #
+ def add!(&blk)
+ t = Benchmark.measure(&blk)
+ @utime = utime + t.utime
+ @stime = stime + t.stime
+ @cutime = cutime + t.cutime
+ @cstime = cstime + t.cstime
+ @real = real + t.real
+ self
+ end
+
+ #
+ # Returns a new Tms object obtained by memberwise summation
+ # of the individual times for this Tms object with those of the +other+
+ # Tms object.
+ # This method and #/() are useful for taking statistics.
+ #
+ def +(other); memberwise(:+, other) end
+
+ #
+ # Returns a new Tms object obtained by memberwise subtraction
+ # of the individual times for the +other+ Tms object from those of this
+ # Tms object.
+ #
+ def -(other); memberwise(:-, other) end
+
+ #
+ # Returns a new Tms object obtained by memberwise multiplication
+ # of the individual times for this Tms object by +x+.
+ #
+ def *(x); memberwise(:*, x) end
+
+ #
+ # Returns a new Tms object obtained by memberwise division
+ # of the individual times for this Tms object by +x+.
+ # This method and #+() are useful for taking statistics.
+ #
+ def /(x); memberwise(:/, x) end
+
+ #
+ # Returns the contents of this Tms object as
+ # a formatted string, according to a +format+ string
+ # like that passed to Kernel.format. In addition, #format
+ # accepts the following extensions:
+ #
+ # %u:: Replaced by the user CPU time, as reported by Tms#utime.
+ # %y:: Replaced by the system CPU time, as reported by Tms#stime (Mnemonic: y of "s*y*stem")
+ # %U:: Replaced by the children's user CPU time, as reported by Tms#cutime
+ # %Y:: Replaced by the children's system CPU time, as reported by Tms#cstime
+ # %t:: Replaced by the total CPU time, as reported by Tms#total
+ # %r:: Replaced by the elapsed real time, as reported by Tms#real
+ # %n:: Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame")
+ #
+ # If +format+ is not given, FORMAT is used as default value, detailing the
+ # user, system, total and real elapsed time.
+ #
+ def format(format = nil, *args)
+ str = (format || FORMAT).dup
+ str.gsub!(/(%[-+.\d]*)n/) { "#{$1}s" % label }
+ str.gsub!(/(%[-+.\d]*)u/) { "#{$1}f" % utime }
+ str.gsub!(/(%[-+.\d]*)y/) { "#{$1}f" % stime }
+ str.gsub!(/(%[-+.\d]*)U/) { "#{$1}f" % cutime }
+ str.gsub!(/(%[-+.\d]*)Y/) { "#{$1}f" % cstime }
+ str.gsub!(/(%[-+.\d]*)t/) { "#{$1}f" % total }
+ str.gsub!(/(%[-+.\d]*)r/) { "(#{$1}f)" % real }
+ format ? str % args : str
+ end
+
+ #
+ # Same as #format.
+ #
+ def to_s
+ format
+ end
+
+ #
+ # Returns a new 6-element array, consisting of the
+ # label, user CPU time, system CPU time, children's
+ # user CPU time, children's system CPU time and elapsed
+ # real time.
+ #
+ def to_a
+ [@label, @utime, @stime, @cutime, @cstime, @real]
+ end
+
+ #
+ # Returns a hash containing the same data as `to_a`.
+ #
+ def to_h
+ {
+ label: @label,
+ utime: @utime,
+ stime: @stime,
+ cutime: @cutime,
+ cstime: @cstime,
+ real: @real
+ }
+ end
+
+ protected
+
+ #
+ # Returns a new Tms object obtained by memberwise operation +op+
+ # of the individual times for this Tms object with those of the other
+ # Tms object (+x+).
+ #
+ # +op+ can be a mathematical operation such as +, -,
+ # *, /
+ #
+ def memberwise(op, x)
+ case x
+ when Benchmark::Tms
+ Benchmark::Tms.new(utime.__send__(op, x.utime),
+ stime.__send__(op, x.stime),
+ cutime.__send__(op, x.cutime),
+ cstime.__send__(op, x.cstime),
+ real.__send__(op, x.real)
+ )
+ else
+ Benchmark::Tms.new(utime.__send__(op, x),
+ stime.__send__(op, x),
+ cutime.__send__(op, x),
+ cstime.__send__(op, x),
+ real.__send__(op, x)
+ )
+ end
+ end
+ end
+
+ # The default caption string (heading above the output times).
+ CAPTION = Benchmark::Tms::CAPTION
+
+ # The default format string used to display times. See also Benchmark::Tms#format.
+ FORMAT = Benchmark::Tms::FORMAT
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-4.0.1/LICENSE b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-4.0.1/LICENSE
new file mode 100644
index 000000000..a1f19ff99
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-4.0.1/LICENSE
@@ -0,0 +1,56 @@
+Ruby is copyrighted free software by Yukihiro Matsumoto 'Infinity', '+Infinity' and
+ * '-Infinity' (case-sensitive)
+ *
+ * === Not a Number
+ *
+ * When a computation results in an undefined value, the special value +NaN+
+ * (for 'not a number') is returned.
+ *
+ * Example:
+ *
+ * BigDecimal("0.0") / BigDecimal("0.0") #=> NaN
+ *
+ * You can also create undefined values.
+ *
+ * NaN is never considered to be the same as any other value, even NaN itself:
+ *
+ * n = BigDecimal('NaN')
+ * n == 0.0 #=> false
+ * n == n #=> false
+ *
+ * === Positive and negative zero
+ *
+ * If a computation results in a value which is too small to be represented as
+ * a BigDecimal within the currently specified limits of precision, zero must
+ * be returned.
+ *
+ * If the value which is too small to be represented is negative, a BigDecimal
+ * value of negative zero is returned.
+ *
+ * BigDecimal("1.0") / BigDecimal("-Infinity") #=> -0.0
+ *
+ * If the value is positive, a value of positive zero is returned.
+ *
+ * BigDecimal("1.0") / BigDecimal("Infinity") #=> 0.0
+ *
+ * (See BigDecimal.mode for how to specify limits of precision.)
+ *
+ * Note that +-0.0+ and +0.0+ are considered to be the same for the purposes of
+ * comparison.
+ *
+ * Note also that in mathematics, there is no particular concept of negative
+ * or positive zero; true mathematical zero has no sign.
+ *
+ * == bigdecimal/util
+ *
+ * When you require +bigdecimal/util+, the #to_d method will be
+ * available on BigDecimal and the native Integer, Float, Rational,
+ * String, Complex, and NilClass classes:
+ *
+ * require 'bigdecimal/util'
+ *
+ * 42.to_d # => 0.42e2
+ * 0.5.to_d # => 0.5e0
+ * (2/3r).to_d(3) # => 0.667e0
+ * "0.5".to_d # => 0.5e0
+ * Complex(0.1234567, 0).to_d(4) # => 0.1235e0
+ * nil.to_d # => 0.0
+ *
+ * == Methods for Working with \JSON
+ *
+ * - {::json_create}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-c-json_create]:
+ * Returns a new \BigDecimal object constructed from the given object.
+ * - {#as_json}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-i-as_json]:
+ * Returns a 2-element hash representing +self+.
+ * - {#to_json}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-i-to_json]:
+ * Returns a \JSON string representing +self+.
+ *
+ * These methods are provided by the {JSON gem}[https://github.com/flori/json]. To make these methods available:
+ *
+ * require 'json/add/bigdecimal'
+ *
+ * * == License
+ *
+ * Copyright (C) 2002 by Shigeo Kobayashi bundle package, and that bundler will look in when installing gems.
+ Defaults to `vendor/cache`.
+* `clean` (`BUNDLE_CLEAN`):
+ Whether Bundler should run `bundle clean` automatically after
+ `bundle install`.
+* `console` (`BUNDLE_CONSOLE`):
+ The console that `bundle console` starts. Defaults to `irb`.
+* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`):
+ Whether a `bundle install` without an explicit `--path` argument defaults
+ to installing gems in `.bundle`.
+* `deployment` (`BUNDLE_DEPLOYMENT`):
+ Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the
+ lockfile has not been updated, running Bundler commands will be blocked.
+* `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`):
+ Allow installing gems even if they do not match the checksum provided by
+ RubyGems.
+* `disable_exec_load` (`BUNDLE_DISABLE_EXEC_LOAD`):
+ Stop Bundler from using `load` to launch an executable in-process in
+ `bundle exec`.
+* `disable_local_branch_check` (`BUNDLE_DISABLE_LOCAL_BRANCH_CHECK`):
+ Allow Bundler to use a local git override without a branch specified in the
+ Gemfile.
+* `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`):
+ Allow Bundler to use a local git override without checking if the revision
+ present in the lockfile is present in the repository.
+* `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`):
+ Stop Bundler from accessing gems installed to RubyGems' normal location.
+* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):
+ Stop Bundler from checking if a newer Bundler version is available on
+ rubygems.org.
+* `force_ruby_platform` (`BUNDLE_FORCE_RUBY_PLATFORM`):
+ Ignore the current machine's platform and install only `ruby` platform gems.
+ As a result, gems with native extensions will be compiled from source.
+* `frozen` (`BUNDLE_FROZEN`):
+ Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the
+ lockfile has not been updated, running Bundler commands will be blocked.
+ Defaults to `true` when `--deployment` is used.
+* `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`):
+ Sets a GitHub username or organization to be used in `README` file when you
+ create a new gem via `bundle gem` command. It can be overridden by passing an
+ explicit `--github-username` flag to `bundle gem`.
+* `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`):
+ Sets the `--key` parameter for `gem push` when using the `rake release`
+ command with a private gemstash server.
+* `gemfile` (`BUNDLE_GEMFILE`):
+ The name of the file that bundler should use as the `Gemfile`. This location
+ of this file also sets the root of the project, which is used to resolve
+ relative paths in the `Gemfile`, among other things. By default, bundler
+ will search up from the current working directory until it finds a
+ `Gemfile`.
+* `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`):
+ Whether Bundler should cache all gems globally, rather than locally to the
+ installing Ruby installation.
+* `ignore_funding_requests` (`BUNDLE_IGNORE_FUNDING_REQUESTS`):
+ When set, no funding requests will be printed.
+* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`):
+ When set, no post install messages will be printed. To silence a single gem,
+ use dot notation like `ignore_messages.httparty true`.
+* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`):
+ Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`.
+* `jobs` (`BUNDLE_JOBS`):
+ The number of gems Bundler can install in parallel. Defaults to the number of
+ available processors.
+* `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`):
+ Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources.
+* `no_install` (`BUNDLE_NO_INSTALL`):
+ Whether `bundle package` should skip installing gems.
+* `no_prune` (`BUNDLE_NO_PRUNE`):
+ Whether Bundler should leave outdated gems unpruned when caching.
+* `only` (`BUNDLE_ONLY`):
+ A space-separated list of groups to install only gems of the specified groups.
+* `path` (`BUNDLE_PATH`):
+ The location on disk where all gems in your bundle will be located regardless
+ of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location
+ will be installed by `bundle install`. Defaults to `Gem.dir`. When --deployment
+ is used, defaults to vendor/bundle.
+* `path.system` (`BUNDLE_PATH__SYSTEM`):
+ Whether Bundler will install gems into the default system path (`Gem.dir`).
+* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`)
+ Makes `--path` relative to the CWD instead of the `Gemfile`.
+* `plugins` (`BUNDLE_PLUGINS`):
+ Enable Bundler's experimental plugin system.
+* `prefer_patch` (BUNDLE_PREFER_PATCH):
+ Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`.
+* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`):
+ Print only version number from `bundler --version`.
+* `redirect` (`BUNDLE_REDIRECT`):
+ The number of redirects allowed for network requests. Defaults to `5`.
+* `retry` (`BUNDLE_RETRY`):
+ The number of times to retry failed network requests. Defaults to `3`.
+* `setup_makes_kernel_gem_public` (`BUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC`):
+ Have `Bundler.setup` make the `Kernel#gem` method public, even though
+ RubyGems declares it as private.
+* `shebang` (`BUNDLE_SHEBANG`):
+ The program name that should be invoked for generated binstubs. Defaults to
+ the ruby install name used to generate the binstub.
+* `silence_deprecations` (`BUNDLE_SILENCE_DEPRECATIONS`):
+ Whether Bundler should silence deprecation warnings for behavior that will
+ be changed in the next major version.
+* `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`):
+ Silence the warning Bundler prints when installing gems as root.
+* `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`):
+ Path to a designated CA certificate file or folder containing multiple
+ certificates for trusted CAs in PEM format.
+* `ssl_client_cert` (`BUNDLE_SSL_CLIENT_CERT`):
+ Path to a designated file containing a X.509 client certificate
+ and key in PEM format.
+* `ssl_verify_mode` (`BUNDLE_SSL_VERIFY_MODE`):
+ The SSL verification mode Bundler uses when making HTTPS requests.
+ Defaults to verify peer.
+* `system_bindir` (`BUNDLE_SYSTEM_BINDIR`):
+ The location where RubyGems installs binstubs. Defaults to `Gem.bindir`.
+* `timeout` (`BUNDLE_TIMEOUT`):
+ The seconds allowed before timing out for network requests. Defaults to `10`.
+* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`):
+ Require passing `--all` to `bundle update` when everything should be updated,
+ and disallow passing no options to `bundle update`.
+* `user_agent` (`BUNDLE_USER_AGENT`):
+ The custom user agent fragment Bundler includes in API requests.
+* `version` (`BUNDLE_VERSION`):
+ The version of Bundler to use when running under Bundler environment.
+ Defaults to `lockfile`. You can also specify `system` or `x.y.z`.
+ `lockfile` will use the Bundler version specified in the `Gemfile.lock`,
+ `system` will use the system version of Bundler, and `x.y.z` will use
+ the specified version of Bundler.
+* `with` (`BUNDLE_WITH`):
+ A `:`-separated list of groups whose gems bundler should install.
+* `without` (`BUNDLE_WITHOUT`):
+ A `:`-separated list of groups whose gems bundler should not install.
+
+## LOCAL GIT REPOS
+
+Bundler also allows you to work against a git repository locally
+instead of using the remote version. This can be achieved by setting
+up a local override:
+
+ bundle config set --local local.GEM_NAME /path/to/local/git/repository
+
+For example, in order to use a local Rack repository, a developer could call:
+
+ bundle config set --local local.rack ~/Work/git/rack
+
+Now instead of checking out the remote git repository, the local
+override will be used. Similar to a path source, every time the local
+git repository change, changes will be automatically picked up by
+Bundler. This means a commit in the local git repo will update the
+revision in the `Gemfile.lock` to the local git repo revision. This
+requires the same attention as git submodules. Before pushing to
+the remote, you need to ensure the local override was pushed, otherwise
+you may point to a commit that only exists in your local machine.
+You'll also need to CGI escape your usernames and passwords as well.
+
+Bundler does many checks to ensure a developer won't work with
+invalid references. Particularly, we force a developer to specify
+a branch in the `Gemfile` in order to use this feature. If the branch
+specified in the `Gemfile` and the current branch in the local git
+repository do not match, Bundler will abort. This ensures that
+a developer is always working against the correct branches, and prevents
+accidental locking to a different branch.
+
+Finally, Bundler also ensures that the current revision in the
+`Gemfile.lock` exists in the local git repository. By doing this, Bundler
+forces you to fetch the latest changes in the remotes.
+
+## MIRRORS OF GEM SOURCES
+
+Bundler supports overriding gem sources with mirrors. This allows you to
+configure rubygems.org as the gem source in your Gemfile while still using your
+mirror to fetch gems.
+
+ bundle config set --global mirror.SOURCE_URL MIRROR_URL
+
+For example, to use a mirror of https://rubygems.org hosted at https://example.org:
+
+ bundle config set --global mirror.https://rubygems.org https://example.org
+
+Each mirror also provides a fallback timeout setting. If the mirror does not
+respond within the fallback timeout, Bundler will try to use the original
+server instead of the mirror.
+
+ bundle config set --global mirror.SOURCE_URL.fallback_timeout TIMEOUT
+
+For example, to fall back to rubygems.org after 3 seconds:
+
+ bundle config set --global mirror.https://rubygems.org.fallback_timeout 3
+
+The default fallback timeout is 0.1 seconds, but the setting can currently
+only accept whole seconds (for example, 1, 15, or 30).
+
+## CREDENTIALS FOR GEM SOURCES
+
+Bundler allows you to configure credentials for any gem source, which allows
+you to avoid putting secrets into your Gemfile.
+
+ bundle config set --global SOURCE_HOSTNAME USERNAME:PASSWORD
+
+For example, to save the credentials of user `claudette` for the gem source at
+`gems.longerous.com`, you would run:
+
+ bundle config set --global gems.longerous.com claudette:s00pers3krit
+
+Or you can set the credentials as an environment variable like this:
+
+ export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
+
+For gems with a git source with HTTP(S) URL you can specify credentials like so:
+
+ bundle config set --global https://github.com/rubygems/rubygems.git username:password
+
+Or you can set the credentials as an environment variable like so:
+
+ export BUNDLE_GITHUB__COM=username:password
+
+This is especially useful for private repositories on hosts such as GitHub,
+where you can use personal OAuth tokens:
+
+ export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x-oauth-basic
+
+Note that any configured credentials will be redacted by informative commands
+such as `bundle config list` or `bundle config get`, unless you use the
+`--parseable` flag. This is to avoid unintentionally leaking credentials when
+copy-pasting bundler output.
+
+Also note that to guarantee a sane mapping between valid environment variable
+names and valid host names, bundler makes the following transformations:
+
+* Any `-` characters in a host name are mapped to a triple underscore (`___`) in the
+ corresponding environment variable.
+
+* Any `.` characters in a host name are mapped to a double underscore (`__`) in the
+ corresponding environment variable.
+
+This means that if you have a gem server named `my.gem-host.com`, you'll need to
+use the `BUNDLE_MY__GEM___HOST__COM` variable to configure credentials for it
+through ENV.
+
+## CONFIGURE BUNDLER DIRECTORIES
+
+Bundler's home, cache and plugin directories and config file can be configured
+through environment variables. The default location for Bundler's home directory is
+`~/.bundle`, which all directories inherit from by default. The following
+outlines the available environment variables and their default values
+
+ BUNDLE_USER_HOME : $HOME/.bundle
+ BUNDLE_USER_CACHE : $BUNDLE_USER_HOME/cache
+ BUNDLE_USER_CONFIG : $BUNDLE_USER_HOME/config
+ BUNDLE_USER_PLUGIN : $BUNDLE_USER_HOME/plugin
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-console.1 b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-console.1
new file mode 100644
index 000000000..37110759b
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-console.1
@@ -0,0 +1,33 @@
+.\" generated with nRonn/v0.11.1
+.\" https://github.com/n-ronn/nronn/tree/0.11.1
+.TH "BUNDLE\-CONSOLE" "1" "December 2024" ""
+.SH "NAME"
+\fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded
+.SH "SYNOPSIS"
+\fBbundle console\fR [GROUP]
+.SH "DESCRIPTION"
+Starts an interactive Ruby console session in the context of the current bundle\.
+.P
+If no \fBGROUP\fR is specified, all gems in the \fBdefault\fR group in the Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR are preliminarily loaded\.
+.P
+If \fBGROUP\fR is specified, all gems in the given group in the Gemfile in addition to the gems in \fBdefault\fR group are loaded\. Even if the given group does not exist in the Gemfile, IRB console starts without any warning or error\.
+.P
+The environment variable \fBBUNDLE_CONSOLE\fR or \fBbundle config set console\fR can be used to change the shell from the following:
+.IP "\(bu" 4
+\fBirb\fR (default)
+.IP "\(bu" 4
+\fBpry\fR (https://github\.com/pry/pry)
+.IP "\(bu" 4
+\fBripl\fR (https://github\.com/cldwalker/ripl)
+.IP "" 0
+.P
+\fBbundle console\fR uses irb by default\. An alternative Pry or Ripl can be used with \fBbundle console\fR by adjusting the \fBconsole\fR Bundler setting\. Also make sure that \fBpry\fR or \fBripl\fR is in your Gemfile\.
+.SH "EXAMPLE"
+.nf
+$ bundle config set console pry
+$ bundle console
+Resolving dependencies\|\.\|\.\|\.
+[1] pry(main)>
+.fi
+.SH "SEE ALSO"
+Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-console.1.ronn b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-console.1.ronn
new file mode 100644
index 000000000..ed842ae1c
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-console.1.ronn
@@ -0,0 +1,39 @@
+bundle-console(1) -- Open an IRB session with the bundle pre-loaded
+===================================================================
+
+## SYNOPSIS
+
+`bundle console` [GROUP]
+
+## DESCRIPTION
+
+Starts an interactive Ruby console session in the context of the current bundle.
+
+If no `GROUP` is specified, all gems in the `default` group in the [Gemfile(5)](https://bundler.io/man/gemfile.5.html) are
+preliminarily loaded.
+
+If `GROUP` is specified, all gems in the given group in the Gemfile in addition
+to the gems in `default` group are loaded. Even if the given group does not
+exist in the Gemfile, IRB console starts without any warning or error.
+
+The environment variable `BUNDLE_CONSOLE` or `bundle config set console` can be used to change
+the shell from the following:
+
+* `irb` (default)
+* `pry` (https://github.com/pry/pry)
+* `ripl` (https://github.com/cldwalker/ripl)
+
+`bundle console` uses irb by default. An alternative Pry or Ripl can be used with
+`bundle console` by adjusting the `console` Bundler setting. Also make sure that
+`pry` or `ripl` is in your Gemfile.
+
+## EXAMPLE
+
+ $ bundle config set console pry
+ $ bundle console
+ Resolving dependencies...
+ [1] pry(main)>
+
+## SEE ALSO
+
+[Gemfile(5)](https://bundler.io/man/gemfile.5.html)
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-doctor.1 b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-doctor.1
new file mode 100644
index 000000000..17446d85e
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-doctor.1
@@ -0,0 +1,30 @@
+.\" generated with nRonn/v0.11.1
+.\" https://github.com/n-ronn/nronn/tree/0.11.1
+.TH "BUNDLE\-DOCTOR" "1" "December 2024" ""
+.SH "NAME"
+\fBbundle\-doctor\fR \- Checks the bundle for common problems
+.SH "SYNOPSIS"
+\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE]
+.SH "DESCRIPTION"
+Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\.
+.P
+Examples of common problems caught by bundle\-doctor include:
+.IP "\(bu" 4
+Invalid Bundler settings
+.IP "\(bu" 4
+Mismatched Ruby versions
+.IP "\(bu" 4
+Mismatched platforms
+.IP "\(bu" 4
+Uninstalled gems
+.IP "\(bu" 4
+Missing dependencies
+.IP "" 0
+.SH "OPTIONS"
+.TP
+\fB\-\-quiet\fR
+Only output warnings and errors\.
+.TP
+\fB\-\-gemfile=GEMFILE\fR
+The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
+
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-doctor.1.ronn b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-doctor.1.ronn
new file mode 100644
index 000000000..5970f6188
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-doctor.1.ronn
@@ -0,0 +1,33 @@
+bundle-doctor(1) -- Checks the bundle for common problems
+=========================================================
+
+## SYNOPSIS
+
+`bundle doctor` [--quiet]
+ [--gemfile=GEMFILE]
+
+## DESCRIPTION
+
+Checks your Gemfile and gem environment for common problems. If issues
+are detected, Bundler prints them and exits status 1. Otherwise,
+Bundler prints a success message and exits status 0.
+
+Examples of common problems caught by bundle-doctor include:
+
+* Invalid Bundler settings
+* Mismatched Ruby versions
+* Mismatched platforms
+* Uninstalled gems
+* Missing dependencies
+
+## OPTIONS
+
+* `--quiet`:
+ Only output warnings and errors.
+
+* `--gemfile=GEMFILE`:
+ The location of the Gemfile(5) which Bundler should use. This defaults
+ to a Gemfile(5) in the current working directory. In general, Bundler
+ will assume that the location of the Gemfile(5) is also the project's
+ root and will try to find `Gemfile.lock` and `vendor/cache` relative
+ to this location.
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-env.1 b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-env.1
new file mode 100644
index 000000000..13befeb8f
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-env.1
@@ -0,0 +1,9 @@
+.\" generated with nRonn/v0.11.1
+.\" https://github.com/n-ronn/nronn/tree/0.11.1
+.TH "BUNDLE\-ENV" "1" "December 2024" ""
+.SH "NAME"
+\fBbundle\-env\fR \- Print information about the environment Bundler is running under
+.SH "SYNOPSIS"
+\fBbundle env\fR
+.SH "DESCRIPTION"
+Prints information about the environment Bundler is running under\.
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-env.1.ronn b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-env.1.ronn
new file mode 100644
index 000000000..c2df9c29c
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-env.1.ronn
@@ -0,0 +1,10 @@
+bundle-env(1) -- Print information about the environment Bundler is running under
+=================================================================================
+
+## SYNOPSIS
+
+`bundle env`
+
+## DESCRIPTION
+
+Prints information about the environment Bundler is running under.
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-exec.1 b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-exec.1
new file mode 100644
index 000000000..1abfc9b1d
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/man/bundle-exec.1
@@ -0,0 +1,107 @@
+.\" generated with nRonn/v0.11.1
+.\" https://github.com/n-ronn/nronn/tree/0.11.1
+.TH "BUNDLE\-EXEC" "1" "December 2024" ""
+.SH "NAME"
+\fBbundle\-exec\fR \- Execute a command in the context of the bundle
+.SH "SYNOPSIS"
+\fBbundle exec\fR [\-\-keep\-file\-descriptors] [\-\-gemfile=GEMFILE] \fIcommand\fR
+.SH "DESCRIPTION"
+This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\.
+.P
+Essentially, if you would normally have run something like \fBrspec spec/my_spec\.rb\fR, and you want to use the gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] and installed via bundle install(1) \fIbundle\-install\.1\.html\fR, you should run \fBbundle exec rspec spec/my_spec\.rb\fR\.
+.P
+Note that \fBbundle exec\fR does not require that an executable is available on your shell's \fB$PATH\fR\.
+.SH "OPTIONS"
+.TP
+\fB\-\-keep\-file\-descriptors\fR
+Passes all file descriptors to the new processes\. Default is true from bundler version 2\.2\.26\. Setting it to false is now deprecated\.
+.TP
+\fB\-\-gemfile=GEMFILE\fR
+Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\.
+.SH "BUNDLE INSTALL \-\-BINSTUBS"
+If you use the \fB\-\-binstubs\fR flag in bundle install(1) \fIbundle\-install\.1\.html\fR, Bundler will automatically create a directory (which defaults to \fBapp_root/bin\fR) containing all of the executables available from gems in the bundle\.
+.P
+After using \fB\-\-binstubs\fR, \fBbin/rspec spec/my_spec\.rb\fR is identical to \fBbundle exec rspec spec/my_spec\.rb\fR\.
+.SH "ENVIRONMENT MODIFICATIONS"
+\fBbundle exec\fR makes a number of changes to the shell environment, then executes the command you specify in full\.
+.IP "\(bu" 4
+make sure that it's still possible to shell out to \fBbundle\fR from inside a command invoked by \fBbundle exec\fR (using \fB$BUNDLE_BIN_PATH\fR)
+.IP "\(bu" 4
+put the directory containing executables (like \fBrails\fR, \fBrspec\fR, \fBrackup\fR) for your bundle on \fB$PATH\fR
+.IP "\(bu" 4
+make sure that if bundler is invoked in the subshell, it uses the same \fBGemfile\fR (by setting \fBBUNDLE_GEMFILE\fR)
+.IP "\(bu" 4
+add \fB\-rbundler/setup\fR to \fB$RUBYOPT\fR, which makes sure that Ruby programs invoked in the subshell can see the gems in the bundle
+.IP "" 0
+.P
+It also modifies Rubygems:
+.IP "\(bu" 4
+disallow loading additional gems not in the bundle
+.IP "\(bu" 4
+modify the \fBgem\fR method to be a no\-op if a gem matching the requirements is in the bundle, and to raise a \fBGem::LoadError\fR if it's not
+.IP "\(bu" 4
+Define \fBGem\.refresh\fR to be a no\-op, since the source index is always frozen when using bundler, and to prevent gems from the system leaking into the environment
+.IP "\(bu" 4
+Override \fBGem\.bin_path\fR to use the gems in the bundle, making system executables work
+.IP "\(bu" 4
+Add all gems in the bundle into Gem\.loaded_specs
+.IP "" 0
+.P
+Finally, \fBbundle exec\fR also implicitly modifies \fBGemfile\.lock\fR if the lockfile and the Gemfile do not match\. Bundler needs the Gemfile to determine things such as a gem's groups, \fBautorequire\fR, and platforms, etc\., and that information isn't stored in the lockfile\. The Gemfile and lockfile must be synced in order to \fBbundle exec\fR successfully, so \fBbundle exec\fR updates the lockfile beforehand\.
+.SS "Loading"
+By default, when attempting to \fBbundle exec\fR to a file with a ruby shebang, Bundler will \fBKernel\.load\fR that file instead of using \fBKernel\.exec\fR\. For the vast majority of cases, this is a performance improvement\. In a rare few cases, this could cause some subtle side\-effects (such as dependence on the exact contents of \fB$0\fR or \fB__FILE__\fR) and the optimization can be disabled by enabling the \fBdisable_exec_load\fR setting\.
+.SS "Shelling out"
+Any Ruby code that opens a subshell (like \fBsystem\fR, backticks, or \fB%x{}\fR) will automatically use the current Bundler environment\. If you need to shell out to a Ruby command that is not part of your current bundle, use the \fBwith_unbundled_env\fR method with a block\. Any subshells created inside the block will be given the environment present before Bundler was activated\. For example, Homebrew commands run Ruby, but don't work inside a bundle:
+.IP "" 4
+.nf
+Bundler\.with_unbundled_env do
+ `brew install wget`
+end
+.fi
+.IP "" 0
+.P
+Using \fBwith_unbundled_env\fR is also necessary if you are shelling out to a different bundle\. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also need to use \fBwith_unbundled_env\fR\.
+.IP "" 4
+.nf
+Bundler\.with_unbundled_env do
+ Dir\.chdir "/other/bundler/project" do
+ `bundle exec \./script`
+ end
+end
+.fi
+.IP "" 0
+.P
+Bundler provides convenience helpers that wrap \fBsystem\fR and \fBexec\fR, and they can be used like this:
+.IP "" 4
+.nf
+Bundler\.clean_system('brew install wget')
+Bundler\.clean_exec('brew install wget')
+.fi
+.IP "" 0
+.SH "RUBYGEMS PLUGINS"
+At present, the Rubygems plugin system requires all files named \fBrubygems_plugin\.rb\fR on the load path of \fIany\fR installed gem when any Ruby code requires \fBrubygems\.rb\fR\. This includes executables installed into the system, like \fBrails\fR, \fBrackup\fR, and \fBrspec\fR\.
+.P
+Since Rubygems plugins can contain arbitrary Ruby code, they commonly end up activating themselves or their dependencies\.
+.P
+For instance, the \fBgemcutter 0\.5\fR gem depended on \fBjson_pure\fR\. If you had that version of gemcutter installed (even if you \fIalso\fR had a newer version without this problem), Rubygems would activate \fBgemcutter 0\.5\fR and \fBjson_pure :ENV which will consult environment variables.
+#
+# See #proxy= and #proxy_from_env for details.
+#
+# == Headers
+#
+# Headers may be specified for use in every request. #headers are appended to
+# any headers on the request. #override_headers replace existing headers on
+# the request.
+#
+# The difference between the two can be seen in setting the User-Agent. Using
+# http.headers['User-Agent'] = 'MyUserAgent' will send "Ruby,
+# MyUserAgent" while http.override_headers['User-Agent'] =
+# 'MyUserAgent' will send "MyUserAgent".
+#
+# == Tuning
+#
+# === Segregation
+#
+# Each Gem::Net::HTTP::Persistent instance has its own pool of connections. There
+# is no sharing with other instances (as was true in earlier versions).
+#
+# === Idle Timeout
+#
+# If a connection hasn't been used for this number of seconds it will
+# automatically be reset upon the next use to avoid attempting to send to a
+# closed connection. The default value is 5 seconds. nil means no timeout.
+# Set through #idle_timeout.
+#
+# Reducing this value may help avoid the "too many connection resets" error
+# when sending non-idempotent requests while increasing this value will cause
+# fewer round-trips.
+#
+# === Read Timeout
+#
+# The amount of time allowed between reading two chunks from the socket. Set
+# through #read_timeout
+#
+# === Max Requests
+#
+# The number of requests that should be made before opening a new connection.
+# Typically many keep-alive capable servers tune this to 100 or less, so the
+# 101st request will fail with ECONNRESET. If unset (default), this value has
+# no effect, if set, connections will be reset on the request after
+# max_requests.
+#
+# === Open Timeout
+#
+# The amount of time to wait for a connection to be opened. Set through
+# #open_timeout.
+#
+# === Socket Options
+#
+# Socket options may be set on newly-created connections. See #socket_options
+# for details.
+#
+# === Connection Termination
+#
+# If you are done using the Gem::Net::HTTP::Persistent instance you may shut down
+# all the connections in the current thread with #shutdown. This is not
+# recommended for normal use, it should only be used when it will be several
+# minutes before you make another HTTP request.
+#
+# If you are using multiple threads, call #shutdown in each thread when the
+# thread is done making requests. If you don't call shutdown, that's OK.
+# Ruby will automatically garbage collect and shutdown your HTTP connections
+# when the thread terminates.
+
+class Gem::Net::HTTP::Persistent
+
+ ##
+ # The beginning of Time
+
+ EPOCH = Time.at 0 # :nodoc:
+
+ ##
+ # Is OpenSSL available? This test works with autoload
+
+ HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc:
+
+ ##
+ # The default connection pool size is 1/4 the allowed open files
+ # (ulimit -n) or 256 if your OS does not support file handle
+ # limits (typically windows).
+
+ if Process.const_defined? :RLIMIT_NOFILE
+ open_file_limits = Process.getrlimit(Process::RLIMIT_NOFILE)
+
+ # Under JRuby on Windows Process responds to `getrlimit` but returns something that does not match docs
+ if open_file_limits.respond_to?(:first)
+ DEFAULT_POOL_SIZE = open_file_limits.first / 4
+ else
+ DEFAULT_POOL_SIZE = 256
+ end
+ else
+ DEFAULT_POOL_SIZE = 256
+ end
+
+ ##
+ # The version of Gem::Net::HTTP::Persistent you are using
+
+ VERSION = '4.0.4'
+
+ ##
+ # Error class for errors raised by Gem::Net::HTTP::Persistent. Various
+ # SystemCallErrors are re-raised with a human-readable message under this
+ # class.
+
+ class Error < StandardError; end
+
+ ##
+ # Use this method to detect the idle timeout of the host at +uri+. The
+ # value returned can be used to configure #idle_timeout. +max+ controls the
+ # maximum idle timeout to detect.
+ #
+ # After
+ #
+ # Idle timeout detection is performed by creating a connection then
+ # performing a HEAD request in a loop until the connection terminates
+ # waiting one additional second per loop.
+ #
+ # NOTE: This may not work on ruby > 1.9.
+
+ def self.detect_idle_timeout uri, max = 10
+ uri = Gem::URI uri unless Gem::URI::Generic === uri
+ uri += '/'
+
+ req = Gem::Net::HTTP::Head.new uri.request_uri
+
+ http = new 'net-http-persistent detect_idle_timeout'
+
+ http.connection_for uri do |connection|
+ sleep_time = 0
+
+ http = connection.http
+
+ loop do
+ response = http.request req
+
+ $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG
+
+ unless Gem::Net::HTTPOK === response then
+ raise Error, "bad response code #{response.code} detecting idle timeout"
+ end
+
+ break if sleep_time >= max
+
+ sleep_time += 1
+
+ $stderr.puts "sleeping #{sleep_time}" if $DEBUG
+ sleep sleep_time
+ end
+ end
+ rescue
+ # ignore StandardErrors, we've probably found the idle timeout.
+ ensure
+ return sleep_time unless $!
+ end
+
+ ##
+ # This client's OpenSSL::X509::Certificate
+
+ attr_reader :certificate
+
+ ##
+ # For Gem::Net::HTTP parity
+
+ alias cert certificate
+
+ ##
+ # An SSL certificate authority. Setting this will set verify_mode to
+ # VERIFY_PEER.
+
+ attr_reader :ca_file
+
+ ##
+ # A directory of SSL certificates to be used as certificate authorities.
+ # Setting this will set verify_mode to VERIFY_PEER.
+
+ attr_reader :ca_path
+
+ ##
+ # An SSL certificate store. Setting this will override the default
+ # certificate store. See verify_mode for more information.
+
+ attr_reader :cert_store
+
+ ##
+ # The ciphers allowed for SSL connections
+
+ attr_reader :ciphers
+
+ ##
+ # Sends debug_output to this IO via Gem::Net::HTTP#set_debug_output.
+ #
+ # Never use this method in production code, it causes a serious security
+ # hole.
+
+ attr_accessor :debug_output
+
+ ##
+ # Current connection generation
+
+ attr_reader :generation # :nodoc:
+
+ ##
+ # Headers that are added to every request using Gem::Net::HTTP#add_field
+
+ attr_reader :headers
+
+ ##
+ # Maps host:port to an HTTP version. This allows us to enable version
+ # specific features.
+
+ attr_reader :http_versions
+
+ ##
+ # Maximum time an unused connection can remain idle before being
+ # automatically closed.
+
+ attr_accessor :idle_timeout
+
+ ##
+ # Maximum number of requests on a connection before it is considered expired
+ # and automatically closed.
+
+ attr_accessor :max_requests
+
+ ##
+ # Number of retries to perform if a request fails.
+ #
+ # See also #max_retries=, Gem::Net::HTTP#max_retries=.
+
+ attr_reader :max_retries
+
+ ##
+ # The value sent in the Keep-Alive header. Defaults to 30. Not needed for
+ # HTTP/1.1 servers.
+ #
+ # This may not work correctly for HTTP/1.0 servers
+ #
+ # This method may be removed in a future version as RFC 2616 does not
+ # require this header.
+
+ attr_accessor :keep_alive
+
+ ##
+ # The name for this collection of persistent connections.
+
+ attr_reader :name
+
+ ##
+ # Seconds to wait until a connection is opened. See Gem::Net::HTTP#open_timeout
+
+ attr_accessor :open_timeout
+
+ ##
+ # Headers that are added to every request using Gem::Net::HTTP#[]=
+
+ attr_reader :override_headers
+
+ ##
+ # This client's SSL private key
+
+ attr_reader :private_key
+
+ ##
+ # For Gem::Net::HTTP parity
+
+ alias key private_key
+
+ ##
+ # The URL through which requests will be proxied
+
+ attr_reader :proxy_uri
+
+ ##
+ # List of host suffixes which will not be proxied
+
+ attr_reader :no_proxy
+
+ ##
+ # Test-only accessor for the connection pool
+
+ attr_reader :pool # :nodoc:
+
+ ##
+ # Seconds to wait until reading one block. See Gem::Net::HTTP#read_timeout
+
+ attr_accessor :read_timeout
+
+ ##
+ # Seconds to wait until writing one block. See Gem::Net::HTTP#write_timeout
+
+ attr_accessor :write_timeout
+
+ ##
+ # By default SSL sessions are reused to avoid extra SSL handshakes. Set
+ # this to false if you have problems communicating with an HTTPS server
+ # like:
+ #
+ # SSL_connect [...] read finished A: unexpected message (OpenSSL::SSL::SSLError)
+
+ attr_accessor :reuse_ssl_sessions
+
+ ##
+ # An array of options for Socket#setsockopt.
+ #
+ # By default the TCP_NODELAY option is set on sockets.
+ #
+ # To set additional options append them to this array:
+ #
+ # http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
+
+ attr_reader :socket_options
+
+ ##
+ # Current SSL connection generation
+
+ attr_reader :ssl_generation # :nodoc:
+
+ ##
+ # SSL session lifetime
+
+ attr_reader :ssl_timeout
+
+ ##
+ # SSL version to use.
+ #
+ # By default, the version will be negotiated automatically between client
+ # and server. Ruby 1.9 and newer only. Deprecated since Ruby 2.5.
+
+ attr_reader :ssl_version
+
+ ##
+ # Minimum SSL version to use, e.g. :TLS1_1
+ #
+ # By default, the version will be negotiated automatically between client
+ # and server. Ruby 2.5 and newer only.
+
+ attr_reader :min_version
+
+ ##
+ # Maximum SSL version to use, e.g. :TLS1_2
+ #
+ # By default, the version will be negotiated automatically between client
+ # and server. Ruby 2.5 and newer only.
+
+ attr_reader :max_version
+
+ ##
+ # Where this instance's last-use times live in the thread local variables
+
+ attr_reader :timeout_key # :nodoc:
+
+ ##
+ # SSL verification callback. Used when ca_file or ca_path is set.
+
+ attr_reader :verify_callback
+
+ ##
+ # Sets the depth of SSL certificate verification
+
+ attr_reader :verify_depth
+
+ ##
+ # HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER which verifies
+ # the server certificate.
+ #
+ # If no ca_file, ca_path or cert_store is set the default system certificate
+ # store is used.
+ #
+ # You can use +verify_mode+ to override any default values.
+
+ attr_reader :verify_mode
+
+ ##
+ # HTTPS verify_hostname.
+ #
+ # If a client sets this to true and enables SNI with SSLSocket#hostname=,
+ # the hostname verification on the server certificate is performed
+ # automatically during the handshake using
+ # OpenSSL::SSL.verify_certificate_identity().
+ #
+ # You can set +verify_hostname+ as true to use hostname verification
+ # during the handshake.
+ #
+ # NOTE: This works with Ruby > 3.0.
+
+ attr_reader :verify_hostname
+
+ ##
+ # Creates a new Gem::Net::HTTP::Persistent.
+ #
+ # Set a +name+ for fun. Your library name should be good enough, but this
+ # otherwise has no purpose.
+ #
+ # +proxy+ may be set to a Gem::URI::HTTP or :ENV to pick up proxy options from
+ # the environment. See proxy_from_env for details.
+ #
+ # In order to use a Gem::URI for the proxy you may need to do some extra work
+ # beyond Gem::URI parsing if the proxy requires a password:
+ #
+ # proxy = Gem::URI 'http://proxy.example'
+ # proxy.user = 'AzureDiamond'
+ # proxy.password = 'hunter2'
+ #
+ # Set +pool_size+ to limit the maximum number of connections allowed.
+ # Defaults to 1/4 the number of allowed file handles or 256 if your OS does
+ # not support a limit on allowed file handles. You can have no more than
+ # this many threads with active HTTP transactions.
+
+ def initialize name: nil, proxy: nil, pool_size: DEFAULT_POOL_SIZE
+ @name = name
+
+ @debug_output = nil
+ @proxy_uri = nil
+ @no_proxy = []
+ @headers = {}
+ @override_headers = {}
+ @http_versions = {}
+ @keep_alive = 30
+ @open_timeout = nil
+ @read_timeout = nil
+ @write_timeout = nil
+ @idle_timeout = 5
+ @max_requests = nil
+ @max_retries = 1
+ @socket_options = []
+ @ssl_generation = 0 # incremented when SSL session variables change
+
+ @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
+ Socket.const_defined? :TCP_NODELAY
+
+ @pool = Gem::Net::HTTP::Persistent::Pool.new size: pool_size do |http_args|
+ Gem::Net::HTTP::Persistent::Connection.new Gem::Net::HTTP, http_args, @ssl_generation
+ end
+
+ @certificate = nil
+ @ca_file = nil
+ @ca_path = nil
+ @ciphers = nil
+ @private_key = nil
+ @ssl_timeout = nil
+ @ssl_version = nil
+ @min_version = nil
+ @max_version = nil
+ @verify_callback = nil
+ @verify_depth = nil
+ @verify_mode = nil
+ @verify_hostname = nil
+ @cert_store = nil
+
+ @generation = 0 # incremented when proxy Gem::URI changes
+
+ if HAVE_OPENSSL then
+ @verify_mode = OpenSSL::SSL::VERIFY_PEER
+ @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
+ end
+
+ self.proxy = proxy if proxy
+ end
+
+ ##
+ # Sets this client's OpenSSL::X509::Certificate
+
+ def certificate= certificate
+ @certificate = certificate
+
+ reconnect_ssl
+ end
+
+ # For Gem::Net::HTTP parity
+ alias cert= certificate=
+
+ ##
+ # Sets the SSL certificate authority file.
+
+ def ca_file= file
+ @ca_file = file
+
+ reconnect_ssl
+ end
+
+ ##
+ # Sets the SSL certificate authority path.
+
+ def ca_path= path
+ @ca_path = path
+
+ reconnect_ssl
+ end
+
+ ##
+ # Overrides the default SSL certificate store used for verifying
+ # connections.
+
+ def cert_store= store
+ @cert_store = store
+
+ reconnect_ssl
+ end
+
+ ##
+ # The ciphers allowed for SSL connections
+
+ def ciphers= ciphers
+ @ciphers = ciphers
+
+ reconnect_ssl
+ end
+
+ ##
+ # Creates a new connection for +uri+
+
+ def connection_for uri
+ use_ssl = uri.scheme.downcase == 'https'
+
+ net_http_args = [uri.hostname, uri.port]
+
+ # I'm unsure if uri.host or uri.hostname should be checked against
+ # the proxy bypass list.
+ if @proxy_uri and not proxy_bypass? uri.host, uri.port then
+ net_http_args.concat @proxy_args
+ else
+ net_http_args.concat [nil, nil, nil, nil]
+ end
+
+ connection = @pool.checkout net_http_args
+
+ http = connection.http
+
+ connection.ressl @ssl_generation if
+ connection.ssl_generation != @ssl_generation
+
+ if not http.started? then
+ ssl http if use_ssl
+ start http
+ elsif expired? connection then
+ reset connection
+ end
+
+ http.keep_alive_timeout = @idle_timeout if @idle_timeout
+ http.max_retries = @max_retries if http.respond_to?(:max_retries=)
+ http.read_timeout = @read_timeout if @read_timeout
+ http.write_timeout = @write_timeout if
+ @write_timeout && http.respond_to?(:write_timeout=)
+
+ return yield connection
+ rescue Errno::ECONNREFUSED
+ if http.proxy?
+ address = http.proxy_address
+ port = http.proxy_port
+ else
+ address = http.address
+ port = http.port
+ end
+
+ raise Error, "connection refused: #{address}:#{port}"
+ rescue Errno::EHOSTDOWN
+ if http.proxy?
+ address = http.proxy_address
+ port = http.proxy_port
+ else
+ address = http.address
+ port = http.port
+ end
+
+ raise Error, "host down: #{address}:#{port}"
+ ensure
+ @pool.checkin net_http_args
+ end
+
+ ##
+ # CGI::escape wrapper
+
+ def escape str
+ CGI.escape str if str
+ end
+
+ ##
+ # CGI::unescape wrapper
+
+ def unescape str
+ CGI.unescape str if str
+ end
+
+
+ ##
+ # Returns true if the connection should be reset due to an idle timeout, or
+ # maximum request count, false otherwise.
+
+ def expired? connection
+ return true if @max_requests && connection.requests >= @max_requests
+ return false unless @idle_timeout
+ return true if @idle_timeout.zero?
+
+ Time.now - connection.last_use > @idle_timeout
+ end
+
+ ##
+ # Starts the Gem::Net::HTTP +connection+
+
+ def start http
+ http.set_debug_output @debug_output if @debug_output
+ http.open_timeout = @open_timeout if @open_timeout
+
+ http.start
+
+ socket = http.instance_variable_get :@socket
+
+ if socket then # for fakeweb
+ @socket_options.each do |option|
+ socket.io.setsockopt(*option)
+ end
+ end
+ end
+
+ ##
+ # Finishes the Gem::Net::HTTP +connection+
+
+ def finish connection
+ connection.finish
+
+ connection.http.instance_variable_set :@last_communicated, nil
+ connection.http.instance_variable_set :@ssl_session, nil unless
+ @reuse_ssl_sessions
+ end
+
+ ##
+ # Returns the HTTP protocol version for +uri+
+
+ def http_version uri
+ @http_versions["#{uri.hostname}:#{uri.port}"]
+ end
+
+ ##
+ # Adds "http://" to the String +uri+ if it is missing.
+
+ def normalize_uri uri
+ (uri =~ /^https?:/) ? uri : "http://#{uri}"
+ end
+
+ ##
+ # Set the maximum number of retries for a request.
+ #
+ # Defaults to one retry.
+ #
+ # Set this to 0 to disable retries.
+
+ def max_retries= retries
+ retries = retries.to_int
+
+ raise ArgumentError, "max_retries must be positive" if retries < 0
+
+ @max_retries = retries
+
+ reconnect
+ end
+
+ ##
+ # Sets this client's SSL private key
+
+ def private_key= key
+ @private_key = key
+
+ reconnect_ssl
+ end
+
+ # For Gem::Net::HTTP parity
+ alias key= private_key=
+
+ ##
+ # Sets the proxy server. The +proxy+ may be the Gem::URI of the proxy server,
+ # the symbol +:ENV+ which will read the proxy from the environment or nil to
+ # disable use of a proxy. See #proxy_from_env for details on setting the
+ # proxy from the environment.
+ #
+ # If the proxy Gem::URI is set after requests have been made, the next request
+ # will shut-down and re-open all connections.
+ #
+ # The +no_proxy+ query parameter can be used to specify hosts which shouldn't
+ # be reached via proxy; if set it should be a comma separated list of
+ # hostname suffixes, optionally with +:port+ appended, for example
+ # example.com,some.host:8080.
+
+ def proxy= proxy
+ @proxy_uri = case proxy
+ when :ENV then proxy_from_env
+ when Gem::URI::HTTP then proxy
+ when nil then # ignore
+ else raise ArgumentError, 'proxy must be :ENV or a Gem::URI::HTTP'
+ end
+
+ @no_proxy.clear
+
+ if @proxy_uri then
+ @proxy_args = [
+ @proxy_uri.hostname,
+ @proxy_uri.port,
+ unescape(@proxy_uri.user),
+ unescape(@proxy_uri.password),
+ ]
+
+ @proxy_connection_id = [nil, *@proxy_args].join ':'
+
+ if @proxy_uri.query then
+ @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
+ end
+ end
+
+ reconnect
+ reconnect_ssl
+ end
+
+ ##
+ # Creates a Gem::URI for an HTTP proxy server from ENV variables.
+ #
+ # If +HTTP_PROXY+ is set a proxy will be returned.
+ #
+ # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the Gem::URI is given the
+ # indicated user and password unless HTTP_PROXY contains either of these in
+ # the Gem::URI.
+ #
+ # The +NO_PROXY+ ENV variable can be used to specify hosts which shouldn't
+ # be reached via proxy; if set it should be a comma separated list of
+ # hostname suffixes, optionally with +:port+ appended, for example
+ # example.com,some.host:8080. When set to * no proxy will
+ # be returned.
+ #
+ # For Windows users, lowercase ENV variables are preferred over uppercase ENV
+ # variables.
+
+ def proxy_from_env
+ env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
+
+ return nil if env_proxy.nil? or env_proxy.empty?
+
+ uri = Gem::URI normalize_uri env_proxy
+
+ env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
+
+ # '*' is special case for always bypass
+ return nil if env_no_proxy == '*'
+
+ if env_no_proxy then
+ uri.query = "no_proxy=#{escape(env_no_proxy)}"
+ end
+
+ unless uri.user or uri.password then
+ uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
+ uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
+ end
+
+ uri
+ end
+
+ ##
+ # Returns true when proxy should by bypassed for host.
+
+ def proxy_bypass? host, port
+ host = host.downcase
+ host_port = [host, port].join ':'
+
+ @no_proxy.each do |name|
+ return true if host[-name.length, name.length] == name or
+ host_port[-name.length, name.length] == name
+ end
+
+ false
+ end
+
+ ##
+ # Forces reconnection of all HTTP connections, including TLS/SSL
+ # connections.
+
+ def reconnect
+ @generation += 1
+ end
+
+ ##
+ # Forces reconnection of only TLS/SSL connections.
+
+ def reconnect_ssl
+ @ssl_generation += 1
+ end
+
+ ##
+ # Finishes then restarts the Gem::Net::HTTP +connection+
+
+ def reset connection
+ http = connection.http
+
+ finish connection
+
+ start http
+ rescue Errno::ECONNREFUSED
+ e = Error.new "connection refused: #{http.address}:#{http.port}"
+ e.set_backtrace $@
+ raise e
+ rescue Errno::EHOSTDOWN
+ e = Error.new "host down: #{http.address}:#{http.port}"
+ e.set_backtrace $@
+ raise e
+ end
+
+ ##
+ # Makes a request on +uri+. If +req+ is nil a Gem::Net::HTTP::Get is performed
+ # against +uri+.
+ #
+ # If a block is passed #request behaves like Gem::Net::HTTP#request (the body of
+ # the response will not have been read).
+ #
+ # +req+ must be a Gem::Net::HTTPGenericRequest subclass (see Gem::Net::HTTP for a list).
+
+ def request uri, req = nil, &block
+ uri = Gem::URI uri
+ req = request_setup req || uri
+ response = nil
+
+ connection_for uri do |connection|
+ http = connection.http
+
+ begin
+ connection.requests += 1
+
+ response = http.request req, &block
+
+ if req.connection_close? or
+ (response.http_version <= '1.0' and
+ not response.connection_keep_alive?) or
+ response.connection_close? then
+ finish connection
+ end
+ rescue Exception # make sure to close the connection when it was interrupted
+ finish connection
+
+ raise
+ ensure
+ connection.last_use = Time.now
+ end
+ end
+
+ @http_versions["#{uri.hostname}:#{uri.port}"] ||= response.http_version
+
+ response
+ end
+
+ ##
+ # Creates a GET request if +req_or_uri+ is a Gem::URI and adds headers to the
+ # request.
+ #
+ # Returns the request.
+
+ def request_setup req_or_uri # :nodoc:
+ req = if req_or_uri.respond_to? 'request_uri' then
+ Gem::Net::HTTP::Get.new req_or_uri.request_uri
+ else
+ req_or_uri
+ end
+
+ @headers.each do |pair|
+ req.add_field(*pair)
+ end
+
+ @override_headers.each do |name, value|
+ req[name] = value
+ end
+
+ unless req['Connection'] then
+ req.add_field 'Connection', 'keep-alive'
+ req.add_field 'Keep-Alive', @keep_alive
+ end
+
+ req
+ end
+
+ ##
+ # Shuts down all connections
+ #
+ # *NOTE*: Calling shutdown for can be dangerous!
+ #
+ # If any thread is still using a connection it may cause an error! Call
+ # #shutdown when you are completely done making requests!
+
+ def shutdown
+ @pool.shutdown { |http| http.finish }
+ end
+
+ ##
+ # Enables SSL on +connection+
+
+ def ssl connection
+ connection.use_ssl = true
+
+ connection.ciphers = @ciphers if @ciphers
+ connection.ssl_timeout = @ssl_timeout if @ssl_timeout
+ connection.ssl_version = @ssl_version if @ssl_version
+ connection.min_version = @min_version if @min_version
+ connection.max_version = @max_version if @max_version
+
+ connection.verify_depth = @verify_depth
+ connection.verify_mode = @verify_mode
+ connection.verify_hostname = @verify_hostname if
+ @verify_hostname != nil && connection.respond_to?(:verify_hostname=)
+
+ if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and
+ not Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then
+ warn <<-WARNING
+ !!!SECURITY WARNING!!!
+
+The SSL HTTP connection to:
+
+ #{connection.address}:#{connection.port}
+
+ !!!MAY NOT BE VERIFIED!!!
+
+On your platform your OpenSSL implementation is broken.
+
+There is no difference between the values of VERIFY_NONE and VERIFY_PEER.
+
+This means that attempting to verify the security of SSL connections may not
+work. This exposes you to man-in-the-middle exploits, snooping on the
+contents of your connection and other dangers to the security of your data.
+
+To disable this warning define the following constant at top-level in your
+application:
+
+ I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil
+
+ WARNING
+ end
+
+ connection.ca_file = @ca_file if @ca_file
+ connection.ca_path = @ca_path if @ca_path
+
+ if @ca_file or @ca_path then
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ connection.verify_callback = @verify_callback if @verify_callback
+ end
+
+ if @certificate and @private_key then
+ connection.cert = @certificate
+ connection.key = @private_key
+ end
+
+ connection.cert_store = if @cert_store then
+ @cert_store
+ else
+ store = OpenSSL::X509::Store.new
+ store.set_default_paths
+ store
+ end
+ end
+
+ ##
+ # SSL session lifetime
+
+ def ssl_timeout= ssl_timeout
+ @ssl_timeout = ssl_timeout
+
+ reconnect_ssl
+ end
+
+ ##
+ # SSL version to use
+
+ def ssl_version= ssl_version
+ @ssl_version = ssl_version
+
+ reconnect_ssl
+ end
+
+ ##
+ # Minimum SSL version to use
+
+ def min_version= min_version
+ @min_version = min_version
+
+ reconnect_ssl
+ end
+
+ ##
+ # maximum SSL version to use
+
+ def max_version= max_version
+ @max_version = max_version
+
+ reconnect_ssl
+ end
+
+ ##
+ # Sets the depth of SSL certificate verification
+
+ def verify_depth= verify_depth
+ @verify_depth = verify_depth
+
+ reconnect_ssl
+ end
+
+ ##
+ # Sets the HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER.
+ #
+ # Setting this to VERIFY_NONE is a VERY BAD IDEA and should NEVER be used.
+ # Securely transfer the correct certificate and update the default
+ # certificate store or set the ca file instead.
+
+ def verify_mode= verify_mode
+ @verify_mode = verify_mode
+
+ reconnect_ssl
+ end
+
+ ##
+ # Sets the HTTPS verify_hostname.
+
+ def verify_hostname= verify_hostname
+ @verify_hostname = verify_hostname
+
+ reconnect_ssl
+ end
+
+ ##
+ # SSL verification callback.
+
+ def verify_callback= callback
+ @verify_callback = callback
+
+ reconnect_ssl
+ end
+end
+
+require_relative 'persistent/connection'
+require_relative 'persistent/pool'
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb
new file mode 100644
index 000000000..8b9ab5cc7
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb
@@ -0,0 +1,41 @@
+##
+# A Gem::Net::HTTP connection wrapper that holds extra information for managing the
+# connection's lifetime.
+
+class Gem::Net::HTTP::Persistent::Connection # :nodoc:
+
+ attr_accessor :http
+
+ attr_accessor :last_use
+
+ attr_accessor :requests
+
+ attr_accessor :ssl_generation
+
+ def initialize http_class, http_args, ssl_generation
+ @http = http_class.new(*http_args)
+ @ssl_generation = ssl_generation
+
+ reset
+ end
+
+ def finish
+ @http.finish
+ rescue IOError
+ ensure
+ reset
+ end
+ alias_method :close, :finish
+
+ def reset
+ @last_use = Gem::Net::HTTP::Persistent::EPOCH
+ @requests = 0
+ end
+
+ def ressl ssl_generation
+ @ssl_generation = ssl_generation
+
+ finish
+ end
+
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb
new file mode 100644
index 000000000..04a1e754b
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb
@@ -0,0 +1,65 @@
+class Gem::Net::HTTP::Persistent::Pool < Bundler::ConnectionPool # :nodoc:
+
+ attr_reader :available # :nodoc:
+ attr_reader :key # :nodoc:
+
+ def initialize(options = {}, &block)
+ super
+
+ @available = Gem::Net::HTTP::Persistent::TimedStackMulti.new(@size, &block)
+ @key = "current-#{@available.object_id}"
+ end
+
+ def checkin net_http_args
+ if net_http_args.is_a?(Hash) && net_http_args.size == 1 && net_http_args[:force]
+ # Bundler::ConnectionPool 2.4+ calls `checkin(force: true)` after fork.
+ # When this happens, we should remove all connections from Thread.current
+ if stacks = Thread.current[@key]
+ stacks.each do |http_args, connections|
+ connections.each do |conn|
+ @available.push conn, connection_args: http_args
+ end
+ connections.clear
+ end
+ end
+ else
+ stack = Thread.current[@key][net_http_args] ||= []
+
+ raise Bundler::ConnectionPool::Error, 'no connections are checked out' if
+ stack.empty?
+
+ conn = stack.pop
+
+ if stack.empty?
+ @available.push conn, connection_args: net_http_args
+
+ Thread.current[@key].delete(net_http_args)
+ Thread.current[@key] = nil if Thread.current[@key].empty?
+ end
+ end
+ nil
+ end
+
+ def checkout net_http_args
+ stacks = Thread.current[@key] ||= {}
+ stack = stacks[net_http_args] ||= []
+
+ if stack.empty? then
+ conn = @available.pop connection_args: net_http_args
+ else
+ conn = stack.last
+ end
+
+ stack.push conn
+
+ conn
+ end
+
+ def shutdown
+ Thread.current[@key] = nil
+ super
+ end
+end
+
+require_relative 'timed_stack_multi'
+
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb
new file mode 100644
index 000000000..214804fcd
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb
@@ -0,0 +1,79 @@
+class Gem::Net::HTTP::Persistent::TimedStackMulti < Bundler::ConnectionPool::TimedStack # :nodoc:
+
+ ##
+ # Returns a new hash that has arrays for keys
+ #
+ # Using a class method to limit the bindings referenced by the hash's
+ # default_proc
+
+ def self.hash_of_arrays # :nodoc:
+ Hash.new { |h,k| h[k] = [] }
+ end
+
+ def initialize(size = 0, &block)
+ super
+
+ @enqueued = 0
+ @ques = self.class.hash_of_arrays
+ @lru = {}
+ @key = :"connection_args-#{object_id}"
+ end
+
+ def empty?
+ (@created - @enqueued) >= @max
+ end
+
+ def length
+ @max - @created + @enqueued
+ end
+
+ private
+
+ def connection_stored? options = {} # :nodoc:
+ !@ques[options[:connection_args]].empty?
+ end
+
+ def fetch_connection options = {} # :nodoc:
+ connection_args = options[:connection_args]
+
+ @enqueued -= 1
+ lru_update connection_args
+ @ques[connection_args].pop
+ end
+
+ def lru_update connection_args # :nodoc:
+ @lru.delete connection_args
+ @lru[connection_args] = true
+ end
+
+ def shutdown_connections # :nodoc:
+ @ques.each_key do |key|
+ super connection_args: key
+ end
+ end
+
+ def store_connection obj, options = {} # :nodoc:
+ @ques[options[:connection_args]].push obj
+ @enqueued += 1
+ end
+
+ def try_create options = {} # :nodoc:
+ connection_args = options[:connection_args]
+
+ if @created >= @max && @enqueued >= 1
+ oldest, = @lru.first
+ @lru.delete oldest
+ @ques[oldest].pop
+
+ @created -= 1
+ end
+
+ if @created < @max
+ @created += 1
+ lru_update connection_args
+ return @create_block.call(connection_args)
+ end
+ end
+
+end
+
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/.document b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/.document
new file mode 100644
index 000000000..0c43bbd6b
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/LICENSE.txt
new file mode 100644
index 000000000..411840a4a
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 John Hawthorn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub.rb
new file mode 100644
index 000000000..eaaba3fc9
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub.rb
@@ -0,0 +1,31 @@
+require_relative "pub_grub/package"
+require_relative "pub_grub/static_package_source"
+require_relative "pub_grub/term"
+require_relative "pub_grub/version_range"
+require_relative "pub_grub/version_constraint"
+require_relative "pub_grub/version_union"
+require_relative "pub_grub/version_solver"
+require_relative "pub_grub/incompatibility"
+require_relative 'pub_grub/solve_failure'
+require_relative 'pub_grub/failure_writer'
+require_relative 'pub_grub/version'
+
+module Bundler::PubGrub
+ class << self
+ attr_writer :logger
+
+ def logger
+ @logger || default_logger
+ end
+
+ private
+
+ def default_logger
+ require "logger"
+
+ logger = ::Logger.new(STDERR)
+ logger.level = $DEBUG ? ::Logger::DEBUG : ::Logger::WARN
+ @logger = logger
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb
new file mode 100644
index 000000000..2236a97b5
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb
@@ -0,0 +1,20 @@
+module Bundler::PubGrub
+ class Assignment
+ attr_reader :term, :cause, :decision_level, :index
+ def initialize(term, cause, decision_level, index)
+ @term = term
+ @cause = cause
+ @decision_level = decision_level
+ @index = index
+ end
+
+ def self.decision(package, version, decision_level, index)
+ term = Term.new(VersionConstraint.exact(package, version), true)
+ new(term, :decision, decision_level, index)
+ end
+
+ def decision?
+ cause == :decision
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb
new file mode 100644
index 000000000..dce20d37a
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb
@@ -0,0 +1,189 @@
+require_relative 'version_constraint'
+require_relative 'incompatibility'
+
+module Bundler::PubGrub
+ # Types:
+ #
+ # Where possible, Bundler::PubGrub will accept user-defined types, so long as they quack.
+ #
+ # ## "Package":
+ #
+ # This class will be used to represent the various packages being solved for.
+ # .to_s will be called when displaying errors and debugging info, it should
+ # probably return the package's name.
+ # It must also have a reasonable definition of #== and #hash
+ #
+ # Example classes: String ("rails")
+ #
+ #
+ # ## "Version":
+ #
+ # This class will be used to represent a single version number.
+ #
+ # Versions don't need to store their associated package, however they will
+ # only be compared against other versions of the same package.
+ #
+ # It must be Comparible (and implement <=> reasonably)
+ #
+ # Example classes: Gem::Version, Integer
+ #
+ #
+ # ## "Dependency"
+ #
+ # This class represents the requirement one package has on another. It is
+ # returned by dependencies_for(package, version) and will be passed to
+ # parse_dependency to convert it to a format Bundler::PubGrub understands.
+ #
+ # It must also have a reasonable definition of #==
+ #
+ # Example classes: String ("~> 1.0"), Gem::Requirement
+ #
+ class BasicPackageSource
+ # Override me!
+ #
+ # This is called per package to find all possible versions of a package.
+ #
+ # It is called at most once per-package
+ #
+ # Returns: Array of versions for a package, in preferred order of selection
+ def all_versions_for(package)
+ raise NotImplementedError
+ end
+
+ # Override me!
+ #
+ # Returns: Hash in the form of { package => requirement, ... }
+ def dependencies_for(package, version)
+ raise NotImplementedError
+ end
+
+ # Override me!
+ #
+ # Convert a (user-defined) dependency into a format Bundler::PubGrub understands.
+ #
+ # Package is passed to this method but for many implementations is not
+ # needed.
+ #
+ # Returns: either a Bundler::PubGrub::VersionRange, Bundler::PubGrub::VersionUnion, or a
+ # Bundler::PubGrub::VersionConstraint
+ def parse_dependency(package, dependency)
+ raise NotImplementedError
+ end
+
+ # Override me!
+ #
+ # If not overridden, this will call dependencies_for with the root package.
+ #
+ # Returns: Hash in the form of { package => requirement, ... } (see dependencies_for)
+ def root_dependencies
+ dependencies_for(@root_package, @root_version)
+ end
+
+ # Override me (maybe)
+ #
+ # If not overridden, the order returned by all_versions_for will be used
+ #
+ # Returns: Array of versions in preferred order
+ def sort_versions_by_preferred(package, sorted_versions)
+ indexes = @version_indexes[package]
+ sorted_versions.sort_by { |version| indexes[version] }
+ end
+
+ def initialize
+ @root_package = Package.root
+ @root_version = Package.root_version
+
+ @cached_versions = Hash.new do |h,k|
+ if k == @root_package
+ h[k] = [@root_version]
+ else
+ h[k] = all_versions_for(k)
+ end
+ end
+ @sorted_versions = Hash.new { |h,k| h[k] = @cached_versions[k].sort }
+ @version_indexes = Hash.new { |h,k| h[k] = @cached_versions[k].each.with_index.to_h }
+
+ @cached_dependencies = Hash.new do |packages, package|
+ if package == @root_package
+ packages[package] = {
+ @root_version => root_dependencies
+ }
+ else
+ packages[package] = Hash.new do |versions, version|
+ versions[version] = dependencies_for(package, version)
+ end
+ end
+ end
+ end
+
+ def versions_for(package, range=VersionRange.any)
+ versions = range.select_versions(@sorted_versions[package])
+
+ # Conditional avoids (among other things) calling
+ # sort_versions_by_preferred with the root package
+ if versions.size > 1
+ sort_versions_by_preferred(package, versions)
+ else
+ versions
+ end
+ end
+
+ def no_versions_incompatibility_for(_package, unsatisfied_term)
+ cause = Incompatibility::NoVersions.new(unsatisfied_term)
+
+ Incompatibility.new([unsatisfied_term], cause: cause)
+ end
+
+ def incompatibilities_for(package, version)
+ package_deps = @cached_dependencies[package]
+ sorted_versions = @sorted_versions[package]
+ package_deps[version].map do |dep_package, dep_constraint_name|
+ low = high = sorted_versions.index(version)
+
+ # find version low such that all >= low share the same dep
+ while low > 0 &&
+ package_deps[sorted_versions[low - 1]][dep_package] == dep_constraint_name
+ low -= 1
+ end
+ low =
+ if low == 0
+ nil
+ else
+ sorted_versions[low]
+ end
+
+ # find version high such that all < high share the same dep
+ while high < sorted_versions.length &&
+ package_deps[sorted_versions[high]][dep_package] == dep_constraint_name
+ high += 1
+ end
+ high =
+ if high == sorted_versions.length
+ nil
+ else
+ sorted_versions[high]
+ end
+
+ range = VersionRange.new(min: low, max: high, include_min: true)
+
+ self_constraint = VersionConstraint.new(package, range: range)
+
+ if !@packages.include?(dep_package)
+ # no such package -> this version is invalid
+ end
+
+ dep_constraint = parse_dependency(dep_package, dep_constraint_name)
+ if !dep_constraint
+ # falsey indicates this dependency was invalid
+ cause = Bundler::PubGrub::Incompatibility::InvalidDependency.new(dep_package, dep_constraint_name)
+ return [Incompatibility.new([Term.new(self_constraint, true)], cause: cause)]
+ elsif !dep_constraint.is_a?(VersionConstraint)
+ # Upgrade range/union to VersionConstraint
+ dep_constraint = VersionConstraint.new(dep_package, range: dep_constraint)
+ end
+
+ Incompatibility.new([Term.new(self_constraint, true), Term.new(dep_constraint, false)], cause: :dependency)
+ end
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb
new file mode 100644
index 000000000..ee099b23f
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb
@@ -0,0 +1,182 @@
+module Bundler::PubGrub
+ class FailureWriter
+ def initialize(root)
+ @root = root
+
+ # { Incompatibility => Integer }
+ @derivations = {}
+
+ # [ [ String, Integer or nil ] ]
+ @lines = []
+
+ # { Incompatibility => Integer }
+ @line_numbers = {}
+
+ count_derivations(root)
+ end
+
+ def write
+ return @root.to_s unless @root.conflict?
+
+ visit(@root)
+
+ padding = @line_numbers.empty? ? 0 : "(#{@line_numbers.values.last}) ".length
+
+ @lines.map do |message, number|
+ next "" if message.empty?
+
+ lead = number ? "(#{number}) " : ""
+ lead = lead.ljust(padding)
+ message = message.gsub("\n", "\n" + " " * (padding + 2))
+ "#{lead}#{message}"
+ end.join("\n")
+ end
+
+ private
+
+ def write_line(incompatibility, message, numbered:)
+ if numbered
+ number = @line_numbers.length + 1
+ @line_numbers[incompatibility] = number
+ end
+
+ @lines << [message, number]
+ end
+
+ def visit(incompatibility, conclusion: false)
+ raise unless incompatibility.conflict?
+
+ numbered = conclusion || @derivations[incompatibility] > 1;
+ conjunction = conclusion || incompatibility == @root ? "So," : "And"
+
+ cause = incompatibility.cause
+
+ if cause.conflict.conflict? && cause.other.conflict?
+ conflict_line = @line_numbers[cause.conflict]
+ other_line = @line_numbers[cause.other]
+
+ if conflict_line && other_line
+ write_line(
+ incompatibility,
+ "Because #{cause.conflict} (#{conflict_line})\nand #{cause.other} (#{other_line}),\n#{incompatibility}.",
+ numbered: numbered
+ )
+ elsif conflict_line || other_line
+ with_line = conflict_line ? cause.conflict : cause.other
+ without_line = conflict_line ? cause.other : cause.conflict
+ line = @line_numbers[with_line]
+
+ visit(without_line);
+ write_line(
+ incompatibility,
+ "#{conjunction} because #{with_line} (#{line}),\n#{incompatibility}.",
+ numbered: numbered
+ )
+ else
+ single_line_conflict = single_line?(cause.conflict.cause)
+ single_line_other = single_line?(cause.other.cause)
+
+ if single_line_conflict || single_line_other
+ first = single_line_other ? cause.conflict : cause.other
+ second = single_line_other ? cause.other : cause.conflict
+ visit(first)
+ visit(second)
+ write_line(
+ incompatibility,
+ "Thus, #{incompatibility}.",
+ numbered: numbered
+ )
+ else
+ visit(cause.conflict, conclusion: true)
+ @lines << ["", nil]
+ visit(cause.other)
+
+ write_line(
+ incompatibility,
+ "#{conjunction} because #{cause.conflict} (#{@line_numbers[cause.conflict]}),\n#{incompatibility}.",
+ numbered: numbered
+ )
+ end
+ end
+ elsif cause.conflict.conflict? || cause.other.conflict?
+ derived = cause.conflict.conflict? ? cause.conflict : cause.other
+ ext = cause.conflict.conflict? ? cause.other : cause.conflict
+
+ derived_line = @line_numbers[derived]
+ if derived_line
+ write_line(
+ incompatibility,
+ "Because #{ext}\nand #{derived} (#{derived_line}),\n#{incompatibility}.",
+ numbered: numbered
+ )
+ elsif collapsible?(derived)
+ derived_cause = derived.cause
+ if derived_cause.conflict.conflict?
+ collapsed_derived = derived_cause.conflict
+ collapsed_ext = derived_cause.other
+ else
+ collapsed_derived = derived_cause.other
+ collapsed_ext = derived_cause.conflict
+ end
+
+ visit(collapsed_derived)
+
+ write_line(
+ incompatibility,
+ "#{conjunction} because #{collapsed_ext}\nand #{ext},\n#{incompatibility}.",
+ numbered: numbered
+ )
+ else
+ visit(derived)
+ write_line(
+ incompatibility,
+ "#{conjunction} because #{ext},\n#{incompatibility}.",
+ numbered: numbered
+ )
+ end
+ else
+ write_line(
+ incompatibility,
+ "Because #{cause.conflict}\nand #{cause.other},\n#{incompatibility}.",
+ numbered: numbered
+ )
+ end
+ end
+
+ def single_line?(cause)
+ !cause.conflict.conflict? && !cause.other.conflict?
+ end
+
+ def collapsible?(incompatibility)
+ return false if @derivations[incompatibility] > 1
+
+ cause = incompatibility.cause
+ # If incompatibility is derived from two derived incompatibilities,
+ # there are too many transitive causes to display concisely.
+ return false if cause.conflict.conflict? && cause.other.conflict?
+
+ # If incompatibility is derived from two external incompatibilities, it
+ # tends to be confusing to collapse it.
+ return false unless cause.conflict.conflict? || cause.other.conflict?
+
+ # If incompatibility's internal cause is numbered, collapsing it would
+ # get too noisy.
+ complex = cause.conflict.conflict? ? cause.conflict : cause.other
+
+ !@line_numbers.has_key?(complex)
+ end
+
+ def count_derivations(incompatibility)
+ if @derivations.has_key?(incompatibility)
+ @derivations[incompatibility] += 1
+ else
+ @derivations[incompatibility] = 1
+ if incompatibility.conflict?
+ cause = incompatibility.cause
+ count_derivations(cause.conflict)
+ count_derivations(cause.other)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb
new file mode 100644
index 000000000..239eaf340
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb
@@ -0,0 +1,150 @@
+module Bundler::PubGrub
+ class Incompatibility
+ ConflictCause = Struct.new(:incompatibility, :satisfier) do
+ alias_method :conflict, :incompatibility
+ alias_method :other, :satisfier
+ end
+
+ InvalidDependency = Struct.new(:package, :constraint) do
+ end
+
+ NoVersions = Struct.new(:constraint) do
+ end
+
+ attr_reader :terms, :cause
+
+ def initialize(terms, cause:, custom_explanation: nil)
+ @cause = cause
+ @terms = cleanup_terms(terms)
+ @custom_explanation = custom_explanation
+
+ if cause == :dependency && @terms.length != 2
+ raise ArgumentError, "a dependency Incompatibility must have exactly two terms. Got #{@terms.inspect}"
+ end
+ end
+
+ def hash
+ cause.hash ^ terms.hash
+ end
+
+ def eql?(other)
+ cause.eql?(other.cause) &&
+ terms.eql?(other.terms)
+ end
+
+ def failure?
+ terms.empty? || (terms.length == 1 && Package.root?(terms[0].package) && terms[0].positive?)
+ end
+
+ def conflict?
+ ConflictCause === cause
+ end
+
+ # Returns all external incompatibilities in this incompatibility's
+ # derivation graph
+ def external_incompatibilities
+ if conflict?
+ [
+ cause.conflict,
+ cause.other
+ ].flat_map(&:external_incompatibilities)
+ else
+ [this]
+ end
+ end
+
+ def to_s
+ return @custom_explanation if @custom_explanation
+
+ case cause
+ when :root
+ "(root dependency)"
+ when :dependency
+ "#{terms[0].to_s(allow_every: true)} depends on #{terms[1].invert}"
+ when Bundler::PubGrub::Incompatibility::InvalidDependency
+ "#{terms[0].to_s(allow_every: true)} depends on unknown package #{cause.package}"
+ when Bundler::PubGrub::Incompatibility::NoVersions
+ "no versions satisfy #{cause.constraint}"
+ when Bundler::PubGrub::Incompatibility::ConflictCause
+ if failure?
+ "version solving has failed"
+ elsif terms.length == 1
+ term = terms[0]
+ if term.positive?
+ if term.constraint.any?
+ "#{term.package} cannot be used"
+ else
+ "#{term.to_s(allow_every: true)} cannot be used"
+ end
+ else
+ "#{term.invert} is required"
+ end
+ else
+ if terms.all?(&:positive?)
+ if terms.length == 2
+ "#{terms[0].to_s(allow_every: true)} is incompatible with #{terms[1]}"
+ else
+ "one of #{terms.map(&:to_s).join(" or ")} must be false"
+ end
+ elsif terms.all?(&:negative?)
+ if terms.length == 2
+ "either #{terms[0].invert} or #{terms[1].invert}"
+ else
+ "one of #{terms.map(&:invert).join(" or ")} must be true";
+ end
+ else
+ positive = terms.select(&:positive?)
+ negative = terms.select(&:negative?).map(&:invert)
+
+ if positive.length == 1
+ "#{positive[0].to_s(allow_every: true)} requires #{negative.join(" or ")}"
+ else
+ "if #{positive.join(" and ")} then #{negative.join(" or ")}"
+ end
+ end
+ end
+ else
+ raise "unhandled cause: #{cause.inspect}"
+ end
+ end
+
+ def inspect
+ "#<#{self.class} #{to_s}>"
+ end
+
+ def pretty_print(q)
+ q.group 2, "#<#{self.class}", ">" do
+ q.breakable
+ q.text to_s
+
+ q.breakable
+ q.text " caused by "
+ q.pp @cause
+ end
+ end
+
+ private
+
+ def cleanup_terms(terms)
+ terms.each do |term|
+ raise "#{term.inspect} must be a term" unless term.is_a?(Term)
+ end
+
+ if terms.length != 1 && ConflictCause === cause
+ terms = terms.reject do |term|
+ term.positive? && Package.root?(term.package)
+ end
+ end
+
+ # Optimized simple cases
+ return terms if terms.length <= 1
+ return terms if terms.length == 2 && terms[0].package != terms[1].package
+
+ terms.group_by(&:package).map do |package, common_terms|
+ common_terms.inject do |acc, term|
+ acc.intersect(term)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb
new file mode 100644
index 000000000..efb9d3da1
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Bundler::PubGrub
+ class Package
+
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def inspect
+ "#<#{self.class} #{name.inspect}>"
+ end
+
+ def <=>(other)
+ name <=> other.name
+ end
+
+ ROOT = Package.new(:root)
+ ROOT_VERSION = 0
+
+ def self.root
+ ROOT
+ end
+
+ def self.root_version
+ ROOT_VERSION
+ end
+
+ def self.root?(package)
+ if package.respond_to?(:root?)
+ package.root?
+ else
+ package == root
+ end
+ end
+
+ def to_s
+ name.to_s
+ end
+ end
+end
diff --git a/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb
new file mode 100644
index 000000000..4c4b8ca84
--- /dev/null
+++ b/vendor/bundle/ruby/3.2.0/gems/bundler-2.6.2/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb
@@ -0,0 +1,121 @@
+require_relative 'assignment'
+
+module Bundler::PubGrub
+ class PartialSolution
+ attr_reader :assignments, :decisions
+ attr_reader :attempted_solutions
+
+ def initialize
+ reset!
+
+ @attempted_solutions = 1
+ @backtracking = false
+ end
+
+ def decision_level
+ @decisions.length
+ end
+
+ def relation(term)
+ package = term.package
+ return :overlap if !@terms.key?(package)
+
+ @relation_cache[package][term] ||=
+ @terms[package].relation(term)
+ end
+
+ def satisfies?(term)
+ relation(term) == :subset
+ end
+
+ def derive(term, cause)
+ add_assignment(Assignment.new(term, cause, decision_level, assignments.length))
+ end
+
+ def satisfier(term)
+ assignment =
+ @assignments_by[term.package].bsearch do |assignment_by|
+ @cumulative_assignments[assignment_by].satisfies?(term)
+ end
+
+ assignment || raise("#{term} unsatisfied")
+ end
+
+ # A list of unsatisfied terms
+ def unsatisfied
+ @required.keys.reject do |package|
+ @decisions.key?(package)
+ end.map do |package|
+ @terms[package]
+ end
+ end
+
+ def decide(package, version)
+ @attempted_solutions += 1 if @backtracking
+ @backtracking = false;
+
+ decisions[package] = version
+ assignment = Assignment.decision(package, version, decision_level, assignments.length)
+ add_assignment(assignment)
+ end
+
+ def backtrack(previous_level)
+ @backtracking = true
+
+ new_assignments = assignments.select do |assignment|
+ assignment.decision_level <= previous_level
+ end
+
+ new_decisions = Hash[decisions.first(previous_level)]
+
+ reset!
+
+ @decisions = new_decisions
+
+ new_assignments.each do |assignment|
+ add_assignment(assignment)
+ end
+ end
+
+ private
+
+ def reset!
+ # { Array