diff options
Diffstat (limited to 'lib/bundler/vendor')
100 files changed, 5282 insertions, 3604 deletions
diff --git a/lib/bundler/vendor/.document b/lib/bundler/vendor/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/connection_pool/.document b/lib/bundler/vendor/connection_pool/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/connection_pool/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb index 984c1c3dcb..317088a866 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -1,10 +1,12 @@ -require "timeout" +require_relative "../../../vendored_timeout" require_relative "connection_pool/version" class Bundler::ConnectionPool class Error < ::RuntimeError; end + class PoolShuttingDownError < ::Bundler::ConnectionPool::Error; end - class TimeoutError < ::Timeout::Error; end + + class TimeoutError < ::Gem::Timeout::Error; end end # Generic connection pool class for sharing a limited number of objects or network connections @@ -34,14 +36,57 @@ end # Accepts the following options: # - :size - number of connections to pool, defaults to 5 # - :timeout - amount of time to wait for a connection if none currently available, defaults to 5 seconds +# - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true # class Bundler::ConnectionPool - DEFAULTS = {size: 5, timeout: 5} + DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true} def self.wrap(options, &block) Wrapper.new(options, &block) end + if Process.respond_to?(:fork) + INSTANCES = ObjectSpace::WeakMap.new + private_constant :INSTANCES + + def self.after_fork + INSTANCES.values.each do |pool| + next unless pool.auto_reload_after_fork + + # We're on after fork, so we know all other threads are dead. + # All we need to do is to ensure the main thread doesn't have a + # checked out connection + pool.checkin(force: true) + pool.reload do |connection| + # Unfortunately we don't know what method to call to close the connection, + # so we try the most common one. + connection.close if connection.respond_to?(:close) + end + end + nil + end + + if ::Process.respond_to?(:_fork) # MRI 3.1+ + module ForkTracker + def _fork + pid = super + if pid == 0 + Bundler::ConnectionPool.after_fork + end + pid + end + end + Process.singleton_class.prepend(ForkTracker) + end + else + INSTANCES = nil + private_constant :INSTANCES + + def self.after_fork + # noop + end + end + def initialize(options = {}, &block) raise ArgumentError, "Connection pool requires a block" unless block @@ -49,10 +94,12 @@ class Bundler::ConnectionPool @size = Integer(options.fetch(:size)) @timeout = options.fetch(:timeout) + @auto_reload_after_fork = options.fetch(:auto_reload_after_fork) @available = TimedStack.new(@size, &block) @key = :"pool-#{@available.object_id}" @key_count = :"pool-#{@available.object_id}-count" + INSTANCES[self] = self if INSTANCES end def with(options = {}) @@ -67,7 +114,7 @@ class Bundler::ConnectionPool end end end - alias then with + alias_method :then, :with def checkout(options = {}) if ::Thread.current[@key] @@ -79,16 +126,16 @@ class Bundler::ConnectionPool end end - def checkin + def checkin(force: false) if ::Thread.current[@key] - if ::Thread.current[@key_count] == 1 + if ::Thread.current[@key_count] == 1 || force @available.push(::Thread.current[@key]) ::Thread.current[@key] = nil ::Thread.current[@key_count] = nil else ::Thread.current[@key_count] -= 1 end - else + elsif !force raise Bundler::ConnectionPool::Error, "no connections are checked out" end @@ -115,6 +162,8 @@ class Bundler::ConnectionPool # Size of this connection pool attr_reader :size + # Automatically drop all connections after fork + attr_reader :auto_reload_after_fork # Number of pool entries available for checkout at this instant. def available diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb index a7b1cf06a8..35d1d7cc35 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb @@ -49,7 +49,7 @@ class Bundler::ConnectionPool::TimedStack @resource.broadcast end end - alias << push + alias_method :<<, :push ## # Retrieves a connection from the stack. If a connection is available it is @@ -74,7 +74,7 @@ class Bundler::ConnectionPool::TimedStack return connection if connection to_wait = deadline - current_time - raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec" if to_wait <= 0 + raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0 @resource.wait(@mutex, to_wait) end end @@ -87,7 +87,7 @@ class Bundler::ConnectionPool::TimedStack # +:reload+ is +true+. def shutdown(reload: false, &block) - raise ArgumentError, "shutdown must receive a block" unless block_given? + raise ArgumentError, "shutdown must receive a block" unless block @mutex.synchronize do @shutdown_block = block diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb index 56ebf69902..384d6fc977 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb @@ -1,3 +1,3 @@ class Bundler::ConnectionPool - VERSION = "2.3.0" + VERSION = "2.4.1" end diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb index 880170c06b..dd796d1021 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb @@ -30,7 +30,6 @@ class Bundler::ConnectionPool METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } end - # rubocop:disable Style/MethodMissingSuper # rubocop:disable Style/MissingRespondToMissing if ::RUBY_VERSION >= "3.0.0" def method_missing(name, *args, **kwargs, &block) diff --git a/lib/bundler/vendor/fileutils/.document b/lib/bundler/vendor/fileutils/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/fileutils/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb index 8f8faf30c8..6db19caf6f 100644 --- a/lib/bundler/vendor/fileutils/lib/fileutils.rb +++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb @@ -3,106 +3,184 @@ begin require 'rbconfig' rescue LoadError - # for make mjit-headers + # for make rjit-headers end +# Namespace for file utility methods for copying, moving, removing, etc. # -# = fileutils.rb +# == What's Here # -# Copyright (c) 2000-2007 Minero Aoki +# First, what’s elsewhere. \Module \Bundler::FileUtils: # -# This program is free software. -# You can distribute/modify this program under the same terms of ruby. +# - Inherits from {class Object}[rdoc-ref:Object]. +# - Supplements {class File}[rdoc-ref:File] +# (but is not included or extended there). # -# == module Bundler::FileUtils +# Here, module \Bundler::FileUtils provides methods that are useful for: # -# Namespace for several file utility methods for copying, moving, removing, etc. +# - {Creating}[rdoc-ref:FileUtils@Creating]. +# - {Deleting}[rdoc-ref:FileUtils@Deleting]. +# - {Querying}[rdoc-ref:FileUtils@Querying]. +# - {Setting}[rdoc-ref:FileUtils@Setting]. +# - {Comparing}[rdoc-ref:FileUtils@Comparing]. +# - {Copying}[rdoc-ref:FileUtils@Copying]. +# - {Moving}[rdoc-ref:FileUtils@Moving]. +# - {Options}[rdoc-ref:FileUtils@Options]. # -# === Module Functions +# === Creating # -# require 'bundler/vendor/fileutils/lib/fileutils' +# - ::mkdir: Creates directories. +# - ::mkdir_p, ::makedirs, ::mkpath: Creates directories, +# also creating ancestor directories as needed. +# - ::link_entry: Creates a hard link. +# - ::ln, ::link: Creates hard links. +# - ::ln_s, ::symlink: Creates symbolic links. +# - ::ln_sf: Creates symbolic links, overwriting if necessary. +# - ::ln_sr: Creates symbolic links relative to targets # -# Bundler::FileUtils.cd(dir, **options) -# Bundler::FileUtils.cd(dir, **options) {|dir| block } -# Bundler::FileUtils.pwd() -# Bundler::FileUtils.mkdir(dir, **options) -# Bundler::FileUtils.mkdir(list, **options) -# Bundler::FileUtils.mkdir_p(dir, **options) -# Bundler::FileUtils.mkdir_p(list, **options) -# Bundler::FileUtils.rmdir(dir, **options) -# Bundler::FileUtils.rmdir(list, **options) -# Bundler::FileUtils.ln(target, link, **options) -# Bundler::FileUtils.ln(targets, dir, **options) -# Bundler::FileUtils.ln_s(target, link, **options) -# Bundler::FileUtils.ln_s(targets, dir, **options) -# Bundler::FileUtils.ln_sf(target, link, **options) -# Bundler::FileUtils.cp(src, dest, **options) -# Bundler::FileUtils.cp(list, dir, **options) -# Bundler::FileUtils.cp_r(src, dest, **options) -# Bundler::FileUtils.cp_r(list, dir, **options) -# Bundler::FileUtils.mv(src, dest, **options) -# Bundler::FileUtils.mv(list, dir, **options) -# Bundler::FileUtils.rm(list, **options) -# Bundler::FileUtils.rm_r(list, **options) -# Bundler::FileUtils.rm_rf(list, **options) -# Bundler::FileUtils.install(src, dest, **options) -# Bundler::FileUtils.chmod(mode, list, **options) -# Bundler::FileUtils.chmod_R(mode, list, **options) -# Bundler::FileUtils.chown(user, group, list, **options) -# Bundler::FileUtils.chown_R(user, group, list, **options) -# Bundler::FileUtils.touch(list, **options) +# === Deleting # -# Possible <tt>options</tt> are: +# - ::remove_dir: Removes a directory and its descendants. +# - ::remove_entry: Removes an entry, including its descendants if it is a directory. +# - ::remove_entry_secure: Like ::remove_entry, but removes securely. +# - ::remove_file: Removes a file entry. +# - ::rm, ::remove: Removes entries. +# - ::rm_f, ::safe_unlink: Like ::rm, but removes forcibly. +# - ::rm_r: Removes entries and their descendants. +# - ::rm_rf, ::rmtree: Like ::rm_r, but removes forcibly. +# - ::rmdir: Removes directories. # -# <tt>:force</tt> :: forced operation (rewrite files if exist, remove -# directories if not empty, etc.); -# <tt>:verbose</tt> :: print command to be run, in bash syntax, before -# performing it; -# <tt>:preserve</tt> :: preserve object's group, user and modification -# time on copying; -# <tt>:noop</tt> :: no changes are made (usable in combination with -# <tt>:verbose</tt> which will print the command to run) +# === Querying # -# Each method documents the options that it honours. See also ::commands, -# ::options and ::options_of methods to introspect which command have which -# options. +# - ::pwd, ::getwd: Returns the path to the working directory. +# - ::uptodate?: Returns whether a given entry is newer than given other entries. # -# All methods that have the concept of a "source" file or directory can take -# either one file or a list of files in that argument. See the method -# documentation for examples. +# === Setting # -# There are some `low level' methods, which do not accept keyword arguments: +# - ::cd, ::chdir: Sets the working directory. +# - ::chmod: Sets permissions for an entry. +# - ::chmod_R: Sets permissions for an entry and its descendants. +# - ::chown: Sets the owner and group for entries. +# - ::chown_R: Sets the owner and group for entries and their descendants. +# - ::touch: Sets modification and access times for entries, +# creating if necessary. # -# Bundler::FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) -# Bundler::FileUtils.copy_file(src, dest, preserve = false, dereference = true) -# Bundler::FileUtils.copy_stream(srcstream, deststream) -# Bundler::FileUtils.remove_entry(path, force = false) -# Bundler::FileUtils.remove_entry_secure(path, force = false) -# Bundler::FileUtils.remove_file(path, force = false) -# Bundler::FileUtils.compare_file(path_a, path_b) -# Bundler::FileUtils.compare_stream(stream_a, stream_b) -# Bundler::FileUtils.uptodate?(file, cmp_list) +# === Comparing # -# == module Bundler::FileUtils::Verbose +# - ::compare_file, ::cmp, ::identical?: Returns whether two entries are identical. +# - ::compare_stream: Returns whether two streams are identical. # -# This module has all methods of Bundler::FileUtils module, but it outputs messages -# before acting. This equates to passing the <tt>:verbose</tt> flag to methods -# in Bundler::FileUtils. +# === Copying # -# == module Bundler::FileUtils::NoWrite +# - ::copy_entry: Recursively copies an entry. +# - ::copy_file: Copies an entry. +# - ::copy_stream: Copies a stream. +# - ::cp, ::copy: Copies files. +# - ::cp_lr: Recursively creates hard links. +# - ::cp_r: Recursively copies files, retaining mode, owner, and group. +# - ::install: Recursively copies files, optionally setting mode, +# owner, and group. # -# This module has all methods of Bundler::FileUtils module, but never changes -# files/directories. This equates to passing the <tt>:noop</tt> flag to methods -# in Bundler::FileUtils. +# === Moving # -# == module Bundler::FileUtils::DryRun +# - ::mv, ::move: Moves entries. # -# This module has all methods of Bundler::FileUtils module, but never changes -# files/directories. This equates to passing the <tt>:noop</tt> and -# <tt>:verbose</tt> flags to methods in Bundler::FileUtils. +# === Options +# +# - ::collect_method: Returns the names of methods that accept a given option. +# - ::commands: Returns the names of methods that accept options. +# - ::have_option?: Returns whether a given method accepts a given option. +# - ::options: Returns all option names. +# - ::options_of: Returns the names of the options for a given method. +# +# == Path Arguments +# +# Some methods in \Bundler::FileUtils accept _path_ arguments, +# which are interpreted as paths to filesystem entries: +# +# - If the argument is a string, that value is the path. +# - If the argument has method +:to_path+, it is converted via that method. +# - If the argument has method +:to_str+, it is converted via that method. +# +# == About the Examples +# +# Some examples here involve trees of file entries. +# For these, we sometimes display trees using the +# {tree command-line utility}[https://en.wikipedia.org/wiki/Tree_(command)], +# which is a recursive directory-listing utility that produces +# a depth-indented listing of files and directories. +# +# We use a helper method to launch the command and control the format: +# +# def tree(dirpath = '.') +# command = "tree --noreport --charset=ascii #{dirpath}" +# system(command) +# end +# +# To illustrate: +# +# tree('src0') +# # => src0 +# # |-- sub0 +# # | |-- src0.txt +# # | `-- src1.txt +# # `-- sub1 +# # |-- src2.txt +# # `-- src3.txt +# +# == Avoiding the TOCTTOU Vulnerability +# +# For certain methods that recursively remove entries, +# there is a potential vulnerability called the +# {Time-of-check to time-of-use}[https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use], +# or TOCTTOU, vulnerability that can exist when: +# +# - An ancestor directory of the entry at the target path is world writable; +# such directories include <tt>/tmp</tt>. +# - The directory tree at the target path includes: +# +# - A world-writable descendant directory. +# - A symbolic link. +# +# To avoid that vulnerability, you can use this method to remove entries: +# +# - Bundler::FileUtils.remove_entry_secure: removes recursively +# if the target path points to a directory. +# +# Also available are these methods, +# each of which calls \Bundler::FileUtils.remove_entry_secure: +# +# - Bundler::FileUtils.rm_r with keyword argument <tt>secure: true</tt>. +# - Bundler::FileUtils.rm_rf with keyword argument <tt>secure: true</tt>. +# +# Finally, this method for moving entries calls \Bundler::FileUtils.remove_entry_secure +# if the source and destination are on different file systems +# (which means that the "move" is really a copy and remove): +# +# - Bundler::FileUtils.mv with keyword argument <tt>secure: true</tt>. +# +# \Method \Bundler::FileUtils.remove_entry_secure removes securely +# by applying a special pre-process: +# +# - If the target path points to a directory, this method uses methods +# {File#chown}[rdoc-ref:File#chown] +# and {File#chmod}[rdoc-ref:File#chmod] +# in removing directories. +# - The owner of the target directory should be either the current process +# or the super user (root). +# +# WARNING: You must ensure that *ALL* parent directories cannot be +# moved by other untrusted users. For example, parent directories +# should not be owned by untrusted users, and should not be world +# writable except when the sticky bit is set. +# +# For details of this security vulnerability, see Perl cases: +# +# - {CVE-2005-0448}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448]. +# - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452]. # module Bundler::FileUtils - VERSION = "1.4.1" + VERSION = "1.7.2" def self.private_module_function(name) #:nodoc: module_function name @@ -110,7 +188,11 @@ module Bundler::FileUtils end # - # Returns the name of the current directory. + # Returns a string containing the path to the current directory: + # + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # + # Related: Bundler::FileUtils.cd. # def pwd Dir.pwd @@ -120,19 +202,38 @@ module Bundler::FileUtils alias getwd pwd module_function :getwd + # Changes the working directory to the given +dir+, which + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]: + # + # With no block given, + # changes the current directory to the directory at +dir+; returns zero: + # + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # Bundler::FileUtils.cd('..') + # Bundler::FileUtils.pwd # => "/rdoc" + # Bundler::FileUtils.cd('fileutils') + # + # With a block given, changes the current directory to the directory + # at +dir+, calls the block with argument +dir+, + # and restores the original current directory; returns the block's value: # - # Changes the current directory to the directory +dir+. + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" + # Bundler::FileUtils.cd('..') { |arg| [arg, Bundler::FileUtils.pwd] } # => ["..", "/rdoc"] + # Bundler::FileUtils.pwd # => "/rdoc/fileutils" # - # If this method is called with block, resumes to the previous - # working directory after the block execution has finished. + # Keyword arguments: # - # Bundler::FileUtils.cd('/') # change directory + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.cd('/', verbose: true) # change directory and report it + # Bundler::FileUtils.cd('..') + # Bundler::FileUtils.cd('fileutils') # - # Bundler::FileUtils.cd('/') do # change directory - # # ... # do something - # end # return to original directory + # Output: + # + # cd .. + # cd fileutils + # + # Related: Bundler::FileUtils.pwd. # def cd(dir, verbose: nil, &block) # :yield: dir fu_output_message "cd #{dir}" if verbose @@ -146,11 +247,19 @@ module Bundler::FileUtils module_function :chdir # - # Returns true if +new+ is newer than all +old_list+. - # Non-existent files are older than any file. + # Returns +true+ if the file at path +new+ + # is newer than all the files at paths in array +old_list+; + # +false+ otherwise. + # + # Argument +new+ and the elements of +old_list+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]: + # + # Bundler::FileUtils.uptodate?('Rakefile', ['Gemfile', 'README.md']) # => true + # Bundler::FileUtils.uptodate?('Gemfile', ['Rakefile', 'README.md']) # => false # - # Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ - # system 'make hello.o' + # A non-existent file is considered to be infinitely old. + # + # Related: Bundler::FileUtils.touch. # def uptodate?(new, old_list) return false unless File.exist?(new) @@ -170,12 +279,39 @@ module Bundler::FileUtils private_module_function :remove_trailing_slash # - # Creates one or more directories. + # Creates directories at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, creates a directory at each +path+ in +list+ + # by calling: <tt>Dir.mkdir(path, mode)</tt>; + # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]: + # + # Bundler::FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"] + # Bundler::FileUtils.mkdir('tmp4') # => ["tmp4"] + # + # Keyword arguments: + # + # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; + # see {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not create directories. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mkdir(%w[tmp0 tmp1], verbose: true) + # Bundler::FileUtils.mkdir(%w[tmp2 tmp3], mode: 0700, verbose: true) + # + # Output: + # + # mkdir tmp0 tmp1 + # mkdir -m 700 tmp2 tmp3 # - # Bundler::FileUtils.mkdir 'test' - # Bundler::FileUtils.mkdir %w(tmp data) - # Bundler::FileUtils.mkdir 'notexist', noop: true # Does not really create. - # Bundler::FileUtils.mkdir 'tmp', mode: 0700 + # Raises an exception if any path points to an existing + # file or directory, or if for any reason a directory cannot be created. + # + # Related: Bundler::FileUtils.mkdir_p. # def mkdir(list, mode: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -189,40 +325,56 @@ module Bundler::FileUtils module_function :mkdir # - # Creates a directory and all its parent directories. - # For example, + # Creates directories at the paths in the given +list+ + # (a single path or an array of paths), + # also creating ancestor directories as needed; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, creates a directory at each +path+ in +list+, + # along with any needed ancestor directories, + # by calling: <tt>Dir.mkdir(path, mode)</tt>; + # see {Dir.mkdir}[rdoc-ref:Dir.mkdir]: + # + # Bundler::FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] + # Bundler::FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"] + # + # Keyword arguments: + # + # - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>; + # see {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not create directories. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mkdir_p(%w[tmp0 tmp1], verbose: true) + # Bundler::FileUtils.mkdir_p(%w[tmp2 tmp3], mode: 0700, verbose: true) + # + # Output: # - # Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby' + # mkdir -p tmp0 tmp1 + # mkdir -p -m 700 tmp2 tmp3 # - # causes to make following directories, if they do not exist. + # Raises an exception if for any reason a directory cannot be created. # - # * /usr - # * /usr/local - # * /usr/local/lib - # * /usr/local/lib/ruby + # Bundler::FileUtils.mkpath and Bundler::FileUtils.makedirs are aliases for Bundler::FileUtils.mkdir_p. # - # You can pass several directories at a time in a list. + # Related: Bundler::FileUtils.mkdir. # def mkdir_p(list, mode: nil, noop: nil, verbose: nil) list = fu_list(list) fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose return *list if noop - list.map {|path| remove_trailing_slash(path)}.each do |path| - # optimize for the most common case - begin - fu_mkdir path, mode - next - rescue SystemCallError - next if File.directory?(path) - end + list.each do |item| + path = remove_trailing_slash(item) stack = [] - until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" + until File.directory?(path) || File.dirname(path) == path stack.push path path = File.dirname(path) end - stack.pop # root directory should exist stack.reverse_each do |dir| begin fu_mkdir dir, mode @@ -253,12 +405,39 @@ module Bundler::FileUtils private_module_function :fu_mkdir # - # Removes one or more directories. + # Removes directories at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # With no keyword arguments, removes the directory at each +path+ in +list+, + # by calling: <tt>Dir.rmdir(path)</tt>; + # see {Dir.rmdir}[rdoc-ref:Dir.rmdir]: + # + # Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"] + # Bundler::FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"] + # + # Keyword arguments: + # + # - <tt>parents: true</tt> - removes successive ancestor directories + # if empty. + # - <tt>noop: true</tt> - does not remove directories. + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.rmdir 'somedir' - # Bundler::FileUtils.rmdir %w(somedir anydir otherdir) - # # Does not really remove directory; outputs message. - # Bundler::FileUtils.rmdir 'somedir', verbose: true, noop: true + # Bundler::FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3], parents: true, verbose: true) + # Bundler::FileUtils.rmdir('tmp4/tmp5', parents: true, verbose: true) + # + # Output: + # + # rmdir -p tmp0/tmp1 tmp2/tmp3 + # rmdir -p tmp4/tmp5 + # + # Raises an exception if a directory does not exist + # or if for any reason a directory cannot be removed. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rmdir(list, parents: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -279,26 +458,60 @@ module Bundler::FileUtils end module_function :rmdir + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # When +src+ is the path to an existing file + # and +dest+ is the path to a non-existent file, + # creates a hard link at +dest+ pointing to +src+; returns zero: + # + # Dir.children('tmp0/') # => ["t.txt"] + # Dir.children('tmp1/') # => [] + # Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk') # => 0 + # Dir.children('tmp1/') # => ["t.lnk"] + # + # When +src+ is the path to an existing file + # and +dest+ is the path to an existing directory, + # creates a hard link at <tt>dest/src</tt> pointing to +src+; returns zero: # - # :call-seq: - # Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil) + # Dir.children('tmp2') # => ["t.dat"] + # Dir.children('tmp3') # => [] + # Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3') # => 0 + # Dir.children('tmp3') # => ["t.dat"] # - # In the first form, creates a hard link +link+ which points to +target+. - # If +link+ already exists, raises Errno::EEXIST. - # But if the +force+ option is set, overwrites +link+. + # When +src+ is an array of paths to existing files + # and +dest+ is the path to an existing directory, + # then for each path +target+ in +src+, + # creates a hard link at <tt>dest/target</tt> pointing to +target+; + # returns +src+: # - # Bundler::FileUtils.ln 'gcc', 'cc', verbose: true - # Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' + # Dir.children('tmp4/') # => [] + # Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/') # => ["tmp0/t.txt", "tmp2/t.dat"] + # Dir.children('tmp4/') # => ["t.dat", "t.txt"] # - # In the second form, creates a link +dir/target+ pointing to +target+. - # In the third form, creates several hard links in the directory +dir+, - # pointing to each item in +targets+. - # If +dir+ is not a directory, raises Errno::ENOTDIR. + # Keyword arguments: # - # Bundler::FileUtils.cd '/sbin' - # Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. + # - <tt>force: true</tt> - overwrites +dest+ if it exists. + # - <tt>noop: true</tt> - does not create links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk', verbose: true) + # Bundler::FileUtils.ln('tmp2/t.dat', 'tmp3', verbose: true) + # Bundler::FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/', verbose: true) + # + # Output: + # + # ln tmp0/t.txt tmp1/t.lnk + # ln tmp2/t.dat tmp3 + # ln tmp0/t.txt tmp2/t.dat tmp4/ + # + # Raises an exception if +dest+ is the path to an existing file + # and keyword argument +force+ is not +true+. + # + # Related: Bundler::FileUtils.link_entry (has different options). # def ln(src, dest, force: nil, noop: nil, verbose: nil) fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose @@ -313,28 +526,103 @@ module Bundler::FileUtils alias link ln module_function :link - # - # Hard link +src+ to +dest+. If +src+ is a directory, this method links - # all its contents recursively. If +dest+ is a directory, links - # +src+ to +dest/src+. - # - # +src+ can be a list of files. - # - # If +dereference_root+ is true, this method dereference tree root. - # - # If +remove_destination+ is true, this method removes each destination file before copy. - # - # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true - # Bundler::FileUtils.cp_lr 'lib/', site_ruby + '/mylib' - # - # # Examples of linking several files to target directory. - # Bundler::FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail' - # Bundler::FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true - # - # # If you want to link all contents of a directory instead of the - # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, - # # use the following code. - # Bundler::FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't. + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a directory and +dest+ does not exist, + # creates links +dest+ and descendents pointing to +src+ and its descendents: + # + # tree('src0') + # # => src0 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # File.exist?('dest0') # => false + # Bundler::FileUtils.cp_lr('src0', 'dest0') + # tree('dest0') + # # => dest0 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ and +dest+ are both paths to directories, + # creates links <tt>dest/src</tt> and descendents + # pointing to +src+ and its descendents: + # + # tree('src1') + # # => src1 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp_lr('src1', 'dest1') + # tree('dest1') + # # => dest1 + # # `-- src1 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ is an array of paths to entries and +dest+ is the path to a directory, + # for each path +filepath+ in +src+, creates a link at <tt>dest/filepath</tt> + # pointing to that path: + # + # tree('src2') + # # => src2 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest2') + # Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2') + # tree('dest2') + # # => dest2 + # # |-- sub0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- sub1 + # # |-- src2.txt + # # `-- src3.txt + # + # Keyword arguments: + # + # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link, + # does not dereference it. + # - <tt>noop: true</tt> - does not create links. + # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp_lr('src0', 'dest0', noop: true, verbose: true) + # Bundler::FileUtils.cp_lr('src1', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2', noop: true, verbose: true) + # + # Output: + # + # cp -lr src0 dest0 + # cp -lr src1 dest1 + # cp -lr src2/sub0 src2/sub1 dest2 + # + # Raises an exception if +dest+ is the path to an existing file or directory + # and keyword argument <tt>remove_destination: true</tt> is not given. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def cp_lr(src, dest, noop: nil, verbose: nil, dereference_root: true, remove_destination: false) @@ -346,27 +634,79 @@ module Bundler::FileUtils end module_function :cp_lr + # Creates {symbolic links}[https://en.wikipedia.org/wiki/Symbolic_link]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to an existing file: + # + # - When +dest+ is the path to a non-existent file, + # creates a symbolic link at +dest+ pointing to +src+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt') + # File.symlink?('dest0.txt') # => true + # + # - When +dest+ is the path to an existing file, + # creates a symbolic link at +dest+ pointing to +src+ + # if and only if keyword argument <tt>force: true</tt> is given + # (raises an exception otherwise): # - # :call-seq: - # Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil) - # Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.touch('dest1.txt') + # Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt', force: true) + # FileTest.symlink?('dest1.txt') # => true # - # In the first form, creates a symbolic link +link+ which points to +target+. - # If +link+ already exists, raises Errno::EEXIST. - # But if the <tt>force</tt> option is set, overwrites +link+. + # Bundler::FileUtils.ln_s('src1.txt', 'dest1.txt') # Raises Errno::EEXIST. # - # Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' - # Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true + # If +dest+ is the path to a directory, + # creates a symbolic link at <tt>dest/src</tt> pointing to +src+: # - # In the second form, creates a link +dir/target+ pointing to +target+. - # In the third form, creates several symbolic links in the directory +dir+, - # pointing to each item in +targets+. - # If +dir+ is not a directory, raises Errno::ENOTDIR. + # Bundler::FileUtils.touch('src2.txt') + # Bundler::FileUtils.mkdir('destdir2') + # Bundler::FileUtils.ln_s('src2.txt', 'destdir2') + # File.symlink?('destdir2/src2.txt') # => true # - # Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin' + # If +src+ is an array of paths to existing files and +dest+ is a directory, + # for each child +child+ in +src+ creates a symbolic link <tt>dest/child</tt> + # pointing to +child+: # - def ln_s(src, dest, force: nil, noop: nil, verbose: nil) + # Bundler::FileUtils.mkdir('srcdir3') + # Bundler::FileUtils.touch('srcdir3/src0.txt') + # Bundler::FileUtils.touch('srcdir3/src1.txt') + # Bundler::FileUtils.mkdir('destdir3') + # Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3') + # File.symlink?('destdir3/src0.txt') # => true + # File.symlink?('destdir3/src1.txt') # => true + # + # Keyword arguments: + # + # - <tt>force: true</tt> - overwrites +dest+ if it exists. + # - <tt>relative: false</tt> - create links relative to +dest+. + # - <tt>noop: true</tt> - does not create links. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.ln_s('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.ln_s('src1.txt', 'destdir1', noop: true, verbose: true) + # Bundler::FileUtils.ln_s('src2.txt', 'dest2.txt', force: true, noop: true, verbose: true) + # Bundler::FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3', noop: true, verbose: true) + # + # Output: + # + # ln -s src0.txt dest0.txt + # ln -s src1.txt destdir1 + # ln -sf src2.txt dest2.txt + # ln -s srcdir3/src0.txt srcdir3/src1.txt destdir3 + # + # Related: Bundler::FileUtils.ln_sf. + # + def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil) + if relative + return ln_sr(src, dest, force: force, noop: noop, verbose: verbose) + end fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose return if noop fu_each_src_dest0(src, dest) do |s,d| @@ -379,29 +719,95 @@ module Bundler::FileUtils alias symlink ln_s module_function :symlink - # - # :call-seq: - # Bundler::FileUtils.ln_sf(*args) - # - # Same as - # - # Bundler::FileUtils.ln_s(*args, force: true) + # Like Bundler::FileUtils.ln_s, but always with keyword argument <tt>force: true</tt> given. # def ln_sf(src, dest, noop: nil, verbose: nil) ln_s src, dest, force: true, noop: noop, verbose: verbose end module_function :ln_sf + # Like Bundler::FileUtils.ln_s, but create links relative to +dest+. # - # Hard links a file system entry +src+ to +dest+. - # If +src+ is a directory, this method links its contents recursively. + def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil) + options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" + dest = File.path(dest) + srcs = Array(src) + link = proc do |s, target_dir_p = true| + s = File.path(s) + if target_dir_p + d = File.join(destdirs = dest, File.basename(s)) + else + destdirs = File.dirname(d = dest) + end + destdirs = fu_split_path(File.realpath(destdirs)) + if fu_starting_path?(s) + srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s))) + base = fu_relative_components_from(srcdirs, destdirs) + s = File.join(*base) + else + srcdirs = fu_clean_components(*fu_split_path(s)) + base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs) + while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last) + srcdirs.shift + base.pop + end + s = File.join(*base, *srcdirs) + end + fu_output_message "ln -s#{options} #{s} #{d}" if verbose + next if noop + remove_file d, true if force + File.symlink s, d + end + case srcs.size + when 0 + when 1 + link[srcs[0], target_directory && File.directory?(dest)] + else + srcs.each(&link) + end + end + module_function :ln_sr + + # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a file and +dest+ does not exist, + # creates a hard link at +dest+ pointing to +src+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.link_entry('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true # - # Both of +src+ and +dest+ must be a path name. - # +src+ must exist, +dest+ must not exist. + # If +src+ is the path to a directory and +dest+ does not exist, + # recursively creates hard links at +dest+ pointing to paths in +src+: # - # If +dereference_root+ is true, this method dereferences the tree root. + # Bundler::FileUtils.mkdir_p(['src1/dir0', 'src1/dir1']) + # src_file_paths = [ + # 'src1/dir0/t0.txt', + # 'src1/dir0/t1.txt', + # 'src1/dir1/t2.txt', + # 'src1/dir1/t3.txt', + # ] + # Bundler::FileUtils.touch(src_file_paths) + # File.directory?('dest1') # => true + # Bundler::FileUtils.link_entry('src1', 'dest1') + # File.file?('dest1/dir0/t0.txt') # => true + # File.file?('dest1/dir0/t1.txt') # => true + # File.file?('dest1/dir1/t2.txt') # => true + # File.file?('dest1/dir1/t3.txt') # => true # - # If +remove_destination+ is true, this method removes each destination file before copy. + # Keyword arguments: + # + # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link. + # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # + # Raises an exception if +dest+ is the path to an existing file or directory + # and keyword argument <tt>remove_destination: true</tt> is not given. + # + # Related: Bundler::FileUtils.ln (has different options). # def link_entry(src, dest, dereference_root = false, remove_destination = false) Entry_.new(src, nil, dereference_root).traverse do |ent| @@ -412,16 +818,57 @@ module Bundler::FileUtils end module_function :link_entry + # Copies files. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a file and +dest+ is not the path to a directory, + # copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.cp('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true + # + # If +src+ is the path to a file and +dest+ is the path to a directory, + # copies +src+ to <tt>dest/src</tt>: + # + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp('src1.txt', 'dest1') + # File.file?('dest1/src1.txt') # => true # - # Copies a file content +src+ to +dest+. If +dest+ is a directory, - # copies +src+ to +dest/src+. + # If +src+ is an array of paths to files and +dest+ is the path to a directory, + # copies from each +src+ to +dest+: # - # If +src+ is a list of files, then +dest+ must be a directory. + # src_file_paths = ['src2.txt', 'src2.dat'] + # Bundler::FileUtils.touch(src_file_paths) + # Bundler::FileUtils.mkdir('dest2') + # Bundler::FileUtils.cp(src_file_paths, 'dest2') + # File.file?('dest2/src2.txt') # => true + # File.file?('dest2/src2.dat') # => true # - # Bundler::FileUtils.cp 'eval.c', 'eval.c.org' - # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' - # Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true - # Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink + # Keyword arguments: + # + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>noop: true</tt> - does not copy files. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.cp('src1.txt', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp(src_file_paths, 'dest2', noop: true, verbose: true) + # + # Output: + # + # cp src0.txt dest0.txt + # cp src1.txt dest1 + # cp src2.txt src2.dat dest2 + # + # Raises an exception if +src+ is a directory. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def cp(src, dest, preserve: nil, noop: nil, verbose: nil) fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose @@ -435,30 +882,105 @@ module Bundler::FileUtils alias copy cp module_function :copy - # - # Copies +src+ to +dest+. If +src+ is a directory, this method copies - # all its contents recursively. If +dest+ is a directory, copies - # +src+ to +dest/src+. - # - # +src+ can be a list of files. - # - # If +dereference_root+ is true, this method dereference tree root. - # - # If +remove_destination+ is true, this method removes each destination file before copy. - # - # # Installing Ruby library "mylib" under the site_ruby - # Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true - # Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib' - # - # # Examples of copying several files to target directory. - # Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' - # Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true - # - # # If you want to copy all contents of a directory instead of the - # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, - # # use following code. - # Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src, - # # but this doesn't. + # Recursively copies files. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # The mode, owner, and group are retained in the copy; + # to change those, use Bundler::FileUtils.install instead. + # + # If +src+ is the path to a file and +dest+ is not the path to a directory, + # copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true + # + # If +src+ is the path to a file and +dest+ is the path to a directory, + # copies +src+ to <tt>dest/src</tt>: + # + # Bundler::FileUtils.touch('src1.txt') + # Bundler::FileUtils.mkdir('dest1') + # Bundler::FileUtils.cp_r('src1.txt', 'dest1') + # File.file?('dest1/src1.txt') # => true + # + # If +src+ is the path to a directory and +dest+ does not exist, + # recursively copies +src+ to +dest+: + # + # tree('src2') + # # => src2 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.exist?('dest2') # => false + # Bundler::FileUtils.cp_r('src2', 'dest2') + # tree('dest2') + # # => dest2 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ and +dest+ are paths to directories, + # recursively copies +src+ to <tt>dest/src</tt>: + # + # tree('src3') + # # => src3 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.mkdir('dest3') + # Bundler::FileUtils.cp_r('src3', 'dest3') + # tree('dest3') + # # => dest3 + # # `-- src3 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # + # If +src+ is an array of paths and +dest+ is a directory, + # recursively copies from each path in +src+ to +dest+; + # the paths in +src+ may point to files and/or directories. + # + # Keyword arguments: + # + # - <tt>dereference_root: false</tt> - if +src+ is a symbolic link, + # does not dereference it. + # - <tt>noop: true</tt> - does not copy files. + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.cp_r('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src1.txt', 'dest1', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src2', 'dest2', noop: true, verbose: true) + # Bundler::FileUtils.cp_r('src3', 'dest3', noop: true, verbose: true) + # + # Output: + # + # cp -r src0.txt dest0.txt + # cp -r src1.txt dest1 + # cp -r src2 dest2 + # cp -r src3 dest3 + # + # Raises an exception of +src+ is the path to a directory + # and +dest+ is the path to a file. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil, dereference_root: true, remove_destination: nil) @@ -470,21 +992,50 @@ module Bundler::FileUtils end module_function :cp_r + # Recursively copies files from +src+ to +dest+. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ is the path to a file, copies +src+ to +dest+: + # + # Bundler::FileUtils.touch('src0.txt') + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.copy_entry('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true # - # Copies a file system entry +src+ to +dest+. - # If +src+ is a directory, this method copies its contents recursively. - # This method preserves file types, c.f. symlink, directory... - # (FIFO, device files and etc. are not supported yet) + # If +src+ is a directory, recursively copies +src+ to +dest+: # - # Both of +src+ and +dest+ must be a path name. - # +src+ must exist, +dest+ must not exist. + # tree('src1') + # # => src1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.copy_entry('src1', 'dest1') + # tree('dest1') + # # => dest1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt # - # If +preserve+ is true, this method preserves owner, group, and - # modified time. Permissions are copied regardless +preserve+. + # The recursive copying preserves file types for regular files, + # directories, and symbolic links; + # other file types (FIFO streams, device files, etc.) are not supported. # - # If +dereference_root+ is true, this method dereference tree root. + # Keyword arguments: # - # If +remove_destination+ is true, this method removes each destination file before copy. + # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link, + # follows the link. + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) if dereference_root @@ -502,9 +1053,25 @@ module Bundler::FileUtils end module_function :copy_entry + # Copies file from +src+ to +dest+, which should not be directories. + # + # Arguments +src+ and +dest+ + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Examples: + # + # Bundler::FileUtils.touch('src0.txt') + # Bundler::FileUtils.copy_file('src0.txt', 'dest0.txt') + # File.file?('dest0.txt') # => true + # + # Keyword arguments: # - # Copies file contents of +src+ to +dest+. - # Both of +src+ and +dest+ must be a path name. + # - <tt>dereference: false</tt> - if +src+ is a symbolic link, + # does not follow the link. + # - <tt>preserve: true</tt> - preserves file times. + # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_file(src, dest, preserve = false, dereference = true) ent = Entry_.new(src, nil, dereference) @@ -513,25 +1080,79 @@ module Bundler::FileUtils end module_function :copy_file + # Copies \IO stream +src+ to \IO stream +dest+ via + # {IO.copy_stream}[rdoc-ref:IO.copy_stream]. # - # Copies stream +src+ to +dest+. - # +src+ must respond to #read(n) and - # +dest+ must respond to #write(str). + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def copy_stream(src, dest) IO.copy_stream(src, dest) end module_function :copy_stream - # - # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different - # disk partition, the file is copied then the original file is removed. - # - # Bundler::FileUtils.mv 'badname.rb', 'goodname.rb' - # Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error - # - # Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/' - # Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true + # Moves entries. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # If +src+ and +dest+ are on different file systems, + # first copies, then removes +src+. + # + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # If +src+ is the path to a single file or directory and +dest+ does not exist, + # moves +src+ to +dest+: + # + # tree('src0') + # # => src0 + # # |-- src0.txt + # # `-- src1.txt + # File.exist?('dest0') # => false + # Bundler::FileUtils.mv('src0', 'dest0') + # File.exist?('src0') # => false + # tree('dest0') + # # => dest0 + # # |-- src0.txt + # # `-- src1.txt + # + # If +src+ is an array of paths to files and directories + # and +dest+ is the path to a directory, + # copies from each path in the array to +dest+: + # + # File.file?('src1.txt') # => true + # tree('src1') + # # => src1 + # # |-- src.dat + # # `-- src.txt + # Dir.empty?('dest1') # => true + # Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1') + # tree('dest1') + # # => dest1 + # # |-- src1 + # # | |-- src.dat + # # | `-- src.txt + # # `-- src1.txt + # + # Keyword arguments: + # + # - <tt>force: true</tt> - if the move includes removing +src+ + # (that is, if +src+ and +dest+ are on different file systems), + # ignores raised exceptions of StandardError and its descendants. + # - <tt>noop: true</tt> - does not move files. + # - <tt>secure: true</tt> - removes +src+ securely; + # see details at Bundler::FileUtils.remove_entry_secure. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.mv('src0', 'dest0', noop: true, verbose: true) + # Bundler::FileUtils.mv(['src1.txt', 'src1'], 'dest1', noop: true, verbose: true) + # + # Output: + # + # mv src0 dest0 + # mv src1.txt src1 dest1 # def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil) fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose @@ -565,13 +1186,32 @@ module Bundler::FileUtils alias move mv module_function :move + # Removes entries at the paths in the given +list+ + # (a single path or an array of paths) + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. # - # Remove file(s) specified in +list+. This method cannot remove directories. - # All StandardErrors are ignored when the :force option is set. + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # Bundler::FileUtils.rm %w( junk.txt dust.txt ) - # Bundler::FileUtils.rm Dir.glob('*.so') - # Bundler::FileUtils.rm 'NotExistFile', force: true # never raises exception + # With no keyword arguments, removes files at the paths given in +list+: + # + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) + # Bundler::FileUtils.rm(['src0.dat', 'src0.txt']) # => ["src0.dat", "src0.txt"] + # + # Keyword arguments: + # + # - <tt>force: true</tt> - ignores raised exceptions of StandardError + # and its descendants. + # - <tt>noop: true</tt> - does not remove files; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.rm(['src0.dat', 'src0.txt'], noop: true, verbose: true) + # + # Output: + # + # rm src0.dat src0.txt + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm(list, force: nil, noop: nil, verbose: nil) list = fu_list(list) @@ -587,10 +1227,16 @@ module Bundler::FileUtils alias remove rm module_function :remove + # Equivalent to: + # + # Bundler::FileUtils.rm(list, force: true, **kwargs) # - # Equivalent to + # Argument +list+ (a single path or an array of paths) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # Bundler::FileUtils.rm(list, force: true) + # See Bundler::FileUtils.rm for keyword arguments. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_f(list, noop: nil, verbose: nil) rm list, force: true, noop: noop, verbose: verbose @@ -600,24 +1246,55 @@ module Bundler::FileUtils alias safe_unlink rm_f module_function :safe_unlink + # Removes entries at the paths in the given +list+ + # (a single path or an array of paths); + # returns +list+, if it is an array, <tt>[list]</tt> otherwise. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # For each file path, removes the file at that path: + # + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) + # Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt']) + # File.exist?('src0.txt') # => false + # File.exist?('src0.dat') # => false + # + # For each directory path, recursively removes files and directories: + # + # tree('src1') + # # => src1 + # # |-- dir0 + # # | |-- src0.txt + # # | `-- src1.txt + # # `-- dir1 + # # |-- src2.txt + # # `-- src3.txt + # Bundler::FileUtils.rm_r('src1') + # File.exist?('src1') # => false + # + # Keyword arguments: + # + # - <tt>force: true</tt> - ignores raised exceptions of StandardError + # and its descendants. + # - <tt>noop: true</tt> - does not remove entries; returns +nil+. + # - <tt>secure: true</tt> - removes +src+ securely; + # see details at Bundler::FileUtils.remove_entry_secure. + # - <tt>verbose: true</tt> - prints an equivalent command: # - # remove files +list+[0] +list+[1]... If +list+[n] is a directory, - # removes its all contents recursively. This method ignores - # StandardError when :force option is set. + # Bundler::FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true) + # Bundler::FileUtils.rm_r('src1', noop: true, verbose: true) # - # Bundler::FileUtils.rm_r Dir.glob('/tmp/*') - # Bundler::FileUtils.rm_r 'some_dir', force: true + # Output: # - # WARNING: This method causes local vulnerability - # if one of parent directories or removing directory tree are world - # writable (including /tmp, whose permission is 1777), and the current - # process has strong privilege such as Unix super user (root), and the - # system has symbolic link. For secure removing, read the documentation - # of remove_entry_secure carefully, and set :secure option to true. - # Default is <tt>secure: false</tt>. + # rm -r src0.dat src0.txt + # rm -r src1 # - # NOTE: This method calls remove_entry_secure if :secure option is set. - # See also remove_entry_secure. + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil) list = fu_list(list) @@ -633,13 +1310,20 @@ module Bundler::FileUtils end module_function :rm_r + # Equivalent to: # - # Equivalent to + # Bundler::FileUtils.rm_r(list, force: true, **kwargs) # - # Bundler::FileUtils.rm_r(list, force: true) + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. # - # WARNING: This method causes local vulnerability. - # Read the documentation of rm_r first. + # May cause a local vulnerability if not called with keyword argument + # <tt>secure: true</tt>; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. + # + # See Bundler::FileUtils.rm_r for keyword arguments. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def rm_rf(list, noop: nil, verbose: nil, secure: nil) rm_r list, force: true, noop: noop, verbose: verbose, secure: secure @@ -649,37 +1333,20 @@ module Bundler::FileUtils alias rmtree rm_rf module_function :rmtree + # Securely removes the entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. # - # This method removes a file system entry +path+. +path+ shall be a - # regular file, a directory, or something. If +path+ is a directory, - # remove it recursively. This method is required to avoid TOCTTOU - # (time-of-check-to-time-of-use) local security vulnerability of rm_r. - # #rm_r causes security hole when: - # - # * Parent directory is world writable (including /tmp). - # * Removing directory tree includes world writable directory. - # * The system has symbolic link. - # - # To avoid this security hole, this method applies special preprocess. - # If +path+ is a directory, this method chown(2) and chmod(2) all - # removing directories. This requires the current process is the - # owner of the removing whole directory tree, or is the super user (root). + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # WARNING: You must ensure that *ALL* parent directories cannot be - # moved by other untrusted users. For example, parent directories - # should not be owned by untrusted users, and should not be world - # writable except when the sticky bit set. + # Avoids a local vulnerability that can exist in certain circumstances; + # see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability]. # - # WARNING: Only the owner of the removing directory tree, or Unix super - # user (root) should invoke this method. Otherwise this method does not - # work. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. # - # For details of this security vulnerability, see Perl's case: - # - # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 - # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 - # - # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_entry_secure(path, force = false) unless fu_have_symlink? @@ -767,12 +1434,17 @@ module Bundler::FileUtils end private_module_function :fu_stat_identical_entry? + # Removes the entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. # - # This method removes a file system entry +path+. - # +path+ might be a regular file, a directory, or something. - # If +path+ is a directory, remove it recursively. + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # See also remove_entry_secure. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. + # + # Related: Bundler::FileUtils.remove_entry_secure. # def remove_entry(path, force = false) Entry_.new(path).postorder_traverse do |ent| @@ -787,9 +1459,16 @@ module Bundler::FileUtils end module_function :remove_entry + # Removes the file entry given by +path+, + # which should be the entry for a regular file or a symbolic link. + # + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Removes a file +path+. - # This method ignores StandardError if +force+ is true. + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. + # + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_file(path, force = false) Entry_.new(path).remove_file @@ -798,20 +1477,32 @@ module Bundler::FileUtils end module_function :remove_file + # Recursively removes the directory entry given by +path+, + # which should be the entry for a regular file, a symbolic link, + # or a directory. + # + # Argument +path+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Optional argument +force+ specifies whether to ignore + # raised exceptions of StandardError and its descendants. # - # Removes a directory +dir+ and its contents recursively. - # This method ignores StandardError if +force+ is true. + # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_dir(path, force = false) remove_entry path, force # FIXME?? check if it is a directory end module_function :remove_dir + # Returns +true+ if the contents of files +a+ and +b+ are identical, + # +false+ otherwise. # - # Returns true if the contents of a file +a+ and a file +b+ are identical. + # Arguments +a+ and +b+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true - # Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false + # Bundler::FileUtils.identical? and Bundler::FileUtils.cmp are aliases for Bundler::FileUtils.compare_file. + # + # Related: Bundler::FileUtils.compare_stream. # def compare_file(a, b) return false unless File.size(a) == File.size(b) @@ -828,19 +1519,19 @@ module Bundler::FileUtils module_function :identical? module_function :cmp + # Returns +true+ if the contents of streams +a+ and +b+ are identical, + # +false+ otherwise. + # + # Arguments +a+ and +b+ + # should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]. # - # Returns true if the contents of a stream +a+ and +b+ are identical. + # Related: Bundler::FileUtils.compare_file. # def compare_stream(a, b) bsize = fu_stream_blksize(a, b) - if RUBY_VERSION > "2.4" - sa = String.new(capacity: bsize) - sb = String.new(capacity: bsize) - else - sa = String.new - sb = String.new - end + sa = String.new(capacity: bsize) + sb = String.new(capacity: bsize) begin a.read(bsize, sa) @@ -851,13 +1542,69 @@ module Bundler::FileUtils end module_function :compare_stream + # Copies a file entry. + # See {install(1)}[https://man7.org/linux/man-pages/man1/install.1.html]. + # + # Arguments +src+ (a single path or an array of paths) + # and +dest+ (a single path) + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]; + # + # If the entry at +dest+ does not exist, copies from +src+ to +dest+: + # + # File.read('src0.txt') # => "aaa\n" + # File.exist?('dest0.txt') # => false + # Bundler::FileUtils.install('src0.txt', 'dest0.txt') + # File.read('dest0.txt') # => "aaa\n" + # + # If +dest+ is a file entry, copies from +src+ to +dest+, overwriting: + # + # File.read('src1.txt') # => "aaa\n" + # File.read('dest1.txt') # => "bbb\n" + # Bundler::FileUtils.install('src1.txt', 'dest1.txt') + # File.read('dest1.txt') # => "aaa\n" + # + # If +dest+ is a directory entry, copies from +src+ to <tt>dest/src</tt>, + # overwriting if necessary: + # + # File.read('src2.txt') # => "aaa\n" + # File.read('dest2/src2.txt') # => "bbb\n" + # Bundler::FileUtils.install('src2.txt', 'dest2') + # File.read('dest2/src2.txt') # => "aaa\n" + # + # If +src+ is an array of paths and +dest+ points to a directory, + # copies each path +path+ in +src+ to <tt>dest/path</tt>: + # + # File.file?('src3.txt') # => true + # File.file?('src3.dat') # => true + # Bundler::FileUtils.mkdir('dest3') + # Bundler::FileUtils.install(['src3.txt', 'src3.dat'], 'dest3') + # File.file?('dest3/src3.txt') # => true + # File.file?('dest3/src3.dat') # => true + # + # Keyword arguments: # - # If +src+ is not same as +dest+, copies it and changes the permission - # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. - # This method removes destination before copy. + # - <tt>group: <i>group</i></tt> - changes the group if not +nil+, + # using {File.chown}[rdoc-ref:File.chown]. + # - <tt>mode: <i>permissions</i></tt> - changes the permissions. + # using {File.chmod}[rdoc-ref:File.chmod]. + # - <tt>noop: true</tt> - does not copy entries; returns +nil+. + # - <tt>owner: <i>owner</i></tt> - changes the owner if not +nil+, + # using {File.chown}[rdoc-ref:File.chown]. + # - <tt>preserve: true</tt> - preserve timestamps + # using {File.utime}[rdoc-ref:File.utime]. + # - <tt>verbose: true</tt> - prints an equivalent command: # - # Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true - # Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true + # Bundler::FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true) + # Bundler::FileUtils.install('src1.txt', 'dest1.txt', noop: true, verbose: true) + # Bundler::FileUtils.install('src2.txt', 'dest2', noop: true, verbose: true) + # + # Output: + # + # install -c src0.txt dest0.txt + # install -c src1.txt dest1.txt + # install -c src2.txt dest2 + # + # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil, noop: nil, verbose: nil) @@ -877,7 +1624,13 @@ module Bundler::FileUtils st = File.stat(s) unless File.exist?(d) and compare_file(s, d) remove_file d, true - copy_file s, d + if d.end_with?('/') + mkdir_p d + copy_file s, d + File.basename(s) + else + mkdir_p File.expand_path('..', d) + copy_file s, d + end File.utime st.atime, st.mtime, d if preserve File.chmod fu_mode(mode, st), d if mode File.chown uid, gid, d if uid or gid @@ -917,11 +1670,8 @@ module Bundler::FileUtils private_module_function :apply_mask def symbolic_modes_to_i(mode_sym, path) #:nodoc: - mode = if File::Stat === path - path.mode - else - File.stat(path).mode - end + path = File.stat(path) unless File::Stat === path + mode = path.mode mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause| target, *actions = clause.split(/([=+-])/) raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty? @@ -938,7 +1688,7 @@ module Bundler::FileUtils when "x" mask | 0111 when "X" - if FileTest.directory? path + if path.directory? mask | 0111 else mask @@ -978,37 +1728,78 @@ module Bundler::FileUtils end private_module_function :mode_to_s + # Changes permissions on the entries at the paths given in +list+ + # (a single path or an array of paths) + # to the permissions given by +mode+; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise: + # + # - Modifies each entry that is a regular file using + # {File.chmod}[rdoc-ref:File.chmod]. + # - Modifies each entry that is a symbolic link using + # {File.lchmod}[rdoc-ref:File.lchmod]. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Argument +mode+ may be either an integer or a string: + # + # - \Integer +mode+: represents the permission bits to be set: + # + # Bundler::FileUtils.chmod(0755, 'src0.txt') + # Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat']) + # + # - \String +mode+: represents the permissions to be set: + # + # The string is of the form <tt>[targets][[operator][perms[,perms]]</tt>, where: + # + # - +targets+ may be any combination of these letters: + # + # - <tt>'u'</tt>: permissions apply to the file's owner. + # - <tt>'g'</tt>: permissions apply to users in the file's group. + # - <tt>'o'</tt>: permissions apply to other users not in the file's group. + # - <tt>'a'</tt> (the default): permissions apply to all users. + # + # - +operator+ may be one of these letters: + # + # - <tt>'+'</tt>: adds permissions. + # - <tt>'-'</tt>: removes permissions. + # - <tt>'='</tt>: sets (replaces) permissions. + # + # - +perms+ (may be repeated, with separating commas) + # may be any combination of these letters: + # + # - <tt>'r'</tt>: Read. + # - <tt>'w'</tt>: Write. + # - <tt>'x'</tt>: Execute (search, for a directory). + # - <tt>'X'</tt>: Search (for a directories only; + # must be used with <tt>'+'</tt>) + # - <tt>'s'</tt>: Uid or gid. + # - <tt>'t'</tt>: Sticky bit. + # + # Examples: + # + # Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt') + # Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby') + # + # Keyword arguments: + # + # - <tt>noop: true</tt> - does not change permissions; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.chmod(0755, 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chmod(0644, ['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.chmod('u=wrx,go=rx', 'src1.txt', noop: true, verbose: true) + # Bundler::FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby', noop: true, verbose: true) + # + # Output: + # + # chmod 755 src0.txt + # chmod 644 src0.txt src0.dat + # chmod u=wrx,go=rx src1.txt + # chmod u=wrx,go=rx /usr/bin/ruby + # + # Related: Bundler::FileUtils.chmod_R. # - # Changes permission bits on the named files (in +list+) to the bit pattern - # represented by +mode+. - # - # +mode+ is the symbolic and absolute mode can be used. - # - # Absolute mode is - # Bundler::FileUtils.chmod 0755, 'somecommand' - # Bundler::FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) - # Bundler::FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true - # - # Symbolic mode is - # Bundler::FileUtils.chmod "u=wrx,go=rx", 'somecommand' - # Bundler::FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) - # Bundler::FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true - # - # "a" :: is user, group, other mask. - # "u" :: is user's mask. - # "g" :: is group's mask. - # "o" :: is other's mask. - # "w" :: is write permission. - # "r" :: is read permission. - # "x" :: is execute permission. - # "X" :: - # is execute permission for directories only, must be used in conjunction with "+" - # "s" :: is uid, gid. - # "t" :: is sticky bit. - # "+" :: is added to a class given the specified mode. - # "-" :: Is removed from a given class given mode. - # "=" :: Is the exact nature of the class will be given a specified mode. - def chmod(mode, list, noop: nil, verbose: nil) list = fu_list(list) fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose @@ -1019,12 +1810,7 @@ module Bundler::FileUtils end module_function :chmod - # - # Changes permission bits on the named files (in +list+) - # to the bit pattern represented by +mode+. - # - # Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}" - # Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}" + # Like Bundler::FileUtils.chmod, but changes permissions recursively. # def chmod_R(mode, list, noop: nil, verbose: nil, force: nil) list = fu_list(list) @@ -1044,15 +1830,68 @@ module Bundler::FileUtils end module_function :chmod_R + # Changes the owner and group on the entries at the paths given in +list+ + # (a single path or an array of paths) + # to the given +user+ and +group+; + # returns +list+ if it is an array, <tt>[list]</tt> otherwise: + # + # - Modifies each entry that is a regular file using + # {File.chown}[rdoc-ref:File.chown]. + # - Modifies each entry that is a symbolic link using + # {File.lchown}[rdoc-ref:File.lchown]. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # User and group: + # + # - Argument +user+ may be a user name or a user id; + # if +nil+ or +-1+, the user is not changed. + # - Argument +group+ may be a group name or a group id; + # if +nil+ or +-1+, the group is not changed. + # - The user must be a member of the group. # - # Changes owner and group on the named files (in +list+) - # to the user +user+ and the group +group+. +user+ and +group+ - # may be an ID (Integer/String) or a name (String). - # If +user+ or +group+ is nil, this method does not change - # the attribute. + # Examples: # - # Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' - # Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true + # # One path. + # # User and group as string names. + # File.stat('src0.txt').uid # => 1004 + # File.stat('src0.txt').gid # => 1004 + # Bundler::FileUtils.chown('user2', 'group1', 'src0.txt') + # File.stat('src0.txt').uid # => 1006 + # File.stat('src0.txt').gid # => 1005 + # + # # User and group as uid and gid. + # Bundler::FileUtils.chown(1004, 1004, 'src0.txt') + # File.stat('src0.txt').uid # => 1004 + # File.stat('src0.txt').gid # => 1004 + # + # # Array of paths. + # Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat']) + # + # # Directory (not recursive). + # Bundler::FileUtils.chown('user2', 'group1', '.') + # + # Keyword arguments: + # + # - <tt>noop: true</tt> - does not change permissions; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.chown('user2', 'group1', 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chown(1004, 1004, 'src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.chown('user2', 'group1', path, noop: true, verbose: true) + # Bundler::FileUtils.chown('user2', 'group1', '.', noop: true, verbose: true) + # + # Output: + # + # chown user2:group1 src0.txt + # chown 1004:1004 src0.txt + # chown 1006:1005 src0.txt src0.dat + # chown user2:group1 src0.txt + # chown user2:group1 . + # + # Related: Bundler::FileUtils.chown_R. # def chown(user, group, list, noop: nil, verbose: nil) list = fu_list(list) @@ -1068,15 +1907,7 @@ module Bundler::FileUtils end module_function :chown - # - # Changes owner and group on the named files (in +list+) - # to the user +user+ and the group +group+ recursively. - # +user+ and +group+ may be an ID (Integer/String) or - # a name (String). If +user+ or +group+ is nil, this - # method does not change the attribute. - # - # Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs' - # Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true + # Like Bundler::FileUtils.chown, but changes owner and group recursively. # def chown_R(user, group, list, noop: nil, verbose: nil, force: nil) list = fu_list(list) @@ -1127,12 +1958,50 @@ module Bundler::FileUtils end private_module_function :fu_get_gid + # Updates modification times (mtime) and access times (atime) + # of the entries given by the paths in +list+ + # (a single path or an array of paths); + # returns +list+ if it is an array, <tt>[list]</tt> otherwise. + # + # By default, creates an empty file for any path to a non-existent entry; + # use keyword argument +nocreate+ to raise an exception instead. + # + # Argument +list+ or its elements + # should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]. + # + # Examples: + # + # # Single path. + # f = File.new('src0.txt') # Existing file. + # f.atime # => 2022-06-10 11:11:21.200277 -0700 + # f.mtime # => 2022-06-10 11:11:21.200277 -0700 + # Bundler::FileUtils.touch('src0.txt') + # f = File.new('src0.txt') + # f.atime # => 2022-06-11 08:28:09.8185343 -0700 + # f.mtime # => 2022-06-11 08:28:09.8185343 -0700 + # + # # Array of paths. + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat']) # - # Updates modification time (mtime) and access time (atime) of file(s) in - # +list+. Files are created if they don't exist. + # Keyword arguments: # - # Bundler::FileUtils.touch 'timestamp' - # Bundler::FileUtils.touch Dir.glob('*.c'); system 'make' + # - <tt>mtime: <i>time</i></tt> - sets the entry's mtime to the given time, + # instead of the current time. + # - <tt>nocreate: true</tt> - raises an exception if the entry does not exist. + # - <tt>noop: true</tt> - does not touch entries; returns +nil+. + # - <tt>verbose: true</tt> - prints an equivalent command: + # + # Bundler::FileUtils.touch('src0.txt', noop: true, verbose: true) + # Bundler::FileUtils.touch(['src0.txt', 'src0.dat'], noop: true, verbose: true) + # Bundler::FileUtils.touch(path, noop: true, verbose: true) + # + # Output: + # + # touch src0.txt + # touch src0.txt src0.dat + # touch src0.txt + # + # Related: Bundler::FileUtils.uptodate?. # def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil) list = fu_list(list) @@ -1290,14 +2159,9 @@ module Bundler::FileUtils def entries opts = {} - opts[:encoding] = ::Encoding::UTF_8 if fu_windows? + opts[:encoding] = fu_windows? ? ::Encoding::UTF_8 : path.encoding - files = if Dir.respond_to?(:children) - Dir.children(path, **opts) - else - Dir.entries(path(), **opts) - .reject {|n| n == '.' or n == '..' } - end + files = Dir.children(path, **opts) untaint = RUBY_VERSION < '2.7' files.map {|n| Entry_.new(prefix(), join(rel(), untaint ? n.untaint : n)) } @@ -1345,6 +2209,7 @@ module Bundler::FileUtils else File.chmod mode, path() end + rescue Errno::EOPNOTSUPP end def chown(uid, gid) @@ -1439,7 +2304,7 @@ module Bundler::FileUtils if st.symlink? begin File.lchmod mode, path - rescue NotImplementedError + rescue NotImplementedError, Errno::EOPNOTSUPP end else File.chmod mode, path @@ -1498,13 +2363,21 @@ module Bundler::FileUtils def postorder_traverse if directory? - entries().each do |ent| + begin + children = entries() + rescue Errno::EACCES + # Failed to get the list of children. + # Assuming there is no children, try to process the parent directory. + yield self + return + end + + children.each do |ent| ent.postorder_traverse do |e| yield e end end end - ensure yield self end @@ -1559,7 +2432,15 @@ module Bundler::FileUtils def join(dir, base) return File.path(dir) if not base or base == '.' return File.path(base) if not dir or dir == '.' - File.join(dir, base) + begin + File.join(dir, base) + rescue EncodingError + if fu_windows? + File.join(dir.encode(::Encoding::UTF_8), base.encode(::Encoding::UTF_8)) + else + raise + end + end end if File::ALT_SEPARATOR @@ -1590,15 +2471,15 @@ module Bundler::FileUtils end private_module_function :fu_each_src_dest - def fu_each_src_dest0(src, dest) #:nodoc: + def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc: if tmp = Array.try_convert(src) tmp.each do |s| s = File.path(s) - yield s, File.join(dest, File.basename(s)) + yield s, (target_directory ? File.join(dest, File.basename(s)) : dest) end else src = File.path(src) - if File.directory?(dest) + if target_directory and File.directory?(dest) yield src, File.join(dest, File.basename(src)) else yield src, File.path(dest) @@ -1614,7 +2495,7 @@ module Bundler::FileUtils def fu_output_message(msg) #:nodoc: output = @fileutils_output if defined?(@fileutils_output) - output ||= $stderr + output ||= $stdout if defined?(@fileutils_label) msg = @fileutils_label + msg end @@ -1622,6 +2503,56 @@ module Bundler::FileUtils end private_module_function :fu_output_message + def fu_split_path(path) + path = File.path(path) + list = [] + until (parent, base = File.split(path); parent == path or parent == ".") + list << base + path = parent + end + list << path + list.reverse! + end + private_module_function :fu_split_path + + def fu_relative_components_from(target, base) #:nodoc: + i = 0 + while target[i]&.== base[i] + i += 1 + end + Array.new(base.size-i, '..').concat(target[i..-1]) + end + private_module_function :fu_relative_components_from + + def fu_clean_components(*comp) + comp.shift while comp.first == "." + return comp if comp.empty? + clean = [comp.shift] + path = File.join(*clean, "") # ending with File::SEPARATOR + while c = comp.shift + if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path)) + clean.pop + path.chomp!(%r((?<=\A|/)[^/]+/\z), "") + else + clean << c + path << c << "/" + end + end + clean + end + private_module_function :fu_clean_components + + if fu_windows? + def fu_starting_path?(path) + path&.start_with?(%r(\w:|/)) + end + else + def fu_starting_path?(path) + path&.start_with?("/") + end + end + private_module_function :fu_starting_path? + # This hash table holds command options. OPT_TABLE = {} #:nodoc: internal use only (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name| @@ -1631,50 +2562,49 @@ module Bundler::FileUtils public + # Returns an array of the string names of \Bundler::FileUtils methods + # that accept one or more keyword arguments: # - # Returns an Array of names of high-level methods that accept any keyword - # arguments. - # - # p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] + # Bundler::FileUtils.commands.sort.take(3) # => ["cd", "chdir", "chmod"] # def self.commands OPT_TABLE.keys end + # Returns an array of the string keyword names: # - # Returns an Array of option names. - # - # p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] + # Bundler::FileUtils.options.take(3) # => ["noop", "verbose", "force"] # def self.options OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } end + # Returns +true+ if method +mid+ accepts the given option +opt+, +false+ otherwise; + # the arguments may be strings or symbols: # - # Returns true if the method +mid+ have an option +opt+. - # - # p Bundler::FileUtils.have_option?(:cp, :noop) #=> true - # p Bundler::FileUtils.have_option?(:rm, :force) #=> true - # p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false + # Bundler::FileUtils.have_option?(:chmod, :noop) # => true + # Bundler::FileUtils.have_option?('chmod', 'secure') # => false # def self.have_option?(mid, opt) li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" li.include?(opt) end + # Returns an array of the string keyword name for method +mid+; + # the argument may be a string or a symbol: # - # Returns an Array of option names of the method +mid+. - # - # p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"] + # Bundler::FileUtils.options_of(:rm) # => ["force", "noop", "verbose"] + # Bundler::FileUtils.options_of('mv') # => ["force", "noop", "verbose", "secure"] # def self.options_of(mid) OPT_TABLE[mid.to_s].map {|sym| sym.to_s } end + # Returns an array of the string method names of the methods + # that accept the given keyword option +opt+; + # the argument must be a symbol: # - # Returns an Array of methods names which have the option +opt+. - # - # p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] + # Bundler::FileUtils.collect_method(:preserve) # => ["cp", "copy", "cp_r", "install"] # def self.collect_method(opt) OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } diff --git a/lib/bundler/vendor/molinillo/lib/molinillo.rb b/lib/bundler/vendor/molinillo/lib/molinillo.rb deleted file mode 100644 index a52b96deaf..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -require_relative 'molinillo/gem_metadata' -require_relative 'molinillo/errors' -require_relative 'molinillo/resolver' -require_relative 'molinillo/modules/ui' -require_relative 'molinillo/modules/specification_provider' - -# Bundler::Molinillo is a generic dependency resolution algorithm. -module Bundler::Molinillo -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb deleted file mode 100644 index bcacf35243..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/resolution_state.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # @!visibility private - module Delegates - # Delegates all {Bundler::Molinillo::ResolutionState} methods to a `#state` property. - module ResolutionState - # (see Bundler::Molinillo::ResolutionState#name) - def name - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.name - end - - # (see Bundler::Molinillo::ResolutionState#requirements) - def requirements - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.requirements - end - - # (see Bundler::Molinillo::ResolutionState#activated) - def activated - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.activated - end - - # (see Bundler::Molinillo::ResolutionState#requirement) - def requirement - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.requirement - end - - # (see Bundler::Molinillo::ResolutionState#possibilities) - def possibilities - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.possibilities - end - - # (see Bundler::Molinillo::ResolutionState#depth) - def depth - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.depth - end - - # (see Bundler::Molinillo::ResolutionState#conflicts) - def conflicts - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.conflicts - end - - # (see Bundler::Molinillo::ResolutionState#unused_unwind_options) - def unused_unwind_options - current_state = state || Bundler::Molinillo::ResolutionState.empty - current_state.unused_unwind_options - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb deleted file mode 100644 index f8c695c1ed..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/delegates/specification_provider.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - module Delegates - # Delegates all {Bundler::Molinillo::SpecificationProvider} methods to a - # `#specification_provider` property. - module SpecificationProvider - # (see Bundler::Molinillo::SpecificationProvider#search_for) - def search_for(dependency) - with_no_such_dependency_error_handling do - specification_provider.search_for(dependency) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#dependencies_for) - def dependencies_for(specification) - with_no_such_dependency_error_handling do - specification_provider.dependencies_for(specification) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#requirement_satisfied_by?) - def requirement_satisfied_by?(requirement, activated, spec) - with_no_such_dependency_error_handling do - specification_provider.requirement_satisfied_by?(requirement, activated, spec) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#dependencies_equal?) - def dependencies_equal?(dependencies, other_dependencies) - with_no_such_dependency_error_handling do - specification_provider.dependencies_equal?(dependencies, other_dependencies) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#name_for) - def name_for(dependency) - with_no_such_dependency_error_handling do - specification_provider.name_for(dependency) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#name_for_explicit_dependency_source) - def name_for_explicit_dependency_source - with_no_such_dependency_error_handling do - specification_provider.name_for_explicit_dependency_source - end - end - - # (see Bundler::Molinillo::SpecificationProvider#name_for_locking_dependency_source) - def name_for_locking_dependency_source - with_no_such_dependency_error_handling do - specification_provider.name_for_locking_dependency_source - end - end - - # (see Bundler::Molinillo::SpecificationProvider#sort_dependencies) - def sort_dependencies(dependencies, activated, conflicts) - with_no_such_dependency_error_handling do - specification_provider.sort_dependencies(dependencies, activated, conflicts) - end - end - - # (see Bundler::Molinillo::SpecificationProvider#allow_missing?) - def allow_missing?(dependency) - with_no_such_dependency_error_handling do - specification_provider.allow_missing?(dependency) - end - end - - private - - # Ensures any raised {NoSuchDependencyError} has its - # {NoSuchDependencyError#required_by} set. - # @yield - def with_no_such_dependency_error_handling - yield - rescue NoSuchDependencyError => error - if state - vertex = activated.vertex_named(name_for(error.dependency)) - error.required_by += vertex.incoming_edges.map { |e| e.origin.name } - error.required_by << name_for_explicit_dependency_source unless vertex.explicit_requirements.empty? - end - raise - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb deleted file mode 100644 index 936399ed2f..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +++ /dev/null @@ -1,255 +0,0 @@ -# frozen_string_literal: true - -require 'tsort' - -require_relative 'dependency_graph/log' -require_relative 'dependency_graph/vertex' - -module Bundler::Molinillo - # A directed acyclic graph that is tuned to hold named dependencies - class DependencyGraph - include Enumerable - - # Enumerates through the vertices of the graph. - # @return [Array<Vertex>] The graph's vertices. - def each - return vertices.values.each unless block_given? - vertices.values.each { |v| yield v } - end - - include TSort - - # @!visibility private - alias tsort_each_node each - - # @!visibility private - def tsort_each_child(vertex, &block) - vertex.successors.each(&block) - end - - # Topologically sorts the given vertices. - # @param [Enumerable<Vertex>] vertices the vertices to be sorted, which must - # all belong to the same graph. - # @return [Array<Vertex>] The sorted vertices. - def self.tsort(vertices) - TSort.tsort( - lambda { |b| vertices.each(&b) }, - lambda { |v, &b| (v.successors & vertices).each(&b) } - ) - end - - # A directed edge of a {DependencyGraph} - # @attr [Vertex] origin The origin of the directed edge - # @attr [Vertex] destination The destination of the directed edge - # @attr [Object] requirement The requirement the directed edge represents - Edge = Struct.new(:origin, :destination, :requirement) - - # @return [{String => Vertex}] the vertices of the dependency graph, keyed - # by {Vertex#name} - attr_reader :vertices - - # @return [Log] the op log for this graph - attr_reader :log - - # Initializes an empty dependency graph - def initialize - @vertices = {} - @log = Log.new - end - - # Tags the current state of the dependency as the given tag - # @param [Object] tag an opaque tag for the current state of the graph - # @return [Void] - def tag(tag) - log.tag(self, tag) - end - - # Rewinds the graph to the state tagged as `tag` - # @param [Object] tag the tag to rewind to - # @return [Void] - def rewind_to(tag) - log.rewind_to(self, tag) - end - - # Initializes a copy of a {DependencyGraph}, ensuring that all {#vertices} - # are properly copied. - # @param [DependencyGraph] other the graph to copy. - def initialize_copy(other) - super - @vertices = {} - @log = other.log.dup - traverse = lambda do |new_v, old_v| - return if new_v.outgoing_edges.size == old_v.outgoing_edges.size - old_v.outgoing_edges.each do |edge| - destination = add_vertex(edge.destination.name, edge.destination.payload) - add_edge_no_circular(new_v, destination, edge.requirement) - traverse.call(destination, edge.destination) - end - end - other.vertices.each do |name, vertex| - new_vertex = add_vertex(name, vertex.payload, vertex.root?) - new_vertex.explicit_requirements.replace(vertex.explicit_requirements) - traverse.call(new_vertex, vertex) - end - end - - # @return [String] a string suitable for debugging - def inspect - "#{self.class}:#{vertices.values.inspect}" - end - - # @param [Hash] options options for dot output. - # @return [String] Returns a dot format representation of the graph - def to_dot(options = {}) - edge_label = options.delete(:edge_label) - raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? - - dot_vertices = [] - dot_edges = [] - vertices.each do |n, v| - dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" - v.outgoing_edges.each do |e| - label = edge_label ? edge_label.call(e) : e.requirement - dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" - end - end - - dot_vertices.uniq! - dot_vertices.sort! - dot_edges.uniq! - dot_edges.sort! - - dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') - dot.join("\n") - end - - # @param [DependencyGraph] other - # @return [Boolean] whether the two dependency graphs are equal, determined - # by a recursive traversal of each {#root_vertices} and its - # {Vertex#successors} - def ==(other) - return false unless other - return true if equal?(other) - vertices.each do |name, vertex| - other_vertex = other.vertex_named(name) - return false unless other_vertex - return false unless vertex.payload == other_vertex.payload - return false unless other_vertex.successors.to_set == vertex.successors.to_set - end - end - - # @param [String] name - # @param [Object] payload - # @param [Array<String>] parent_names - # @param [Object] requirement the requirement that is requiring the child - # @return [void] - def add_child_vertex(name, payload, parent_names, requirement) - root = !parent_names.delete(nil) { true } - vertex = add_vertex(name, payload, root) - vertex.explicit_requirements << requirement if root - parent_names.each do |parent_name| - parent_vertex = vertex_named(parent_name) - add_edge(parent_vertex, vertex, requirement) - end - vertex - end - - # Adds a vertex with the given name, or updates the existing one. - # @param [String] name - # @param [Object] payload - # @return [Vertex] the vertex that was added to `self` - def add_vertex(name, payload, root = false) - log.add_vertex(self, name, payload, root) - end - - # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively - # removing any non-root vertices that were orphaned in the process - # @param [String] name - # @return [Array<Vertex>] the vertices which have been detached - def detach_vertex_named(name) - log.detach_vertex_named(self, name) - end - - # @param [String] name - # @return [Vertex,nil] the vertex with the given name - def vertex_named(name) - vertices[name] - end - - # @param [String] name - # @return [Vertex,nil] the root vertex with the given name - def root_vertex_named(name) - vertex = vertex_named(name) - vertex if vertex && vertex.root? - end - - # Adds a new {Edge} to the dependency graph - # @param [Vertex] origin - # @param [Vertex] destination - # @param [Object] requirement the requirement that this edge represents - # @return [Edge] the added edge - def add_edge(origin, destination, requirement) - if destination.path_to?(origin) - raise CircularDependencyError.new(path(destination, origin)) - end - add_edge_no_circular(origin, destination, requirement) - end - - # Deletes an {Edge} from the dependency graph - # @param [Edge] edge - # @return [Void] - def delete_edge(edge) - log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) - end - - # Sets the payload of the vertex with the given name - # @param [String] name the name of the vertex - # @param [Object] payload the payload - # @return [Void] - def set_payload(name, payload) - log.set_payload(self, name, payload) - end - - private - - # Adds a new {Edge} to the dependency graph without checking for - # circularity. - # @param (see #add_edge) - # @return (see #add_edge) - def add_edge_no_circular(origin, destination, requirement) - log.add_edge_no_circular(self, origin.name, destination.name, requirement) - end - - # Returns the path between two vertices - # @raise [ArgumentError] if there is no path between the vertices - # @param [Vertex] from - # @param [Vertex] to - # @return [Array<Vertex>] the shortest path from `from` to `to` - def path(from, to) - distances = Hash.new(vertices.size + 1) - distances[from.name] = 0 - predecessors = {} - each do |vertex| - vertex.successors.each do |successor| - if distances[successor.name] > distances[vertex.name] + 1 - distances[successor.name] = distances[vertex.name] + 1 - predecessors[successor] = vertex - end - end - end - - path = [to] - while before = predecessors[to] - path << before - to = before - break if to == from - end - - unless path.last.equal?(from) - raise ArgumentError, "There is no path from #{from.name} to #{to.name}" - end - - path.reverse - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb deleted file mode 100644 index c04c7eec9c..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - class DependencyGraph - # An action that modifies a {DependencyGraph} that is reversible. - # @abstract - class Action - # rubocop:disable Lint/UnusedMethodArgument - - # @return [Symbol] The name of the action. - def self.action_name - raise 'Abstract' - end - - # Performs the action on the given graph. - # @param [DependencyGraph] graph the graph to perform the action on. - # @return [Void] - def up(graph) - raise 'Abstract' - end - - # Reverses the action on the given graph. - # @param [DependencyGraph] graph the graph to reverse the action on. - # @return [Void] - def down(graph) - raise 'Abstract' - end - - # @return [Action,Nil] The previous action - attr_accessor :previous - - # @return [Action,Nil] The next action - attr_accessor :next - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb deleted file mode 100644 index 946a08236e..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#add_edge_no_circular) - class AddEdgeNoCircular < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges << edge - edge.destination.incoming_edges << edge - edge - end - - # (see Action#down) - def down(graph) - edge = make_edge(graph) - delete_first(edge.origin.outgoing_edges, edge) - delete_first(edge.destination.incoming_edges, edge) - end - - # @!group AddEdgeNoCircular - - # @return [String] the name of the origin of the edge - attr_reader :origin - - # @return [String] the name of the destination of the edge - attr_reader :destination - - # @return [Object] the requirement that the edge represents - attr_reader :requirement - - # @param [DependencyGraph] graph the graph to find vertices from - # @return [Edge] The edge this action adds - def make_edge(graph) - Edge.new(graph.vertex_named(origin), graph.vertex_named(destination), requirement) - end - - # Initialize an action to add an edge to a dependency graph - # @param [String] origin the name of the origin of the edge - # @param [String] destination the name of the destination of the edge - # @param [Object] requirement the requirement that the edge represents - def initialize(origin, destination, requirement) - @origin = origin - @destination = destination - @requirement = requirement - end - - private - - def delete_first(array, item) - return unless index = array.index(item) - array.delete_at(index) - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb deleted file mode 100644 index 483527daf8..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#add_vertex) - class AddVertex < Action # :nodoc: - # @!group Action - - # (see Action.action_name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - if existing = graph.vertices[name] - @existing_payload = existing.payload - @existing_root = existing.root - end - vertex = existing || Vertex.new(name, payload) - graph.vertices[vertex.name] = vertex - vertex.payload ||= payload - vertex.root ||= root - vertex - end - - # (see Action#down) - def down(graph) - if defined?(@existing_payload) - vertex = graph.vertices[name] - vertex.payload = @existing_payload - vertex.root = @existing_root - else - graph.vertices.delete(name) - end - end - - # @!group AddVertex - - # @return [String] the name of the vertex - attr_reader :name - - # @return [Object] the payload for the vertex - attr_reader :payload - - # @return [Boolean] whether the vertex is root or not - attr_reader :root - - # Initialize an action to add a vertex to a dependency graph - # @param [String] name the name of the vertex - # @param [Object] payload the payload for the vertex - # @param [Boolean] root whether the vertex is root or not - def initialize(name, payload, root) - @name = name - @payload = payload - @root = root - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb deleted file mode 100644 index d81940585a..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # (see DependencyGraph#delete_edge) - class DeleteEdge < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :delete_edge - end - - # (see Action#up) - def up(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges.delete(edge) - edge.destination.incoming_edges.delete(edge) - end - - # (see Action#down) - def down(graph) - edge = make_edge(graph) - edge.origin.outgoing_edges << edge - edge.destination.incoming_edges << edge - edge - end - - # @!group DeleteEdge - - # @return [String] the name of the origin of the edge - attr_reader :origin_name - - # @return [String] the name of the destination of the edge - attr_reader :destination_name - - # @return [Object] the requirement that the edge represents - attr_reader :requirement - - # @param [DependencyGraph] graph the graph to find vertices from - # @return [Edge] The edge this action adds - def make_edge(graph) - Edge.new( - graph.vertex_named(origin_name), - graph.vertex_named(destination_name), - requirement - ) - end - - # Initialize an action to add an edge to a dependency graph - # @param [String] origin_name the name of the origin of the edge - # @param [String] destination_name the name of the destination of the edge - # @param [Object] requirement the requirement that the edge represents - def initialize(origin_name, destination_name, requirement) - @origin_name = origin_name - @destination_name = destination_name - @requirement = requirement - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb deleted file mode 100644 index 36fce7c526..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#detach_vertex_named - class DetachVertexNamed < Action - # @!group Action - - # (see Action#name) - def self.action_name - :add_vertex - end - - # (see Action#up) - def up(graph) - return [] unless @vertex = graph.vertices.delete(name) - - removed_vertices = [@vertex] - @vertex.outgoing_edges.each do |e| - v = e.destination - v.incoming_edges.delete(e) - if !v.root? && v.incoming_edges.empty? - removed_vertices.concat graph.detach_vertex_named(v.name) - end - end - - @vertex.incoming_edges.each do |e| - v = e.origin - v.outgoing_edges.delete(e) - end - - removed_vertices - end - - # (see Action#down) - def down(graph) - return unless @vertex - graph.vertices[@vertex.name] = @vertex - @vertex.outgoing_edges.each do |e| - e.destination.incoming_edges << e - end - @vertex.incoming_edges.each do |e| - e.origin.outgoing_edges << e - end - end - - # @!group DetachVertexNamed - - # @return [String] the name of the vertex to detach - attr_reader :name - - # Initialize an action to detach a vertex from a dependency graph - # @param [String] name the name of the vertex to detach - def initialize(name) - @name = name - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb deleted file mode 100644 index 6f0de19886..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: true - -require_relative 'add_edge_no_circular' -require_relative 'add_vertex' -require_relative 'delete_edge' -require_relative 'detach_vertex_named' -require_relative 'set_payload' -require_relative 'tag' - -module Bundler::Molinillo - class DependencyGraph - # A log for dependency graph actions - class Log - # Initializes an empty log - def initialize - @current_action = @first_action = nil - end - - # @!macro [new] action - # {include:DependencyGraph#$0} - # @param [Graph] graph the graph to perform the action on - # @param (see DependencyGraph#$0) - # @return (see DependencyGraph#$0) - - # @macro action - def tag(graph, tag) - push_action(graph, Tag.new(tag)) - end - - # @macro action - def add_vertex(graph, name, payload, root) - push_action(graph, AddVertex.new(name, payload, root)) - end - - # @macro action - def detach_vertex_named(graph, name) - push_action(graph, DetachVertexNamed.new(name)) - end - - # @macro action - def add_edge_no_circular(graph, origin, destination, requirement) - push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) - end - - # {include:DependencyGraph#delete_edge} - # @param [Graph] graph the graph to perform the action on - # @param [String] origin_name - # @param [String] destination_name - # @param [Object] requirement - # @return (see DependencyGraph#delete_edge) - def delete_edge(graph, origin_name, destination_name, requirement) - push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) - end - - # @macro action - def set_payload(graph, name, payload) - push_action(graph, SetPayload.new(name, payload)) - end - - # Pops the most recent action from the log and undoes the action - # @param [DependencyGraph] graph - # @return [Action] the action that was popped off the log - def pop!(graph) - return unless action = @current_action - unless @current_action = action.previous - @first_action = nil - end - action.down(graph) - action - end - - extend Enumerable - - # @!visibility private - # Enumerates each action in the log - # @yield [Action] - def each - return enum_for unless block_given? - action = @first_action - loop do - break unless action - yield action - action = action.next - end - self - end - - # @!visibility private - # Enumerates each action in the log in reverse order - # @yield [Action] - def reverse_each - return enum_for(:reverse_each) unless block_given? - action = @current_action - loop do - break unless action - yield action - action = action.previous - end - self - end - - # @macro action - def rewind_to(graph, tag) - loop do - action = pop!(graph) - raise "No tag #{tag.inspect} found" unless action - break if action.class.action_name == :tag && action.tag == tag - end - end - - private - - # Adds the given action to the log, running the action - # @param [DependencyGraph] graph - # @param [Action] action - # @return The value returned by `action.up` - def push_action(graph, action) - action.previous = @current_action - @current_action.next = action if @current_action - @current_action = action - @first_action ||= action - action.up(graph) - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb deleted file mode 100644 index 2e9b90e6cd..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#set_payload - class SetPayload < Action # :nodoc: - # @!group Action - - # (see Action.action_name) - def self.action_name - :set_payload - end - - # (see Action#up) - def up(graph) - vertex = graph.vertex_named(name) - @old_payload = vertex.payload - vertex.payload = payload - end - - # (see Action#down) - def down(graph) - graph.vertex_named(name).payload = @old_payload - end - - # @!group SetPayload - - # @return [String] the name of the vertex - attr_reader :name - - # @return [Object] the payload for the vertex - attr_reader :payload - - # Initialize an action to add set the payload for a vertex in a dependency - # graph - # @param [String] name the name of the vertex - # @param [Object] payload the payload for the vertex - def initialize(name, payload) - @name = name - @payload = payload - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb deleted file mode 100644 index 5b5da3e4f9..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative 'action' -module Bundler::Molinillo - class DependencyGraph - # @!visibility private - # @see DependencyGraph#tag - class Tag < Action - # @!group Action - - # (see Action.action_name) - def self.action_name - :tag - end - - # (see Action#up) - def up(graph) - end - - # (see Action#down) - def down(graph) - end - - # @!group Tag - - # @return [Object] An opaque tag - attr_reader :tag - - # Initialize an action to tag a state of a dependency graph - # @param [Object] tag an opaque tag - def initialize(tag) - @tag = tag - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb deleted file mode 100644 index 1185a8ab05..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - class DependencyGraph - # A vertex in a {DependencyGraph} that encapsulates a {#name} and a - # {#payload} - class Vertex - # @return [String] the name of the vertex - attr_accessor :name - - # @return [Object] the payload the vertex holds - attr_accessor :payload - - # @return [Array<Object>] the explicit requirements that required - # this vertex - attr_reader :explicit_requirements - - # @return [Boolean] whether the vertex is considered a root vertex - attr_accessor :root - alias root? root - - # Initializes a vertex with the given name and payload. - # @param [String] name see {#name} - # @param [Object] payload see {#payload} - def initialize(name, payload) - @name = name.frozen? ? name : name.dup.freeze - @payload = payload - @explicit_requirements = [] - @outgoing_edges = [] - @incoming_edges = [] - end - - # @return [Array<Object>] all of the requirements that required - # this vertex - def requirements - (incoming_edges.map(&:requirement) + explicit_requirements).uniq - end - - # @return [Array<Edge>] the edges of {#graph} that have `self` as their - # {Edge#origin} - attr_accessor :outgoing_edges - - # @return [Array<Edge>] the edges of {#graph} that have `self` as their - # {Edge#destination} - attr_accessor :incoming_edges - - # @return [Array<Vertex>] the vertices of {#graph} that have an edge with - # `self` as their {Edge#destination} - def predecessors - incoming_edges.map(&:origin) - end - - # @return [Set<Vertex>] the vertices of {#graph} where `self` is a - # {#descendent?} - def recursive_predecessors - _recursive_predecessors - end - - # @param [Set<Vertex>] vertices the set to add the predecessors to - # @return [Set<Vertex>] the vertices of {#graph} where `self` is a - # {#descendent?} - def _recursive_predecessors(vertices = new_vertex_set) - incoming_edges.each do |edge| - vertex = edge.origin - next unless vertices.add?(vertex) - vertex._recursive_predecessors(vertices) - end - - vertices - end - protected :_recursive_predecessors - - # @return [Array<Vertex>] the vertices of {#graph} that have an edge with - # `self` as their {Edge#origin} - def successors - outgoing_edges.map(&:destination) - end - - # @return [Set<Vertex>] the vertices of {#graph} where `self` is an - # {#ancestor?} - def recursive_successors - _recursive_successors - end - - # @param [Set<Vertex>] vertices the set to add the successors to - # @return [Set<Vertex>] the vertices of {#graph} where `self` is an - # {#ancestor?} - def _recursive_successors(vertices = new_vertex_set) - outgoing_edges.each do |edge| - vertex = edge.destination - next unless vertices.add?(vertex) - vertex._recursive_successors(vertices) - end - - vertices - end - protected :_recursive_successors - - # @return [String] a string suitable for debugging - def inspect - "#{self.class}:#{name}(#{payload.inspect})" - end - - # @return [Boolean] whether the two vertices are equal, determined - # by a recursive traversal of each {Vertex#successors} - def ==(other) - return true if equal?(other) - shallow_eql?(other) && - successors.to_set == other.successors.to_set - end - - # @param [Vertex] other the other vertex to compare to - # @return [Boolean] whether the two vertices are equal, determined - # solely by {#name} and {#payload} equality - def shallow_eql?(other) - return true if equal?(other) - other && - name == other.name && - payload == other.payload - end - - alias eql? == - - # @return [Fixnum] a hash for the vertex based upon its {#name} - def hash - name.hash - end - - # Is there a path from `self` to `other` following edges in the - # dependency graph? - # @return whether there is a path following edges within this {#graph} - def path_to?(other) - _path_to?(other) - end - - alias descendent? path_to? - - # @param [Vertex] other the vertex to check if there's a path to - # @param [Set<Vertex>] visited the vertices of {#graph} that have been visited - # @return [Boolean] whether there is a path to `other` from `self` - def _path_to?(other, visited = new_vertex_set) - return false unless visited.add?(self) - return true if equal?(other) - successors.any? { |v| v._path_to?(other, visited) } - end - protected :_path_to? - - # Is there a path from `other` to `self` following edges in the - # dependency graph? - # @return whether there is a path following edges within this {#graph} - def ancestor?(other) - other.path_to?(self) - end - - alias is_reachable_from? ancestor? - - def new_vertex_set - require 'set' - Set.new - end - private :new_vertex_set - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb b/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb deleted file mode 100644 index e210202b69..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/errors.rb +++ /dev/null @@ -1,143 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # An error that occurred during the resolution process - class ResolverError < StandardError; end - - # An error caused by searching for a dependency that is completely unknown, - # i.e. has no versions available whatsoever. - class NoSuchDependencyError < ResolverError - # @return [Object] the dependency that could not be found - attr_accessor :dependency - - # @return [Array<Object>] the specifications that depended upon {#dependency} - attr_accessor :required_by - - # Initializes a new error with the given missing dependency. - # @param [Object] dependency @see {#dependency} - # @param [Array<Object>] required_by @see {#required_by} - def initialize(dependency, required_by = []) - @dependency = dependency - @required_by = required_by.uniq - super() - end - - # The error message for the missing dependency, including the specifications - # that had this dependency. - def message - sources = required_by.map { |r| "`#{r}`" }.join(' and ') - message = "Unable to find a specification for `#{dependency}`" - message += " depended upon by #{sources}" unless sources.empty? - message - end - end - - # An error caused by attempting to fulfil a dependency that was circular - # - # @note This exception will be thrown if and only if a {Vertex} is added to a - # {DependencyGraph} that has a {DependencyGraph::Vertex#path_to?} an - # existing {DependencyGraph::Vertex} - class CircularDependencyError < ResolverError - # [Set<Object>] the dependencies responsible for causing the error - attr_reader :dependencies - - # Initializes a new error with the given circular vertices. - # @param [Array<DependencyGraph::Vertex>] vertices the vertices in the dependency - # that caused the error - def initialize(vertices) - super "There is a circular dependency between #{vertices.map(&:name).join(' and ')}" - @dependencies = vertices.map { |vertex| vertex.payload.possibilities.last }.to_set - end - end - - # An error caused by conflicts in version - class VersionConflict < ResolverError - # @return [{String => Resolution::Conflict}] the conflicts that caused - # resolution to fail - attr_reader :conflicts - - # @return [SpecificationProvider] the specification provider used during - # resolution - attr_reader :specification_provider - - # Initializes a new error with the given version conflicts. - # @param [{String => Resolution::Conflict}] conflicts see {#conflicts} - # @param [SpecificationProvider] specification_provider see {#specification_provider} - def initialize(conflicts, specification_provider) - pairs = [] - conflicts.values.flat_map(&:requirements).each do |conflicting| - conflicting.each do |source, conflict_requirements| - conflict_requirements.each do |c| - pairs << [c, source] - end - end - end - - super "Unable to satisfy the following requirements:\n\n" \ - "#{pairs.map { |r, d| "- `#{r}` required by `#{d}`" }.join("\n")}" - - @conflicts = conflicts - @specification_provider = specification_provider - end - - require_relative 'delegates/specification_provider' - include Delegates::SpecificationProvider - - # @return [String] An error message that includes requirement trees, - # which is much more detailed & customizable than the default message - # @param [Hash] opts the options to create a message with. - # @option opts [String] :solver_name The user-facing name of the solver - # @option opts [String] :possibility_type The generic name of a possibility - # @option opts [Proc] :reduce_trees A proc that reduced the list of requirement trees - # @option opts [Proc] :printable_requirement A proc that pretty-prints requirements - # @option opts [Proc] :additional_message_for_conflict A proc that appends additional - # messages for each conflict - # @option opts [Proc] :version_for_spec A proc that returns the version number for a - # possibility - def message_with_trees(opts = {}) - solver_name = opts.delete(:solver_name) { self.class.name.split('::').first } - possibility_type = opts.delete(:possibility_type) { 'possibility named' } - reduce_trees = opts.delete(:reduce_trees) { proc { |trees| trees.uniq.sort_by(&:to_s) } } - printable_requirement = opts.delete(:printable_requirement) { proc { |req| req.to_s } } - additional_message_for_conflict = opts.delete(:additional_message_for_conflict) { proc {} } - version_for_spec = opts.delete(:version_for_spec) { proc(&:to_s) } - incompatible_version_message_for_conflict = opts.delete(:incompatible_version_message_for_conflict) do - proc do |name, _conflict| - %(#{solver_name} could not find compatible versions for #{possibility_type} "#{name}":) - end - end - - conflicts.sort.reduce(''.dup) do |o, (name, conflict)| - o << "\n" << incompatible_version_message_for_conflict.call(name, conflict) << "\n" - if conflict.locked_requirement - o << %( In snapshot (#{name_for_locking_dependency_source}):\n) - o << %( #{printable_requirement.call(conflict.locked_requirement)}\n) - o << %(\n) - end - o << %( In #{name_for_explicit_dependency_source}:\n) - trees = reduce_trees.call(conflict.requirement_trees) - - o << trees.map do |tree| - t = ''.dup - depth = 2 - tree.each do |req| - t << ' ' * depth << printable_requirement.call(req) - unless tree.last == req - if spec = conflict.activated_by_name[name_for(req)] - t << %( was resolved to #{version_for_spec.call(spec)}, which) - end - t << %( depends on) - end - t << %(\n) - depth += 1 - end - t - end.join("\n") - - additional_message_for_conflict.call(o, name, conflict) - - o - end.strip - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb deleted file mode 100644 index e13a781a50..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # The version of Bundler::Molinillo. - VERSION = '0.7.0'.freeze -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb deleted file mode 100644 index eeae79af3c..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # Provides information about specifications and dependencies to the resolver, - # allowing the {Resolver} class to remain generic while still providing power - # and flexibility. - # - # This module contains the methods that users of Bundler::Molinillo must to implement, - # using knowledge of their own model classes. - module SpecificationProvider - # Search for the specifications that match the given dependency. - # The specifications in the returned array will be considered in reverse - # order, so the latest version ought to be last. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `dependency` parameter. - # - # @param [Object] dependency - # @return [Array<Object>] the specifications that satisfy the given - # `dependency`. - def search_for(dependency) - [] - end - - # Returns the dependencies of `specification`. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `specification` parameter. - # - # @param [Object] specification - # @return [Array<Object>] the dependencies that are required by the given - # `specification`. - def dependencies_for(specification) - [] - end - - # Determines whether the given `requirement` is satisfied by the given - # `spec`, in the context of the current `activated` dependency graph. - # - # @param [Object] requirement - # @param [DependencyGraph] activated the current dependency graph in the - # resolution process. - # @param [Object] spec - # @return [Boolean] whether `requirement` is satisfied by `spec` in the - # context of the current `activated` dependency graph. - def requirement_satisfied_by?(requirement, activated, spec) - true - end - - # Determines whether two arrays of dependencies are equal, and thus can be - # grouped. - # - # @param [Array<Object>] dependencies - # @param [Array<Object>] other_dependencies - # @return [Boolean] whether `dependencies` and `other_dependencies` should - # be considered equal. - def dependencies_equal?(dependencies, other_dependencies) - dependencies == other_dependencies - end - - # Returns the name for the given `dependency`. - # @note This method should be 'pure', i.e. the return value should depend - # only on the `dependency` parameter. - # - # @param [Object] dependency - # @return [String] the name for the given `dependency`. - def name_for(dependency) - dependency.to_s - end - - # @return [String] the name of the source of explicit dependencies, i.e. - # those passed to {Resolver#resolve} directly. - def name_for_explicit_dependency_source - 'user-specified dependency' - end - - # @return [String] the name of the source of 'locked' dependencies, i.e. - # those passed to {Resolver#resolve} directly as the `base` - def name_for_locking_dependency_source - 'Lockfile' - end - - # Sort dependencies so that the ones that are easiest to resolve are first. - # Easiest to resolve is (usually) defined by: - # 1) Is this dependency already activated? - # 2) How relaxed are the requirements? - # 3) Are there any conflicts for this dependency? - # 4) How many possibilities are there to satisfy this dependency? - # - # @param [Array<Object>] dependencies - # @param [DependencyGraph] activated the current dependency graph in the - # resolution process. - # @param [{String => Array<Conflict>}] conflicts - # @return [Array<Object>] a sorted copy of `dependencies`. - def sort_dependencies(dependencies, activated, conflicts) - dependencies.sort_by do |dependency| - name = name_for(dependency) - [ - activated.vertex_named(name).payload ? 0 : 1, - conflicts[name] ? 0 : 1, - ] - end - end - - # Returns whether this dependency, which has no possible matching - # specifications, can safely be ignored. - # - # @param [Object] dependency - # @return [Boolean] whether this dependency can safely be skipped. - def allow_missing?(dependency) - false - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb deleted file mode 100644 index a166bc6991..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # Conveys information about the resolution process to a user. - module UI - # The {IO} object that should be used to print output. `STDOUT`, by default. - # - # @return [IO] - def output - STDOUT - end - - # Called roughly every {#progress_rate}, this method should convey progress - # to the user. - # - # @return [void] - def indicate_progress - output.print '.' unless debug? - end - - # How often progress should be conveyed to the user via - # {#indicate_progress}, in seconds. A third of a second, by default. - # - # @return [Float] - def progress_rate - 0.33 - end - - # Called before resolution begins. - # - # @return [void] - def before_resolution - output.print 'Resolving dependencies...' - end - - # Called after resolution ends (either successfully or with an error). - # By default, prints a newline. - # - # @return [void] - def after_resolution - output.puts - end - - # Conveys debug information to the user. - # - # @param [Integer] depth the current depth of the resolution process. - # @return [void] - def debug(depth = 0) - if debug? - debug_info = yield - debug_info = debug_info.inspect unless debug_info.is_a?(String) - debug_info = debug_info.split("\n").map { |s| ":#{depth.to_s.rjust 4}: #{s}" } - output.puts debug_info - end - end - - # Whether or not debug messages should be printed. - # By default, whether or not the `MOLINILLO_DEBUG` environment variable is - # set. - # - # @return [Boolean] - def debug? - return @debug_mode if defined?(@debug_mode) - @debug_mode = ENV['MOLINILLO_DEBUG'] - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb deleted file mode 100644 index c689ca7635..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +++ /dev/null @@ -1,839 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - class Resolver - # A specific resolution from a given {Resolver} - class Resolution - # A conflict that the resolution process encountered - # @attr [Object] requirement the requirement that immediately led to the conflict - # @attr [{String,Nil=>[Object]}] requirements the requirements that caused the conflict - # @attr [Object, nil] existing the existing spec that was in conflict with - # the {#possibility} - # @attr [Object] possibility_set the set of specs that was unable to be - # activated due to a conflict. - # @attr [Object] locked_requirement the relevant locking requirement. - # @attr [Array<Array<Object>>] requirement_trees the different requirement - # trees that led to every requirement for the conflicting name. - # @attr [{String=>Object}] activated_by_name the already-activated specs. - # @attr [Object] underlying_error an error that has occurred during resolution, and - # will be raised at the end of it if no resolution is found. - Conflict = Struct.new( - :requirement, - :requirements, - :existing, - :possibility_set, - :locked_requirement, - :requirement_trees, - :activated_by_name, - :underlying_error - ) - - class Conflict - # @return [Object] a spec that was unable to be activated due to a conflict - def possibility - possibility_set && possibility_set.latest_version - end - end - - # A collection of possibility states that share the same dependencies - # @attr [Array] dependencies the dependencies for this set of possibilities - # @attr [Array] possibilities the possibilities - PossibilitySet = Struct.new(:dependencies, :possibilities) - - class PossibilitySet - # String representation of the possibility set, for debugging - def to_s - "[#{possibilities.join(', ')}]" - end - - # @return [Object] most up-to-date dependency in the possibility set - def latest_version - possibilities.last - end - end - - # Details of the state to unwind to when a conflict occurs, and the cause of the unwind - # @attr [Integer] state_index the index of the state to unwind to - # @attr [Object] state_requirement the requirement of the state we're unwinding to - # @attr [Array] requirement_tree for the requirement we're relaxing - # @attr [Array] conflicting_requirements the requirements that combined to cause the conflict - # @attr [Array] requirement_trees for the conflict - # @attr [Array] requirements_unwound_to_instead array of unwind requirements that were chosen over this unwind - UnwindDetails = Struct.new( - :state_index, - :state_requirement, - :requirement_tree, - :conflicting_requirements, - :requirement_trees, - :requirements_unwound_to_instead - ) - - class UnwindDetails - include Comparable - - # We compare UnwindDetails when choosing which state to unwind to. If - # two options have the same state_index we prefer the one most - # removed from a requirement that caused the conflict. Both options - # would unwind to the same state, but a `grandparent` option will - # filter out fewer of its possibilities after doing so - where a state - # is both a `parent` and a `grandparent` to requirements that have - # caused a conflict this is the correct behaviour. - # @param [UnwindDetail] other UnwindDetail to be compared - # @return [Integer] integer specifying ordering - def <=>(other) - if state_index > other.state_index - 1 - elsif state_index == other.state_index - reversed_requirement_tree_index <=> other.reversed_requirement_tree_index - else - -1 - end - end - - # @return [Integer] index of state requirement in reversed requirement tree - # (the conflicting requirement itself will be at position 0) - def reversed_requirement_tree_index - @reversed_requirement_tree_index ||= - if state_requirement - requirement_tree.reverse.index(state_requirement) - else - 999_999 - end - end - - # @return [Boolean] where the requirement of the state we're unwinding - # to directly caused the conflict. Note: in this case, it is - # impossible for the state we're unwinding to to be a parent of - # any of the other conflicting requirements (or we would have - # circularity) - def unwinding_to_primary_requirement? - requirement_tree.last == state_requirement - end - - # @return [Array] array of sub-dependencies to avoid when choosing a - # new possibility for the state we've unwound to. Only relevant for - # non-primary unwinds - def sub_dependencies_to_avoid - @requirements_to_avoid ||= - requirement_trees.map do |tree| - index = tree.index(state_requirement) - tree[index + 1] if index - end.compact - end - - # @return [Array] array of all the requirements that led to the need for - # this unwind - def all_requirements - @all_requirements ||= requirement_trees.flatten(1) - end - end - - # @return [SpecificationProvider] the provider that knows about - # dependencies, requirements, specifications, versions, etc. - attr_reader :specification_provider - - # @return [UI] the UI that knows how to communicate feedback about the - # resolution process back to the user - attr_reader :resolver_ui - - # @return [DependencyGraph] the base dependency graph to which - # dependencies should be 'locked' - attr_reader :base - - # @return [Array] the dependencies that were explicitly required - attr_reader :original_requested - - # Initializes a new resolution. - # @param [SpecificationProvider] specification_provider - # see {#specification_provider} - # @param [UI] resolver_ui see {#resolver_ui} - # @param [Array] requested see {#original_requested} - # @param [DependencyGraph] base see {#base} - def initialize(specification_provider, resolver_ui, requested, base) - @specification_provider = specification_provider - @resolver_ui = resolver_ui - @original_requested = requested - @base = base - @states = [] - @iteration_counter = 0 - @parents_of = Hash.new { |h, k| h[k] = [] } - end - - # Resolves the {#original_requested} dependencies into a full dependency - # graph - # @raise [ResolverError] if successful resolution is impossible - # @return [DependencyGraph] the dependency graph of successfully resolved - # dependencies - def resolve - start_resolution - - while state - break if !state.requirement && state.requirements.empty? - indicate_progress - if state.respond_to?(:pop_possibility_state) # DependencyState - debug(depth) { "Creating possibility state for #{requirement} (#{possibilities.count} remaining)" } - state.pop_possibility_state.tap do |s| - if s - states.push(s) - activated.tag(s) - end - end - end - process_topmost_state - end - - resolve_activated_specs - ensure - end_resolution - end - - # @return [Integer] the number of resolver iterations in between calls to - # {#resolver_ui}'s {UI#indicate_progress} method - attr_accessor :iteration_rate - private :iteration_rate - - # @return [Time] the time at which resolution began - attr_accessor :started_at - private :started_at - - # @return [Array<ResolutionState>] the stack of states for the resolution - attr_accessor :states - private :states - - private - - # Sets up the resolution process - # @return [void] - def start_resolution - @started_at = Time.now - - push_initial_state - - debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } - resolver_ui.before_resolution - end - - def resolve_activated_specs - activated.vertices.each do |_, vertex| - next unless vertex.payload - - latest_version = vertex.payload.possibilities.reverse_each.find do |possibility| - vertex.requirements.all? { |req| requirement_satisfied_by?(req, activated, possibility) } - end - - activated.set_payload(vertex.name, latest_version) - end - activated.freeze - end - - # Ends the resolution process - # @return [void] - def end_resolution - resolver_ui.after_resolution - debug do - "Finished resolution (#{@iteration_counter} steps) " \ - "(Took #{(ended_at = Time.now) - @started_at} seconds) (#{ended_at})" - end - debug { 'Unactivated: ' + Hash[activated.vertices.reject { |_n, v| v.payload }].keys.join(', ') } if state - debug { 'Activated: ' + Hash[activated.vertices.select { |_n, v| v.payload }].keys.join(', ') } if state - end - - require_relative 'state' - require_relative 'modules/specification_provider' - - require_relative 'delegates/resolution_state' - require_relative 'delegates/specification_provider' - - include Bundler::Molinillo::Delegates::ResolutionState - include Bundler::Molinillo::Delegates::SpecificationProvider - - # Processes the topmost available {RequirementState} on the stack - # @return [void] - def process_topmost_state - if possibility - attempt_to_activate - else - create_conflict - unwind_for_conflict - end - rescue CircularDependencyError => underlying_error - create_conflict(underlying_error) - unwind_for_conflict - end - - # @return [Object] the current possibility that the resolution is trying - # to activate - def possibility - possibilities.last - end - - # @return [RequirementState] the current state the resolution is - # operating upon - def state - states.last - end - - # Creates and pushes the initial state for the resolution, based upon the - # {#requested} dependencies - # @return [void] - def push_initial_state - graph = DependencyGraph.new.tap do |dg| - original_requested.each do |requested| - vertex = dg.add_vertex(name_for(requested), nil, true) - vertex.explicit_requirements << requested - end - dg.tag(:initial_state) - end - - push_state_for_requirements(original_requested, true, graph) - end - - # Unwinds the states stack because a conflict has been encountered - # @return [void] - def unwind_for_conflict - details_for_unwind = build_details_for_unwind - unwind_options = unused_unwind_options - debug(depth) { "Unwinding for conflict: #{requirement} to #{details_for_unwind.state_index / 2}" } - conflicts.tap do |c| - sliced_states = states.slice!((details_for_unwind.state_index + 1)..-1) - raise_error_unless_state(c) - activated.rewind_to(sliced_states.first || :initial_state) if sliced_states - state.conflicts = c - state.unused_unwind_options = unwind_options - filter_possibilities_after_unwind(details_for_unwind) - index = states.size - 1 - @parents_of.each { |_, a| a.reject! { |i| i >= index } } - state.unused_unwind_options.reject! { |uw| uw.state_index >= index } - end - end - - # Raises a VersionConflict error, or any underlying error, if there is no - # current state - # @return [void] - def raise_error_unless_state(conflicts) - return if state - - error = conflicts.values.map(&:underlying_error).compact.first - raise error || VersionConflict.new(conflicts, specification_provider) - end - - # @return [UnwindDetails] Details of the nearest index to which we could unwind - def build_details_for_unwind - # Get the possible unwinds for the current conflict - current_conflict = conflicts[name] - binding_requirements = binding_requirements_for_conflict(current_conflict) - unwind_details = unwind_options_for_requirements(binding_requirements) - - last_detail_for_current_unwind = unwind_details.sort.last - current_detail = last_detail_for_current_unwind - - # Look for past conflicts that could be unwound to affect the - # requirement tree for the current conflict - all_reqs = last_detail_for_current_unwind.all_requirements - all_reqs_size = all_reqs.size - relevant_unused_unwinds = unused_unwind_options.select do |alternative| - diff_reqs = all_reqs - alternative.requirements_unwound_to_instead - next if diff_reqs.size == all_reqs_size - # Find the highest index unwind whilst looping through - current_detail = alternative if alternative > current_detail - alternative - end - - # Add the current unwind options to the `unused_unwind_options` array. - # The "used" option will be filtered out during `unwind_for_conflict`. - state.unused_unwind_options += unwind_details.reject { |detail| detail.state_index == -1 } - - # Update the requirements_unwound_to_instead on any relevant unused unwinds - relevant_unused_unwinds.each do |d| - (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! - end - unwind_details.each do |d| - (d.requirements_unwound_to_instead << current_detail.state_requirement).uniq! - end - - current_detail - end - - # @param [Array<Object>] binding_requirements array of requirements that combine to create a conflict - # @return [Array<UnwindDetails>] array of UnwindDetails that have a chance - # of resolving the passed requirements - def unwind_options_for_requirements(binding_requirements) - unwind_details = [] - - trees = [] - binding_requirements.reverse_each do |r| - partial_tree = [r] - trees << partial_tree - unwind_details << UnwindDetails.new(-1, nil, partial_tree, binding_requirements, trees, []) - - # If this requirement has alternative possibilities, check if any would - # satisfy the other requirements that created this conflict - requirement_state = find_state_for(r) - if conflict_fixing_possibilities?(requirement_state, binding_requirements) - unwind_details << UnwindDetails.new( - states.index(requirement_state), - r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - - # Next, look at the parent of this requirement, and check if the requirement - # could have been avoided if an alternative PossibilitySet had been chosen - parent_r = parent_of(r) - next if parent_r.nil? - partial_tree.unshift(parent_r) - requirement_state = find_state_for(parent_r) - if requirement_state.possibilities.any? { |set| !set.dependencies.include?(r) } - unwind_details << UnwindDetails.new( - states.index(requirement_state), - parent_r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - - # Finally, look at the grandparent and up of this requirement, looking - # for any possibilities that wouldn't create their parent requirement - grandparent_r = parent_of(parent_r) - until grandparent_r.nil? - partial_tree.unshift(grandparent_r) - requirement_state = find_state_for(grandparent_r) - if requirement_state.possibilities.any? { |set| !set.dependencies.include?(parent_r) } - unwind_details << UnwindDetails.new( - states.index(requirement_state), - grandparent_r, - partial_tree, - binding_requirements, - trees, - [] - ) - end - parent_r = grandparent_r - grandparent_r = parent_of(parent_r) - end - end - - unwind_details - end - - # @param [DependencyState] state - # @param [Array] binding_requirements array of requirements - # @return [Boolean] whether or not the given state has any possibilities - # that could satisfy the given requirements - def conflict_fixing_possibilities?(state, binding_requirements) - return false unless state - - state.possibilities.any? do |possibility_set| - possibility_set.possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, binding_requirements) - end - end - end - - # Filter's a state's possibilities to remove any that would not fix the - # conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just - # unwound from - # @return [void] - def filter_possibilities_after_unwind(unwind_details) - return unless state && !state.possibilities.empty? - - if unwind_details.unwinding_to_primary_requirement? - filter_possibilities_for_primary_unwind(unwind_details) - else - filter_possibilities_for_parent_unwind(unwind_details) - end - end - - # Filter's a state's possibilities to remove any that would not satisfy - # the requirements in the conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just unwound from - # @return [void] - def filter_possibilities_for_primary_unwind(unwind_details) - unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } - unwinds_to_state << unwind_details - unwind_requirement_sets = unwinds_to_state.map(&:conflicting_requirements) - - state.possibilities.reject! do |possibility_set| - possibility_set.possibilities.none? do |poss| - unwind_requirement_sets.any? do |requirements| - possibility_satisfies_requirements?(poss, requirements) - end - end - end - end - - # @param [Object] possibility a single possibility - # @param [Array] requirements an array of requirements - # @return [Boolean] whether the possibility satisfies all of the - # given requirements - def possibility_satisfies_requirements?(possibility, requirements) - name = name_for(possibility) - - activated.tag(:swap) - activated.set_payload(name, possibility) if activated.vertex_named(name) - satisfied = requirements.all? { |r| requirement_satisfied_by?(r, activated, possibility) } - activated.rewind_to(:swap) - - satisfied - end - - # Filter's a state's possibilities to remove any that would (eventually) - # create a requirement in the conflict we've just rewound from - # @param [UnwindDetails] unwind_details details of the conflict just unwound from - # @return [void] - def filter_possibilities_for_parent_unwind(unwind_details) - unwinds_to_state = unused_unwind_options.select { |uw| uw.state_index == unwind_details.state_index } - unwinds_to_state << unwind_details - - primary_unwinds = unwinds_to_state.select(&:unwinding_to_primary_requirement?).uniq - parent_unwinds = unwinds_to_state.uniq - primary_unwinds - - allowed_possibility_sets = primary_unwinds.flat_map do |unwind| - states[unwind.state_index].possibilities.select do |possibility_set| - possibility_set.possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, unwind.conflicting_requirements) - end - end - end - - requirements_to_avoid = parent_unwinds.flat_map(&:sub_dependencies_to_avoid) - - state.possibilities.reject! do |possibility_set| - !allowed_possibility_sets.include?(possibility_set) && - (requirements_to_avoid - possibility_set.dependencies).empty? - end - end - - # @param [Conflict] conflict - # @return [Array] minimal array of requirements that would cause the passed - # conflict to occur. - def binding_requirements_for_conflict(conflict) - return [conflict.requirement] if conflict.possibility.nil? - - possible_binding_requirements = conflict.requirements.values.flatten(1).uniq - - # When there's a `CircularDependency` error the conflicting requirement - # (the one causing the circular) won't be `conflict.requirement` - # (which won't be for the right state, because we won't have created it, - # because it's circular). - # We need to make sure we have that requirement in the conflict's list, - # otherwise we won't be able to unwind properly, so we just return all - # the requirements for the conflict. - return possible_binding_requirements if conflict.underlying_error - - possibilities = search_for(conflict.requirement) - - # If all the requirements together don't filter out all possibilities, - # then the only two requirements we need to consider are the initial one - # (where the dependency's version was first chosen) and the last - if binding_requirement_in_set?(nil, possible_binding_requirements, possibilities) - return [conflict.requirement, requirement_for_existing_name(name_for(conflict.requirement))].compact - end - - # Loop through the possible binding requirements, removing each one - # that doesn't bind. Use a `reverse_each` as we want the earliest set of - # binding requirements, and don't use `reject!` as we wish to refine the - # array *on each iteration*. - binding_requirements = possible_binding_requirements.dup - possible_binding_requirements.reverse_each do |req| - next if req == conflict.requirement - unless binding_requirement_in_set?(req, binding_requirements, possibilities) - binding_requirements -= [req] - end - end - - binding_requirements - end - - # @param [Object] requirement we wish to check - # @param [Array] possible_binding_requirements array of requirements - # @param [Array] possibilities array of possibilities the requirements will be used to filter - # @return [Boolean] whether or not the given requirement is required to filter - # out all elements of the array of possibilities. - def binding_requirement_in_set?(requirement, possible_binding_requirements, possibilities) - possibilities.any? do |poss| - possibility_satisfies_requirements?(poss, possible_binding_requirements - [requirement]) - end - end - - # @param [Object] requirement - # @return [Object] the requirement that led to `requirement` being added - # to the list of requirements. - def parent_of(requirement) - return unless requirement - return unless index = @parents_of[requirement].last - return unless parent_state = @states[index] - parent_state.requirement - end - - # @param [String] name - # @return [Object] the requirement that led to a version of a possibility - # with the given name being activated. - def requirement_for_existing_name(name) - return nil unless vertex = activated.vertex_named(name) - return nil unless vertex.payload - states.find { |s| s.name == name }.requirement - end - - # @param [Object] requirement - # @return [ResolutionState] the state whose `requirement` is the given - # `requirement`. - def find_state_for(requirement) - return nil unless requirement - states.find { |i| requirement == i.requirement } - end - - # @param [Object] underlying_error - # @return [Conflict] a {Conflict} that reflects the failure to activate - # the {#possibility} in conjunction with the current {#state} - def create_conflict(underlying_error = nil) - vertex = activated.vertex_named(name) - locked_requirement = locked_requirement_named(name) - - requirements = {} - unless vertex.explicit_requirements.empty? - requirements[name_for_explicit_dependency_source] = vertex.explicit_requirements - end - requirements[name_for_locking_dependency_source] = [locked_requirement] if locked_requirement - vertex.incoming_edges.each do |edge| - (requirements[edge.origin.payload.latest_version] ||= []).unshift(edge.requirement) - end - - activated_by_name = {} - activated.each { |v| activated_by_name[v.name] = v.payload.latest_version if v.payload } - conflicts[name] = Conflict.new( - requirement, - requirements, - vertex.payload && vertex.payload.latest_version, - possibility, - locked_requirement, - requirement_trees, - activated_by_name, - underlying_error - ) - end - - # @return [Array<Array<Object>>] The different requirement - # trees that led to every requirement for the current spec. - def requirement_trees - vertex = activated.vertex_named(name) - vertex.requirements.map { |r| requirement_tree_for(r) } - end - - # @param [Object] requirement - # @return [Array<Object>] the list of requirements that led to - # `requirement` being required. - def requirement_tree_for(requirement) - tree = [] - while requirement - tree.unshift(requirement) - requirement = parent_of(requirement) - end - tree - end - - # Indicates progress roughly once every second - # @return [void] - def indicate_progress - @iteration_counter += 1 - @progress_rate ||= resolver_ui.progress_rate - if iteration_rate.nil? - if Time.now - started_at >= @progress_rate - self.iteration_rate = @iteration_counter - end - end - - if iteration_rate && (@iteration_counter % iteration_rate) == 0 - resolver_ui.indicate_progress - end - end - - # Calls the {#resolver_ui}'s {UI#debug} method - # @param [Integer] depth the depth of the {#states} stack - # @param [Proc] block a block that yields a {#to_s} - # @return [void] - def debug(depth = 0, &block) - resolver_ui.debug(depth, &block) - end - - # Attempts to activate the current {#possibility} - # @return [void] - def attempt_to_activate - debug(depth) { 'Attempting to activate ' + possibility.to_s } - existing_vertex = activated.vertex_named(name) - if existing_vertex.payload - debug(depth) { "Found existing spec (#{existing_vertex.payload})" } - attempt_to_filter_existing_spec(existing_vertex) - else - latest = possibility.latest_version - possibility.possibilities.select! do |possibility| - requirement_satisfied_by?(requirement, activated, possibility) - end - if possibility.latest_version.nil? - # ensure there's a possibility for better error messages - possibility.possibilities << latest if latest - create_conflict - unwind_for_conflict - else - activate_new_spec - end - end - end - - # Attempts to update the existing vertex's `PossibilitySet` with a filtered version - # @return [void] - def attempt_to_filter_existing_spec(vertex) - filtered_set = filtered_possibility_set(vertex) - if !filtered_set.possibilities.empty? - activated.set_payload(name, filtered_set) - new_requirements = requirements.dup - push_state_for_requirements(new_requirements, false) - else - create_conflict - debug(depth) { "Unsatisfied by existing spec (#{vertex.payload})" } - unwind_for_conflict - end - end - - # Generates a filtered version of the existing vertex's `PossibilitySet` using the - # current state's `requirement` - # @param [Object] vertex existing vertex - # @return [PossibilitySet] filtered possibility set - def filtered_possibility_set(vertex) - PossibilitySet.new(vertex.payload.dependencies, vertex.payload.possibilities & possibility.possibilities) - end - - # @param [String] requirement_name the spec name to search for - # @return [Object] the locked spec named `requirement_name`, if one - # is found on {#base} - def locked_requirement_named(requirement_name) - vertex = base.vertex_named(requirement_name) - vertex && vertex.payload - end - - # Add the current {#possibility} to the dependency graph of the current - # {#state} - # @return [void] - def activate_new_spec - conflicts.delete(name) - debug(depth) { "Activated #{name} at #{possibility}" } - activated.set_payload(name, possibility) - require_nested_dependencies_for(possibility) - end - - # Requires the dependencies that the recently activated spec has - # @param [Object] possibility_set the PossibilitySet that has just been - # activated - # @return [void] - def require_nested_dependencies_for(possibility_set) - nested_dependencies = dependencies_for(possibility_set.latest_version) - debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } - nested_dependencies.each do |d| - activated.add_child_vertex(name_for(d), nil, [name_for(possibility_set.latest_version)], d) - parent_index = states.size - 1 - parents = @parents_of[d] - parents << parent_index if parents.empty? - end - - push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) - end - - # Pushes a new {DependencyState} that encapsulates both existing and new - # requirements - # @param [Array] new_requirements - # @param [Boolean] requires_sort - # @param [Object] new_activated - # @return [void] - def push_state_for_requirements(new_requirements, requires_sort = true, new_activated = activated) - new_requirements = sort_dependencies(new_requirements.uniq, new_activated, conflicts) if requires_sort - new_requirement = nil - loop do - new_requirement = new_requirements.shift - break if new_requirement.nil? || states.none? { |s| s.requirement == new_requirement } - end - new_name = new_requirement ? name_for(new_requirement) : ''.freeze - possibilities = possibilities_for_requirement(new_requirement) - handle_missing_or_push_dependency_state DependencyState.new( - new_name, new_requirements, new_activated, - new_requirement, possibilities, depth, conflicts.dup, unused_unwind_options.dup - ) - end - - # Checks a proposed requirement with any existing locked requirement - # before generating an array of possibilities for it. - # @param [Object] requirement the proposed requirement - # @param [Object] activated - # @return [Array] possibilities - def possibilities_for_requirement(requirement, activated = self.activated) - return [] unless requirement - if locked_requirement_named(name_for(requirement)) - return locked_requirement_possibility_set(requirement, activated) - end - - group_possibilities(search_for(requirement)) - end - - # @param [Object] requirement the proposed requirement - # @param [Object] activated - # @return [Array] possibility set containing only the locked requirement, if any - def locked_requirement_possibility_set(requirement, activated = self.activated) - all_possibilities = search_for(requirement) - locked_requirement = locked_requirement_named(name_for(requirement)) - - # Longwinded way to build a possibilities array with either the locked - # requirement or nothing in it. Required, since the API for - # locked_requirement isn't guaranteed. - locked_possibilities = all_possibilities.select do |possibility| - requirement_satisfied_by?(locked_requirement, activated, possibility) - end - - group_possibilities(locked_possibilities) - end - - # Build an array of PossibilitySets, with each element representing a group of - # dependency versions that all have the same sub-dependency version constraints - # and are contiguous. - # @param [Array] possibilities an array of possibilities - # @return [Array<PossibilitySet>] an array of possibility sets - def group_possibilities(possibilities) - possibility_sets = [] - current_possibility_set = nil - - possibilities.reverse_each do |possibility| - dependencies = dependencies_for(possibility) - if current_possibility_set && dependencies_equal?(current_possibility_set.dependencies, dependencies) - current_possibility_set.possibilities.unshift(possibility) - else - possibility_sets.unshift(PossibilitySet.new(dependencies, [possibility])) - current_possibility_set = possibility_sets.first - end - end - - possibility_sets - end - - # Pushes a new {DependencyState}. - # If the {#specification_provider} says to - # {SpecificationProvider#allow_missing?} that particular requirement, and - # there are no possibilities for that requirement, then `state` is not - # pushed, and the vertex in {#activated} is removed, and we continue - # resolving the remaining requirements. - # @param [DependencyState] state - # @return [void] - def handle_missing_or_push_dependency_state(state) - if state.requirement && state.possibilities.empty? && allow_missing?(state.requirement) - state.activated.detach_vertex_named(state.name) - push_state_for_requirements(state.requirements.dup, false, state.activated) - else - states.push(state).tap { activated.tag(state) } - end - end - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb deleted file mode 100644 index 95eaab5991..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/resolver.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative 'dependency_graph' - -module Bundler::Molinillo - # This class encapsulates a dependency resolver. - # The resolver is responsible for determining which set of dependencies to - # activate, with feedback from the {#specification_provider} - # - # - class Resolver - require_relative 'resolution' - - # @return [SpecificationProvider] the specification provider used - # in the resolution process - attr_reader :specification_provider - - # @return [UI] the UI module used to communicate back to the user - # during the resolution process - attr_reader :resolver_ui - - # Initializes a new resolver. - # @param [SpecificationProvider] specification_provider - # see {#specification_provider} - # @param [UI] resolver_ui - # see {#resolver_ui} - def initialize(specification_provider, resolver_ui) - @specification_provider = specification_provider - @resolver_ui = resolver_ui - end - - # Resolves the requested dependencies into a {DependencyGraph}, - # locking to the base dependency graph (if specified) - # @param [Array] requested an array of 'requested' dependencies that the - # {#specification_provider} can understand - # @param [DependencyGraph,nil] base the base dependency graph to which - # dependencies should be 'locked' - def resolve(requested, base = DependencyGraph.new) - Resolution.new(specification_provider, - resolver_ui, - requested, - base). - resolve - end - end -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb b/lib/bundler/vendor/molinillo/lib/molinillo/state.rb deleted file mode 100644 index 68fa1f54e3..0000000000 --- a/lib/bundler/vendor/molinillo/lib/molinillo/state.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -module Bundler::Molinillo - # A state that a {Resolution} can be in - # @attr [String] name the name of the current requirement - # @attr [Array<Object>] requirements currently unsatisfied requirements - # @attr [DependencyGraph] activated the graph of activated dependencies - # @attr [Object] requirement the current requirement - # @attr [Object] possibilities the possibilities to satisfy the current requirement - # @attr [Integer] depth the depth of the resolution - # @attr [Hash] conflicts unresolved conflicts, indexed by dependency name - # @attr [Array<UnwindDetails>] unused_unwind_options unwinds for previous conflicts that weren't explored - ResolutionState = Struct.new( - :name, - :requirements, - :activated, - :requirement, - :possibilities, - :depth, - :conflicts, - :unused_unwind_options - ) - - class ResolutionState - # Returns an empty resolution state - # @return [ResolutionState] an empty state - def self.empty - new(nil, [], DependencyGraph.new, nil, nil, 0, {}, []) - end - end - - # A state that encapsulates a set of {#requirements} with an {Array} of - # possibilities - class DependencyState < ResolutionState - # Removes a possibility from `self` - # @return [PossibilityState] a state with a single possibility, - # the possibility that was removed from `self` - def pop_possibility_state - PossibilityState.new( - name, - requirements.dup, - activated, - requirement, - [possibilities.pop], - depth + 1, - conflicts.dup, - unused_unwind_options.dup - ).tap do |state| - state.activated.tag(state) - end - end - end - - # A state that encapsulates a single possibility to fulfill the given - # {#requirement} - class PossibilityState < ResolutionState - end -end diff --git a/lib/bundler/vendor/net-http-persistent/.document b/lib/bundler/vendor/net-http-persistent/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/net-http-persistent/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb index beff6d658b..c15b346330 100644 --- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb @@ -1,14 +1,14 @@ -require 'net/http' -require_relative '../../../../uri/lib/uri' +require_relative '../../../../../vendored_net_http' +require_relative '../../../../../vendored_uri' require 'cgi' # for escaping require_relative '../../../../connection_pool/lib/connection_pool' autoload :OpenSSL, 'openssl' ## -# Persistent connections for Net::HTTP +# Persistent connections for Gem::Net::HTTP # -# Bundler::Persistent::Net::HTTP::Persistent maintains persistent connections across all the +# Gem::Net::HTTP::Persistent maintains persistent connections across all the # servers you wish to talk to. For each host:port you communicate with a # single persistent connection is created. # @@ -22,34 +22,34 @@ autoload :OpenSSL, 'openssl' # # require 'bundler/vendor/net-http-persistent/lib/net/http/persistent' # -# uri = Bundler::URI 'http://example.com/awesome/web/service' +# uri = Gem::URI 'http://example.com/awesome/web/service' # -# http = Bundler::Persistent::Net::HTTP::Persistent.new +# http = Gem::Net::HTTP::Persistent.new # # # perform a GET # response = http.request uri # # # or # -# get = Net::HTTP::Get.new uri.request_uri +# get = Gem::Net::HTTP::Get.new uri.request_uri # response = http.request get # # # create a POST # post_uri = uri + 'create' -# post = Net::HTTP::Post.new post_uri.path +# post = Gem::Net::HTTP::Post.new post_uri.path # post.set_form_data 'some' => 'cool data' # -# # perform the POST, the Bundler::URI is always required +# # perform the POST, the Gem::URI is always required # response http.request post_uri, post # # Note that for GET, HEAD and other requests that do not have a body you want -# to use Bundler::URI#request_uri not Bundler::URI#path. The request_uri contains the query +# to use Gem::URI#request_uri not Gem::URI#path. The request_uri contains the query # params which are sent in the body for other requests. # # == TLS/SSL # # TLS connections are automatically created depending upon the scheme of the -# Bundler::URI. TLS connections are automatically verified against the default +# Gem::URI. TLS connections are automatically verified against the default # certificate store for your computer. You can override this by changing # verify_mode or by specifying an alternate cert_store. # @@ -72,7 +72,7 @@ autoload :OpenSSL, 'openssl' # == Proxies # # A proxy can be set through #proxy= or at initialization time by providing a -# second argument to ::new. The proxy may be the Bundler::URI of the proxy server or +# second argument to ::new. The proxy may be the Gem::URI of the proxy server or # <code>:ENV</code> which will consult environment variables. # # See #proxy= and #proxy_from_env for details. @@ -92,7 +92,7 @@ autoload :OpenSSL, 'openssl' # # === Segregation # -# Each Bundler::Persistent::Net::HTTP::Persistent instance has its own pool of connections. There +# 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 @@ -131,7 +131,7 @@ autoload :OpenSSL, 'openssl' # # === Connection Termination # -# If you are done using the Bundler::Persistent::Net::HTTP::Persistent instance you may shut down +# 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. @@ -141,7 +141,7 @@ autoload :OpenSSL, 'openssl' # Ruby will automatically garbage collect and shutdown your HTTP connections # when the thread terminates. -class Bundler::Persistent::Net::HTTP::Persistent +class Gem::Net::HTTP::Persistent ## # The beginning of Time @@ -172,12 +172,12 @@ class Bundler::Persistent::Net::HTTP::Persistent end ## - # The version of Bundler::Persistent::Net::HTTP::Persistent you are using + # The version of Gem::Net::HTTP::Persistent you are using - VERSION = '4.0.0' + VERSION = '4.0.2' ## - # Error class for errors raised by Bundler::Persistent::Net::HTTP::Persistent. Various + # Error class for errors raised by Gem::Net::HTTP::Persistent. Various # SystemCallErrors are re-raised with a human-readable message under this # class. @@ -197,10 +197,10 @@ class Bundler::Persistent::Net::HTTP::Persistent # NOTE: This may not work on ruby > 1.9. def self.detect_idle_timeout uri, max = 10 - uri = Bundler::URI uri unless Bundler::URI::Generic === uri + uri = Gem::URI uri unless Gem::URI::Generic === uri uri += '/' - req = Net::HTTP::Head.new uri.request_uri + req = Gem::Net::HTTP::Head.new uri.request_uri http = new 'net-http-persistent detect_idle_timeout' @@ -214,7 +214,7 @@ class Bundler::Persistent::Net::HTTP::Persistent $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG - unless Net::HTTPOK === response then + unless Gem::Net::HTTPOK === response then raise Error, "bad response code #{response.code} detecting idle timeout" end @@ -238,7 +238,7 @@ class Bundler::Persistent::Net::HTTP::Persistent attr_reader :certificate ## - # For Net::HTTP parity + # For Gem::Net::HTTP parity alias cert certificate @@ -266,7 +266,7 @@ class Bundler::Persistent::Net::HTTP::Persistent attr_reader :ciphers ## - # Sends debug_output to this IO via Net::HTTP#set_debug_output. + # 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. @@ -279,7 +279,7 @@ class Bundler::Persistent::Net::HTTP::Persistent attr_reader :generation # :nodoc: ## - # Headers that are added to every request using Net::HTTP#add_field + # Headers that are added to every request using Gem::Net::HTTP#add_field attr_reader :headers @@ -304,7 +304,7 @@ class Bundler::Persistent::Net::HTTP::Persistent ## # Number of retries to perform if a request fails. # - # See also #max_retries=, Net::HTTP#max_retries=. + # See also #max_retries=, Gem::Net::HTTP#max_retries=. attr_reader :max_retries @@ -325,12 +325,12 @@ class Bundler::Persistent::Net::HTTP::Persistent attr_reader :name ## - # Seconds to wait until a connection is opened. See Net::HTTP#open_timeout + # 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 Net::HTTP#[]= + # Headers that are added to every request using Gem::Net::HTTP#[]= attr_reader :override_headers @@ -340,7 +340,7 @@ class Bundler::Persistent::Net::HTTP::Persistent attr_reader :private_key ## - # For Net::HTTP parity + # For Gem::Net::HTTP parity alias key private_key @@ -360,12 +360,12 @@ class Bundler::Persistent::Net::HTTP::Persistent attr_reader :pool # :nodoc: ## - # Seconds to wait until reading one block. See Net::HTTP#read_timeout + # 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 Net::HTTP#write_timeout + # Seconds to wait until writing one block. See Gem::Net::HTTP#write_timeout attr_accessor :write_timeout @@ -450,18 +450,18 @@ class Bundler::Persistent::Net::HTTP::Persistent attr_reader :verify_mode ## - # Creates a new Bundler::Persistent::Net::HTTP::Persistent. + # 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 Bundler::URI::HTTP or :ENV to pick up proxy options from + # +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 Bundler::URI for the proxy you may need to do some extra work - # beyond Bundler::URI parsing if the proxy requires a password: + # 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 = Bundler::URI 'http://proxy.example' + # proxy = Gem::URI 'http://proxy.example' # proxy.user = 'AzureDiamond' # proxy.password = 'hunter2' # @@ -492,8 +492,8 @@ class Bundler::Persistent::Net::HTTP::Persistent @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if Socket.const_defined? :TCP_NODELAY - @pool = Bundler::Persistent::Net::HTTP::Persistent::Pool.new size: pool_size do |http_args| - Bundler::Persistent::Net::HTTP::Persistent::Connection.new Net::HTTP, http_args, @ssl_generation + @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 @@ -510,7 +510,7 @@ class Bundler::Persistent::Net::HTTP::Persistent @verify_mode = nil @cert_store = nil - @generation = 0 # incremented when proxy Bundler::URI changes + @generation = 0 # incremented when proxy Gem::URI changes if HAVE_OPENSSL then @verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -529,7 +529,7 @@ class Bundler::Persistent::Net::HTTP::Persistent reconnect_ssl end - # For Net::HTTP parity + # For Gem::Net::HTTP parity alias cert= certificate= ## @@ -648,7 +648,7 @@ class Bundler::Persistent::Net::HTTP::Persistent end ## - # Starts the Net::HTTP +connection+ + # Starts the Gem::Net::HTTP +connection+ def start http http.set_debug_output @debug_output if @debug_output @@ -666,7 +666,7 @@ class Bundler::Persistent::Net::HTTP::Persistent end ## - # Finishes the Net::HTTP +connection+ + # Finishes the Gem::Net::HTTP +connection+ def finish connection connection.finish @@ -716,16 +716,16 @@ class Bundler::Persistent::Net::HTTP::Persistent reconnect_ssl end - # For Net::HTTP parity + # For Gem::Net::HTTP parity alias key= private_key= ## - # Sets the proxy server. The +proxy+ may be the Bundler::URI of the proxy server, + # 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 Bundler::URI is set after requests have been made, the next request + # 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 @@ -736,9 +736,9 @@ class Bundler::Persistent::Net::HTTP::Persistent def proxy= proxy @proxy_uri = case proxy when :ENV then proxy_from_env - when Bundler::URI::HTTP then proxy + when Gem::URI::HTTP then proxy when nil then # ignore - else raise ArgumentError, 'proxy must be :ENV or a Bundler::URI::HTTP' + else raise ArgumentError, 'proxy must be :ENV or a Gem::URI::HTTP' end @no_proxy.clear @@ -763,13 +763,13 @@ class Bundler::Persistent::Net::HTTP::Persistent end ## - # Creates a Bundler::URI for an HTTP proxy server from ENV variables. + # 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 Bundler::URI is given the + # 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 Bundler::URI. + # 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 @@ -785,7 +785,7 @@ class Bundler::Persistent::Net::HTTP::Persistent return nil if env_proxy.nil? or env_proxy.empty? - uri = Bundler::URI normalize_uri env_proxy + uri = Gem::URI normalize_uri env_proxy env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY'] @@ -835,7 +835,7 @@ class Bundler::Persistent::Net::HTTP::Persistent end ## - # Finishes then restarts the Net::HTTP +connection+ + # Finishes then restarts the Gem::Net::HTTP +connection+ def reset connection http = connection.http @@ -854,16 +854,16 @@ class Bundler::Persistent::Net::HTTP::Persistent end ## - # Makes a request on +uri+. If +req+ is nil a Net::HTTP::Get is performed + # 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 Net::HTTP#request (the body of + # 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 Net::HTTPGenericRequest subclass (see Net::HTTP for a list). + # +req+ must be a Gem::Net::HTTPGenericRequest subclass (see Gem::Net::HTTP for a list). def request uri, req = nil, &block - uri = Bundler::URI uri + uri = Gem::URI uri req = request_setup req || uri response = nil @@ -896,14 +896,14 @@ class Bundler::Persistent::Net::HTTP::Persistent end ## - # Creates a GET request if +req_or_uri+ is a Bundler::URI and adds headers to the + # 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 - Net::HTTP::Get.new req_or_uri.request_uri + Gem::Net::HTTP::Get.new req_or_uri.request_uri else req_or_uri end diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb index a57a5d1352..8b9ab5cc78 100644 --- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb @@ -1,8 +1,8 @@ ## -# A Net::HTTP connection wrapper that holds extra information for managing the +# A Gem::Net::HTTP connection wrapper that holds extra information for managing the # connection's lifetime. -class Bundler::Persistent::Net::HTTP::Persistent::Connection # :nodoc: +class Gem::Net::HTTP::Persistent::Connection # :nodoc: attr_accessor :http @@ -25,9 +25,10 @@ class Bundler::Persistent::Net::HTTP::Persistent::Connection # :nodoc: ensure reset end + alias_method :close, :finish def reset - @last_use = Bundler::Persistent::Net::HTTP::Persistent::EPOCH + @last_use = Gem::Net::HTTP::Persistent::EPOCH @requests = 0 end diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb index 9dfa6ffdb1..04a1e754bf 100644 --- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb @@ -1,4 +1,4 @@ -class Bundler::Persistent::Net::HTTP::Persistent::Pool < Bundler::ConnectionPool # :nodoc: +class Gem::Net::HTTP::Persistent::Pool < Bundler::ConnectionPool # :nodoc: attr_reader :available # :nodoc: attr_reader :key # :nodoc: @@ -6,25 +6,37 @@ class Bundler::Persistent::Net::HTTP::Persistent::Pool < Bundler::ConnectionPool def initialize(options = {}, &block) super - @available = Bundler::Persistent::Net::HTTP::Persistent::TimedStackMulti.new(@size, &block) + @available = Gem::Net::HTTP::Persistent::TimedStackMulti.new(@size, &block) @key = "current-#{@available.object_id}" end def checkin net_http_args - stack = Thread.current[@key][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? + raise Bundler::ConnectionPool::Error, 'no connections are checked out' if + stack.empty? - conn = stack.pop + conn = stack.pop - if stack.empty? - @available.push conn, connection_args: net_http_args + 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? + Thread.current[@key].delete(net_http_args) + Thread.current[@key] = nil if Thread.current[@key].empty? + end end - nil end diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb index 2da881c554..214804fcd9 100644 --- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb @@ -1,4 +1,4 @@ -class Bundler::Persistent::Net::HTTP::Persistent::TimedStackMulti < Bundler::ConnectionPool::TimedStack # :nodoc: +class Gem::Net::HTTP::Persistent::TimedStackMulti < Bundler::ConnectionPool::TimedStack # :nodoc: ## # Returns a new hash that has arrays for keys diff --git a/lib/bundler/vendor/pub_grub/.document b/lib/bundler/vendor/pub_grub/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub.rb new file mode 100644 index 0000000000..eaaba3fc98 --- /dev/null +++ b/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/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/assignment.rb new file mode 100644 index 0000000000..2236a97b5b --- /dev/null +++ b/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/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb new file mode 100644 index 0000000000..dce20d37ad --- /dev/null +++ b/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/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/failure_writer.rb new file mode 100644 index 0000000000..ee099b23f4 --- /dev/null +++ b/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/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/incompatibility.rb new file mode 100644 index 0000000000..239eaf3401 --- /dev/null +++ b/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/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/package.rb new file mode 100644 index 0000000000..efb9d3da16 --- /dev/null +++ b/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/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/partial_solution.rb new file mode 100644 index 0000000000..4c4b8ca844 --- /dev/null +++ b/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<Assignment> } + @assignments = [] + + # { Package => Array<Assignment> } + @assignments_by = Hash.new { |h,k| h[k] = [] } + @cumulative_assignments = {}.compare_by_identity + + # { Package => Package::Version } + @decisions = {} + + # { Package => Term } + @terms = {} + @relation_cache = Hash.new { |h,k| h[k] = {} } + + # { Package => Boolean } + @required = {} + end + + def add_assignment(assignment) + term = assignment.term + package = term.package + + @assignments << assignment + @assignments_by[package] << assignment + + @required[package] = true if term.positive? + + if @terms.key?(package) + old_term = @terms[package] + @terms[package] = old_term.intersect(term) + else + @terms[package] = term + end + @relation_cache[package].clear + + @cumulative_assignments[assignment] = @terms[package] + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/rubygems.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/rubygems.rb new file mode 100644 index 0000000000..245c23be22 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/rubygems.rb @@ -0,0 +1,45 @@ +module Bundler::PubGrub + module RubyGems + extend self + + def requirement_to_range(requirement) + ranges = requirement.requirements.map do |(op, ver)| + case op + when "~>" + name = "~> #{ver}" + bump = ver.class.new(ver.bump.to_s + ".A") + VersionRange.new(name: name, min: ver, max: bump, include_min: true) + when ">" + VersionRange.new(min: ver) + when ">=" + VersionRange.new(min: ver, include_min: true) + when "<" + VersionRange.new(max: ver) + when "<=" + VersionRange.new(max: ver, include_max: true) + when "=" + VersionRange.new(min: ver, max: ver, include_min: true, include_max: true) + when "!=" + VersionRange.new(min: ver, max: ver, include_min: true, include_max: true).invert + else + raise "bad version specifier: #{op}" + end + end + + ranges.inject(&:intersect) + end + + def requirement_to_constraint(package, requirement) + Bundler::PubGrub::VersionConstraint.new(package, range: requirement_to_range(requirement)) + end + + def parse_range(dep) + requirement_to_range(Gem::Requirement.new(dep)) + end + + def parse_constraint(package, dep) + range = parse_range(dep) + Bundler::PubGrub::VersionConstraint.new(package, range: range) + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/solve_failure.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/solve_failure.rb new file mode 100644 index 0000000000..961a7a7c0c --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/solve_failure.rb @@ -0,0 +1,19 @@ +require_relative 'failure_writer' + +module Bundler::PubGrub + class SolveFailure < StandardError + attr_reader :incompatibility + + def initialize(incompatibility) + @incompatibility = incompatibility + end + + def to_s + "Could not find compatible versions\n\n#{explanation}" + end + + def explanation + @explanation ||= FailureWriter.new(@incompatibility).write + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb new file mode 100644 index 0000000000..36ab06254d --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/static_package_source.rb @@ -0,0 +1,61 @@ +require_relative 'package' +require_relative 'rubygems' +require_relative 'version_constraint' +require_relative 'incompatibility' +require_relative 'basic_package_source' + +module Bundler::PubGrub + class StaticPackageSource < BasicPackageSource + class DSL + def initialize(packages, root_deps) + @packages = packages + @root_deps = root_deps + end + + def root(deps:) + @root_deps.update(deps) + end + + def add(name, version, deps: {}) + version = Gem::Version.new(version) + @packages[name] ||= {} + raise ArgumentError, "#{name} #{version} declared twice" if @packages[name].key?(version) + @packages[name][version] = clean_deps(name, version, deps) + end + + private + + # Exclude redundant self-referencing dependencies + def clean_deps(name, version, deps) + deps.reject {|dep_name, req| name == dep_name && Bundler::PubGrub::RubyGems.parse_range(req).include?(version) } + end + end + + def initialize + @root_deps = {} + @packages = {} + + yield DSL.new(@packages, @root_deps) + + super() + end + + def all_versions_for(package) + @packages[package].keys + end + + def root_dependencies + @root_deps + end + + def dependencies_for(package, version) + @packages[package][version] + end + + def parse_dependency(package, dependency) + return false unless @packages.key?(package) + + Bundler::PubGrub::RubyGems.parse_constraint(package, dependency) + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/term.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/term.rb new file mode 100644 index 0000000000..1d0f763378 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/term.rb @@ -0,0 +1,105 @@ +module Bundler::PubGrub + class Term + attr_reader :package, :constraint, :positive + + def initialize(constraint, positive) + @constraint = constraint + @package = @constraint.package + @positive = positive + end + + def to_s(allow_every: false) + if positive + @constraint.to_s(allow_every: allow_every) + else + "not #{@constraint}" + end + end + + def hash + constraint.hash ^ positive.hash + end + + def eql?(other) + positive == other.positive && + constraint.eql?(other.constraint) + end + + def invert + self.class.new(@constraint, !@positive) + end + alias_method :inverse, :invert + + def intersect(other) + raise ArgumentError, "packages must match" if package != other.package + + if positive? && other.positive? + self.class.new(constraint.intersect(other.constraint), true) + elsif negative? && other.negative? + self.class.new(constraint.union(other.constraint), false) + else + positive = positive? ? self : other + negative = negative? ? self : other + self.class.new(positive.constraint.intersect(negative.constraint.invert), true) + end + end + + def difference(other) + intersect(other.invert) + end + + def relation(other) + if positive? && other.positive? + constraint.relation(other.constraint) + elsif negative? && other.positive? + if constraint.allows_all?(other.constraint) + :disjoint + else + :overlap + end + elsif positive? && other.negative? + if !other.constraint.allows_any?(constraint) + :subset + elsif other.constraint.allows_all?(constraint) + :disjoint + else + :overlap + end + elsif negative? && other.negative? + if constraint.allows_all?(other.constraint) + :subset + else + :overlap + end + else + raise + end + end + + def normalized_constraint + @normalized_constraint ||= positive ? constraint : constraint.invert + end + + def satisfies?(other) + raise ArgumentError, "packages must match" unless package == other.package + + relation(other) == :subset + end + + def positive? + @positive + end + + def negative? + !positive? + end + + def empty? + @empty ||= normalized_constraint.empty? + end + + def inspect + "#<#{self.class} #{self}>" + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version.rb new file mode 100644 index 0000000000..d7984b3863 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version.rb @@ -0,0 +1,3 @@ +module Bundler::PubGrub + VERSION = "0.5.0" +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb new file mode 100644 index 0000000000..b71f3eaf53 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_constraint.rb @@ -0,0 +1,129 @@ +require_relative 'version_range' + +module Bundler::PubGrub + class VersionConstraint + attr_reader :package, :range + + # @param package [Bundler::PubGrub::Package] + # @param range [Bundler::PubGrub::VersionRange] + def initialize(package, range: nil) + @package = package + @range = range + end + + def hash + package.hash ^ range.hash + end + + def ==(other) + package == other.package && + range == other.range + end + + def eql?(other) + package.eql?(other.package) && + range.eql?(other.range) + end + + class << self + def exact(package, version) + range = VersionRange.new(min: version, max: version, include_min: true, include_max: true) + new(package, range: range) + end + + def any(package) + new(package, range: VersionRange.any) + end + + def empty(package) + new(package, range: VersionRange.empty) + end + end + + def intersect(other) + unless package == other.package + raise ArgumentError, "Can only intersect between VersionConstraint of the same package" + end + + self.class.new(package, range: range.intersect(other.range)) + end + + def union(other) + unless package == other.package + raise ArgumentError, "Can only intersect between VersionConstraint of the same package" + end + + self.class.new(package, range: range.union(other.range)) + end + + def invert + new_range = range.invert + self.class.new(package, range: new_range) + end + + def difference(other) + intersect(other.invert) + end + + def allows_all?(other) + range.allows_all?(other.range) + end + + def allows_any?(other) + range.intersects?(other.range) + end + + def subset?(other) + other.allows_all?(self) + end + + def overlap?(other) + other.allows_any?(self) + end + + def disjoint?(other) + !overlap?(other) + end + + def relation(other) + if subset?(other) + :subset + elsif overlap?(other) + :overlap + else + :disjoint + end + end + + def to_s(allow_every: false) + if Package.root?(package) + package.to_s + elsif allow_every && any? + "every version of #{package}" + else + "#{package} #{constraint_string}" + end + end + + def constraint_string + if any? + ">= 0" + else + range.to_s + end + end + + def empty? + range.empty? + end + + # Does this match every version of the package + def any? + range.any? + end + + def inspect + "#<#{self.class} #{self}>" + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb new file mode 100644 index 0000000000..8d73c3f7b5 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb @@ -0,0 +1,411 @@ +# frozen_string_literal: true + +module Bundler::PubGrub + class VersionRange + attr_reader :min, :max, :include_min, :include_max + + alias_method :include_min?, :include_min + alias_method :include_max?, :include_max + + class Empty < VersionRange + undef_method :min, :max + undef_method :include_min, :include_min? + undef_method :include_max, :include_max? + + def initialize + end + + def empty? + true + end + + def eql?(other) + other.empty? + end + + def hash + [].hash + end + + def intersects?(_) + false + end + + def intersect(other) + self + end + + def allows_all?(other) + other.empty? + end + + def include?(_) + false + end + + def any? + false + end + + def to_s + "(no versions)" + end + + def ==(other) + other.class == self.class + end + + def invert + VersionRange.any + end + + def select_versions(_) + [] + end + end + + EMPTY = Empty.new + Empty.singleton_class.undef_method(:new) + + def self.empty + EMPTY + end + + def self.any + new + end + + def initialize(min: nil, max: nil, include_min: false, include_max: false, name: nil) + @min = min + @max = max + @include_min = include_min + @include_max = include_max + @name = name + end + + def hash + @hash ||= min.hash ^ max.hash ^ include_min.hash ^ include_max.hash + end + + def eql?(other) + if other.is_a?(VersionRange) + !other.empty? && + min.eql?(other.min) && + max.eql?(other.max) && + include_min.eql?(other.include_min) && + include_max.eql?(other.include_max) + else + ranges.eql?(other.ranges) + end + end + + def ranges + [self] + end + + def include?(version) + compare_version(version) == 0 + end + + # Partitions passed versions into [lower, within, higher] + # + # versions must be sorted + def partition_versions(versions) + min_index = + if !min || versions.empty? + 0 + elsif include_min? + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] >= min } + else + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] > min } + end + + lower = versions.slice(0, min_index) + versions = versions.slice(min_index, versions.size) + + max_index = + if !max || versions.empty? + versions.size + elsif include_max? + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] > max } + else + (0..versions.size).bsearch { |i| versions[i].nil? || versions[i] >= max } + end + + [ + lower, + versions.slice(0, max_index), + versions.slice(max_index, versions.size) + ] + end + + # Returns versions which are included by this range. + # + # versions must be sorted + def select_versions(versions) + return versions if any? + + partition_versions(versions)[1] + end + + def compare_version(version) + if min + case version <=> min + when -1 + return -1 + when 0 + return -1 if !include_min + when 1 + end + end + + if max + case version <=> max + when -1 + when 0 + return 1 if !include_max + when 1 + return 1 + end + end + + 0 + end + + def strictly_lower?(other) + return false if !max || !other.min + + case max <=> other.min + when 0 + !include_max || !other.include_min + when -1 + true + when 1 + false + end + end + + def strictly_higher?(other) + other.strictly_lower?(self) + end + + def intersects?(other) + return false if other.empty? + return other.intersects?(self) if other.is_a?(VersionUnion) + !strictly_lower?(other) && !strictly_higher?(other) + end + alias_method :allows_any?, :intersects? + + def intersect(other) + return other if other.empty? + return other.intersect(self) if other.is_a?(VersionUnion) + + min_range = + if !min + other + elsif !other.min + self + else + case min <=> other.min + when 0 + include_min ? other : self + when -1 + other + when 1 + self + end + end + + max_range = + if !max + other + elsif !other.max + self + else + case max <=> other.max + when 0 + include_max ? other : self + when -1 + self + when 1 + other + end + end + + if !min_range.equal?(max_range) && min_range.min && max_range.max + case min_range.min <=> max_range.max + when -1 + when 0 + if !min_range.include_min || !max_range.include_max + return EMPTY + end + when 1 + return EMPTY + end + end + + VersionRange.new( + min: min_range.min, + include_min: min_range.include_min, + max: max_range.max, + include_max: max_range.include_max + ) + end + + # The span covered by two ranges + # + # If self and other are contiguous, this builds a union of the two ranges. + # (if they aren't you are probably calling the wrong method) + def span(other) + return self if other.empty? + + min_range = + if !min + self + elsif !other.min + other + else + case min <=> other.min + when 0 + include_min ? self : other + when -1 + self + when 1 + other + end + end + + max_range = + if !max + self + elsif !other.max + other + else + case max <=> other.max + when 0 + include_max ? self : other + when -1 + other + when 1 + self + end + end + + VersionRange.new( + min: min_range.min, + include_min: min_range.include_min, + max: max_range.max, + include_max: max_range.include_max + ) + end + + def union(other) + return other.union(self) if other.is_a?(VersionUnion) + + if contiguous_to?(other) + span(other) + else + VersionUnion.union([self, other]) + end + end + + def contiguous_to?(other) + return false if other.empty? + + intersects?(other) || + (min == other.max && (include_min || other.include_max)) || + (max == other.min && (include_max || other.include_min)) + end + + def allows_all?(other) + return true if other.empty? + + if other.is_a?(VersionUnion) + return VersionUnion.new([self]).allows_all?(other) + end + + return false if max && !other.max + return false if min && !other.min + + if min + case min <=> other.min + when -1 + when 0 + return false if !include_min && other.include_min + when 1 + return false + end + end + + if max + case max <=> other.max + when -1 + return false + when 0 + return false if !include_max && other.include_max + when 1 + end + end + + true + end + + def any? + !min && !max + end + + def empty? + false + end + + def to_s + @name ||= constraints.join(", ") + end + + def inspect + "#<#{self.class} #{to_s}>" + end + + def upper_invert + return self.class.empty unless max + + VersionRange.new(min: max, include_min: !include_max) + end + + def invert + return self.class.empty if any? + + low = VersionRange.new(max: min, include_max: !include_min) + high = VersionRange.new(min: max, include_min: !include_max) + + if !min + high + elsif !max + low + else + low.union(high) + end + end + + def ==(other) + self.class == other.class && + min == other.min && + max == other.max && + include_min == other.include_min && + include_max == other.include_max + end + + private + + def constraints + return ["any"] if any? + return ["= #{min}"] if min.to_s == max.to_s + + c = [] + c << "#{include_min ? ">=" : ">"} #{min}" if min + c << "#{include_max ? "<=" : "<"} #{max}" if max + c + end + + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb new file mode 100644 index 0000000000..4caf6b355b --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb @@ -0,0 +1,248 @@ +require_relative 'partial_solution' +require_relative 'term' +require_relative 'incompatibility' +require_relative 'solve_failure' + +module Bundler::PubGrub + class VersionSolver + attr_reader :logger + attr_reader :source + attr_reader :solution + + def initialize(source:, root: Package.root, logger: Bundler::PubGrub.logger) + @logger = logger + + @source = source + + # { package => [incompatibility, ...]} + @incompatibilities = Hash.new do |h, k| + h[k] = [] + end + + @seen_incompatibilities = {} + + @solution = PartialSolution.new + + add_incompatibility Incompatibility.new([ + Term.new(VersionConstraint.any(root), false) + ], cause: :root) + + propagate(root) + end + + def solved? + solution.unsatisfied.empty? + end + + # Returns true if there is more work to be done, false otherwise + def work + return false if solved? + + next_package = choose_package_version + propagate(next_package) + + if solved? + logger.info { "Solution found after #{solution.attempted_solutions} attempts:" } + solution.decisions.each do |package, version| + next if Package.root?(package) + logger.info { "* #{package} #{version}" } + end + + false + else + true + end + end + + def solve + work until solved? + + solution.decisions + end + + alias_method :result, :solve + + private + + def propagate(initial_package) + changed = [initial_package] + while package = changed.shift + @incompatibilities[package].reverse_each do |incompatibility| + result = propagate_incompatibility(incompatibility) + if result == :conflict + root_cause = resolve_conflict(incompatibility) + changed.clear + changed << propagate_incompatibility(root_cause) + elsif result # should be a Package + changed << result + end + end + changed.uniq! + end + end + + def propagate_incompatibility(incompatibility) + unsatisfied = nil + incompatibility.terms.each do |term| + relation = solution.relation(term) + if relation == :disjoint + return nil + elsif relation == :overlap + # If more than one term is inconclusive, we can't deduce anything + return nil if unsatisfied + unsatisfied = term + end + end + + if !unsatisfied + return :conflict + end + + logger.debug { "derived: #{unsatisfied.invert}" } + + solution.derive(unsatisfied.invert, incompatibility) + + unsatisfied.package + end + + def next_package_to_try + solution.unsatisfied.min_by do |term| + package = term.package + range = term.constraint.range + matching_versions = source.versions_for(package, range) + higher_versions = source.versions_for(package, range.upper_invert) + + [matching_versions.count <= 1 ? 0 : 1, higher_versions.count] + end.package + end + + def choose_package_version + if solution.unsatisfied.empty? + logger.info "No packages unsatisfied. Solving complete!" + return nil + end + + package = next_package_to_try + unsatisfied_term = solution.unsatisfied.find { |t| t.package == package } + version = source.versions_for(package, unsatisfied_term.constraint.range).first + logger.debug { "attempting #{package} #{version}" } + + if version.nil? + add_incompatibility source.no_versions_incompatibility_for(package, unsatisfied_term) + return package + end + + conflict = false + + source.incompatibilities_for(package, version).each do |incompatibility| + if @seen_incompatibilities.include?(incompatibility) + logger.debug { "knew: #{incompatibility}" } + next + end + @seen_incompatibilities[incompatibility] = true + + add_incompatibility incompatibility + + conflict ||= incompatibility.terms.all? do |term| + term.package == package || solution.satisfies?(term) + end + end + + unless conflict + logger.info { "selected #{package} #{version}" } + + solution.decide(package, version) + else + logger.info { "conflict: #{conflict.inspect}" } + end + + package + end + + def resolve_conflict(incompatibility) + logger.info { "conflict: #{incompatibility}" } + + new_incompatibility = nil + + while !incompatibility.failure? + most_recent_term = nil + most_recent_satisfier = nil + difference = nil + + previous_level = 1 + + incompatibility.terms.each do |term| + satisfier = solution.satisfier(term) + + if most_recent_satisfier.nil? + most_recent_term = term + most_recent_satisfier = satisfier + elsif most_recent_satisfier.index < satisfier.index + previous_level = [previous_level, most_recent_satisfier.decision_level].max + most_recent_term = term + most_recent_satisfier = satisfier + difference = nil + else + previous_level = [previous_level, satisfier.decision_level].max + end + + if most_recent_term == term + difference = most_recent_satisfier.term.difference(most_recent_term) + if difference.empty? + difference = nil + else + difference_satisfier = solution.satisfier(difference.inverse) + previous_level = [previous_level, difference_satisfier.decision_level].max + end + end + end + + if previous_level < most_recent_satisfier.decision_level || + most_recent_satisfier.decision? + + logger.info { "backtracking to #{previous_level}" } + solution.backtrack(previous_level) + + if new_incompatibility + add_incompatibility(new_incompatibility) + end + + return incompatibility + end + + new_terms = [] + new_terms += incompatibility.terms - [most_recent_term] + new_terms += most_recent_satisfier.cause.terms.reject { |term| + term.package == most_recent_satisfier.term.package + } + if difference + new_terms << difference.invert + end + + new_incompatibility = Incompatibility.new(new_terms, cause: Incompatibility::ConflictCause.new(incompatibility, most_recent_satisfier.cause)) + + if incompatibility.to_s == new_incompatibility.to_s + logger.info { "!! failed to resolve conflicts, this shouldn't have happened" } + break + end + + incompatibility = new_incompatibility + + partially = difference ? " partially" : "" + logger.info { "! #{most_recent_term} is#{partially} satisfied by #{most_recent_satisfier.term}" } + logger.info { "! which is caused by #{most_recent_satisfier.cause}" } + logger.info { "! thus #{incompatibility}" } + end + + raise SolveFailure.new(incompatibility) + end + + def add_incompatibility(incompatibility) + logger.debug { "fact: #{incompatibility}" } + incompatibility.terms.each do |term| + package = term.package + @incompatibilities[package] << incompatibility + end + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb new file mode 100644 index 0000000000..bbc10c3804 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_union.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +module Bundler::PubGrub + class VersionUnion + attr_reader :ranges + + def self.normalize_ranges(ranges) + ranges = ranges.flat_map do |range| + range.ranges + end + + ranges.reject!(&:empty?) + + return [] if ranges.empty? + + mins, ranges = ranges.partition { |r| !r.min } + original_ranges = mins + ranges.sort_by { |r| [r.min, r.include_min ? 0 : 1] } + ranges = [original_ranges.shift] + original_ranges.each do |range| + if ranges.last.contiguous_to?(range) + ranges << ranges.pop.span(range) + else + ranges << range + end + end + + ranges + end + + def self.union(ranges, normalize: true) + ranges = normalize_ranges(ranges) if normalize + + if ranges.size == 0 + VersionRange.empty + elsif ranges.size == 1 + ranges[0] + else + new(ranges) + end + end + + def initialize(ranges) + raise ArgumentError unless ranges.all? { |r| r.instance_of?(VersionRange) } + @ranges = ranges + end + + def hash + ranges.hash + end + + def eql?(other) + ranges.eql?(other.ranges) + end + + def include?(version) + !!ranges.bsearch {|r| r.compare_version(version) } + end + + def select_versions(all_versions) + versions = [] + ranges.inject(all_versions) do |acc, range| + _, matching, higher = range.partition_versions(acc) + versions.concat matching + higher + end + versions + end + + def intersects?(other) + my_ranges = ranges.dup + other_ranges = other.ranges.dup + + my_range = my_ranges.shift + other_range = other_ranges.shift + while my_range && other_range + if my_range.intersects?(other_range) + return true + end + + if !my_range.max || other_range.empty? || (other_range.max && other_range.max < my_range.max) + other_range = other_ranges.shift + else + my_range = my_ranges.shift + end + end + end + alias_method :allows_any?, :intersects? + + def allows_all?(other) + my_ranges = ranges.dup + + my_range = my_ranges.shift + + other.ranges.all? do |other_range| + while my_range + break if my_range.allows_all?(other_range) + my_range = my_ranges.shift + end + + !!my_range + end + end + + def empty? + false + end + + def any? + false + end + + def intersect(other) + my_ranges = ranges.dup + other_ranges = other.ranges.dup + new_ranges = [] + + my_range = my_ranges.shift + other_range = other_ranges.shift + while my_range && other_range + new_ranges << my_range.intersect(other_range) + + if !my_range.max || other_range.empty? || (other_range.max && other_range.max < my_range.max) + other_range = other_ranges.shift + else + my_range = my_ranges.shift + end + end + new_ranges.reject!(&:empty?) + VersionUnion.union(new_ranges, normalize: false) + end + + def upper_invert + ranges.last.upper_invert + end + + def invert + ranges.map(&:invert).inject(:intersect) + end + + def union(other) + VersionUnion.union([self, other]) + end + + def to_s + output = [] + + ranges = self.ranges.dup + while !ranges.empty? + ne = [] + range = ranges.shift + while !ranges.empty? && ranges[0].min.to_s == range.max.to_s + ne << range.max + range = range.span(ranges.shift) + end + + ne.map! {|x| "!= #{x}" } + if ne.empty? + output << range.to_s + elsif range.any? + output << ne.join(', ') + else + output << "#{range}, #{ne.join(', ')}" + end + end + + output.join(" OR ") + end + + def inspect + "#<#{self.class} #{to_s}>" + end + + def ==(other) + self.class == other.class && + self.ranges == other.ranges + end + end +end diff --git a/lib/bundler/vendor/thor/.document b/lib/bundler/vendor/thor/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/thor/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/thor/lib/thor.rb b/lib/bundler/vendor/thor/lib/thor.rb index 0794dbb522..627722164f 100644 --- a/lib/bundler/vendor/thor/lib/thor.rb +++ b/lib/bundler/vendor/thor/lib/thor.rb @@ -65,8 +65,15 @@ class Bundler::Thor # Defines the long description of the next command. # + # Long description is by default indented, line-wrapped and repeated whitespace merged. + # In order to print long description verbatim, with indentation and spacing exactly + # as found in the code, use the +wrap+ option + # + # long_desc 'your very long description', wrap: false + # # ==== Parameters # long description<String> + # options<Hash> # def long_desc(long_description, options = {}) if options[:for] @@ -74,6 +81,7 @@ class Bundler::Thor command.long_description = long_description if long_description else @long_desc = long_description + @long_desc_wrap = options[:wrap] != false end end @@ -133,7 +141,7 @@ class Bundler::Thor # # magic # end # - # method_option :foo => :bar, :for => :previous_command + # method_option :foo, :for => :previous_command # # def next_command # # magic @@ -153,6 +161,9 @@ class Bundler::Thor # :hide - If you want to hide this option from the help. # def method_option(name, options = {}) + unless [ Symbol, String ].any? { |klass| name.is_a?(klass) } + raise ArgumentError, "Expected a Symbol or String, got #{name.inspect}" + end scope = if options[:for] find_and_refresh_command(options[:for]).options else @@ -163,6 +174,81 @@ class Bundler::Thor end alias_method :option, :method_option + # Adds and declares option group for exclusive options in the + # block and arguments. You can declare options as the outside of the block. + # + # If :for is given as option, it allows you to change the options from + # a previous defined command. + # + # ==== Parameters + # Array[Bundler::Thor::Option.name] + # options<Hash>:: :for is applied for previous defined command. + # + # ==== Examples + # + # exclusive do + # option :one + # option :two + # end + # + # Or + # + # option :one + # option :two + # exclusive :one, :two + # + # If you give "--one" and "--two" at the same time ExclusiveArgumentsError + # will be raised. + # + def method_exclusive(*args, &block) + register_options_relation_for(:method_options, + :method_exclusive_option_names, *args, &block) + end + alias_method :exclusive, :method_exclusive + + # Adds and declares option group for required at least one of options in the + # block of arguments. You can declare options as the outside of the block. + # + # If :for is given as option, it allows you to change the options from + # a previous defined command. + # + # ==== Parameters + # Array[Bundler::Thor::Option.name] + # options<Hash>:: :for is applied for previous defined command. + # + # ==== Examples + # + # at_least_one do + # option :one + # option :two + # end + # + # Or + # + # option :one + # option :two + # at_least_one :one, :two + # + # If you do not give "--one" and "--two" AtLeastOneRequiredArgumentError + # will be raised. + # + # You can use at_least_one and exclusive at the same time. + # + # exclusive do + # at_least_one do + # option :one + # option :two + # end + # end + # + # Then it is required either only one of "--one" or "--two". + # + def method_at_least_one(*args, &block) + register_options_relation_for(:method_options, + :method_at_least_one_option_names, *args, &block) + end + alias_method :at_least_one, :method_at_least_one + # Prints help information for the given command. # # ==== Parameters @@ -178,9 +264,16 @@ class Bundler::Thor shell.say " #{banner(command).split("\n").join("\n ")}" shell.say class_options_help(shell, nil => command.options.values) + print_exclusive_options(shell, command) + print_at_least_one_required_options(shell, command) + if command.long_description shell.say "Description:" - shell.print_wrapped(command.long_description, :indent => 2) + if command.wrap_long_description + shell.print_wrapped(command.long_description, indent: 2) + else + shell.say command.long_description + end else shell.say command.description end @@ -197,7 +290,7 @@ class Bundler::Thor Bundler::Thor::Util.thor_classes_in(self).each do |klass| list += klass.printable_commands(false) end - list.sort! { |a, b| a[0] <=> b[0] } + sort_commands!(list) if defined?(@package_name) && @package_name shell.say "#{@package_name} commands:" @@ -205,9 +298,11 @@ class Bundler::Thor shell.say "Commands:" end - shell.print_table(list, :indent => 2, :truncate => true) + shell.print_table(list, indent: 2, truncate: true) shell.say class_options_help(shell) + print_exclusive_options(shell) + print_at_least_one_required_options(shell) end # Returns commands ready to be printed. @@ -238,7 +333,7 @@ class Bundler::Thor define_method(subcommand) do |*args| args, opts = Bundler::Thor::Arguments.split(args) - invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}] + invoke_args = [args, opts, {invoked_via_subcommand: true, class_options: options}] invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h") invoke subcommand_class, *invoke_args end @@ -346,6 +441,24 @@ class Bundler::Thor protected + # Returns this class exclusive options array set. + # + # ==== Returns + # Array[Array[Bundler::Thor::Option.name]] + # + def method_exclusive_option_names #:nodoc: + @method_exclusive_option_names ||= [] + end + + # Returns this class at least one of required options array set. + # + # ==== Returns + # Array[Array[Bundler::Thor::Option.name]] + # + def method_at_least_one_option_names #:nodoc: + @method_at_least_one_option_names ||= [] + end + def stop_on_unknown_option #:nodoc: @stop_on_unknown_option ||= [] end @@ -355,8 +468,30 @@ class Bundler::Thor @disable_required_check ||= [:help] end + def print_exclusive_options(shell, command = nil) # :nodoc: + opts = [] + opts = command.method_exclusive_option_names unless command.nil? + opts += class_exclusive_option_names + unless opts.empty? + shell.say "Exclusive Options:" + shell.print_table(opts.map{ |ex| ex.map{ |e| "--#{e}"}}, indent: 2 ) + shell.say + end + end + + def print_at_least_one_required_options(shell, command = nil) # :nodoc: + opts = [] + opts = command.method_at_least_one_option_names unless command.nil? + opts += class_at_least_one_option_names + unless opts.empty? + shell.say "Required At Least One:" + shell.print_table(opts.map{ |ex| ex.map{ |e| "--#{e}"}}, indent: 2 ) + shell.say + end + end + # The method responsible for dispatching given the args. - def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength + def dispatch(meth, given_args, given_opts, config) #:nodoc: meth ||= retrieve_command_name(given_args) command = all_commands[normalize_command_name(meth)] @@ -415,12 +550,16 @@ class Bundler::Thor @usage ||= nil @desc ||= nil @long_desc ||= nil + @long_desc_wrap ||= nil @hide ||= nil if @usage && @desc base_class = @hide ? Bundler::Thor::HiddenCommand : Bundler::Thor::Command - commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options) - @usage, @desc, @long_desc, @method_options, @hide = nil + relations = {exclusive_option_names: method_exclusive_option_names, + at_least_one_option_names: method_at_least_one_option_names} + commands[meth] = base_class.new(meth, @desc, @long_desc, @long_desc_wrap, @usage, method_options, relations) + @usage, @desc, @long_desc, @long_desc_wrap, @method_options, @hide = nil + @method_exclusive_option_names, @method_at_least_one_option_names = nil true elsif all_commands[meth] || meth == "method_missing" true @@ -495,6 +634,14 @@ class Bundler::Thor " end alias_method :subtask_help, :subcommand_help + + # Sort the commands, lexicographically by default. + # + # Can be overridden in the subclass to change the display order of the + # commands. + def sort_commands!(list) + list.sort! { |a, b| a[0] <=> b[0] } + end end include Bundler::Thor::Base diff --git a/lib/bundler/vendor/thor/lib/thor/actions.rb b/lib/bundler/vendor/thor/lib/thor/actions.rb index de9323b2db..ca58182691 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions.rb @@ -46,17 +46,17 @@ class Bundler::Thor # Add runtime options that help actions execution. # def add_runtime_options! - class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, - :desc => "Overwrite files that already exist" + class_option :force, type: :boolean, aliases: "-f", group: :runtime, + desc: "Overwrite files that already exist" - class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, - :desc => "Run but do not make any changes" + class_option :pretend, type: :boolean, aliases: "-p", group: :runtime, + desc: "Run but do not make any changes" - class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, - :desc => "Suppress status output" + class_option :quiet, type: :boolean, aliases: "-q", group: :runtime, + desc: "Suppress status output" - class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, - :desc => "Skip files that already exist" + class_option :skip, type: :boolean, aliases: "-s", group: :runtime, + desc: "Skip files that already exist" end end @@ -113,9 +113,9 @@ class Bundler::Thor # def relative_to_original_destination_root(path, remove_dot = true) root = @destination_stack[0] - if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size]) + if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ""].include?(path[root.size..root.size]) path = path.dup - path[0...root.size] = '.' + path[0...root.size] = "." remove_dot ? (path[2..-1] || "") : path else path @@ -161,6 +161,8 @@ class Bundler::Thor # to the block you provide. The path is set back to the previous path when # the method exits. # + # Returns the value yielded by the block. + # # ==== Parameters # dir<String>:: the directory to move to. # config<Hash>:: give :verbose => true to log and use padding. @@ -173,22 +175,24 @@ class Bundler::Thor shell.padding += 1 if verbose @destination_stack.push File.expand_path(dir, destination_root) - # If the directory doesnt exist and we're not pretending + # If the directory doesn't exist and we're not pretending if !File.exist?(destination_root) && !pretend require "fileutils" FileUtils.mkdir_p(destination_root) end + result = nil if pretend # In pretend mode, just yield down to the block - block.arity == 1 ? yield(destination_root) : yield + result = block.arity == 1 ? yield(destination_root) : yield else require "fileutils" - FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield } + FileUtils.cd(destination_root) { result = block.arity == 1 ? yield(destination_root) : yield } end @destination_stack.pop shell.padding -= 1 if verbose + result end # Goes to the root and execute the given block. @@ -221,7 +225,7 @@ class Bundler::Thor require "open-uri" URI.open(path, "Accept" => "application/x-thor-template", &:read) else - open(path, &:read) + File.open(path, &:read) end instance_eval(contents, path) @@ -280,7 +284,7 @@ class Bundler::Thor # def run_ruby_script(command, config = {}) return unless behavior == :invoke - run command, config.merge(:with => Bundler::Thor::Util.ruby_command) + run command, config.merge(with: Bundler::Thor::Util.ruby_command) end # Run a thor command. A hash of options can be given and it's converted to @@ -311,7 +315,7 @@ class Bundler::Thor args.push Bundler::Thor::Options.to_switches(config) command = args.join(" ").strip - run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture + run command, with: :thor, verbose: verbose, pretend: pretend, capture: capture end protected @@ -319,7 +323,7 @@ class Bundler::Thor # Allow current root to be shared between invocations. # def _shared_configuration #:nodoc: - super.merge!(:destination_root => destination_root) + super.merge!(destination_root: destination_root) end def _cleanup_options_and_set(options, key) #:nodoc: diff --git a/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb index 330fc08cae..6724835b01 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb @@ -43,7 +43,8 @@ class Bundler::Thor # Boolean:: true if it is identical, false otherwise. # def identical? - exists? && File.binread(destination) == render + # binread uses ASCII-8BIT, so to avoid false negatives, the string must use the same + exists? && File.binread(destination) == String.new(render).force_encoding("ASCII-8BIT") end # Holds the content to be added to the file. @@ -60,7 +61,7 @@ class Bundler::Thor invoke_with_conflict_check do require "fileutils" FileUtils.mkdir_p(File.dirname(destination)) - File.open(destination, "wb") { |f| f.write render } + File.open(destination, "wb", config[:perm]) { |f| f.write render } end given_destination end diff --git a/lib/bundler/vendor/thor/lib/thor/actions/directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb index d37327a139..2f9687c0a5 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/directory.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb @@ -58,7 +58,7 @@ class Bundler::Thor def initialize(base, source, destination = nil, config = {}, &block) @source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first) @block = block - super(base, destination, {:recursive => true}.merge(config)) + super(base, destination, {recursive: true}.merge(config)) end def invoke! diff --git a/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb index 284d92c19a..c0bca78525 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb @@ -33,7 +33,7 @@ class Bundler::Thor # def initialize(base, destination, config = {}) @base = base - @config = {:verbose => true}.merge(config) + @config = {verbose: true}.merge(config) self.destination = destination end diff --git a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb index 90a8d2e847..80a0255996 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -66,12 +66,15 @@ class Bundler::Thor # ==== Parameters # source<String>:: the address of the given content. # destination<String>:: the relative path to the destination root. - # config<Hash>:: give :verbose => false to not log the status. + # config<Hash>:: give :verbose => false to not log the status, and + # :http_headers => <Hash> to add headers to an http request. # # ==== Examples # # get "http://gist.github.com/103208", "doc/README" # + # get "http://gist.github.com/103208", "doc/README", :http_headers => {"Content-Type" => "application/json"} + # # get "http://gist.github.com/103208" do |content| # content.split("\n").first # end @@ -82,10 +85,10 @@ class Bundler::Thor render = if source =~ %r{^https?\://} require "open-uri" - URI.send(:open, source) { |input| input.binmode.read } + URI.send(:open, source, config.fetch(:http_headers, {})) { |input| input.binmode.read } else source = File.expand_path(find_in_source_paths(source.to_s)) - open(source) { |input| input.binmode.read } + File.open(source) { |input| input.binmode.read } end destination ||= if block_given? @@ -120,12 +123,7 @@ class Bundler::Thor context = config.delete(:context) || instance_eval("binding") create_file destination, nil, config do - match = ERB.version.match(/(\d+\.\d+\.\d+)/) - capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+ - CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer") - else - CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer") - end + capturable_erb = CapturableERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer") content = capturable_erb.tap do |erb| erb.filename = source end.result(context) @@ -210,9 +208,9 @@ class Bundler::Thor # # ==== Examples # - # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n" + # inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " filter_parameter :password\n" # - # inject_into_class "app/controllers/application_controller.rb", ApplicationController do + # inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do # " filter_parameter :password\n" # end # @@ -233,9 +231,9 @@ class Bundler::Thor # # ==== Examples # - # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n" + # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper", " def help; 'help'; end\n" # - # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do + # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper" do # " def help; 'help'; end\n" # end # @@ -331,7 +329,7 @@ class Bundler::Thor path = File.expand_path(path, destination_root) say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true) - if !options[:pretend] && File.exist?(path) + if !options[:pretend] && (File.exist?(path) || File.symlink?(path)) require "fileutils" ::FileUtils.rm_rf(path) end diff --git a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb index 09ce0864f0..70526e615f 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb @@ -21,7 +21,7 @@ class Bundler::Thor # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n") # end # - WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' } + WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"} def insert_into_file(destination, *args, &block) data = block_given? ? block : args.shift @@ -37,7 +37,7 @@ class Bundler::Thor attr_reader :replacement, :flag, :behavior def initialize(base, destination, data, config) - super(base, destination, {:verbose => true}.merge(config)) + super(base, destination, {verbose: true}.merge(config)) @behavior, @flag = if @config.key?(:after) [:after, @config.delete(:after)] @@ -59,6 +59,8 @@ class Bundler::Thor if exists? if replace!(/#{flag}/, content, config[:force]) say_status(:invoke) + elsif replacement_present? + say_status(:unchanged, color: :blue) else say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red) end @@ -96,6 +98,8 @@ class Bundler::Thor end elsif warning warning + elsif behavior == :unchanged + :unchanged else :subtract end @@ -103,15 +107,21 @@ class Bundler::Thor super(status, (color || config[:verbose])) end + def content + @content ||= File.read(destination) + end + + def replacement_present? + content.include?(replacement) + end + # Adds the content to the file. # def replace!(regexp, string, force) - return if pretend? - content = File.read(destination) - if force || !content.include?(replacement) + if force || !replacement_present? success = content.gsub!(regexp, string) - File.open(destination, "wb") { |file| file.write(content) } + File.open(destination, "wb") { |file| file.write(content) } unless pretend? success end end diff --git a/lib/bundler/vendor/thor/lib/thor/base.rb b/lib/bundler/vendor/thor/lib/thor/base.rb index 8487f9590a..b156899c1e 100644 --- a/lib/bundler/vendor/thor/lib/thor/base.rb +++ b/lib/bundler/vendor/thor/lib/thor/base.rb @@ -24,9 +24,9 @@ class Bundler::Thor class << self def deprecation_warning(message) #:nodoc: - unless ENV['THOR_SILENCE_DEPRECATION'] + unless ENV["THOR_SILENCE_DEPRECATION"] warn "Deprecation warning: #{message}\n" + - 'You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.' + "You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION." end end end @@ -60,6 +60,7 @@ class Bundler::Thor command_options = config.delete(:command_options) # hook for start parse_options = parse_options.merge(command_options) if command_options + if local_options.is_a?(Array) array_options = local_options hash_options = {} @@ -73,9 +74,24 @@ class Bundler::Thor # Let Bundler::Thor::Options parse the options first, so it can remove # declared options from the array. This will leave us with # a list of arguments that weren't declared. - stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command] - disable_required_check = self.class.disable_required_check? config[:current_command] - opts = Bundler::Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check) + current_command = config[:current_command] + stop_on_unknown = self.class.stop_on_unknown_option? current_command + + # Give a relation of options. + # After parsing, Bundler::Thor::Options check whether right relations are kept + relations = if current_command.nil? + {exclusive_option_names: [], at_least_one_option_names: []} + else + current_command.options_relation + end + + self.class.class_exclusive_option_names.map { |n| relations[:exclusive_option_names] << n } + self.class.class_at_least_one_option_names.map { |n| relations[:at_least_one_option_names] << n } + + disable_required_check = self.class.disable_required_check? current_command + + opts = Bundler::Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check, relations) + self.options = opts.parse(array_options) self.options = config[:class_options].merge(options) if config[:class_options] @@ -310,9 +326,92 @@ class Bundler::Thor # :hide:: -- If you want to hide this option from the help. # def class_option(name, options = {}) + unless [ Symbol, String ].any? { |klass| name.is_a?(klass) } + raise ArgumentError, "Expected a Symbol or String, got #{name.inspect}" + end build_option(name, options, class_options) end + # Adds and declares option group for exclusive options in the + # block and arguments. You can declare options as the outside of the block. + # + # ==== Parameters + # Array[Bundler::Thor::Option.name] + # + # ==== Examples + # + # class_exclusive do + # class_option :one + # class_option :two + # end + # + # Or + # + # class_option :one + # class_option :two + # class_exclusive :one, :two + # + # If you give "--one" and "--two" at the same time ExclusiveArgumentsError + # will be raised. + # + def class_exclusive(*args, &block) + register_options_relation_for(:class_options, + :class_exclusive_option_names, *args, &block) + end + + # Adds and declares option group for required at least one of options in the + # block and arguments. You can declare options as the outside of the block. + # + # ==== Examples + # + # class_at_least_one do + # class_option :one + # class_option :two + # end + # + # Or + # + # class_option :one + # class_option :two + # class_at_least_one :one, :two + # + # If you do not give "--one" and "--two" AtLeastOneRequiredArgumentError + # will be raised. + # + # You can use class_at_least_one and class_exclusive at the same time. + # + # class_exclusive do + # class_at_least_one do + # class_option :one + # class_option :two + # end + # end + # + # Then it is required either only one of "--one" or "--two". + # + def class_at_least_one(*args, &block) + register_options_relation_for(:class_options, + :class_at_least_one_option_names, *args, &block) + end + + # Returns this class exclusive options array set, looking up in the ancestors chain. + # + # ==== Returns + # Array[Array[Bundler::Thor::Option.name]] + # + def class_exclusive_option_names + @class_exclusive_option_names ||= from_superclass(:class_exclusive_option_names, []) + end + + # Returns this class at least one of required options array set, looking up in the ancestors chain. + # + # ==== Returns + # Array[Array[Bundler::Thor::Option.name]] + # + def class_at_least_one_option_names + @class_at_least_one_option_names ||= from_superclass(:class_at_least_one_option_names, []) + end + # Removes a previous defined argument. If :undefine is given, undefine # accessors as well. # @@ -506,7 +605,7 @@ class Bundler::Thor # def public_command(*names) names.each do |name| - class_eval "def #{name}(*); super end" + class_eval "def #{name}(*); super end", __FILE__, __LINE__ end end alias_method :public_task, :public_command @@ -558,20 +657,19 @@ class Bundler::Thor return if options.empty? list = [] - padding = options.map { |o| o.aliases.size }.max.to_i * 4 - + padding = options.map { |o| o.aliases_for_usage.size }.max.to_i options.each do |option| next if option.hide item = [option.usage(padding)] item.push(option.description ? "# #{option.description}" : "") list << item - list << ["", "# Default: #{option.default}"] if option.show_default? - list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum + list << ["", "# Default: #{option.print_default}"] if option.show_default? + list << ["", "# Possible values: #{option.enum_to_s}"] if option.enum end shell.say(group_name ? "#{group_name} options:" : "Options:") - shell.print_table(list, :indent => 2) + shell.print_table(list, indent: 2) shell.say "" end @@ -588,7 +686,7 @@ class Bundler::Thor # options<Hash>:: Described in both class_option and method_option. # scope<Hash>:: Options hash that is being built up def build_option(name, options, scope) #:nodoc: - scope[name] = Bundler::Thor::Option.new(name, {:check_default_type => check_default_type}.merge!(options)) + scope[name] = Bundler::Thor::Option.new(name, {check_default_type: check_default_type}.merge!(options)) end # Receives a hash of options, parse them and add to the scope. This is a @@ -610,7 +708,7 @@ class Bundler::Thor def find_and_refresh_command(name) #:nodoc: if commands[name.to_s] commands[name.to_s] - elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition + elsif command = all_commands[name.to_s] # rubocop:disable Lint/AssignmentInCondition commands[name.to_s] = command.clone else raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." @@ -618,7 +716,7 @@ class Bundler::Thor end alias_method :find_and_refresh_task, :find_and_refresh_command - # Everytime someone inherits from a Bundler::Thor class, register the klass + # Every time someone inherits from a Bundler::Thor class, register the klass # and file into baseclass. def inherited(klass) super(klass) @@ -694,6 +792,34 @@ class Bundler::Thor def dispatch(command, given_args, given_opts, config) #:nodoc: raise NotImplementedError end + + # Register a relation of options for target(method_option/class_option) + # by args and block. + def register_options_relation_for(target, relation, *args, &block) # :nodoc: + opt = args.pop if args.last.is_a? Hash + opt ||= {} + names = args.map{ |arg| arg.to_s } + names += built_option_names(target, opt, &block) if block_given? + command_scope_member(relation, opt) << names + end + + # Get target(method_options or class_options) options + # of before and after by block evaluation. + def built_option_names(target, opt = {}, &block) # :nodoc: + before = command_scope_member(target, opt).map{ |k,v| v.name } + instance_eval(&block) + after = command_scope_member(target, opt).map{ |k,v| v.name } + after - before + end + + # Get command scope member by name. + def command_scope_member(name, options = {}) # :nodoc: + if options[:for] + find_and_refresh_command(options[:for]).send(name) + else + send(name) + end + end end end end diff --git a/lib/bundler/vendor/thor/lib/thor/command.rb b/lib/bundler/vendor/thor/lib/thor/command.rb index 040c971c0c..68c8fffedb 100644 --- a/lib/bundler/vendor/thor/lib/thor/command.rb +++ b/lib/bundler/vendor/thor/lib/thor/command.rb @@ -1,14 +1,15 @@ class Bundler::Thor - class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name) + class Command < Struct.new(:name, :description, :long_description, :wrap_long_description, :usage, :options, :options_relation, :ancestor_name) FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/ - def initialize(name, description, long_description, usage, options = nil) - super(name.to_s, description, long_description, usage, options || {}) + def initialize(name, description, long_description, wrap_long_description, usage, options = nil, options_relation = nil) + super(name.to_s, description, long_description, wrap_long_description, usage, options || {}, options_relation || {}) end def initialize_copy(other) #:nodoc: super(other) self.options = other.options.dup if other.options + self.options_relation = other.options_relation.dup if other.options_relation end def hidden? @@ -62,6 +63,14 @@ class Bundler::Thor end.join("\n") end + def method_exclusive_option_names #:nodoc: + self.options_relation[:exclusive_option_names] || [] + end + + def method_at_least_one_option_names #:nodoc: + self.options_relation[:at_least_one_option_names] || [] + end + protected # Add usage with required arguments @@ -127,7 +136,7 @@ class Bundler::Thor # A dynamic command that handles method missing scenarios. class DynamicCommand < Command def initialize(name, options = nil) - super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options) + super(name.to_s, "A dynamically-generated command", name.to_s, nil, name.to_s, options) end def run(instance, args = []) diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb index c167aa33b8..b16a98f782 100644 --- a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -28,10 +28,20 @@ class Bundler::Thor super(convert_key(key)) end + def except(*keys) + dup.tap do |hash| + keys.each { |key| hash.delete(convert_key(key)) } + end + end + def fetch(key, *args) super(convert_key(key), *args) end + def slice(*keys) + super(*keys.map{ |key| convert_key(key) }) + end + def key?(key) super(convert_key(key)) end diff --git a/lib/bundler/vendor/thor/lib/thor/error.rb b/lib/bundler/vendor/thor/lib/thor/error.rb index 7d57129b83..928646e501 100644 --- a/lib/bundler/vendor/thor/lib/thor/error.rb +++ b/lib/bundler/vendor/thor/lib/thor/error.rb @@ -1,18 +1,15 @@ class Bundler::Thor Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) # rubocop:disable Naming/ConstantName - # In order to support versions of Ruby that don't have keyword - # arguments, we need our own spell checker class that doesn't take key - # words. Even though this code wouldn't be hit because of the check - # above, it's still necessary because the interpreter would otherwise be - # unable to parse the file. - class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc: - def initialize(dictionary) - @dictionary = dictionary - end - end - - DidYouMean::Correctable - end + Module.new do + def to_s + super + DidYouMean.formatter.message_for(corrections) + end + + def corrections + @corrections ||= self.class.const_get(:SpellChecker).new(self).corrections + end + end + end # Bundler::Thor::Error is raised when it's caused by wrong usage of thor classes. Those # errors have their backtrace suppressed and are nicely shown to the user. @@ -37,7 +34,7 @@ class Bundler::Thor end def spell_checker - NoKwargSpellChecker.new(error.all_commands) + DidYouMean::SpellChecker.new(dictionary: error.all_commands) end end @@ -79,7 +76,7 @@ class Bundler::Thor end def spell_checker - @spell_checker ||= NoKwargSpellChecker.new(error.switches) + @spell_checker ||= DidYouMean::SpellChecker.new(dictionary: error.switches) end end @@ -101,10 +98,9 @@ class Bundler::Thor class MalformattedArgumentError < InvocationError end - if Correctable - DidYouMean::SPELL_CHECKERS.merge!( - 'Bundler::Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker, - 'Bundler::Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker - ) + class ExclusiveArgumentError < InvocationError + end + + class AtLeastOneRequiredArgumentError < InvocationError end end diff --git a/lib/bundler/vendor/thor/lib/thor/group.rb b/lib/bundler/vendor/thor/lib/thor/group.rb index 7861d05345..7ea11e8f93 100644 --- a/lib/bundler/vendor/thor/lib/thor/group.rb +++ b/lib/bundler/vendor/thor/lib/thor/group.rb @@ -169,7 +169,7 @@ class Bundler::Thor::Group # options are added to group_options hash. Options that already exists # in base_options are not added twice. # - def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength + def get_options_from_invocations(group_options, base_options) #:nodoc: invocations.each do |name, from_option| value = if from_option option = class_options[name] diff --git a/lib/bundler/vendor/thor/lib/thor/invocation.rb b/lib/bundler/vendor/thor/lib/thor/invocation.rb index 248a466f8e..5ce74710ba 100644 --- a/lib/bundler/vendor/thor/lib/thor/invocation.rb +++ b/lib/bundler/vendor/thor/lib/thor/invocation.rb @@ -143,7 +143,7 @@ class Bundler::Thor # Configuration values that are shared between invocations. def _shared_configuration #:nodoc: - {:invocations => @_invocations} + {invocations: @_invocations} end # This method simply retrieves the class and command to be invoked. diff --git a/lib/bundler/vendor/thor/lib/thor/nested_context.rb b/lib/bundler/vendor/thor/lib/thor/nested_context.rb index fd36b9d43f..7d60cb1c12 100644 --- a/lib/bundler/vendor/thor/lib/thor/nested_context.rb +++ b/lib/bundler/vendor/thor/lib/thor/nested_context.rb @@ -13,10 +13,10 @@ class Bundler::Thor end def entered? - @depth > 0 + @depth.positive? end - private + private def push @depth += 1 diff --git a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb index dfe7398583..b9e94e4669 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb @@ -24,6 +24,17 @@ class Bundler::Thor validate! # Trigger specific validations end + def print_default + if @type == :array and @default.is_a?(Array) + @default.map { |x| + p = x.gsub('"','\\"') + "\"#{p}\"" + }.join(" ") + else + @default + end + end + def usage required? ? banner : "[#{banner}]" end @@ -41,11 +52,19 @@ class Bundler::Thor end end + def enum_to_s + if enum.respond_to? :join + enum.join(", ") + else + "#{enum.first}..#{enum.last}" + end + end + protected def validate! raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? - raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array) + raise ArgumentError, "An argument cannot have an enum other than an enumerable." if @enum && !@enum.is_a?(Enumerable) end def valid_type?(type) diff --git a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb index 3a5d82cf29..b6f9c9a37a 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb @@ -1,5 +1,5 @@ class Bundler::Thor - class Arguments #:nodoc: # rubocop:disable ClassLength + class Arguments #:nodoc: NUMERIC = /[-+]?(\d*\.\d+|\d+)/ # Receives an array of args and returns two arrays, one with arguments @@ -30,11 +30,7 @@ class Bundler::Thor arguments.each do |argument| if !argument.default.nil? - begin - @assigns[argument.human_name] = argument.default.dup - rescue TypeError # Compatibility shim for un-dup-able Fixnum in Ruby < 2.4 - @assigns[argument.human_name] = argument.default - end + @assigns[argument.human_name] = argument.default.dup elsif argument.required? @non_assigned_required << argument end @@ -121,8 +117,18 @@ class Bundler::Thor # def parse_array(name) return shift if peek.is_a?(Array) + array = [] - array << shift while current_is_value? + + while current_is_value? + value = shift + + if !value.empty? + validate_enum_value!(name, value, "Expected all values of '%s' to be one of %s; got %s") + end + + array << value + end array end @@ -138,11 +144,9 @@ class Bundler::Thor end value = $&.index(".") ? shift.to_f : shift.to_i - if @switches.is_a?(Hash) && switch = @switches[name] - if switch.enum && !switch.enum.include?(value) - raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" - end - end + + validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s") + value end @@ -156,15 +160,27 @@ class Bundler::Thor nil else value = shift - if @switches.is_a?(Hash) && switch = @switches[name] - if switch.enum && !switch.enum.include?(value) - raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" - end - end + + validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s") + value end end + # Raises an error if the switch is an enum and the values aren't included on it. + # + def validate_enum_value!(name, value, message) + return unless @switches.is_a?(Hash) + + switch = @switches[name] + + return unless switch + + if switch.enum && !switch.enum.include?(value) + raise MalformattedArgumentError, message % [name, switch.enum_to_s, value] + end + end + # Raises an error if @non_assigned_required array is not empty. # def check_requirement! diff --git a/lib/bundler/vendor/thor/lib/thor/parser/option.rb b/lib/bundler/vendor/thor/lib/thor/parser/option.rb index 5a5af6f888..c6af4e1e87 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/option.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/option.rb @@ -11,7 +11,7 @@ class Bundler::Thor super @lazy_default = options[:lazy_default] @group = options[:group].to_s.capitalize if options[:group] - @aliases = Array(options[:aliases]) + @aliases = normalize_aliases(options[:aliases]) @hide = options[:hide] end @@ -58,7 +58,7 @@ class Bundler::Thor default = nil if VALID_TYPES.include?(value) value - elsif required = (value == :required) # rubocop:disable AssignmentInCondition + elsif required = (value == :required) # rubocop:disable Lint/AssignmentInCondition :string end when TrueClass, FalseClass @@ -69,7 +69,7 @@ class Bundler::Thor value.class.name.downcase.to_sym end - new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases) + new(name.to_s, required: required, type: type, default: default, aliases: aliases) end def switch_name @@ -90,13 +90,26 @@ class Bundler::Thor sample = "[#{sample}]".dup unless required? if boolean? - sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-") + sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.match(/\Ano[\-_]/) end + aliases_for_usage.ljust(padding) + sample + end + + def aliases_for_usage if aliases.empty? - (" " * padding) << sample + "" + else + "#{aliases.join(', ')}, " + end + end + + def show_default? + case default + when TrueClass, FalseClass + true else - "#{aliases.join(', ')}, #{sample}" + super end end @@ -138,8 +151,8 @@ class Bundler::Thor raise ArgumentError, err elsif @check_default_type == nil Bundler::Thor.deprecation_warning "#{err}.\n" + - 'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' + - ' or call `allow_incompatible_default_type!` in your code' + "This will be rejected in the future unless you explicitly pass the options `check_default_type: false`" + + " or call `allow_incompatible_default_type!` in your code" end end end @@ -155,5 +168,11 @@ class Bundler::Thor def dasherize(str) (str.length > 1 ? "--" : "-") + str.tr("_", "-") end + + private + + def normalize_aliases(aliases) + Array(aliases).map { |short| short.to_s.sub(/^(?!\-)/, "-") } + end end end diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb index 3a8927d09c..978e76b132 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb @@ -1,5 +1,5 @@ class Bundler::Thor - class Options < Arguments #:nodoc: # rubocop:disable ClassLength + class Options < Arguments #:nodoc: LONG_RE = /^(--\w+(?:-\w+)*)$/ SHORT_RE = /^(-[a-z])$/i EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i @@ -29,8 +29,10 @@ class Bundler::Thor # # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters # an unknown option or a regular argument. - def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false) + def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false, relations = {}) @stop_on_unknown = stop_on_unknown + @exclusives = (relations[:exclusive_option_names] || []).select{|array| !array.empty?} + @at_least_ones = (relations[:at_least_one_option_names] || []).select{|array| !array.empty?} @disable_required_check = disable_required_check options = hash_options.values super(options) @@ -45,12 +47,12 @@ class Bundler::Thor @switches = {} @extra = [] @stopped_parsing_after_extra_index = nil + @is_treated_as_value = false options.each do |option| @switches[option.switch_name] = option - option.aliases.each do |short| - name = short.to_s.sub(/^(?!\-)/, "-") + option.aliases.each do |name| @shorts[name] ||= option.switch_name end end @@ -74,8 +76,19 @@ class Bundler::Thor end end - def parse(args) # rubocop:disable MethodLength + def shift + @is_treated_as_value = false + super + end + + def unshift(arg, is_value: false) + @is_treated_as_value = is_value + super(arg) + end + + def parse(args) # rubocop:disable Metrics/MethodLength @pile = args.dup + @is_treated_as_value = false @parsing_options = true while peek @@ -88,7 +101,10 @@ class Bundler::Thor when SHORT_SQ_RE unshift($1.split("").map { |f| "-#{f}" }) next - when EQ_RE, SHORT_NUM + when EQ_RE + unshift($2, is_value: true) + switch = $1 + when SHORT_NUM unshift($2) switch = $1 when LONG_RE, SHORT_RE @@ -117,12 +133,38 @@ class Bundler::Thor end check_requirement! unless @disable_required_check + check_exclusive! + check_at_least_one! assigns = Bundler::Thor::CoreExt::HashWithIndifferentAccess.new(@assigns) assigns.freeze assigns end + def check_exclusive! + opts = @assigns.keys + # When option A and B are exclusive, if A and B are given at the same time, + # the diffrence of argument array size will decrease. + found = @exclusives.find{ |ex| (ex - opts).size < ex.size - 1 } + if found + names = names_to_switch_names(found & opts).map{|n| "'#{n}'"} + class_name = self.class.name.split("::").last.downcase + fail ExclusiveArgumentError, "Found exclusive #{class_name} #{names.join(", ")}" + end + end + + def check_at_least_one! + opts = @assigns.keys + # When at least one is required of the options A and B, + # if the both options were not given, none? would be true. + found = @at_least_ones.find{ |one_reqs| one_reqs.none?{ |o| opts.include? o} } + if found + names = names_to_switch_names(found).map{|n| "'#{n}'"} + class_name = self.class.name.split("::").last.downcase + fail AtLeastOneRequiredArgumentError, "Not found at least one of required #{class_name} #{names.join(", ")}" + end + end + def check_unknown! to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra @@ -133,6 +175,17 @@ class Bundler::Thor protected + # Option names changes to swith name or human name + def names_to_switch_names(names = []) + @switches.map do |_, o| + if names.include? o.name + o.respond_to?(:switch_name) ? o.switch_name : o.human_name + else + nil + end + end.compact + end + def assign_result!(option, result) if option.repeatable && option.type == :hash (@assigns[option.human_name] ||= {}).merge!(result) @@ -148,6 +201,7 @@ class Bundler::Thor # Two booleans are returned. The first is true if the current value # starts with a hyphen; the second is true if it is a registered switch. def current_is_switch? + return [false, false] if @is_treated_as_value case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM [true, switch?($1)] @@ -159,6 +213,7 @@ class Bundler::Thor end def current_is_switch_formatted? + return false if @is_treated_as_value case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE true @@ -168,6 +223,7 @@ class Bundler::Thor end def current_is_value? + return true if @is_treated_as_value peek && (!parsing_options? || super) end @@ -176,7 +232,7 @@ class Bundler::Thor end def switch_option(arg) - if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition + if match = no_or_skip?(arg) # rubocop:disable Lint/AssignmentInCondition @switches[arg] || @switches["--#{match}"] else @switches[arg] diff --git a/lib/bundler/vendor/thor/lib/thor/rake_compat.rb b/lib/bundler/vendor/thor/lib/thor/rake_compat.rb index f8f86372cc..c6a4653fc1 100644 --- a/lib/bundler/vendor/thor/lib/thor/rake_compat.rb +++ b/lib/bundler/vendor/thor/lib/thor/rake_compat.rb @@ -41,7 +41,7 @@ instance_eval do def task(*) task = super - if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition non_namespaced_name = task.name.split(":").last description = non_namespaced_name @@ -59,7 +59,7 @@ instance_eval do end def namespace(name) - if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition const_name = Bundler::Thor::Util.camel_case(name.to_s).to_sym klass.const_set(const_name, Class.new(Bundler::Thor)) new_klass = klass.const_get(const_name) diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index 54c5525093..c7cc873131 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -2,12 +2,10 @@ require_relative "../thor" require_relative "group" require "yaml" -require "digest/md5" +require "digest/sha2" require "pathname" -class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength - autoload :OpenURI, "open-uri" - +class Bundler::Thor::Runner < Bundler::Thor #:nodoc: map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version def self.banner(command, all = false, subcommand = false) @@ -25,7 +23,7 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng initialize_thorfiles(meth) klass, command = Bundler::Thor::Util.find_class_and_command_by_namespace(meth) self.class.handle_no_command_error(command, false) if klass.nil? - klass.start(["-h", command].compact, :shell => shell) + klass.start(["-h", command].compact, shell: shell) else super end @@ -40,30 +38,42 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng klass, command = Bundler::Thor::Util.find_class_and_command_by_namespace(meth) self.class.handle_no_command_error(command, false) if klass.nil? args.unshift(command) if command - klass.start(args, :shell => shell) + klass.start(args, shell: shell) end desc "install NAME", "Install an optionally named Bundler::Thor file into your system commands" - method_options :as => :string, :relative => :boolean, :force => :boolean - def install(name) # rubocop:disable MethodLength + method_options as: :string, relative: :boolean, force: :boolean + def install(name) # rubocop:disable Metrics/MethodLength initialize_thorfiles - # If a directory name is provided as the argument, look for a 'main.thor' - # command in said directory. - begin - if File.directory?(File.expand_path(name)) - base = File.join(name, "main.thor") - package = :directory - contents = open(base, &:read) - else - base = name - package = :file - contents = open(name, &:read) + is_uri = name =~ %r{^https?\://} + + if is_uri + base = name + package = :file + require "open-uri" + begin + contents = URI.open(name, &:read) + rescue OpenURI::HTTPError + raise Error, "Error opening URI '#{name}'" + end + else + # If a directory name is provided as the argument, look for a 'main.thor' + # command in said directory. + begin + if File.directory?(File.expand_path(name)) + base = File.join(name, "main.thor") + package = :directory + contents = File.open(base, &:read) + else + base = name + package = :file + require "open-uri" + contents = URI.open(name, &:read) + end + rescue Errno::ENOENT + raise Error, "Error opening file '#{name}'" end - rescue OpenURI::HTTPError - raise Error, "Error opening URI '#{name}'" - rescue Errno::ENOENT - raise Error, "Error opening file '#{name}'" end say "Your Thorfile contains:" @@ -84,16 +94,16 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng as = basename if as.empty? end - location = if options[:relative] || name =~ %r{^https?://} + location = if options[:relative] || is_uri name else File.expand_path(name) end thor_yaml[as] = { - :filename => Digest::MD5.hexdigest(name + as), - :location => location, - :namespaces => Bundler::Thor::Util.namespaces_in_content(contents, base) + filename: Digest::SHA256.hexdigest(name + as), + location: location, + namespaces: Bundler::Thor::Util.namespaces_in_content(contents, base) } save_yaml(thor_yaml) @@ -154,14 +164,14 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng end desc "installed", "List the installed Bundler::Thor modules and commands" - method_options :internal => :boolean + method_options internal: :boolean def installed initialize_thorfiles(nil, true) display_klasses(true, options["internal"]) end desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)" - method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean + method_options substring: :boolean, group: :string, all: :boolean, debug: :boolean def list(search = "") initialize_thorfiles @@ -303,7 +313,7 @@ private say shell.set_color(namespace, :blue, true) say "-" * namespace.size - print_table(list, :truncate => true) + print_table(list, truncate: true) say end alias_method :display_tasks, :display_commands diff --git a/lib/bundler/vendor/thor/lib/thor/shell.rb b/lib/bundler/vendor/thor/lib/thor/shell.rb index e36fa472d6..265f3ba046 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell.rb @@ -21,7 +21,7 @@ class Bundler::Thor end module Shell - SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width] + SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_error, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width] attr_writer :shell autoload :Basic, File.expand_path("shell/basic", __dir__) @@ -75,7 +75,7 @@ class Bundler::Thor # Allow shell to be shared between invocations. # def _shared_configuration #:nodoc: - super.merge!(:shell => shell) + super.merge!(shell: shell) end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb index 2dddd4a53a..dc3179e5f3 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -1,8 +1,10 @@ +require_relative "column_printer" +require_relative "table_printer" +require_relative "wrapped_printer" + class Bundler::Thor module Shell class Basic - DEFAULT_TERMINAL_WIDTH = 80 - attr_accessor :base attr_reader :padding @@ -103,6 +105,23 @@ class Bundler::Thor stdout.flush end + # Say (print) an error to the user. If the sentence ends with a whitespace + # or tab character, a new line is not appended (print + flush). Otherwise + # are passed straight to puts (behavior got from Highline). + # + # ==== Example + # say_error("error: something went wrong") + # + def say_error(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/)) + return if quiet? + + buffer = prepare_message(message, *color) + buffer << "\n" if force_new_line && !message.to_s.end_with?("\n") + + stderr.print(buffer) + stderr.flush + end + # Say a status with the given color and appends the message. Since this # method is used frequently by actions, it allows nil or false to be given # in log_status, avoiding the message from being shown. If a Symbol is @@ -111,13 +130,14 @@ class Bundler::Thor def say_status(status, message, log_status = true) return if quiet? || log_status == false spaces = " " * (padding + 1) - color = log_status.is_a?(Symbol) ? log_status : :green - status = status.to_s.rjust(12) + margin = " " * status.length + spaces + + color = log_status.is_a?(Symbol) ? log_status : :green status = set_color status, color, true if color - buffer = "#{status}#{spaces}#{message}" - buffer = "#{buffer}\n" unless buffer.end_with?("\n") + message = message.to_s.chomp.gsub(/(?<!\A)^/, margin) + buffer = "#{status}#{spaces}#{message}\n" stdout.print(buffer) stdout.flush @@ -127,14 +147,14 @@ class Bundler::Thor # "yes". # def yes?(statement, color = nil) - !!(ask(statement, color, :add_to_history => false) =~ is?(:yes)) + !!(ask(statement, color, add_to_history: false) =~ is?(:yes)) end # Make a question the to user and returns true if the user replies "n" or # "no". # def no?(statement, color = nil) - !!(ask(statement, color, :add_to_history => false) =~ is?(:no)) + !!(ask(statement, color, add_to_history: false) =~ is?(:no)) end # Prints values in columns @@ -143,16 +163,8 @@ class Bundler::Thor # Array[String, String, ...] # def print_in_columns(array) - return if array.empty? - colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2 - array.each_with_index do |value, index| - # Don't output trailing spaces when printing the last column - if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length - stdout.puts value - else - stdout.printf("%-#{colwidth}s", value) - end - end + printer = ColumnPrinter.new(stdout) + printer.print(array) end # Prints a table. @@ -163,58 +175,11 @@ class Bundler::Thor # ==== Options # indent<Integer>:: Indent the first column by indent value. # colwidth<Integer>:: Force the first column to colwidth spaces wide. + # borders<Boolean>:: Adds ascii borders. # - def print_table(array, options = {}) # rubocop:disable MethodLength - return if array.empty? - - formats = [] - indent = options[:indent].to_i - colwidth = options[:colwidth] - options[:truncate] = terminal_width if options[:truncate] == true - - formats << "%-#{colwidth + 2}s".dup if colwidth - start = colwidth ? 1 : 0 - - colcount = array.max { |a, b| a.size <=> b.size }.size - - maximas = [] - - start.upto(colcount - 1) do |index| - maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max - maximas << maxima - formats << if index == colcount - 1 - # Don't output 2 trailing spaces when printing the last column - "%-s".dup - else - "%-#{maxima + 2}s".dup - end - end - - formats[0] = formats[0].insert(0, " " * indent) - formats << "%s" - - array.each do |row| - sentence = "".dup - - row.each_with_index do |column, index| - maxima = maximas[index] - - f = if column.is_a?(Numeric) - if index == row.size - 1 - # Don't output 2 trailing spaces when printing the last column - "%#{maxima}s" - else - "%#{maxima}s " - end - else - formats[index] - end - sentence << f % column.to_s - end - - sentence = truncate(sentence, options[:truncate]) if options[:truncate] - stdout.puts sentence - end + def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength + printer = TablePrinter.new(stdout, options) + printer.print(array) end # Prints a long string, word-wrapping the text to the current width of the @@ -227,33 +192,8 @@ class Bundler::Thor # indent<Integer>:: Indent each line of the printed paragraph by indent value. # def print_wrapped(message, options = {}) - indent = options[:indent] || 0 - width = terminal_width - indent - paras = message.split("\n\n") - - paras.map! do |unwrapped| - words = unwrapped.split(" ") - counter = words.first.length - words.inject do |memo, word| - word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n") - counter = 0 if word.include? "\n" - if (counter + word.length + 1) < width - memo = "#{memo} #{word}" - counter += (word.length + 1) - else - memo = "#{memo}\n#{word}" - counter = word.length - end - memo - end - end.compact! - - paras.each do |para| - para.split("\n").each do |line| - stdout.puts line.insert(0, " " * indent) - end - stdout.puts unless para == paras.last - end + printer = WrappedPrinter.new(stdout, options) + printer.print(message) end # Deals with file collision and returns true if the file should be @@ -271,7 +211,7 @@ class Bundler::Thor loop do answer = ask( %[Overwrite #{destination}? (enter "h" for help) #{options}], - :add_to_history => false + add_to_history: false ) case answer @@ -298,24 +238,11 @@ class Bundler::Thor say "Please specify merge tool to `THOR_MERGE` env." else - say file_collision_help + say file_collision_help(block_given?) end end end - # This code was copied from Rake, available under MIT-LICENSE - # Copyright (c) 2003, 2004 Jim Weirich - def terminal_width - result = if ENV["THOR_COLUMNS"] - ENV["THOR_COLUMNS"].to_i - else - unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH - end - result < 10 ? DEFAULT_TERMINAL_WIDTH : result - rescue - DEFAULT_TERMINAL_WIDTH - end - # Called if something goes wrong during the execution. This is used by Bundler::Thor # internally and should not be used inside your scripts. If something went # wrong, you can always raise an exception. If you raise a Bundler::Thor::Error, it @@ -366,16 +293,21 @@ class Bundler::Thor end end - def file_collision_help #:nodoc: - <<-HELP + def file_collision_help(block_given) #:nodoc: + help = <<-HELP Y - yes, overwrite n - no, do not overwrite a - all, overwrite this and all others q - quit, abort - d - diff, show the differences between the old and the new h - help, show this help - m - merge, run merge tool HELP + if block_given + help << <<-HELP + d - diff, show the differences between the old and the new + m - merge, run merge tool + HELP + end + help end def show_diff(destination, content) #:nodoc: @@ -393,46 +325,8 @@ class Bundler::Thor mute? || (base && base.options[:quiet]) end - # Calculate the dynamic width of the terminal - def dynamic_width - @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) - end - - def dynamic_width_stty - `stty size 2>/dev/null`.split[1].to_i - end - - def dynamic_width_tput - `tput cols 2>/dev/null`.to_i - end - def unix? - RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i - end - - def truncate(string, width) - as_unicode do - chars = string.chars.to_a - if chars.length <= width - chars.join - else - chars[0, width - 3].join + "..." - end - end - end - - if "".respond_to?(:encode) - def as_unicode - yield - end - else - def as_unicode - old = $KCODE - $KCODE = "U" - yield - ensure - $KCODE = old - end + Terminal.unix? end def ask_simply(statement, color, options) diff --git a/lib/bundler/vendor/thor/lib/thor/shell/color.rb b/lib/bundler/vendor/thor/lib/thor/shell/color.rb index dc167ed3cc..5d708fadca 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/color.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/color.rb @@ -105,52 +105,7 @@ class Bundler::Thor end def are_colors_disabled? - !ENV['NO_COLOR'].nil? - end - - # Overwrite show_diff to show diff with colors if Diff::LCS is - # available. - # - def show_diff(destination, content) #:nodoc: - if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? - actual = File.binread(destination).to_s.split("\n") - content = content.to_s.split("\n") - - Diff::LCS.sdiff(actual, content).each do |diff| - output_diff_line(diff) - end - else - super - end - end - - def output_diff_line(diff) #:nodoc: - case diff.action - when "-" - say "- #{diff.old_element.chomp}", :red, true - when "+" - say "+ #{diff.new_element.chomp}", :green, true - when "!" - say "- #{diff.old_element.chomp}", :red, true - say "+ #{diff.new_element.chomp}", :green, true - else - say " #{diff.old_element.chomp}", nil, true - end - end - - # Check if Diff::LCS is loaded. If it is, use it to create pretty output - # for diff. - # - def diff_lcs_loaded? #:nodoc: - return true if defined?(Diff::LCS) - return @diff_lcs_loaded unless @diff_lcs_loaded.nil? - - @diff_lcs_loaded = begin - require "diff/lcs" - true - rescue LoadError - false - end + !ENV["NO_COLOR"].nil? && !ENV["NO_COLOR"].empty? end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/column_printer.rb b/lib/bundler/vendor/thor/lib/thor/shell/column_printer.rb new file mode 100644 index 0000000000..56a9e6181b --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/column_printer.rb @@ -0,0 +1,29 @@ +require_relative "terminal" + +class Bundler::Thor + module Shell + class ColumnPrinter + attr_reader :stdout, :options + + def initialize(stdout, options = {}) + @stdout = stdout + @options = options + @indent = options[:indent].to_i + end + + def print(array) + return if array.empty? + colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2 + array.each_with_index do |value, index| + # Don't output trailing spaces when printing the last column + if ((((index + 1) % (Terminal.terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length + stdout.puts value + else + stdout.printf("%-#{colwidth}s", value) + end + end + end + end + end +end + diff --git a/lib/bundler/vendor/thor/lib/thor/shell/html.rb b/lib/bundler/vendor/thor/lib/thor/shell/html.rb index 77a6d13a23..0277b882b7 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/html.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/html.rb @@ -76,51 +76,6 @@ class Bundler::Thor def can_display_colors? true end - - # Overwrite show_diff to show diff with colors if Diff::LCS is - # available. - # - def show_diff(destination, content) #:nodoc: - if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? - actual = File.binread(destination).to_s.split("\n") - content = content.to_s.split("\n") - - Diff::LCS.sdiff(actual, content).each do |diff| - output_diff_line(diff) - end - else - super - end - end - - def output_diff_line(diff) #:nodoc: - case diff.action - when "-" - say "- #{diff.old_element.chomp}", :red, true - when "+" - say "+ #{diff.new_element.chomp}", :green, true - when "!" - say "- #{diff.old_element.chomp}", :red, true - say "+ #{diff.new_element.chomp}", :green, true - else - say " #{diff.old_element.chomp}", nil, true - end - end - - # Check if Diff::LCS is loaded. If it is, use it to create pretty output - # for diff. - # - def diff_lcs_loaded? #:nodoc: - return true if defined?(Diff::LCS) - return @diff_lcs_loaded unless @diff_lcs_loaded.nil? - - @diff_lcs_loaded = begin - require "diff/lcs" - true - rescue LoadError - false - end - end end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb b/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb new file mode 100644 index 0000000000..525f9ce5bb --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb @@ -0,0 +1,134 @@ +require_relative "column_printer" +require_relative "terminal" + +class Bundler::Thor + module Shell + class TablePrinter < ColumnPrinter + BORDER_SEPARATOR = :separator + + def initialize(stdout, options = {}) + super + @formats = [] + @maximas = [] + @colwidth = options[:colwidth] + @truncate = options[:truncate] == true ? Terminal.terminal_width : options[:truncate] + @padding = 1 + end + + def print(array) + return if array.empty? + + prepare(array) + + print_border_separator if options[:borders] + + array.each do |row| + if options[:borders] && row == BORDER_SEPARATOR + print_border_separator + next + end + + sentence = "".dup + + row.each_with_index do |column, index| + sentence << format_cell(column, row.size, index) + end + + sentence = truncate(sentence) + sentence << "|" if options[:borders] + stdout.puts indentation + sentence + + end + print_border_separator if options[:borders] + end + + private + + def prepare(array) + array = array.reject{|row| row == BORDER_SEPARATOR } + + @formats << "%-#{@colwidth + 2}s".dup if @colwidth + start = @colwidth ? 1 : 0 + + colcount = array.max { |a, b| a.size <=> b.size }.size + + start.upto(colcount - 1) do |index| + maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max + + @maximas << maxima + @formats << if options[:borders] + "%-#{maxima}s".dup + elsif index == colcount - 1 + # Don't output 2 trailing spaces when printing the last column + "%-s".dup + else + "%-#{maxima + 2}s".dup + end + end + + @formats << "%s" + end + + def format_cell(column, row_size, index) + maxima = @maximas[index] + + f = if column.is_a?(Numeric) + if options[:borders] + # With borders we handle padding separately + "%#{maxima}s" + elsif index == row_size - 1 + # Don't output 2 trailing spaces when printing the last column + "%#{maxima}s" + else + "%#{maxima}s " + end + else + @formats[index] + end + + cell = "".dup + cell << "|" + " " * @padding if options[:borders] + cell << f % column.to_s + cell << " " * @padding if options[:borders] + cell + end + + def print_border_separator + separator = @maximas.map do |maxima| + "+" + "-" * (maxima + 2 * @padding) + end + stdout.puts indentation + separator.join + "+" + end + + def truncate(string) + return string unless @truncate + as_unicode do + chars = string.chars.to_a + if chars.length <= @truncate + chars.join + else + chars[0, @truncate - 3 - @indent].join + "..." + end + end + end + + def indentation + " " * @indent + end + + if "".respond_to?(:encode) + def as_unicode + yield + end + else + def as_unicode + old = $KCODE # rubocop:disable Style/GlobalVars + $KCODE = "U" # rubocop:disable Style/GlobalVars + yield + ensure + $KCODE = old # rubocop:disable Style/GlobalVars + end + end + end + end +end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/terminal.rb b/lib/bundler/vendor/thor/lib/thor/shell/terminal.rb new file mode 100644 index 0000000000..2c60684308 --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/terminal.rb @@ -0,0 +1,42 @@ +class Bundler::Thor + module Shell + module Terminal + DEFAULT_TERMINAL_WIDTH = 80 + + class << self + # This code was copied from Rake, available under MIT-LICENSE + # Copyright (c) 2003, 2004 Jim Weirich + def terminal_width + result = if ENV["THOR_COLUMNS"] + ENV["THOR_COLUMNS"].to_i + else + unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH + end + result < 10 ? DEFAULT_TERMINAL_WIDTH : result + rescue + DEFAULT_TERMINAL_WIDTH + end + + def unix? + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i + end + + private + + # Calculate the dynamic width of the terminal + def dynamic_width + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty + `stty size 2>/dev/null`.split[1].to_i + end + + def dynamic_width_tput + `tput cols 2>/dev/null`.to_i + end + + end + end + end +end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb b/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb new file mode 100644 index 0000000000..ba88e952db --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb @@ -0,0 +1,38 @@ +require_relative "column_printer" +require_relative "terminal" + +class Bundler::Thor + module Shell + class WrappedPrinter < ColumnPrinter + def print(message) + width = Terminal.terminal_width - @indent + paras = message.split("\n\n") + + paras.map! do |unwrapped| + words = unwrapped.split(" ") + counter = words.first.length + words.inject do |memo, word| + word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n") + counter = 0 if word.include? "\n" + if (counter + word.length + 1) < width + memo = "#{memo} #{word}" + counter += (word.length + 1) + else + memo = "#{memo}\n#{word}" + counter = word.length + end + memo + end + end.compact! + + paras.each do |para| + para.split("\n").each do |line| + stdout.puts line.insert(0, " " * @indent) + end + stdout.puts unless para == paras.last + end + end + end + end +end + diff --git a/lib/bundler/vendor/thor/lib/thor/util.rb b/lib/bundler/vendor/thor/lib/thor/util.rb index ddf4d21b90..68916daf2e 100644 --- a/lib/bundler/vendor/thor/lib/thor/util.rb +++ b/lib/bundler/vendor/thor/lib/thor/util.rb @@ -90,7 +90,7 @@ class Bundler::Thor def snake_case(str) return str.downcase if str =~ /^[A-Z_]+$/ str.gsub(/\B[A-Z]/, '_\&').squeeze("_") =~ /_*(.*)/ - $+.downcase + Regexp.last_match(-1).downcase end # Receives a string and convert it to camel case. camel_case returns CamelCase. @@ -130,9 +130,10 @@ class Bundler::Thor # def find_class_and_command_by_namespace(namespace, fallback = true) if namespace.include?(":") # look for a namespaced command - pieces = namespace.split(":") - command = pieces.pop - klass = Bundler::Thor::Util.find_by_namespace(pieces.join(":")) + *pieces, command = namespace.split(":") + namespace = pieces.join(":") + namespace = "default" if namespace.empty? + klass = Bundler::Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.commands.keys.include?(command) } end unless klass # look for a Bundler::Thor::Group with the right name klass = Bundler::Thor::Util.find_by_namespace(namespace) @@ -150,7 +151,7 @@ class Bundler::Thor # inside the sandbox to avoid namespacing conflicts. # def load_thorfile(path, content = nil, debug = false) - content ||= File.binread(path) + content ||= File.read(path) begin Bundler::Thor::Sandbox.class_eval(content, path) @@ -189,7 +190,7 @@ class Bundler::Thor # Returns the root where thor files are located, depending on the OS. # def thor_root - File.join(user_home, ".thor").tr('\\', "/") + File.join(user_home, ".thor").tr("\\", "/") end # Returns the files in the thor root. On Windows thor_root will be something @@ -211,7 +212,7 @@ class Bundler::Thor # def globs_for(path) path = escape_globs(path) - ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] + ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/**/*.thor"] end # Return the path to the ruby interpreter taking into account multiple @@ -236,7 +237,7 @@ class Bundler::Thor # symlink points to 'ruby_install_name' ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby end - rescue NotImplementedError # rubocop:disable HandleExceptions + rescue NotImplementedError # rubocop:disable Lint/HandleExceptions # just ignore on windows end end diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb index a3efa9f762..1fb00017ed 100644 --- a/lib/bundler/vendor/thor/lib/thor/version.rb +++ b/lib/bundler/vendor/thor/lib/thor/version.rb @@ -1,3 +1,3 @@ class Bundler::Thor - VERSION = "1.1.0" + VERSION = "1.3.0" end diff --git a/lib/bundler/vendor/tmpdir/lib/tmpdir.rb b/lib/bundler/vendor/tmpdir/lib/tmpdir.rb deleted file mode 100644 index 70d43e0c6b..0000000000 --- a/lib/bundler/vendor/tmpdir/lib/tmpdir.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true -# -# tmpdir - retrieve temporary directory path -# -# $Id$ -# - -require_relative '../../fileutils/lib/fileutils' -begin - require 'etc.so' -rescue LoadError # rescue LoadError for miniruby -end - -class Bundler::Dir < Dir - - @systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp' - - ## - # Returns the operating system's temporary file path. - - def self.tmpdir - tmp = nil - ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @systmpdir], ['/tmp']*2, ['.']*2].each do |name, dir = ENV[name]| - next if !dir - dir = File.expand_path(dir) - stat = File.stat(dir) rescue next - case - when !stat.directory? - warn "#{name} is not a directory: #{dir}" - when !stat.writable? - warn "#{name} is not writable: #{dir}" - when stat.world_writable? && !stat.sticky? - warn "#{name} is world-writable: #{dir}" - else - tmp = dir - break - end - end - raise ArgumentError, "could not find a temporary directory" unless tmp - tmp - end - - # Bundler::Dir.mktmpdir creates a temporary directory. - # - # The directory is created with 0700 permission. - # Application should not change the permission to make the temporary directory accessible from other users. - # - # The prefix and suffix of the name of the directory is specified by - # the optional first argument, <i>prefix_suffix</i>. - # - If it is not specified or nil, "d" is used as the prefix and no suffix is used. - # - If it is a string, it is used as the prefix and no suffix is used. - # - If it is an array, first element is used as the prefix and second element is used as a suffix. - # - # Bundler::Dir.mktmpdir {|dir| dir is ".../d..." } - # Bundler::Dir.mktmpdir("foo") {|dir| dir is ".../foo..." } - # Bundler::Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" } - # - # The directory is created under Bundler::Dir.tmpdir or - # the optional second argument <i>tmpdir</i> if non-nil value is given. - # - # Bundler::Dir.mktmpdir {|dir| dir is "#{Bundler::Dir.tmpdir}/d..." } - # Bundler::Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." } - # - # If a block is given, - # it is yielded with the path of the directory. - # The directory and its contents are removed - # using Bundler::FileUtils.remove_entry before Bundler::Dir.mktmpdir returns. - # The value of the block is returned. - # - # Bundler::Dir.mktmpdir {|dir| - # # use the directory... - # open("#{dir}/foo", "w") { ... } - # } - # - # If a block is not given, - # The path of the directory is returned. - # In this case, Bundler::Dir.mktmpdir doesn't remove the directory. - # - # dir = Bundler::Dir.mktmpdir - # begin - # # use the directory... - # open("#{dir}/foo", "w") { ... } - # ensure - # # remove the directory. - # Bundler::FileUtils.remove_entry dir - # end - # - def self.mktmpdir(prefix_suffix=nil, *rest, **options) - base = nil - path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|p, _, _, d| - base = d - mkdir(p, 0700) - } - if block_given? - begin - yield path.dup - ensure - unless base - stat = File.stat(File.dirname(path)) - if stat.world_writable? and !stat.sticky? - raise ArgumentError, "parent directory is world writable but not sticky" - end - end - Bundler::FileUtils.remove_entry path - end - else - path - end - end - - module Tmpname # :nodoc: - module_function - - def tmpdir - Bundler::Dir.tmpdir - end - - UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~" - - class << (RANDOM = Random.new) - MAX = 36**6 # < 0x100000000 - def next - rand(MAX).to_s(36) - end - end - private_constant :RANDOM - - def create(basename, tmpdir=nil, max_try: nil, **opts) - origdir = tmpdir - tmpdir ||= tmpdir() - n = nil - prefix, suffix = basename - prefix = (String.try_convert(prefix) or - raise ArgumentError, "unexpected prefix: #{prefix.inspect}") - prefix = prefix.delete(UNUSABLE_CHARS) - suffix &&= (String.try_convert(suffix) or - raise ArgumentError, "unexpected suffix: #{suffix.inspect}") - suffix &&= suffix.delete(UNUSABLE_CHARS) - begin - t = Time.now.strftime("%Y%m%d") - path = "#{prefix}#{t}-#{$$}-#{RANDOM.next}"\ - "#{n ? %[-#{n}] : ''}#{suffix||''}" - path = File.join(tmpdir, path) - yield(path, n, opts, origdir) - rescue Errno::EEXIST - n ||= 0 - n += 1 - retry if !max_try or n < max_try - raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'" - end - path - end - end -end diff --git a/lib/bundler/vendor/tsort/.document b/lib/bundler/vendor/tsort/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/tsort/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/tsort/lib/tsort.rb b/lib/bundler/vendor/tsort/lib/tsort.rb new file mode 100644 index 0000000000..cf8731f760 --- /dev/null +++ b/lib/bundler/vendor/tsort/lib/tsort.rb @@ -0,0 +1,455 @@ +# frozen_string_literal: true + +#-- +# tsort.rb - provides a module for topological sorting and strongly connected components. +#++ +# + +# +# Bundler::TSort implements topological sorting using Tarjan's algorithm for +# strongly connected components. +# +# Bundler::TSort is designed to be able to be used with any object which can be +# interpreted as a directed graph. +# +# Bundler::TSort requires two methods to interpret an object as a graph, +# tsort_each_node and tsort_each_child. +# +# * tsort_each_node is used to iterate for all nodes over a graph. +# * tsort_each_child is used to iterate for child nodes of a given node. +# +# The equality of nodes are defined by eql? and hash since +# Bundler::TSort uses Hash internally. +# +# == A Simple Example +# +# The following example demonstrates how to mix the Bundler::TSort module into an +# existing class (in this case, Hash). Here, we're treating each key in +# the hash as a node in the graph, and so we simply alias the required +# #tsort_each_node method to Hash's #each_key method. For each key in the +# hash, the associated value is an array of the node's child nodes. This +# choice in turn leads to our implementation of the required #tsort_each_child +# method, which fetches the array of child nodes and then iterates over that +# array using the user-supplied block. +# +# require 'bundler/vendor/tsort/lib/tsort' +# +# class Hash +# include Bundler::TSort +# alias tsort_each_node each_key +# def tsort_each_child(node, &block) +# fetch(node).each(&block) +# end +# end +# +# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort +# #=> [3, 2, 1, 4] +# +# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components +# #=> [[4], [2, 3], [1]] +# +# == A More Realistic Example +# +# A very simple `make' like tool can be implemented as follows: +# +# require 'bundler/vendor/tsort/lib/tsort' +# +# class Make +# def initialize +# @dep = {} +# @dep.default = [] +# end +# +# def rule(outputs, inputs=[], &block) +# triple = [outputs, inputs, block] +# outputs.each {|f| @dep[f] = [triple]} +# @dep[triple] = inputs +# end +# +# def build(target) +# each_strongly_connected_component_from(target) {|ns| +# if ns.length != 1 +# fs = ns.delete_if {|n| Array === n} +# raise Bundler::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") +# end +# n = ns.first +# if Array === n +# outputs, inputs, block = n +# inputs_time = inputs.map {|f| File.mtime f}.max +# begin +# outputs_time = outputs.map {|f| File.mtime f}.min +# rescue Errno::ENOENT +# outputs_time = nil +# end +# if outputs_time == nil || +# inputs_time != nil && outputs_time <= inputs_time +# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i +# block.call +# end +# end +# } +# end +# +# def tsort_each_child(node, &block) +# @dep[node].each(&block) +# end +# include Bundler::TSort +# end +# +# def command(arg) +# print arg, "\n" +# system arg +# end +# +# m = Make.new +# m.rule(%w[t1]) { command 'date > t1' } +# m.rule(%w[t2]) { command 'date > t2' } +# m.rule(%w[t3]) { command 'date > t3' } +# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } +# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } +# m.build('t5') +# +# == Bugs +# +# * 'tsort.rb' is wrong name because this library uses +# Tarjan's algorithm for strongly connected components. +# Although 'strongly_connected_components.rb' is correct but too long. +# +# == References +# +# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", +# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972. +# + +module Bundler::TSort + + VERSION = "0.2.0" + + class Cyclic < StandardError + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # If there is a cycle, Bundler::TSort::Cyclic is raised. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.tsort #=> [4, 2, 3, 1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.tsort # raises Bundler::TSort::Cyclic + # + def tsort + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.tsort(each_node, each_child) + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # If there is a cycle, Bundler::TSort::Cyclic is raised. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.tsort(each_node, each_child) # raises Bundler::TSort::Cyclic + # + def self.tsort(each_node, each_child) + tsort_each(each_node, each_child).to_a + end + + # The iterator version of the #tsort method. + # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #tsort_each returns +nil+. + # If there is a cycle, Bundler::TSort::Cyclic is raised. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.tsort_each {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def tsort_each(&block) # :yields: node + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.tsort_each(each_node, each_child, &block) + end + + # The iterator version of the Bundler::TSort.tsort method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Bundler::TSort.tsort_each(each_node, each_child) {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def self.tsort_each(each_node, each_child) # :yields: node + return to_enum(__method__, each_node, each_child) unless block_given? + + each_strongly_connected_component(each_node, each_child) {|component| + if component.size == 1 + yield component.first + else + raise Cyclic.new("topological sort failed: #{component.inspect}") + end + } + end + + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] + # + def strongly_connected_components + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.strongly_connected_components(each_node, each_child) + end + + # Returns strongly connected components as an array of arrays of nodes. + # The array is sorted from children to parents. + # Each elements of the array represents a strongly connected component. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2], [3], [1]] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p Bundler::TSort.strongly_connected_components(each_node, each_child) + # #=> [[4], [2, 3], [1]] + # + def self.strongly_connected_components(each_node, each_child) + each_strongly_connected_component(each_node, each_child).to_a + end + + # The iterator version of the #strongly_connected_components method. + # <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to + # <tt><em>obj</em>.strongly_connected_components.each</tt>, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #each_strongly_connected_component returns +nil+. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def each_strongly_connected_component(&block) # :yields: nodes + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + Bundler::TSort.each_strongly_connected_component(each_node, each_child, &block) + end + + # The iterator version of the Bundler::TSort.strongly_connected_components method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2] + # # [3] + # # [1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } + # #=> [4] + # # [2, 3] + # # [1] + # + def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes + return to_enum(__method__, each_node, each_child) unless block_given? + + id_map = {} + stack = [] + each_node.call {|node| + unless id_map.include? node + each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| + yield c + } + end + } + nil + end + + # Iterates over strongly connected component in the subgraph reachable from + # _node_. + # + # Return value is unspecified. + # + # #each_strongly_connected_component_from doesn't call #tsort_each_node. + # + # class G + # include Bundler::TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # graph.each_strongly_connected_component_from(2) {|scc| p scc } + # #=> [4] + # # [2, 3] + # + def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes + Bundler::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) + end + + # Iterates over strongly connected components in a graph. + # The graph is represented by _node_ and _each_child_. + # + # _node_ is the first node. + # _each_child_ should have +call+ method which takes a node argument + # and yields for each child node. + # + # Return value is unspecified. + # + # #Bundler::TSort.each_strongly_connected_component_from is a class method and + # it doesn't need a class to represent a graph which includes Bundler::TSort. + # + # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_child = lambda {|n, &b| graph[n].each(&b) } + # Bundler::TSort.each_strongly_connected_component_from(1, each_child) {|scc| + # p scc + # } + # #=> [4] + # # [2, 3] + # # [1] + # + def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes + return to_enum(__method__, node, each_child, id_map, stack) unless block_given? + + minimum_id = node_id = id_map[node] = id_map.size + stack_length = stack.length + stack << node + + each_child.call(node) {|child| + if id_map.include? child + child_id = id_map[child] + minimum_id = child_id if child_id && child_id < minimum_id + else + sub_minimum_id = + each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| + yield c + } + minimum_id = sub_minimum_id if sub_minimum_id < minimum_id + end + } + + if node_id == minimum_id + component = stack.slice!(stack_length .. -1) + component.each {|n| id_map[n] = nil} + yield component + end + + minimum_id + end + + # Should be implemented by a extended class. + # + # #tsort_each_node is used to iterate for all nodes over a graph. + # + def tsort_each_node # :yields: node + raise NotImplementedError.new + end + + # Should be implemented by a extended class. + # + # #tsort_each_child is used to iterate for child nodes of _node_. + # + def tsort_each_child(node) # :yields: child + raise NotImplementedError.new + end +end diff --git a/lib/bundler/vendor/uri/.document b/lib/bundler/vendor/uri/.document new file mode 100644 index 0000000000..0c43bbd6b3 --- /dev/null +++ b/lib/bundler/vendor/uri/.document @@ -0,0 +1 @@ +# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/uri/lib/uri.rb b/lib/bundler/vendor/uri/lib/uri.rb index 0e574dd2b1..976320f6bd 100644 --- a/lib/bundler/vendor/uri/lib/uri.rb +++ b/lib/bundler/vendor/uri/lib/uri.rb @@ -30,7 +30,7 @@ # class RSYNC < Generic # DEFAULT_PORT = 873 # end -# @@schemes['RSYNC'] = RSYNC +# register_scheme 'RSYNC', RSYNC # end # #=> Bundler::URI::RSYNC # @@ -70,7 +70,6 @@ # - Bundler::URI::REGEXP - (in uri/common.rb) # - Bundler::URI::REGEXP::PATTERN - (in uri/common.rb) # - Bundler::URI::Util - (in uri/common.rb) -# - Bundler::URI::Escape - (in uri/common.rb) # - Bundler::URI::Error - (in uri/common.rb) # - Bundler::URI::InvalidURIError - (in uri/common.rb) # - Bundler::URI::InvalidComponentError - (in uri/common.rb) @@ -101,3 +100,5 @@ require_relative 'uri/https' require_relative 'uri/ldap' require_relative 'uri/ldaps' require_relative 'uri/mailto' +require_relative 'uri/ws' +require_relative 'uri/wss' diff --git a/lib/bundler/vendor/uri/lib/uri/common.rb b/lib/bundler/vendor/uri/lib/uri/common.rb index 6539e1810f..93f4f226ad 100644 --- a/lib/bundler/vendor/uri/lib/uri/common.rb +++ b/lib/bundler/vendor/uri/lib/uri/common.rb @@ -13,9 +13,12 @@ require_relative "rfc2396_parser" require_relative "rfc3986_parser" module Bundler::URI + include RFC2396_REGEXP + REGEXP = RFC2396_REGEXP Parser = RFC2396_Parser RFC3986_PARSER = RFC3986_Parser.new + Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) # Bundler::URI::Parser.new DEFAULT_PARSER = Parser.new @@ -27,6 +30,7 @@ module Bundler::URI DEFAULT_PARSER.regexp.each_pair do |sym, str| const_set(sym, str) end + Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) module Util # :nodoc: def make_components_hash(klass, array_hash) @@ -60,24 +64,70 @@ module Bundler::URI module_function :make_components_hash end - include REGEXP + module Schemes + end + private_constant :Schemes + + # Registers the given +klass+ as the class to be instantiated + # when parsing a \Bundler::URI with the given +scheme+: + # + # Bundler::URI.register_scheme('MS_SEARCH', Bundler::URI::Generic) # => Bundler::URI::Generic + # Bundler::URI.scheme_list['MS_SEARCH'] # => Bundler::URI::Generic + # + # Note that after calling String#upcase on +scheme+, it must be a valid + # constant name. + def self.register_scheme(scheme, klass) + Schemes.const_set(scheme.to_s.upcase, klass) + end - @@schemes = {} - # Returns a Hash of the defined schemes. + # Returns a hash of the defined schemes: + # + # Bundler::URI.scheme_list + # # => + # {"MAILTO"=>Bundler::URI::MailTo, + # "LDAPS"=>Bundler::URI::LDAPS, + # "WS"=>Bundler::URI::WS, + # "HTTP"=>Bundler::URI::HTTP, + # "HTTPS"=>Bundler::URI::HTTPS, + # "LDAP"=>Bundler::URI::LDAP, + # "FILE"=>Bundler::URI::File, + # "FTP"=>Bundler::URI::FTP} + # + # Related: Bundler::URI.register_scheme. def self.scheme_list - @@schemes + Schemes.constants.map { |name| + [name.to_s.upcase, Schemes.const_get(name)] + }.to_h end + INITIAL_SCHEMES = scheme_list + private_constant :INITIAL_SCHEMES + Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + + # Returns a new object constructed from the given +scheme+, +arguments+, + # and +default+: + # + # - The new object is an instance of <tt>Bundler::URI.scheme_list[scheme.upcase]</tt>. + # - The object is initialized by calling the class initializer + # using +scheme+ and +arguments+. + # See Bundler::URI::Generic.new. # - # Construct a Bundler::URI instance, using the scheme to detect the appropriate class - # from +Bundler::URI.scheme_list+. + # Examples: + # + # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top'] + # Bundler::URI.for('https', *values) + # # => #<Bundler::URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> + # Bundler::URI.for('foo', *values, default: Bundler::URI::HTTP) + # # => #<Bundler::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # def self.for(scheme, *arguments, default: Generic) - if scheme - uri_class = @@schemes[scheme.upcase] || default - else - uri_class = default + const_name = scheme.to_s.upcase + + uri_class = INITIAL_SCHEMES[const_name] + uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false) + Schemes.const_get(const_name, false) end + uri_class ||= default return uri_class.new(scheme, *arguments) end @@ -99,95 +149,49 @@ module Bundler::URI # class BadURIError < Error; end - # - # == Synopsis - # - # Bundler::URI::split(uri) - # - # == Args - # - # +uri+:: - # String with Bundler::URI. - # - # == Description - # - # Splits the string on following parts and returns array with result: - # - # * Scheme - # * Userinfo - # * Host - # * Port - # * Registry - # * Path - # * Opaque - # * Query - # * Fragment - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # Bundler::URI.split("http://www.ruby-lang.org/") - # # => ["http", nil, "www.ruby-lang.org", nil, nil, "/", nil, nil, nil] + # Returns a 9-element array representing the parts of the \Bundler::URI + # formed from the string +uri+; + # each array element is a string or +nil+: + # + # names = %w[scheme userinfo host port registry path opaque query fragment] + # values = Bundler::URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') + # names.zip(values) + # # => + # [["scheme", "https"], + # ["userinfo", "john.doe"], + # ["host", "www.example.com"], + # ["port", "123"], + # ["registry", nil], + # ["path", "/forum/questions/"], + # ["opaque", nil], + # ["query", "tag=networking&order=newest"], + # ["fragment", "top"]] # def self.split(uri) RFC3986_PARSER.split(uri) end + # Returns a new \Bundler::URI object constructed from the given string +uri+: # - # == Synopsis - # - # Bundler::URI::parse(uri_str) - # - # == Args - # - # +uri_str+:: - # String with Bundler::URI. - # - # == Description - # - # Creates one of the Bundler::URI's subclasses instance from the string. + # Bundler::URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') + # # => #<Bundler::URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> + # Bundler::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') + # # => #<Bundler::URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # - # == Raises - # - # Bundler::URI::InvalidURIError:: - # Raised if Bundler::URI given is not a correct one. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://www.ruby-lang.org/") - # # => #<Bundler::URI::HTTP http://www.ruby-lang.org/> - # uri.scheme - # # => "http" - # uri.host - # # => "www.ruby-lang.org" - # - # It's recommended to first ::escape the provided +uri_str+ if there are any - # invalid Bundler::URI characters. + # It's recommended to first ::escape string +uri+ + # if it may contain invalid Bundler::URI characters. # def self.parse(uri) RFC3986_PARSER.parse(uri) end + # Merges the given Bundler::URI strings +str+ + # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html]. # - # == Synopsis - # - # Bundler::URI::join(str[, str, ...]) - # - # == Args - # - # +str+:: - # String(s) to work with, will be converted to RFC3986 URIs before merging. - # - # == Description - # - # Joins URIs. - # - # == Usage + # Each string in +str+ is converted to an + # {RFC3986 Bundler::URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged. # - # require 'bundler/vendor/uri/lib/uri' + # Examples: # # Bundler::URI.join("http://example.com/","main.rbx") # # => #<Bundler::URI::HTTP http://example.com/main.rbx> @@ -232,7 +236,7 @@ module Bundler::URI # Bundler::URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") # # => ["http://foo.example.com/bla", "mailto:test@example.com"] # - def self.extract(str, schemes = nil, &block) + def self.extract(str, schemes = nil, &block) # :nodoc: warn "Bundler::URI.extract is obsolete", uplevel: 1 if $VERBOSE DEFAULT_PARSER.extract(str, schemes, &block) end @@ -269,7 +273,7 @@ module Bundler::URI # p $& # end # - def self.regexp(schemes = nil) + def self.regexp(schemes = nil)# :nodoc: warn "Bundler::URI.regexp is obsolete", uplevel: 1 if $VERBOSE DEFAULT_PARSER.make_regexp(schemes) end @@ -278,6 +282,7 @@ module Bundler::URI 256.times do |i| TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) end + TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze TBLDECWWWCOMP_ = {} # :nodoc: @@ -291,18 +296,91 @@ module Bundler::URI TBLDECWWWCOMP_['+'] = ' ' TBLDECWWWCOMP_.freeze - # Encodes given +str+ to URL-encoded form data. + # Returns a URL-encoded string derived from the given string +str+. + # + # The returned string: + # + # - Preserves: + # + # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>. + # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>, + # and <tt>'0'..'9'</tt>. + # + # Example: + # + # Bundler::URI.encode_www_form_component('*.-_azAZ09') + # # => "*.-_azAZ09" + # + # - Converts: + # + # - Character <tt>' '</tt> to character <tt>'+'</tt>. + # - Any other character to "percent notation"; + # the percent notation for character <i>c</i> is <tt>'%%%X' % c.ord</tt>. # - # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP - # (ASCII space) to + and converts others to %XX. + # Example: # - # If +enc+ is given, convert +str+ to the encoding before percent encoding. + # Bundler::URI.encode_www_form_component('Here are some punctuation characters: ,;?:') + # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A" # - # This is an implementation of - # https://www.w3.org/TR/2013/CR-html5-20130806/forms.html#url-encoded-form-data. + # Encoding: # - # See Bundler::URI.decode_www_form_component, Bundler::URI.encode_www_form. + # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored. + # - Otherwise +str+ is converted first to Encoding::UTF_8 + # (with suitable character replacements), + # and then to encoding +enc+. + # + # In either case, the returned string has forced encoding Encoding::US_ASCII. + # + # Related: Bundler::URI.encode_uri_component (encodes <tt>' '</tt> as <tt>'%20'</tt>). def self.encode_www_form_component(str, enc=nil) + _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc) + end + + # Returns a string decoded from the given \URL-encoded string +str+. + # + # The given string is first encoded as Encoding::ASCII-8BIT (using String#b), + # then decoded (as below), and finally force-encoded to the given encoding +enc+. + # + # The returned string: + # + # - Preserves: + # + # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>. + # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>, + # and <tt>'0'..'9'</tt>. + # + # Example: + # + # Bundler::URI.decode_www_form_component('*.-_azAZ09') + # # => "*.-_azAZ09" + # + # - Converts: + # + # - Character <tt>'+'</tt> to character <tt>' '</tt>. + # - Each "percent notation" to an ASCII character. + # + # Example: + # + # Bundler::URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A') + # # => "Here are some punctuation characters: ,;?:" + # + # Related: Bundler::URI.decode_uri_component (preserves <tt>'+'</tt>). + def self.decode_www_form_component(str, enc=Encoding::UTF_8) + _decode_uri_component(/\+|%\h\h/, str, enc) + end + + # Like Bundler::URI.encode_www_form_component, except that <tt>' '</tt> (space) + # is encoded as <tt>'%20'</tt> (instead of <tt>'+'</tt>). + def self.encode_uri_component(str, enc=nil) + _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc) + end + + # Like Bundler::URI.decode_www_form_component, except that <tt>'+'</tt> is preserved. + def self.decode_uri_component(str, enc=Encoding::UTF_8) + _decode_uri_component(/%\h\h/, str, enc) + end + + def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT if enc && enc != Encoding::ASCII_8BIT @@ -311,47 +389,115 @@ module Bundler::URI end str.force_encoding(Encoding::ASCII_8BIT) end - str.gsub!(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_) + str.gsub!(regexp, table) str.force_encoding(Encoding::US_ASCII) end + private_class_method :_encode_uri_component - # Decodes given +str+ of URL-encoded form data. - # - # This decodes + to SP. - # - # See Bundler::URI.encode_www_form_component, Bundler::URI.decode_www_form. - def self.decode_www_form_component(str, enc=Encoding::UTF_8) - raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/ =~ str - str.b.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc) + def self._decode_uri_component(regexp, str, enc) + raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) + str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) end + private_class_method :_decode_uri_component - # Generates URL-encoded form data from given +enum+. + # Returns a URL-encoded string derived from the given + # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes] + # +enum+. + # + # The result is suitable for use as form data + # for an \HTTP request whose <tt>Content-Type</tt> is + # <tt>'application/x-www-form-urlencoded'</tt>. + # + # The returned string consists of the elements of +enum+, + # each converted to one or more URL-encoded strings, + # and all joined with character <tt>'&'</tt>. + # + # Simple examples: + # + # Bundler::URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]]) + # # => "foo=0&bar=1&baz=2" + # Bundler::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) + # # => "foo=0&bar=1&baz=2" + # + # The returned string is formed using method Bundler::URI.encode_www_form_component, + # which converts certain characters: # - # This generates application/x-www-form-urlencoded data defined in HTML5 - # from given an Enumerable object. + # Bundler::URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@') + # # => "f%23o=%2F&b-r=%24&b+z=%40" # - # This internally uses Bundler::URI.encode_www_form_component(str). + # When +enum+ is Array-like, each element +ele+ is converted to a field: # - # This method doesn't convert the encoding of given items, so convert them - # before calling this method if you want to send data as other than original - # encoding or mixed encoding data. (Strings which are encoded in an HTML5 - # ASCII incompatible encoding are converted to UTF-8.) + # - If +ele+ is an array of two or more elements, + # the field is formed from its first two elements + # (and any additional elements are ignored): # - # This method doesn't handle files. When you send a file, use - # multipart/form-data. + # name = Bundler::URI.encode_www_form_component(ele[0], enc) + # value = Bundler::URI.encode_www_form_component(ele[1], enc) + # "#{name}=#{value}" # - # This refers https://url.spec.whatwg.org/#concept-urlencoded-serializer + # Examples: # - # Bundler::URI.encode_www_form([["q", "ruby"], ["lang", "en"]]) - # #=> "q=ruby&lang=en" - # Bundler::URI.encode_www_form("q" => "ruby", "lang" => "en") - # #=> "q=ruby&lang=en" - # Bundler::URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en") - # #=> "q=ruby&q=perl&lang=en" - # Bundler::URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]]) - # #=> "q=ruby&q=perl&lang=en" + # Bundler::URI.encode_www_form([%w[foo bar], %w[baz bat bah]]) + # # => "foo=bar&baz=bat" + # Bundler::URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']]) + # # => "foo=0&bar=baz" + # + # - If +ele+ is an array of one element, + # the field is formed from <tt>ele[0]</tt>: + # + # Bundler::URI.encode_www_form_component(ele[0]) + # + # Example: + # + # Bundler::URI.encode_www_form([['foo'], [:bar], [0]]) + # # => "foo&bar&0" + # + # - Otherwise the field is formed from +ele+: + # + # Bundler::URI.encode_www_form_component(ele) + # + # Example: + # + # Bundler::URI.encode_www_form(['foo', :bar, 0]) + # # => "foo&bar&0" + # + # The elements of an Array-like +enum+ may be mixture: + # + # Bundler::URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat]) + # # => "foo=0&bar=1&baz&bat" + # + # When +enum+ is Hash-like, + # each +key+/+value+ pair is converted to one or more fields: + # + # - If +value+ is + # {Array-convertible}[rdoc-ref:implicit_conversion.rdoc@Array-Convertible+Objects], + # each element +ele+ in +value+ is paired with +key+ to form a field: + # + # name = Bundler::URI.encode_www_form_component(key, enc) + # value = Bundler::URI.encode_www_form_component(ele, enc) + # "#{name}=#{value}" + # + # Example: + # + # Bundler::URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]}) + # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2" + # + # - Otherwise, +key+ and +value+ are paired to form a field: + # + # name = Bundler::URI.encode_www_form_component(key, enc) + # value = Bundler::URI.encode_www_form_component(value, enc) + # "#{name}=#{value}" + # + # Example: + # + # Bundler::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) + # # => "foo=0&bar=1&baz=2" + # + # The elements of a Hash-like +enum+ may be mixture: + # + # Bundler::URI.encode_www_form({foo: [0, 1], bar: 2}) + # # => "foo=0&foo=1&bar=2" # - # See Bundler::URI.encode_www_form_component, Bundler::URI.decode_www_form. def self.encode_www_form(enum, enc=nil) enum.map do |k,v| if v.nil? @@ -372,22 +518,39 @@ module Bundler::URI end.join('&') end - # Decodes URL-encoded form data from given +str+. + # Returns name/value pairs derived from the given string +str+, + # which must be an ASCII string. + # + # The method may be used to decode the body of Net::HTTPResponse object +res+ + # for which <tt>res['Content-Type']</tt> is <tt>'application/x-www-form-urlencoded'</tt>. # - # This decodes application/x-www-form-urlencoded data - # and returns an array of key-value arrays. + # The returned data is an array of 2-element subarrays; + # each subarray is a name/value pair (both are strings). + # Each returned string has encoding +enc+, + # and has had invalid characters removed via + # {String#scrub}[rdoc-ref:String#scrub]. # - # This refers http://url.spec.whatwg.org/#concept-urlencoded-parser, - # so this supports only &-separator, and doesn't support ;-separator. + # A simple example: # - # ary = Bundler::URI.decode_www_form("a=1&a=2&b=3") - # ary #=> [['a', '1'], ['a', '2'], ['b', '3']] - # ary.assoc('a').last #=> '1' - # ary.assoc('b').last #=> '3' - # ary.rassoc('a').last #=> '2' - # Hash[ary] #=> {"a"=>"2", "b"=>"3"} + # Bundler::URI.decode_www_form('foo=0&bar=1&baz') + # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] + # + # The returned strings have certain conversions, + # similar to those performed in Bundler::URI.decode_www_form_component: + # + # Bundler::URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40') + # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]] + # + # The given string may contain consecutive separators: + # + # Bundler::URI.decode_www_form('foo=0&&bar=1&&baz=2') + # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]] + # + # A different separator may be specified: + # + # Bundler::URI.decode_www_form('foo=0--bar=1--baz', separator: '--') + # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] # - # See Bundler::URI.decode_www_form_component, Bundler::URI.encode_www_form. def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false) raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only? ary = [] @@ -653,6 +816,7 @@ module Bundler::URI "utf-16"=>"utf-16le", "utf-16le"=>"utf-16le", } # :nodoc: + Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor) # :nodoc: # return encoding or nil @@ -665,7 +829,15 @@ end # module Bundler::URI module Bundler # - # Returns +uri+ converted to an Bundler::URI object. + # Returns a \Bundler::URI object derived from the given +uri+, + # which may be a \Bundler::URI string or an existing \Bundler::URI object: + # + # # Returns a new Bundler::URI. + # uri = Bundler::URI('http://github.com/ruby/ruby') + # # => #<Bundler::URI::HTTP http://github.com/ruby/ruby> + # # Returns the given Bundler::URI. + # Bundler::URI(uri) + # # => #<Bundler::URI::HTTP http://github.com/ruby/ruby> # def URI(uri) if uri.is_a?(Bundler::URI::Generic) diff --git a/lib/bundler/vendor/uri/lib/uri/file.rb b/lib/bundler/vendor/uri/lib/uri/file.rb index df42f8bcdd..8d75a9de7a 100644 --- a/lib/bundler/vendor/uri/lib/uri/file.rb +++ b/lib/bundler/vendor/uri/lib/uri/file.rb @@ -33,6 +33,9 @@ module Bundler::URI # If an Array is used, the components must be passed in the # order <code>[host, path]</code>. # + # A path from e.g. the File class should be escaped before + # being passed. + # # Examples: # # require 'bundler/vendor/uri/lib/uri' @@ -44,6 +47,9 @@ module Bundler::URI # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # + # uri3 = Bundler::URI::File.build({:path => Bundler::URI::escape('/path/my file.txt')}) + # uri3.to_s # => "file:///path/my%20file.txt" + # def self.build(args) tmp = Util::make_components_hash(self, args) super(tmp) @@ -90,5 +96,5 @@ module Bundler::URI end end - @@schemes['FILE'] = File + register_scheme 'FILE', File end diff --git a/lib/bundler/vendor/uri/lib/uri/ftp.rb b/lib/bundler/vendor/uri/lib/uri/ftp.rb index 2252e405d5..48b4c6718d 100644 --- a/lib/bundler/vendor/uri/lib/uri/ftp.rb +++ b/lib/bundler/vendor/uri/lib/uri/ftp.rb @@ -262,5 +262,6 @@ module Bundler::URI return str end end - @@schemes['FTP'] = FTP + + register_scheme 'FTP', FTP end diff --git a/lib/bundler/vendor/uri/lib/uri/generic.rb b/lib/bundler/vendor/uri/lib/uri/generic.rb index f29ba6cf18..762c425ac1 100644 --- a/lib/bundler/vendor/uri/lib/uri/generic.rb +++ b/lib/bundler/vendor/uri/lib/uri/generic.rb @@ -564,16 +564,26 @@ module Bundler::URI end end - # Returns the user component. + # Returns the user component (without Bundler::URI decoding). def user @user end - # Returns the password component. + # Returns the password component (without Bundler::URI decoding). def password @password end + # Returns the user component after Bundler::URI decoding. + def decoded_user + Bundler::URI.decode_uri_component(@user) if @user + end + + # Returns the password component after Bundler::URI decoding. + def decoded_password + Bundler::URI.decode_uri_component(@password) if @password + end + # # Checks the host +v+ component for RFC2396 compliance # and against the Bundler::URI::Parser Regexp for :HOST. @@ -643,7 +653,7 @@ module Bundler::URI # def hostname v = self.host - /\A\[(.*)\]\z/ =~ v ? $1 : v + v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v end # Sets the host part of the Bundler::URI as the argument with brackets for IPv6 addresses. @@ -659,7 +669,7 @@ module Bundler::URI # it is wrapped with brackets. # def hostname=(v) - v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v + v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':') self.host = v end @@ -1366,6 +1376,7 @@ module Bundler::URI end str end + alias to_str to_s # # Compares two URIs. @@ -1514,9 +1525,19 @@ module Bundler::URI proxy_uri = env["CGI_#{name.upcase}"] end elsif name == 'http_proxy' - unless proxy_uri = env[name] - if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost'] + p_port = ENV_JAVA['http.proxyPort'] + if p_user = ENV_JAVA['http.proxyUser'] + p_pass = ENV_JAVA['http.proxyPass'] + proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}" + else + proxy_uri = "http://#{p_addr}:#{p_port}" + end + else + unless proxy_uri = env[name] + if proxy_uri = env[name.upcase] + warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + end end end else diff --git a/lib/bundler/vendor/uri/lib/uri/http.rb b/lib/bundler/vendor/uri/lib/uri/http.rb index 50d7e427a5..2c44810644 100644 --- a/lib/bundler/vendor/uri/lib/uri/http.rb +++ b/lib/bundler/vendor/uri/lib/uri/http.rb @@ -80,8 +80,46 @@ module Bundler::URI url = @query ? "#@path?#@query" : @path.dup url.start_with?(?/.freeze) ? url : ?/ + url end - end - @@schemes['HTTP'] = HTTP + # + # == Description + # + # Returns the authority for an HTTP uri, as defined in + # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2. + # + # + # Example: + # + # Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com" + # + def authority + if port == default_port + host + else + "#{host}:#{port}" + end + end + + # + # == Description + # + # Returns the origin for an HTTP uri, as defined in + # https://datatracker.ietf.org/doc/html/rfc6454. + # + # + # Example: + # + # Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000" + # Bundler::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com" + # Bundler::URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com" + # + def origin + "#{scheme}://#{authority}" + end + end + register_scheme 'HTTP', HTTP end diff --git a/lib/bundler/vendor/uri/lib/uri/https.rb b/lib/bundler/vendor/uri/lib/uri/https.rb index 4fd4e9af7b..e4556e3ecb 100644 --- a/lib/bundler/vendor/uri/lib/uri/https.rb +++ b/lib/bundler/vendor/uri/lib/uri/https.rb @@ -18,5 +18,6 @@ module Bundler::URI # A Default port of 443 for Bundler::URI::HTTPS DEFAULT_PORT = 443 end - @@schemes['HTTPS'] = HTTPS + + register_scheme 'HTTPS', HTTPS end diff --git a/lib/bundler/vendor/uri/lib/uri/ldap.rb b/lib/bundler/vendor/uri/lib/uri/ldap.rb index 6e9e1918f6..9811b6e2f5 100644 --- a/lib/bundler/vendor/uri/lib/uri/ldap.rb +++ b/lib/bundler/vendor/uri/lib/uri/ldap.rb @@ -257,5 +257,5 @@ module Bundler::URI end end - @@schemes['LDAP'] = LDAP + register_scheme 'LDAP', LDAP end diff --git a/lib/bundler/vendor/uri/lib/uri/ldaps.rb b/lib/bundler/vendor/uri/lib/uri/ldaps.rb index 0af35bb16b..c786168450 100644 --- a/lib/bundler/vendor/uri/lib/uri/ldaps.rb +++ b/lib/bundler/vendor/uri/lib/uri/ldaps.rb @@ -17,5 +17,6 @@ module Bundler::URI # A Default port of 636 for Bundler::URI::LDAPS DEFAULT_PORT = 636 end - @@schemes['LDAPS'] = LDAPS + + register_scheme 'LDAPS', LDAPS end diff --git a/lib/bundler/vendor/uri/lib/uri/mailto.rb b/lib/bundler/vendor/uri/lib/uri/mailto.rb index ff7ab7e114..ff2e30be86 100644 --- a/lib/bundler/vendor/uri/lib/uri/mailto.rb +++ b/lib/bundler/vendor/uri/lib/uri/mailto.rb @@ -15,7 +15,7 @@ module Bundler::URI # RFC6068, the mailto URL scheme. # class MailTo < Generic - include REGEXP + include RFC2396_REGEXP # A Default port of nil for Bundler::URI::MailTo. DEFAULT_PORT = nil @@ -289,5 +289,5 @@ module Bundler::URI alias to_rfc822text to_mailtext end - @@schemes['MAILTO'] = MailTo + register_scheme 'MAILTO', MailTo end diff --git a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb index e48e164f4c..09c22c9906 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb @@ -116,7 +116,7 @@ module Bundler::URI # See also Bundler::URI::Parser.initialize_regexp. attr_reader :regexp - # Returns a split Bundler::URI against regexp[:ABS_URI]. + # Returns a split Bundler::URI against +regexp[:ABS_URI]+. def split(uri) case uri when '' @@ -257,8 +257,8 @@ module Bundler::URI end end - # Returns Regexp that is default self.regexp[:ABS_URI_REF], - # unless +schemes+ is provided. Then it is a Regexp.union with self.pattern[:X_ABS_URI]. + # Returns Regexp that is default +self.regexp[:ABS_URI_REF]+, + # unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+. def make_regexp(schemes = nil) unless schemes @regexp[:ABS_URI_REF] @@ -277,7 +277,7 @@ module Bundler::URI # +str+:: # String to make safe # +unsafe+:: - # Regexp to apply. Defaults to self.regexp[:UNSAFE] + # Regexp to apply. Defaults to +self.regexp[:UNSAFE]+ # # == Description # @@ -309,7 +309,7 @@ module Bundler::URI # +str+:: # String to remove escapes from # +escaped+:: - # Regexp to apply. Defaults to self.regexp[:ESCAPED] + # Regexp to apply. Defaults to +self.regexp[:ESCAPED]+ # # == Description # @@ -322,8 +322,14 @@ module Bundler::URI end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private @@ -491,8 +497,8 @@ module Bundler::URI ret = {} # for Bundler::URI::split - ret[:ABS_URI] = Regexp.new('\A\s*' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) - ret[:REL_URI] = Regexp.new('\A\s*' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) + ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) + ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) # for Bundler::URI::extract ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) diff --git a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb index 2029cfd056..4c9882f595 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb @@ -1,10 +1,73 @@ -# frozen_string_literal: false +# frozen_string_literal: true module Bundler::URI class RFC3986_Parser # :nodoc: # Bundler::URI defined in RFC3986 - # this regexp is modified not to host is not empty string - RFC3986_URI = /\A(?<Bundler::URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ - RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+)\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])+)(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/ + HOST = %r[ + (?<IP-literal>\[(?: + (?<IPv6address> + (?:\h{1,4}:){6} + (?<ls32>\h{1,4}:\h{1,4} + | (?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d) + \.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>) + ) + | ::(?:\h{1,4}:){5}\g<ls32> + | \h{1,4}?::(?:\h{1,4}:){4}\g<ls32> + | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32> + | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32> + | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32> + | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32> + | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4} + | (?:(?:\h{1,4}:){,6}\h{1,4})?:: + ) + | (?<IPvFuture>v\h++\.[!$&-.0-9:;=A-Z_a-z~]++) + )\]) + | \g<IPv4address> + | (?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+) + ]x + + USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/ + + SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source + SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source + SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source + FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source + + RFC3986_URI = %r[\A + (?<seg>#{SEG}){0} + (?<Bundler::URI> + (?<scheme>#{SCHEME}): + (?<hier-part>// + (?<authority> + (?:(?<userinfo>#{USERINFO.source})@)? + (?<host>#{HOST.source.delete(" \n")}) + (?::(?<port>\d*+))? + ) + (?<path-abempty>(?:/\g<seg>*+)?) + | (?<path-absolute>/((?!/)\g<seg>++)?) + | (?<path-rootless>(?!/)\g<seg>++) + | (?<path-empty>) + ) + (?:\?(?<query>[^\#]*+))? + (?:\#(?<fragment>#{FRAGMENT}))? + )\z]x + + RFC3986_relative_ref = %r[\A + (?<seg>#{SEG}){0} + (?<relative-ref> + (?<relative-part>// + (?<authority> + (?:(?<userinfo>#{USERINFO.source})@)? + (?<host>#{HOST.source.delete(" \n")}(?<!/))? + (?::(?<port>\d*+))? + ) + (?<path-abempty>(?:/\g<seg>*+)?) + | (?<path-absolute>/\g<seg>*+) + | (?<path-noscheme>#{SEG_NC}++(?:/\g<seg>*+)?) + | (?<path-empty>) + ) + (?:\?(?<query>[^#]*+))? + (?:\#(?<fragment>#{FRAGMENT}))? + )\z]x attr_reader :regexp def initialize @@ -20,9 +83,9 @@ module Bundler::URI uri.ascii_only? or raise InvalidURIError, "Bundler::URI must be ascii only #{uri.dump}" if m = RFC3986_URI.match(uri) - query = m["query".freeze] - scheme = m["scheme".freeze] - opaque = m["path-rootless".freeze] + query = m["query"] + scheme = m["scheme"] + opaque = m["path-rootless"] if opaque opaque << "?#{query}" if query [ scheme, @@ -33,35 +96,35 @@ module Bundler::URI nil, # path opaque, nil, # query - m["fragment".freeze] + m["fragment"] ] else # normal [ scheme, - m["userinfo".freeze], - m["host".freeze], - m["port".freeze], + m["userinfo"], + m["host"], + m["port"], nil, # registry - (m["path-abempty".freeze] || - m["path-absolute".freeze] || - m["path-empty".freeze]), + (m["path-abempty"] || + m["path-absolute"] || + m["path-empty"]), nil, # opaque query, - m["fragment".freeze] + m["fragment"] ] end elsif m = RFC3986_relative_ref.match(uri) [ nil, # scheme - m["userinfo".freeze], - m["host".freeze], - m["port".freeze], + m["userinfo"], + m["host"], + m["port"], nil, # registry, - (m["path-abempty".freeze] || - m["path-absolute".freeze] || - m["path-noscheme".freeze] || - m["path-empty".freeze]), + (m["path-abempty"] || + m["path-absolute"] || + m["path-noscheme"] || + m["path-empty"]), nil, # opaque - m["query".freeze], - m["fragment".freeze] + m["query"], + m["fragment"] ] else raise InvalidURIError, "bad Bundler::URI(is not Bundler::URI?): #{uri.inspect}" @@ -79,23 +142,29 @@ module Bundler::URI end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private def default_regexp # :nodoc: { - SCHEME: /\A[A-Za-z][A-Za-z0-9+\-.]*\z/, - USERINFO: /\A(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*\z/, - HOST: /\A(?:(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{,4}::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*))\z/, - ABS_PATH: /\A\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/, - REL_PATH: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/, - QUERY: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, - FRAGMENT: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/, - OPAQUE: /\A(?:[^\/].*)?\z/, - PORT: /\A[\x09\x0a\x0c\x0d ]*\d*[\x09\x0a\x0c\x0d ]*\z/, + SCHEME: %r[\A#{SCHEME}\z]o, + USERINFO: %r[\A#{USERINFO}\z]o, + HOST: %r[\A#{HOST}\z]o, + ABS_PATH: %r[\A/#{SEG}*+\z]o, + REL_PATH: %r[\A(?!/)#{SEG}++\z]o, + QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z], + FRAGMENT: %r[\A#{FRAGMENT}\z]o, + OPAQUE: %r[\A(?:[^/].*)?\z], + PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, } end diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb index f2bb0ebad2..1fa1c7c09a 100644 --- a/lib/bundler/vendor/uri/lib/uri/version.rb +++ b/lib/bundler/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Bundler::URI # :stopdoc: - VERSION_CODE = '001001'.freeze + VERSION_CODE = '001300'.freeze VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze # :startdoc: end diff --git a/lib/bundler/vendor/uri/lib/uri/ws.rb b/lib/bundler/vendor/uri/lib/uri/ws.rb index 58e08bf98e..10ae6f5834 100644 --- a/lib/bundler/vendor/uri/lib/uri/ws.rb +++ b/lib/bundler/vendor/uri/lib/uri/ws.rb @@ -79,6 +79,5 @@ module Bundler::URI end end - @@schemes['WS'] = WS - + register_scheme 'WS', WS end diff --git a/lib/bundler/vendor/uri/lib/uri/wss.rb b/lib/bundler/vendor/uri/lib/uri/wss.rb index 3827053c7b..e8db1ceabf 100644 --- a/lib/bundler/vendor/uri/lib/uri/wss.rb +++ b/lib/bundler/vendor/uri/lib/uri/wss.rb @@ -18,5 +18,6 @@ module Bundler::URI # A Default port of 443 for Bundler::URI::WSS DEFAULT_PORT = 443 end - @@schemes['WSS'] = WSS + + register_scheme 'WSS', WSS end |