diff options
Diffstat (limited to 'lib')
355 files changed, 7832 insertions, 8947 deletions
diff --git a/lib/English.gemspec b/lib/English.gemspec index 5f4eb420c2..9c09555ca1 100644 --- a/lib/English.gemspec +++ b/lib/English.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "english" - spec.version = "0.8.0" + spec.version = "0.8.1" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] @@ -15,8 +15,13 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + excludes = %W[ + :^/test :^/spec :^/feature :^/bin + :^/Rakefile :^/Gemfile\* :^/.git* + :^/#{File.basename(__FILE__)} + ] + spec.files = IO.popen(%W[git ls-files -z --] + excludes, err: IO::NULL) do |f| + f.readlines("\x0", chomp: true) end spec.require_paths = ["lib"] end diff --git a/lib/English.rb b/lib/English.rb index 03fe721991..bf7896dcd6 100644 --- a/lib/English.rb +++ b/lib/English.rb @@ -9,7 +9,7 @@ # "waterbuffalo" =~ /buff/ # print $', $$, "\n" # -# With English: +# With 'English': # # require "English" # @@ -20,30 +20,30 @@ # Below is a full list of descriptive aliases and their associated global # variable: # -# $ERROR_INFO:: $! -# $ERROR_POSITION:: $@ -# $FS:: $; -# $FIELD_SEPARATOR:: $; -# $OFS:: $, -# $OUTPUT_FIELD_SEPARATOR:: $, -# $RS:: $/ -# $INPUT_RECORD_SEPARATOR:: $/ -# $ORS:: $\ -# $OUTPUT_RECORD_SEPARATOR:: $\ -# $INPUT_LINE_NUMBER:: $. -# $NR:: $. -# $LAST_READ_LINE:: $_ -# $DEFAULT_OUTPUT:: $> -# $DEFAULT_INPUT:: $< -# $PID:: $$ -# $PROCESS_ID:: $$ -# $CHILD_STATUS:: $? -# $LAST_MATCH_INFO:: $~ -# $ARGV:: $* -# $MATCH:: $& -# $PREMATCH:: $` -# $POSTMATCH:: $' -# $LAST_PAREN_MATCH:: $+ +# <tt>$ERROR_INFO</tt>:: <tt>$!</tt> +# <tt>$ERROR_POSITION</tt>:: <tt>$@</tt> +# <tt>$FS</tt>:: <tt>$;</tt> +# <tt>$FIELD_SEPARATOR</tt>:: <tt>$;</tt> +# <tt>$OFS</tt>:: <tt>$,</tt> +# <tt>$OUTPUT_FIELD_SEPARATOR</tt>:: <tt>$,</tt> +# <tt>$RS</tt>:: <tt>$/</tt> +# <tt>$INPUT_RECORD_SEPARATOR</tt>:: <tt>$/</tt> +# <tt>$ORS</tt>:: <tt>$\</tt> +# <tt>$OUTPUT_RECORD_SEPARATOR</tt>:: <tt>$\</tt> +# <tt>$NR</tt>:: <tt>$.</tt> +# <tt>$INPUT_LINE_NUMBER</tt>:: <tt>$.</tt> +# <tt>$LAST_READ_LINE</tt>:: <tt>$_</tt> +# <tt>$DEFAULT_OUTPUT</tt>:: <tt>$></tt> +# <tt>$DEFAULT_INPUT</tt>:: <tt>$<</tt> +# <tt>$PID</tt>:: <tt>$$</tt> +# <tt>$PROCESS_ID</tt>:: <tt>$$</tt> +# <tt>$CHILD_STATUS</tt>:: <tt>$?</tt> +# <tt>$LAST_MATCH_INFO</tt>:: <tt>$~</tt> +# <tt>$ARGV</tt>:: <tt>$*</tt> +# <tt>$MATCH</tt>:: <tt>$&</tt> +# <tt>$PREMATCH</tt>:: <tt>$`</tt> +# <tt>$POSTMATCH</tt>:: <tt>$'</tt> +# <tt>$LAST_PAREN_MATCH</tt>:: <tt>$+</tt> # module English end if false diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index fab79f42a9..85f23b2596 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -1,5 +1,10 @@ # -*- frozen-string-literal: true -*- +# :stopdoc: +module Gem +end +# :startdoc: + module Gem::BUNDLED_GEMS # :nodoc: SINCE = { "racc" => "3.3.0", @@ -15,16 +20,17 @@ module Gem::BUNDLED_GEMS # :nodoc: "resolv-replace" => "3.4.0", "rinda" => "3.4.0", "syslog" => "3.4.0", - "ostruct" => "3.5.0", - "pstore" => "3.5.0", - "rdoc" => "3.5.0", - "win32ole" => "3.5.0", - "fiddle" => "3.5.0", - "logger" => "3.5.0", - "benchmark" => "3.5.0", - "irb" => "3.5.0", - "reline" => "3.5.0", - # "readline" => "3.5.0", # This is wrapper for reline. We don't warn for this. + "ostruct" => "4.0.0", + "pstore" => "4.0.0", + "rdoc" => "4.0.0", + "win32ole" => "4.0.0", + "fiddle" => "4.0.0", + "logger" => "4.0.0", + "benchmark" => "4.0.0", + "irb" => "4.0.0", + "reline" => "4.0.0", + # "readline" => "4.0.0", # This is wrapper for reline. We don't warn for this. + "tsort" => "4.1.0", }.freeze EXACT = { @@ -49,12 +55,7 @@ module Gem::BUNDLED_GEMS # :nodoc: kernel_class.send(:alias_method, :no_warning_require, :require) kernel_class.send(:define_method, :require) do |name| if message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names) - uplevel = ::Gem::BUNDLED_GEMS.uplevel - if uplevel > 0 - Kernel.warn message, uplevel: uplevel - else - Kernel.warn message - end + Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel end kernel_class.send(:no_warning_require, name) end @@ -86,11 +87,10 @@ module Gem::BUNDLED_GEMS # :nodoc: uplevel += 1 # Don't show script name when bundle exec and call ruby script directly. if cl.path.end_with?("bundle") - frame_count = 0 - break + return end end - require_found ? 1 : frame_count - 1 + require_found ? 1 : (frame_count - 1).nonzero? end def self.warning?(name, specs: nil) @@ -105,11 +105,14 @@ module Gem::BUNDLED_GEMS # :nodoc: # and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`. feature.delete_prefix!(ARCHDIR) feature.delete_prefix!(LIBDIR) - segments = feature.split("/") + # 1. A segment for the EXACT mapping and SINCE check + # 2. A segment for the SINCE check for dashed names + # 3. A segment to check if there's a subfeature + segments = feature.split("/", 3) name = segments.shift name = EXACT[name] || name if !SINCE[name] - name = [name, segments.shift].join("-") + name = "#{name}-#{segments.shift}" return unless SINCE[name] end segments.any? @@ -119,29 +122,43 @@ module Gem::BUNDLED_GEMS # :nodoc: false end + if suppress_list = Thread.current[:__bundled_gems_warning_suppression] + return if suppress_list.include?(name) || suppress_list.include?(feature) + end + return if specs.include?(name) + # Don't warn if a hyphenated gem provides this feature + # (e.g., benchmark-ips provides benchmark/ips, not the benchmark gem) + if subfeature + feature_parts = feature.split("/") + if feature_parts.size >= 2 + hyphenated_gem = "#{feature_parts[0]}-#{feature_parts[1]}" + return if specs.include?(hyphenated_gem) + end + end + return if WARNED[name] WARNED[name] = true - level = RUBY_VERSION < SINCE[name] ? "warning" : "error" + level = RUBY_VERSION < SINCE[name] ? :warning : :error if subfeature "#{feature} is found in #{name}, which" else - "#{feature} #{level == "warning" ? "was loaded" : "used to be loaded"} from the standard library, but" + "#{feature} #{level == :warning ? "was loaded" : "used to be loaded"} from the standard library, but" end + build_message(name, level) end def self.build_message(name, level) - msg = if level == "warning" + msg = if level == :warning " will no longer be part of the default gems starting from Ruby #{SINCE[name]}" else " is not part of the default gems since Ruby #{SINCE[name]}." end if defined?(Bundler) - motivation = level == "warning" ? "silence this warning" : "fix this error" + motivation = level == :warning ? "silence this warning" : "fix this error" msg += "\nYou can add #{name} to your Gemfile or gemspec to #{motivation}." # We detect the gem name from caller_locations. First we walk until we find `require` @@ -194,19 +211,28 @@ module Gem::BUNDLED_GEMS # :nodoc: require "bundler" Bundler.reset! + # Build and activate a temporary definition containing the original gems + the requested gem builder = Bundler::Dsl.new - if Bundler::SharedHelpers.in_bundle? - if Bundler.locked_gems - Bundler.locked_gems.specs.each{|spec| builder.gem spec.name, spec.version.to_s } - elsif Bundler.definition.gemfiles.size > 0 - Bundler.definition.gemfiles.each{|gemfile| builder.eval_gemfile(gemfile) } + lockfile = nil + if Bundler::SharedHelpers.in_bundle? && Bundler.definition.gemfiles.size > 0 + Bundler.definition.gemfiles.each {|gemfile| builder.eval_gemfile(gemfile) } + lockfile = begin + Bundler.default_lockfile + rescue Bundler::GemfileNotFound + nil end + else + # Fake BUNDLE_GEMFILE and BUNDLE_LOCKFILE to let checks pass + orig_gemfile = ENV["BUNDLE_GEMFILE"] + orig_lockfile = ENV["BUNDLE_LOCKFILE"] + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock" end builder.gem gem - definition = builder.to_definition(nil, true) + definition = builder.to_definition(lockfile, nil) definition.validate_runtime! begin @@ -222,6 +248,8 @@ module Gem::BUNDLED_GEMS # :nodoc: rescue Bundler::GemNotFound warn "Failed to activate #{gem}, please install it with 'gem install #{gem}'" ensure + ENV['BUNDLE_GEMFILE'] = orig_gemfile if orig_gemfile + ENV['BUNDLE_LOCKFILE'] = orig_lockfile if orig_lockfile Bundler.ui = orig_ui Bundler::Definition.no_lock = orig_no_lock end @@ -236,7 +264,7 @@ class LoadError name = path.tr("/", "-") if !defined?(Bundler) && Gem::BUNDLED_GEMS::SINCE[name] && !Gem::BUNDLED_GEMS::WARNED[name] - warn name + Gem::BUNDLED_GEMS.build_message(name, "error"), uplevel: Gem::BUNDLED_GEMS.uplevel + warn name + Gem::BUNDLED_GEMS.build_message(name, :error), uplevel: Gem::BUNDLED_GEMS.uplevel end super end diff --git a/lib/bundler.rb b/lib/bundler.rb index eea3b0cf17..51ea3beeb0 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true +require_relative "bundler/rubygems_ext" require_relative "bundler/vendored_fileutils" -require "pathname" +autoload :Pathname, "pathname" unless defined?(Pathname) require "rbconfig" require_relative "bundler/errors" require_relative "bundler/environment_preserver" require_relative "bundler/plugin" -require_relative "bundler/rubygems_ext" require_relative "bundler/rubygems_integration" require_relative "bundler/version" require_relative "bundler/current_ruby" @@ -53,9 +53,7 @@ module Bundler autoload :FeatureFlag, File.expand_path("bundler/feature_flag", __dir__) autoload :FREEBSD, File.expand_path("bundler/constants", __dir__) autoload :GemHelper, File.expand_path("bundler/gem_helper", __dir__) - autoload :GemHelpers, File.expand_path("bundler/gem_helpers", __dir__) autoload :GemVersionPromoter, File.expand_path("bundler/gem_version_promoter", __dir__) - autoload :Graph, File.expand_path("bundler/graph", __dir__) autoload :Index, File.expand_path("bundler/index", __dir__) autoload :Injector, File.expand_path("bundler/injector", __dir__) autoload :Installer, File.expand_path("bundler/installer", __dir__) @@ -114,13 +112,13 @@ module Bundler end def configured_bundle_path - @configured_bundle_path ||= settings.path.tap(&:validate!) + @configured_bundle_path ||= Bundler.settings.path.tap(&:validate!) end # Returns absolute location of where binstubs are installed to. def bin_path @bin_path ||= begin - path = settings[:bin] || "bin" + path = Bundler.settings[:bin] || "bin" path = Pathname.new(path).expand_path(root).expand_path mkdir_p(path) path @@ -174,14 +172,14 @@ module Bundler self_manager.restart_with_locked_bundler_if_needed end - # Automatically install dependencies if Bundler.settings[:auto_install] exists. + # Automatically install dependencies if settings[:auto_install] exists. # This is set through config cmd `bundle config set --global auto_install 1`. # # Note that this method `nil`s out the global Definition object, so it # should be called first, before you instantiate anything like an # `Installer` that'll keep a reference to the old one instead. def auto_install - return unless settings[:auto_install] + return unless Bundler.settings[:auto_install] begin definition.specs @@ -220,8 +218,7 @@ module Bundler end def environment - SharedHelpers.major_deprecation 2, "Bundler.environment has been removed in favor of Bundler.load", print_caller_location: true - load + SharedHelpers.feature_removed! "Bundler.environment has been removed in favor of Bundler.load" end # Returns an instance of Bundler::Definition for given Gemfile and lockfile @@ -239,10 +236,10 @@ module Bundler end def frozen_bundle? - frozen = settings[:frozen] + frozen = Bundler.settings[:frozen] return frozen unless frozen.nil? - settings[:deployment] + Bundler.settings[:deployment] end def locked_gems @@ -343,7 +340,7 @@ module Bundler def app_cache(custom_path = nil) path = custom_path || root - Pathname.new(path).join(settings.app_cache_path) + Pathname.new(path).join(Bundler.settings.app_cache_path) end def tmp(name = Process.pid.to_s) @@ -366,16 +363,11 @@ module Bundler ORIGINAL_ENV.clone end - # @deprecated Use `unbundled_env` instead def clean_env - message = - "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \ - "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`" removed_message = "`Bundler.clean_env` has been removed in favor of `Bundler.unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - unbundled_env + Bundler::SharedHelpers.feature_removed!(removed_message) end # @return [Hash] Environment with all bundler-related variables removed @@ -393,16 +385,11 @@ module Bundler with_env(original_env) { yield } end - # @deprecated Use `with_unbundled_env` instead def with_clean_env - message = - "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \ - "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`" removed_message = "`Bundler.with_clean_env` has been removed in favor of `Bundler.with_unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - with_env(unbundled_env) { yield } + Bundler::SharedHelpers.feature_removed!(removed_message) end # Run block with all bundler-related variables removed @@ -415,16 +402,11 @@ module Bundler with_original_env { Kernel.system(*args) } end - # @deprecated Use `unbundled_system` instead def clean_system(*args) - message = - "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \ - "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`" removed_message = "`Bundler.clean_system` has been removed in favor of `Bundler.unbundled_system`. " \ "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - with_env(unbundled_env) { Kernel.system(*args) } + Bundler::SharedHelpers.feature_removed!(removed_message) end # Run subcommand in an environment with all bundler related variables removed @@ -437,16 +419,11 @@ module Bundler with_original_env { Kernel.exec(*args) } end - # @deprecated Use `unbundled_exec` instead def clean_exec(*args) - message = - "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \ - "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`" removed_message = "`Bundler.clean_exec` has been removed in favor of `Bundler.unbundled_exec`. " \ "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - with_env(unbundled_env) { Kernel.exec(*args) } + Bundler::SharedHelpers.feature_removed!(removed_message) end # Run a `Kernel.exec` to a subcommand in an environment with all bundler related variables removed @@ -455,10 +432,14 @@ module Bundler end def local_platform - return Gem::Platform::RUBY if settings[:force_ruby_platform] + return Gem::Platform::RUBY if Bundler.settings[:force_ruby_platform] Gem::Platform.local end + def generic_local_platform + Gem::Platform.generic(local_platform) + end + def default_gemfile SharedHelpers.default_gemfile end @@ -564,7 +545,7 @@ module Bundler end def feature_flag - @feature_flag ||= FeatureFlag.new(VERSION) + @feature_flag ||= FeatureFlag.new(Bundler.settings[:simulate_version] || VERSION) end def reset! @@ -580,7 +561,6 @@ module Bundler def reset_paths! @bin_path = nil - @bundler_major_version = nil @bundle_path = nil @configure = nil @configured_bundle_path = nil diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb index 5d2a8b53bb..49d2518078 100644 --- a/lib/bundler/build_metadata.rb +++ b/lib/bundler/build_metadata.rb @@ -4,21 +4,26 @@ module Bundler # Represents metadata from when the Bundler gem was built. module BuildMetadata # begin ivars - @release = false + @built_at = nil # end ivars # A hash representation of the build metadata. def self.to_h { - "Built At" => built_at, + "Timestamp" => timestamp, "Git SHA" => git_commit_sha, - "Released Version" => release?, } end + # A timestamp representing the date the bundler gem was built, or the + # current time if never built + def self.timestamp + @timestamp ||= @built_at || Time.now.utc.strftime("%Y-%m-%d").freeze + end + # A string representing the date the bundler gem was built. def self.built_at - @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze + @built_at end # The SHA for the git commit the bundler gem was built from. @@ -34,10 +39,5 @@ module Bundler @git_commit_sha ||= "unknown" end - - # Whether this is an official release build of Bundler. - def self.release? - @release - end end end diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 88411f295d..49319e81b4 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -23,16 +23,16 @@ Gem::Specification.new do |s| s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" s.metadata = { - "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", - "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md", + "bug_tracker_uri" => "https://github.com/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", + "changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md", "homepage_uri" => "https://bundler.io/", - "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler", + "source_code_uri" => "https://github.com/ruby/rubygems/tree/master/bundler", } - s.required_ruby_version = ">= 3.1.0" + s.required_ruby_version = ">= 3.2.0" # It should match the RubyGems version shipped with `required_ruby_version` above - s.required_rubygems_version = ">= 3.3.3" + s.required_rubygems_version = ">= 3.4.1" s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } diff --git a/lib/bundler/capistrano.rb b/lib/bundler/capistrano.rb index 705840143f..6d2437d895 100644 --- a/lib/bundler/capistrano.rb +++ b/lib/bundler/capistrano.rb @@ -1,22 +1,4 @@ # frozen_string_literal: true require_relative "shared_helpers" -Bundler::SharedHelpers.major_deprecation 2, - "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler" - -# Capistrano task for Bundler. -# -# Add "require 'bundler/capistrano'" in your Capistrano deploy.rb, and -# Bundler will be activated after each new deployment. -require_relative "deployment" -require "capistrano/version" - -if defined?(Capistrano::Version) && Gem::Version.new(Capistrano::Version).release >= Gem::Version.new("3.0") - raise "For Capistrano 3.x integration, please use https://github.com/capistrano/bundler" -end - -Capistrano::Configuration.instance(:must_exist).load do - before "deploy:finalize_update", "bundle:install" - Bundler::Deployment.define_task(self, :task, except: { no_release: true }) - set :rake, lambda { "#{fetch(:bundle_cmd, "bundle")} exec rake" } -end +Bundler::SharedHelpers.feature_removed! "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler" diff --git a/lib/bundler/checksum.rb b/lib/bundler/checksum.rb index 356f4a48bc..ce05818bb0 100644 --- a/lib/bundler/checksum.rb +++ b/lib/bundler/checksum.rb @@ -205,6 +205,12 @@ module Bundler @store[spec.lock_name].nil? end + def empty?(spec) + return false unless spec.source.is_a?(Bundler::Source::Rubygems) + + @store[spec.lock_name].empty? + end + def register(spec, checksum) register_checksum(spec.lock_name, checksum) end diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 51f71af501..1f6a65ca57 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -11,7 +11,7 @@ module Bundler AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze - EXTENSIONS = ["c", "rust"].freeze + EXTENSIONS = ["c", "rust", "go"].freeze COMMAND_ALIASES = { "check" => "c", @@ -24,7 +24,7 @@ module Bundler }.freeze def self.start(*) - check_deprecated_ext_option(ARGV) if ARGV.include?("--ext") + check_invalid_ext_option(ARGV) if ARGV.include?("--ext") super ensure @@ -59,17 +59,29 @@ module Bundler def initialize(*args) super + current_cmd = args.last[:current_command].name + custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] if custom_gemfile && !custom_gemfile.empty? Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile) - Bundler.reset_settings_and_root! + reset_settings = true + end + + # lock --lockfile works differently than install --lockfile + unless current_cmd == "lock" + custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile] + if custom_lockfile && !custom_lockfile.empty? + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile) + reset_settings = true + end end + Bundler.reset_settings_and_root! if reset_settings + Bundler.auto_switch Bundler.settings.set_command_option_if_given :retry, options[:retry] - current_cmd = args.last[:current_command].name Bundler.auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) rescue UnknownArgumentError => e raise InvalidOption, e.message @@ -77,7 +89,7 @@ module Bundler self.options ||= {} unprinted_warnings = Bundler.ui.unprinted_warnings Bundler.ui = UI::Shell.new(options) - Bundler.ui.level = "debug" if options["verbose"] + Bundler.ui.level = "debug" if options[:verbose] || Bundler.settings[:verbose] unprinted_warnings.each {|w| Bundler.ui.warn(w) } end @@ -92,7 +104,7 @@ module Bundler primary_commands = ["install", "update", "cache", "exec", "config", "help"] list = self.class.printable_commands(true) - by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] } + by_name = list.group_by {|name, _message| name.match(/^bundler? (\w+)/)[1] } utilities = by_name.keys.sort - primary_commands primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first } utilities.map! {|name| by_name[name].first } @@ -107,7 +119,33 @@ module Bundler shell.say self.class.send(:class_options_help, shell) end - default_task(Bundler.feature_flag.default_cli_command) + + desc "install_or_cli_help", "Deprecated alias of install", hide: true + def install_or_cli_help + Bundler.ui.warn <<~MSG + `bundle install_or_cli_help` is a deprecated alias of `bundle install`. + It might be called due to the 'default_cli_command' being set to 'install_or_cli_help', + if so fix that by running `bundle config set default_cli_command install --global`. + MSG + invoke_other_command("install") + end + + def self.default_command(meth = nil) + return super if meth + + unless Bundler.settings[:default_cli_command] + Bundler.ui.info <<~MSG + In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. + Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. + You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, + or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. + This message will be removed after a default_cli_command value is set. + + MSG + end + + Bundler.settings[:default_cli_command] || "install" + end class_option "no-color", type: :boolean, desc: "Disable colorization in output" class_option "retry", type: :numeric, aliases: "-r", banner: "NUM", @@ -130,7 +168,7 @@ module Bundler if man_pages.include?(command) man_page = man_pages[command] - if Bundler.which("man") && !man_path.match?(%r{^file:/.+!/META-INF/jruby.home/.+}) + if Bundler.which("man") && !man_path.match?(%r{^(?:file:/.+!|uri:classloader:)/META-INF/jruby.home/.+}) Kernel.exec("man", man_page) else puts File.read("#{man_path}/#{File.basename(man_page)}.ronn") @@ -143,7 +181,7 @@ module Bundler end def self.handle_no_command_error(command, has_namespace = $thor_runner) - if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command) + if Bundler.settings[:plugins] && Bundler::Plugin.command?(command) return Bundler::Plugin.exec_command(command, ARGV[1..-1]) end @@ -173,7 +211,7 @@ module Bundler D method_option "dry-run", type: :boolean, default: false, banner: "Lock the Gemfile" method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" - method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)" def check remembered_flag_deprecation("path") @@ -187,12 +225,11 @@ module Bundler long_desc <<-D Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning. D - method_option "install", type: :boolean, banner: "Runs 'bundle install' after removing the gems from the Gemfile" + method_option "install", type: :boolean, banner: "Runs 'bundle install' after removing the gems from the Gemfile (removed)" def remove(*gems) if ARGV.include?("--install") - message = "The `--install` flag has been deprecated. `bundle install` is triggered by default." removed_message = "The `--install` flag has been removed. `bundle install` is triggered by default." - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + raise InvalidOption, removed_message end require_relative "cli/remove" @@ -210,42 +247,52 @@ module Bundler If the bundle has already been installed, bundler will tell you so and then exit. D - method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin" - method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install" - method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments" - method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install" + method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin (removed)" + method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install (removed)" + method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments (removed)" + method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install (removed)" method_option "full-index", type: :boolean, banner: "Fall back to using the single-file index of all gems" method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" method_option "jobs", aliases: "-j", type: :numeric, banner: "Specify the number of jobs to run in parallel" method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "lockfile", type: :string, banner: "Use the specified lockfile instead of the default." method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely" method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache." - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." - method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache." - method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "no-lock", type: :boolean, banner: "Don't create a lockfile." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" + method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)." + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." method_option "quiet", type: :boolean, banner: "Only output warnings and errors." - method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default (usually 'ruby')" + method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default, usually 'ruby' (removed)" method_option "standalone", type: :array, lazy_default: [], banner: "Make a bundle that can work without the Bundler runtime" - method_option "system", type: :boolean, banner: "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application" + method_option "system", type: :boolean, banner: "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application (removed)" method_option "trust-policy", alias: "P", type: :string, banner: "Gem trust policy (like gem install -P). Must be one of #{Bundler.rubygems.security_policy_keys.join("|")}" method_option "target-rbconfig", type: :string, banner: "Path to rbconfig.rb for the deployment target platform" - method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group." - method_option "with", type: :array, banner: "Include gems that are part of the specified named group." + method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group (removed)." + method_option "with", type: :array, banner: "Include gems that are part of the specified named group (removed)." def install - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") - %w[clean deployment frozen no-prune path shebang without with].each do |option| remembered_flag_deprecation(option) end print_remembered_flag_deprecation("--system", "path.system", "true") if ARGV.include?("--system") - remembered_negative_flag_deprecation("no-deployment") + remembered_flag_deprecation("deployment", negative: true) + + if ARGV.include?("--binstubs") + removed_message = "The --binstubs option has been removed in favor of `bundle binstubs --all`" + raise InvalidOption, removed_message + end require_relative "cli/install" + options = self.options.dup + options["lockfile"] ||= ENV["BUNDLE_LOCKFILE"] Bundler.settings.temporary(no_install: false) do - Install.new(options.dup).run + Install.new(options).run end + rescue GemfileNotFound => error + invoke_other_command("cli_help") + raise error # re-raise to show the error and get a failing exit status end map aliases_for("install") @@ -263,7 +310,7 @@ module Bundler method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "quiet", type: :boolean, banner: "Only output warnings and errors." method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)" - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock" method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version" @@ -274,7 +321,6 @@ module Bundler method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated." method_option "all", type: :boolean, banner: "Update everything." def update(*gems) - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") require_relative "cli/update" Bundler.settings.temporary(no_install: false) do Update.new(options, gems).run @@ -287,12 +333,11 @@ module Bundler Calling show with [GEM] will list the exact location of that gem on your machine. D method_option "paths", type: :boolean, banner: "List the paths of all gems that are required by your Gemfile." - method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated." + method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated (removed)." def show(gem_name = nil) if ARGV.include?("--outdated") - message = "the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement" - removed_message = "the `--outdated` flag to `bundle show` was undocumented and has been removed without replacement" - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + removed_message = "the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`" + raise InvalidOption, removed_message end require_relative "cli/show" Show.new(options, gem_name).run @@ -302,6 +347,7 @@ module Bundler method_option "name-only", type: :boolean, banner: "print only the gem names" method_option "only-group", type: :array, default: [], banner: "print gems from a given set of groups" method_option "without-group", type: :array, default: [], banner: "print all gems except from a given set of groups" + method_option "format", type: :string, banner: "format output ('json' is the only supported format)" method_option "paths", type: :boolean, banner: "print the path to each gem in the bundle" def list require_relative "cli/list" @@ -325,12 +371,14 @@ module Bundler will create binstubs for all given gems. D method_option "force", type: :boolean, default: false, banner: "Overwrite existing binstubs if they exist" - method_option "path", type: :string, lazy_default: "bin", banner: "Binstub destination directory (default bin)" + method_option "path", type: :string, lazy_default: "bin", banner: "Binstub destination directory, `bin` by default (removed)" method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default (usually 'ruby')" method_option "standalone", type: :boolean, banner: "Make binstubs that can work without the Bundler runtime" method_option "all", type: :boolean, banner: "Install binstubs for all gems" method_option "all-platforms", type: :boolean, default: false, banner: "Install binstubs for all platforms" def binstubs(*gems) + remembered_flag_deprecation("path", option_name: "bin") + require_relative "cli/binstubs" Binstubs.new(options, gems).run end @@ -396,15 +444,15 @@ module Bundler end desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache" - method_option "all", type: :boolean, default: Bundler.feature_flag.cache_all?, banner: "Include all sources (including path and git)." + method_option "all", type: :boolean, default: Bundler.settings[:cache_all], banner: "Include all sources (including path and git) (removed)." method_option "all-platforms", type: :boolean, banner: "Include gems for all platforms present in the lockfile, not only the current one" method_option "cache-path", type: :string, banner: "Specify a different cache path than the default (vendor/cache)." method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" method_option "no-install", type: :boolean, banner: "Don't install the gems, only update the cache." - method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache." - method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)." + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." method_option "quiet", type: :boolean, banner: "Only output warnings and errors." - method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install" + method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install (removed)" long_desc <<-D The cache command will copy the .gem files for every gem in the bundle into the directory ./vendor/cache. If you then check that directory into your source @@ -413,18 +461,19 @@ module Bundler D def cache print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all") + print_remembered_flag_deprecation("--no-all", "cache_all", "false") if ARGV.include?("--no-all") - if ARGV.include?("--path") - message = - "The `--path` flag is deprecated because its semantics are unclear. " \ - "Use `bundle config cache_path` to configure the path of your cache of gems, " \ - "and `bundle config path` to configure the path where your gems are installed, " \ - "and stop using this flag" + %w[frozen no-prune].each do |option| + remembered_flag_deprecation(option) + end + + if flag_passed?("--path") removed_message = "The `--path` flag has been removed because its semantics were unclear. " \ "Use `bundle config cache_path` to configure the path of your cache of gems, " \ - "and `bundle config path` to configure the path where your gems are installed." - SharedHelpers.major_deprecation 2, message, removed_message: removed_message + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + raise InvalidOption, removed_message end require_relative "cli/cache" @@ -434,7 +483,7 @@ module Bundler map aliases_for("cache") desc "exec [OPTIONS]", "Run the command in context of the bundle" - method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is deprecated" + method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is not permitted (removed)." method_option :gemfile, type: :string, required: false, banner: "Use the specified gemfile instead of Gemfile" long_desc <<-D Exec runs a command, providing it access to the gems in the bundle. While using @@ -443,9 +492,8 @@ module Bundler D def exec(*args) if ARGV.include?("--no-keep-file-descriptors") - message = "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" removed_message = "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + raise InvalidOption, removed_message end require_relative "cli/exec" @@ -486,13 +534,13 @@ module Bundler def version cli_help = current_command.name == "cli_help" if cli_help || ARGV.include?("version") - build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" + build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})" end - if !cli_help && Bundler.feature_flag.print_only_version_number? - Bundler.ui.info "#{Bundler::VERSION}#{build_info}" + if !cli_help + Bundler.ui.info "#{Bundler.verbose_version}#{build_info}" else - Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" + Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}" end end @@ -512,41 +560,32 @@ module Bundler end end - unless Bundler.feature_flag.bundler_3_mode? - desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true - long_desc <<-D - Viz generates a PNG file of the current Gemfile as a dependency graph. - Viz requires the ruby-graphviz gem (and its dependencies). - The associated gems must also be installed via 'bundle install'. - D - method_option :file, type: :string, default: "gem_graph", aliases: "-f", desc: "The name to use for the generated file. see format option" - method_option :format, type: :string, default: "png", aliases: "-F", desc: "This is output format option. Supported format is png, jpg, svg, dot ..." - method_option :requirements, type: :boolean, default: false, aliases: "-R", desc: "Set to show the version of each required dependency." - method_option :version, type: :boolean, default: false, aliases: "-v", desc: "Set to show each gem version." - method_option :without, type: :array, default: [], aliases: "-W", banner: "GROUP[ GROUP...]", desc: "Exclude gems that are part of the specified named group." - def viz - SharedHelpers.major_deprecation 2, "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" - require_relative "cli/viz" - Viz.new(options.dup).run - end + desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true + def viz + SharedHelpers.feature_removed! "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" end desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem" - method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], desc: "Generate a binary executable for your library." - method_option :coc, type: :boolean, desc: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`." - method_option :edit, type: :string, aliases: "-e", required: false, banner: "EDITOR", lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, desc: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" - method_option :ext, type: :string, desc: "Generate the boilerplate for C extension code.", enum: EXTENSIONS - method_option :git, type: :boolean, default: true, desc: "Initialize a git repo inside your library." - method_option :mit, type: :boolean, desc: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." - method_option :rubocop, type: :boolean, desc: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`." - method_option :changelog, type: :boolean, desc: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`." + method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], banner: "Generate a binary executable for your library." + method_option :coc, type: :boolean, banner: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`." + method_option :edit, type: :string, aliases: "-e", required: false, lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, banner: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" + method_option :ext, type: :string, banner: "Generate the boilerplate for C extension code.", enum: EXTENSIONS + method_option :git, type: :boolean, default: true, banner: "Initialize a git repo inside your library." + method_option :mit, type: :boolean, banner: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." + method_option :rubocop, type: :boolean, banner: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true` (removed)." + method_option :changelog, type: :boolean, banner: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`." method_option :test, type: :string, lazy_default: Bundler.settings["gem.test"] || "", aliases: "-t", banner: "Use the specified test framework for your library", enum: %w[rspec minitest test-unit], desc: "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." - method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], desc: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`" - method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], desc: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`" + method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], banner: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`" + method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], banner: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`" method_option :github_username, type: :string, default: Bundler.settings["gem.github_username"], banner: "Set your username on GitHub", desc: "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`." + method_option :bundle, type: :boolean, default: Bundler.settings["gem.bundle"], banner: "Automatically run `bundle install` after creation. Set a default with `bundle config set --global gem.bundle true`" def gem(name) require_relative "cli/gem" + + raise InvalidOption, "--rubocop has been removed, use --linter=rubocop" if ARGV.include?("--rubocop") + raise InvalidOption, "--no-rubocop has been removed, use --no-linter" if ARGV.include?("--no-rubocop") + cmd_args = args + [self] cmd_args.unshift(options) @@ -557,7 +596,7 @@ module Bundler File.expand_path("templates", __dir__) end - desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", hide: true + desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory" method_option "dry-run", type: :boolean, default: false, banner: "Only print out changes, do not clean gems" method_option "force", type: :boolean, default: false, banner: "Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application." def clean @@ -573,12 +612,8 @@ module Bundler end desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", hide: true - method_option "source", type: :string, banner: "Install gem from the given source" - method_option "group", type: :string, banner: "Install gem into a bundler group" - def inject(name, version) - SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command" - require_relative "cli/inject" - Inject.new(options.dup, name, version).run + def inject(*) + SharedHelpers.feature_removed! "The `inject` command has been replaced by the `add` command" end desc "lock", "Creates a lockfile without installing" @@ -632,7 +667,7 @@ module Bundler end end - if Bundler.feature_flag.plugins? + if Bundler.settings[:plugins] require_relative "cli/plugin" desc "plugin", "Manage the bundler plugins" subcommand "plugin", Plugin @@ -666,18 +701,15 @@ module Bundler end end - def self.check_deprecated_ext_option(arguments) - # when deprecated version of `--ext` is called - # print out deprecation warning and pretend `--ext=c` was provided - if deprecated_ext_value?(arguments) - message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been deprecated. Please select a language, e.g. `--ext=rust` to generate a Rust extension. This gem will now be generated as if `--ext=c` was used." + def self.check_invalid_ext_option(arguments) + # when invalid version of `--ext` is called + if invalid_ext_value?(arguments) removed_message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension." - SharedHelpers.major_deprecation 2, message, removed_message: removed_message - arguments[arguments.index("--ext")] = "--ext=c" + raise InvalidOption, removed_message end end - def self.deprecated_ext_value?(arguments) + def self.invalid_ext_value?(arguments) index = arguments.index("--ext") next_argument = arguments[index + 1] @@ -685,15 +717,15 @@ module Bundler # for example `bundle gem hello --ext c` return false if EXTENSIONS.include?(next_argument) - # deprecated call when --ext is called with no value in last position + # invalid call when --ext is called with no value in last position # for example `bundle gem hello_gem --ext` return true if next_argument.nil? - # deprecated call when --ext is followed by other parameter + # invalid call when --ext is followed by other parameter # for example `bundle gem --ext --no-ci hello_gem` return true if next_argument.start_with?("-") - # deprecated call when --ext is followed by gem name + # invalid call when --ext is followed by gem name # for example `bundle gem --ext hello_gem` return true if next_argument @@ -707,6 +739,19 @@ module Bundler config[:current_command] end + def invoke_other_command(name) + _, _, config = @_initializer + original_command = config[:current_command] + command = self.class.all_commands[name] + config[:current_command] = command + send(name) + ensure + config[:current_command] = original_command + end + + def current_command=(command) + end + def print_command return unless Bundler.ui.debug? cmd = current_command @@ -720,7 +765,7 @@ module Bundler end command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip command.reject!(&:empty?) - Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" + Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler.verbose_version}" end def warn_on_outdated_bundler @@ -747,44 +792,30 @@ module Bundler nil end - def remembered_negative_flag_deprecation(name) - positive_name = name.gsub(/\Ano-/, "") - option = current_command.options[positive_name] - flag_name = "--no-" + option.switch_name.gsub(/\A--/, "") - - flag_deprecation(positive_name, flag_name, option) - end - - def remembered_flag_deprecation(name) + def remembered_flag_deprecation(name, negative: false, option_name: nil) option = current_command.options[name] flag_name = option.switch_name - - flag_deprecation(name, flag_name, option) - end - - def flag_deprecation(name, flag_name, option) - name_index = ARGV.find {|arg| flag_name == arg.split("=")[0] } - return unless name_index + flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative + return unless flag_passed?(flag_name) value = options[name] value = value.join(" ").to_s if option.type == :array value = "'#{value}'" unless option.type == :boolean - print_remembered_flag_deprecation(flag_name, name.tr("-", "_"), value) + print_remembered_flag_deprecation(flag_name, option_name || name.tr("-", "_"), value) end def print_remembered_flag_deprecation(flag_name, option_name, option_value) - message = - "The `#{flag_name}` flag is deprecated because it relies on being " \ - "remembered across bundler invocations, which bundler will no longer " \ - "do in future versions. Instead please use `bundle config set #{option_name} " \ - "#{option_value}`, and stop using this flag" removed_message = "The `#{flag_name}` flag has been removed because it relied on being " \ - "remembered across bundler invocations, which bundler will no longer " \ - "do. Instead please use `bundle config set #{option_name} " \ - "#{option_value}`, and stop using this flag" - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message + "remembered across bundler invocations, which bundler no longer does. " \ + "Instead please use `bundle config set #{option_name} #{option_value}`, " \ + "and stop using this flag" + raise InvalidOption, removed_message + end + + def flag_passed?(name) + ARGV.any? {|arg| name == arg.split("=")[0] } end end end diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb index 12a681a816..9f17604096 100644 --- a/lib/bundler/cli/add.rb +++ b/lib/bundler/cli/add.rb @@ -36,6 +36,16 @@ module Bundler end def validate_options! + raise InvalidOption, "You cannot specify `--git` and `--github` at the same time." if options["git"] && options["github"] + + unless options["git"] || options["github"] + raise InvalidOption, "You cannot specify `--branch` unless `--git` or `--github` is specified." if options["branch"] + + raise InvalidOption, "You cannot specify `--ref` unless `--git` or `--github` is specified." if options["ref"] + end + + raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"] + raise InvalidOption, "You cannot specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic] # raise error when no gems are specified diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb index 2e63a16ec3..59605df847 100644 --- a/lib/bundler/cli/cache.rb +++ b/lib/bundler/cli/cache.rb @@ -10,17 +10,12 @@ module Bundler def run Bundler.ui.level = "warn" if options[:quiet] - Bundler.settings.set_command_option_if_given :path, options[:path] Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"] - setup_cache_all install - # TODO: move cache contents here now that all bundles are locked - custom_path = Bundler.settings[:path] if options[:path] - Bundler.settings.temporary(cache_all_platforms: options["all-platforms"]) do - Bundler.load.cache(custom_path) + Bundler.load.cache end end @@ -33,11 +28,5 @@ module Bundler options["no-cache"] = true Bundler::CLI::Install.new(options).run end - - def setup_cache_all - all = options.fetch(:all, Bundler.feature_flag.cache_all? || nil) - - Bundler.settings.set_command_option_if_given :cache_all, all - end end end diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 7ef6deb2cf..2f332ff364 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -94,11 +94,14 @@ module Bundler end def self.gem_not_found_message(missing_gem_name, alternatives) - require_relative "../similarity_detector" message = "Could not find gem '#{missing_gem_name}'." alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } - suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name) - message += "\nDid you mean #{suggestions}?" if suggestions + if alternate_names.include?(missing_gem_name.downcase) + message += "\nDid you mean '#{missing_gem_name.downcase}'?" + elsif defined?(DidYouMean::SpellChecker) + suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name) + message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? + end message end @@ -130,9 +133,23 @@ module Bundler def self.clean_after_install? clean = Bundler.settings[:clean] return clean unless clean.nil? - clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil? + clean ||= Bundler.feature_flag.bundler_5_mode? && Bundler.settings[:path].nil? clean &&= !Bundler.use_system_gems? clean end + + def self.word_list(words) + if words.empty? + return "" + end + + words = words.map {|word| "'#{word}'" } + + if words.length == 1 + return words[0] + end + + [words[0..-2].join(", "), words[-1]].join(" or ") + end end end diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb index 77b502fe60..6a77e4a65e 100644 --- a/lib/bundler/cli/config.rb +++ b/lib/bundler/cli/config.rb @@ -26,8 +26,7 @@ module Bundler end message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead." - removed_message = "Using the `config` command without a subcommand [list, get, set, unset] is has been removed. Use `bundle #{new_args.join(" ")}` instead." - SharedHelpers.major_deprecation 3, message, removed_message: removed_message + SharedHelpers.feature_deprecated! message Base.new(options, name, value, self).run end diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb index f6389e8ea0..2d1a2ce458 100644 --- a/lib/bundler/cli/console.rb +++ b/lib/bundler/cli/console.rb @@ -21,6 +21,11 @@ module Bundler get_constant(name) rescue LoadError if name == "irb" + if defined?(Gem::BUNDLED_GEMS) && Gem::BUNDLED_GEMS.respond_to?(:force_activate) + Gem::BUNDLED_GEMS.force_activate "irb" + require name + return get_constant(name) + end Bundler.ui.error "#{name} is not available" exit 1 else diff --git a/lib/bundler/cli/doctor/diagnose.rb b/lib/bundler/cli/doctor/diagnose.rb index c5da23acb8..a878025dda 100644 --- a/lib/bundler/cli/doctor/diagnose.rb +++ b/lib/bundler/cli/doctor/diagnose.rb @@ -24,7 +24,7 @@ module Bundler def dylibs_darwin(path) output = `/usr/bin/otool -L #{path.shellescape}`.chomp - dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq + dylibs = output.split("\n")[1..-1].filter_map {|l| l.match(DARWIN_REGEX)&.match(1) }.uniq # ignore @rpath and friends dylibs.reject {|dylib| dylib.start_with? "@" } end diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb index 9428e9db3b..2fdc416286 100644 --- a/lib/bundler/cli/exec.rb +++ b/lib/bundler/cli/exec.rb @@ -19,11 +19,13 @@ module Bundler validate_cmd! SharedHelpers.set_bundle_environment if bin_path = Bundler.which(cmd) - if !Bundler.settings[:disable_exec_load] && ruby_shebang?(bin_path) - return kernel_load(bin_path, *args) + if !Bundler.settings[:disable_exec_load] && directly_loadable?(bin_path) + bin_path.delete_suffix!(".bat") if Gem.win_platform? + kernel_load(bin_path, *args) + else + bin_path = "./" + bin_path unless File.absolute_path?(bin_path) + kernel_exec(bin_path, *args) end - bin_path = "./" + bin_path unless File.absolute_path?(bin_path) - kernel_exec(bin_path, *args) else # exec using the given command kernel_exec(cmd, *args) @@ -69,6 +71,29 @@ module Bundler "#{file} #{args.join(" ")}".strip end + def directly_loadable?(file) + if Gem.win_platform? + script_wrapper?(file) + else + ruby_shebang?(file) + end + end + + def script_wrapper?(file) + script_file = file.delete_suffix(".bat") + return false unless File.exist?(script_file) + + if File.zero?(script_file) + Bundler.ui.warn "#{script_file} is empty" + return false + end + + header = File.open(file, "r") {|f| f.read(32) } + ruby_exe = "#{RbConfig::CONFIG["RUBY_INSTALL_NAME"]}#{RbConfig::CONFIG["EXEEXT"]}" + ruby_exe = "ruby.exe" if ruby_exe.empty? + header.include?(ruby_exe) + end + def ruby_shebang?(file) possibilities = [ "#!/usr/bin/env ruby\n", diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 22bcf0e47a..236ce530ec 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "pathname" - module Bundler class CLI Bundler.require_thor_actions @@ -15,6 +13,8 @@ module Bundler "test-unit" => "3.0", }.freeze + DEFAULT_GITHUB_USERNAME = "[USERNAME]" + attr_reader :options, :gem_name, :thor, :name, :target, :extension def initialize(options, gem_name, thor) @@ -26,12 +26,11 @@ module Bundler thor.destination_root = nil @name = @gem_name - @target = SharedHelpers.pwd.join(gem_name) + @target = Pathname.new(SharedHelpers.pwd).join(gem_name) @extension = options[:ext] validate_ext_name if @extension - validate_rust_builder_rubygems_version if @extension == "rust" end def run @@ -48,13 +47,16 @@ module Bundler git_author_name = use_git ? `git config user.name`.chomp : "" git_username = use_git ? `git config github.user`.chomp : "" git_user_email = use_git ? `git config user.email`.chomp : "" + github_username = github_username(git_username) - github_username = if options[:github_username].nil? - git_username - elsif options[:github_username] == false - "" + if github_username.empty? + homepage_uri = "TODO: Put your gem's website or public repo URL here." + source_code_uri = "TODO: Put your gem's public repo URL here." + changelog_uri = "TODO: Put your gem's CHANGELOG.md URL here." else - options[:github_username] + homepage_uri = "https://github.com/#{github_username}/#{name}" + source_code_uri = "https://github.com/#{github_username}/#{name}" + changelog_uri = "https://github.com/#{github_username}/#{name}/blob/main/CHANGELOG.md" end config = { @@ -69,12 +71,17 @@ module Bundler test: options[:test], ext: extension, exe: options[:exe], + bundle: options[:bundle], bundler_version: bundler_dependency_version, git: use_git, - github_username: github_username.empty? ? "[USERNAME]" : github_username, + github_username: github_username.empty? ? DEFAULT_GITHUB_USERNAME : github_username, required_ruby_version: required_ruby_version, rust_builder_required_rubygems_version: rust_builder_required_rubygems_version, minitest_constant_name: minitest_constant_name, + ignore_paths: %w[bin/], + homepage_uri: homepage_uri, + source_code_uri: source_code_uri, + changelog_uri: changelog_uri, } ensure_safe_gem_name(name, constant_array) @@ -95,7 +102,18 @@ module Bundler bin/setup ] - templates.merge!("gitignore.tt" => ".gitignore") if use_git + case Bundler.preferred_gemfile_name + when "Gemfile" + config[:ignore_paths] << "Gemfile" + when "gems.rb" + config[:ignore_paths] << "gems.rb" + config[:ignore_paths] << "gems.locked" + end + + if use_git + templates.merge!("gitignore.tt" => ".gitignore") + config[:ignore_paths] << ".gitignore" + end if test_framework = ask_and_set_test_framework config[:test] = test_framework @@ -109,6 +127,8 @@ module Bundler "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb" ) config[:test_task] = :spec + config[:ignore_paths] << ".rspec" + config[:ignore_paths] << "spec/" when "minitest" # Generate path for minitest target file (FileList["test/**/test_*.rb"]) # foo => test/test_foo.rb @@ -123,12 +143,14 @@ module Bundler "test/minitest/test_newgem.rb.tt" => "test/#{minitest_namespaced_path}.rb" ) config[:test_task] = :test + config[:ignore_paths] << "test/" when "test-unit" templates.merge!( "test/test-unit/test_helper.rb.tt" => "test/test_helper.rb", "test/test-unit/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb" ) config[:test_task] = :test + config[:ignore_paths] << "test/" end end @@ -136,19 +158,19 @@ module Bundler case config[:ci] when "github" templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml") - config[:ci_config_path] = ".github " + config[:ignore_paths] << ".github/" when "gitlab" templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml") - config[:ci_config_path] = ".gitlab-ci.yml " + config[:ignore_paths] << ".gitlab-ci.yml" when "circle" templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml") - config[:ci_config_path] = ".circleci " + config[:ignore_paths] << ".circleci/" end if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?", - "This means that any other developer or company will be legally allowed to use your code " \ - "for free as long as they admit you created it. You can read more about the MIT license " \ - "at https://choosealicense.com/licenses/mit.") + "Using a MIT license means that any other developer or company will be legally allowed " \ + "to use your code for free as long as they admit you created it. You can read more about " \ + "the MIT license at https://choosealicense.com/licenses/mit.") config[:mit] = true Bundler.ui.info "MIT License enabled in config" templates.merge!("LICENSE.txt.tt" => "LICENSE.txt") @@ -156,12 +178,8 @@ module Bundler if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?", "Codes of conduct can increase contributions to your project by contributors who " \ - "prefer collaborative, safe spaces. You can read more about the code of conduct at " \ - "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \ - "of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \ - "address is specified as a contact in the generated code of conduct so that people know " \ - "who to contact in case of a violation. For suggestions about " \ - "how to enforce codes of conduct, see https://bit.ly/coc-enforcement.") + "prefer safe, respectful, productive, and collaborative spaces. \n" \ + "See https://github.com/ruby/rubygems/blob/master/CODE_OF_CONDUCT.md") config[:coc] = true Bundler.ui.info "Code of conduct enabled in config" templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md") @@ -185,10 +203,12 @@ module Bundler config[:linter_version] = rubocop_version Bundler.ui.info "RuboCop enabled in config" templates.merge!("rubocop.yml.tt" => ".rubocop.yml") + config[:ignore_paths] << ".rubocop.yml" when "standard" config[:linter_version] = standard_version Bundler.ui.info "Standard enabled in config" templates.merge!("standard.yml.tt" => ".standard.yml") + config[:ignore_paths] << ".standard.yml" end if config[:exe] @@ -213,13 +233,25 @@ module Bundler ) end + if extension == "go" + templates.merge!( + "ext/newgem/go.mod.tt" => "ext/#{name}/go.mod", + "ext/newgem/extconf-go.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", + "ext/newgem/newgem.go.tt" => "ext/#{name}/#{underscored_name}.go", + "ext/newgem/newgem-go.c.tt" => "ext/#{name}/#{underscored_name}.c", + ) + + config[:go_module_username] = config[:github_username] == DEFAULT_GITHUB_USERNAME ? "username" : config[:github_username] + end + if target.exist? && !target.directory? Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`." exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError] end if use_git - Bundler.ui.info "Initializing git repo in #{target}" + Bundler.ui.info "\nInitializing git repo in #{target}" require "shellwords" `git init #{target.to_s.shellescape}` @@ -241,26 +273,33 @@ module Bundler IO.popen(%w[git add .], { chdir: target }, &:read) end + if config[:bundle] + Bundler.ui.info "Running bundle install in the new gem directory." + Dir.chdir(target) do + system("bundle install") + end + end + # Open gemspec in editor open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit] - Bundler.ui.info "Gem '#{name}' was successfully created. " \ + Bundler.ui.info "\nGem '#{name}' was successfully created. " \ "For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html" end private def resolve_name(name) - SharedHelpers.pwd.join(name).basename.to_s + Pathname.new(SharedHelpers.pwd).join(name).basename.to_s end - def ask_and_set(key, header, message) + def ask_and_set(key, prompt, explanation) choice = options[key] choice = Bundler.settings["gem.#{key}"] if choice.nil? if choice.nil? - Bundler.ui.confirm header - choice = Bundler.ui.yes? "#{message} y/(n):" + Bundler.ui.info "\n#{explanation}" + choice = Bundler.ui.yes? "#{prompt} y/(n):" Bundler.settings.set_global("gem.#{key}", choice) end @@ -282,7 +321,7 @@ module Bundler test_framework = options[:test] || Bundler.settings["gem.test"] if test_framework.to_s.empty? - Bundler.ui.confirm "Do you want to generate tests with your gem?" + Bundler.ui.info "\nDo you want to generate tests with your gem?" Bundler.ui.info hint_text("test") result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):" @@ -322,12 +361,11 @@ module Bundler ci_template = options[:ci] || Bundler.settings["gem.ci"] if ci_template.to_s.empty? - Bundler.ui.confirm "Do you want to set up continuous integration for your gem? " \ + Bundler.ui.info "\nDo you want to set up continuous integration for your gem? " \ "Supported services:\n" \ "* CircleCI: https://circleci.com/\n" \ "* GitHub Actions: https://github.com/features/actions\n" \ - "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" \ - "\n" + "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" Bundler.ui.info hint_text("ci") result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):" @@ -352,14 +390,12 @@ module Bundler def ask_and_set_linter return if skip?(:linter) linter_template = options[:linter] || Bundler.settings["gem.linter"] - linter_template = deprecated_rubocop_option if linter_template.nil? if linter_template.to_s.empty? - Bundler.ui.confirm "Do you want to add a code linter and formatter to your gem? " \ + Bundler.ui.info "\nDo you want to add a code linter and formatter to your gem? " \ "Supported Linters:\n" \ "* RuboCop: https://rubocop.org\n" \ - "* Standard: https://github.com/standardrb/standard\n" \ - "\n" + "* Standard: https://github.com/standardrb/standard\n" Bundler.ui.info hint_text("linter") result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):" @@ -386,27 +422,6 @@ module Bundler linter_template end - def deprecated_rubocop_option - if !options[:rubocop].nil? - if options[:rubocop] - Bundler::SharedHelpers.major_deprecation 2, - "--rubocop is deprecated, use --linter=rubocop", - removed_message: "--rubocop has been removed, use --linter=rubocop" - "rubocop" - else - Bundler::SharedHelpers.major_deprecation 2, - "--no-rubocop is deprecated, use --linter", - removed_message: "--no-rubocop has been removed, use --linter" - false - end - elsif !Bundler.settings["gem.rubocop"].nil? - Bundler::SharedHelpers.major_deprecation 2, - "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead", - removed_message: "config gem.rubocop has been removed; we've updated your config to use gem.linter instead" - Bundler.settings["gem.rubocop"] ? "rubocop" : false - end - end - def bundler_dependency_version v = Gem::Version.new(Bundler::VERSION) req = v.segments[0..1] @@ -446,7 +461,7 @@ module Bundler end def required_ruby_version - "3.1.0" + "3.2.0" end def rubocop_version @@ -457,10 +472,13 @@ module Bundler "1.3" end - def validate_rust_builder_rubygems_version - if Gem::Version.new(rust_builder_required_rubygems_version) > Gem.rubygems_version - Bundler.ui.error "Your RubyGems version (#{Gem.rubygems_version}) is too old to build Rust extension. Please update your RubyGems using `gem update --system` or any other way and try again." - exit 1 + def github_username(git_username) + if options[:github_username].nil? + git_username + elsif options[:github_username] == false + "" + else + options[:github_username] end end end diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb deleted file mode 100644 index a09d5c6bda..0000000000 --- a/lib/bundler/cli/inject.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class CLI::Inject - attr_reader :options, :name, :version, :group, :source, :gems - def initialize(options, name, version) - @options = options - @name = name - @version = version || last_version_number - @group = options[:group].split(",") unless options[:group].nil? - @source = options[:source] - @gems = [] - end - - def run - # The required arguments allow Thor to give useful feedback when the arguments - # are incorrect. This adds those first two arguments onto the list as a whole. - gems.unshift(source).unshift(group).unshift(version).unshift(name) - - # Build an array of Dependency objects out of the arguments - deps = [] - # when `inject` support addition of more than one gem, then this loop will - # help. Currently this loop is running once. - gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source| - ops = Gem::Requirement::OPS.map {|key, _val| key } - has_op = ops.any? {|op| gem_version.start_with? op } - gem_version = "~> #{gem_version}" unless has_op - deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source) - end - - added = Injector.inject(deps, options) - - if added.any? - Bundler.ui.confirm "Added to Gemfile:" - Bundler.ui.confirm(added.map do |d| - name = "'#{d.name}'" - requirement = ", '#{d.requirement}'" - group = ", group: #{d.groups.inspect}" if d.groups != Array(:default) - source = ", source: '#{d.source}'" unless d.source.nil? - %(gem #{name}#{requirement}#{group}#{source}) - end.join("\n")) - else - Bundler.ui.confirm "All gems were already present in the Gemfile" - end - end - - private - - def last_version_number - definition = Bundler.definition(true) - definition.remotely! - specs = definition.index[name].sort_by(&:version) - unless options[:pre] - specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } - end - spec = specs.last - spec.version.to_s - end - end -end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index b0b354cf10..67feba84bd 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -20,54 +20,32 @@ module Bundler Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Gem.freebsd_platform? - # Disable color in deployment mode - Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment] - if target_rbconfig_path = options[:"target-rbconfig"] Bundler.rubygems.set_target_rbconfig(target_rbconfig_path) end - check_for_options_conflicts - check_trust_policy - if options[:deployment] || options[:frozen] || Bundler.frozen_bundle? - unless Bundler.default_lockfile.exist? - flag = "--deployment flag" if options[:deployment] - flag ||= "--frozen flag" if options[:frozen] - flag ||= "deployment setting" if Bundler.settings[:deployment] - flag ||= "frozen setting" if Bundler.settings[:frozen] - raise ProductionError, "The #{flag} requires a lockfile. Please make " \ - "sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \ - "before deploying." - end - - options[:local] = true if Bundler.app_cache.exist? - - Bundler.settings.set_command_option :deployment, true if options[:deployment] - Bundler.settings.set_command_option :frozen, true if options[:frozen] - end - - # When install is called with --no-deployment, disable deployment mode - if options[:deployment] == false - Bundler.settings.set_command_option :frozen, nil - options[:system] = true + if Bundler.frozen_bundle? && !Bundler.default_lockfile.exist? + flag = "deployment setting" if Bundler.settings[:deployment] + flag = "frozen setting" if Bundler.settings[:frozen] + raise ProductionError, "The #{flag} requires a lockfile. Please make " \ + "sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \ + "before deploying." end normalize_settings Bundler::Fetcher.disable_endpoint = options["full-index"] - if options["binstubs"] - Bundler::SharedHelpers.major_deprecation 2, - "The --binstubs option will be removed in favor of `bundle binstubs --all`", - removed_message: "The --binstubs option have been removed in favor of `bundle binstubs --all`" - end - - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] - definition = Bundler.definition + # For install we want to enable strict validation + # (rather than some optimizations we perform at app runtime). + definition = Bundler.definition(strict: true) definition.validate_runtime! + definition.lockfile = options["lockfile"] if options["lockfile"] + definition.lockfile = false if options["no-lock"] installer = Installer.install(Bundler.root, definition, options) @@ -87,8 +65,6 @@ module Bundler Bundler::CLI::Common.output_post_install_messages installer.post_install_messages - warn_ambiguous_gems - if CLI::Common.clean_after_install? require_relative "clean" Bundler::CLI::Clean.new(options).run @@ -114,26 +90,10 @@ module Bundler end def gems_installed_for(definition) - count = definition.specs.count + count = definition.specs.count {|spec| spec.name != "bundler" } "#{count} #{count == 1 ? "gem" : "gems"} now installed" end - def check_for_group_conflicts_in_cli_options - conflicting_groups = Array(options[:without]) & Array(options[:with]) - return if conflicting_groups.empty? - raise InvalidOption, "You can't list a group in both with and without." \ - " The offending groups are: #{conflicting_groups.join(", ")}." - end - - def check_for_options_conflicts - if (options[:path] || options[:deployment]) && options[:system] - error_message = String.new - error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path] - error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment] - raise InvalidOption.new(error_message) - end - end - def check_trust_policy trust_policy = options["trust-policy"] unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy) @@ -143,30 +103,11 @@ module Bundler Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy end - def normalize_groups - check_for_group_conflicts_in_cli_options - - # need to nil them out first to get around validation for backwards compatibility - Bundler.settings.set_command_option :without, nil - Bundler.settings.set_command_option :with, nil - Bundler.settings.set_command_option :without, options[:without] - Bundler.settings.set_command_option :with, options[:with] - end - def normalize_settings - Bundler.settings.set_command_option :path, nil if options[:system] - Bundler.settings.set_command_option_if_given :path, options[:path] - if options["standalone"] && Bundler.settings[:path].nil? && !options["local"] - Bundler.settings.temporary(path_relative_to_cwd: false) do - Bundler.settings.set_command_option :path, "bundle" - end + Bundler.settings.set_command_option :path, "bundle" end - bin_option = options["binstubs"] - bin_option = nil if bin_option&.empty? - Bundler.settings.set_command_option :bin, bin_option if options["binstubs"] - Bundler.settings.set_command_option_if_given :shebang, options["shebang"] Bundler.settings.set_command_option_if_given :jobs, options["jobs"] @@ -177,23 +118,7 @@ module Bundler Bundler.settings.set_command_option_if_given :clean, options["clean"] - normalize_groups if options[:without] || options[:with] - - options[:force] = options[:redownload] - end - - def warn_ambiguous_gems - # TODO: remove this when we drop Bundler 1.x support - Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris| - Bundler.ui.warn "Warning: the gem '#{name}' was found in multiple sources." - Bundler.ui.warn "Installed from: #{installed_from_uri}" - Bundler.ui.warn "Also found in:" - also_found_in_uris.each {|uri| Bundler.ui.warn " * #{uri}" } - Bundler.ui.warn "You should add a source requirement to restrict this gem to your preferred source." - Bundler.ui.warn "For example:" - Bundler.ui.warn " gem '#{name}', :source => '#{installed_from_uri}'" - Bundler.ui.warn "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again." - end + options[:force] = options[:redownload] if options[:redownload] end end end diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb index fbe9184d12..cbfb7da2d8 100644 --- a/lib/bundler/cli/issue.rb +++ b/lib/bundler/cli/issue.rb @@ -10,7 +10,7 @@ module Bundler be sure to check out these resources: 1. Check out our troubleshooting guide for quick fixes to common issues: - https://github.com/rubygems/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md + https://github.com/ruby/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md 2. Instructions for common Bundler uses can be found on the documentation site: https://bundler.io/ @@ -22,7 +22,7 @@ module Bundler still aren't working the way you expect them to, please let us know so that we can diagnose and help fix the problem you're having, by filling in the new issue form located at - https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, + https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, and copy and pasting the information below. EOS diff --git a/lib/bundler/cli/list.rb b/lib/bundler/cli/list.rb index f56bf5b86a..6a467f45a9 100644 --- a/lib/bundler/cli/list.rb +++ b/lib/bundler/cli/list.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true +require "json" + module Bundler class CLI::List def initialize(options) @options = options @without_group = options["without-group"].map(&:to_sym) @only_group = options["only-group"].map(&:to_sym) + @format = options["format"] end def run @@ -25,6 +28,36 @@ module Bundler end end.reject {|s| s.name == "bundler" }.sort_by(&:name) + case @format + when "json" + print_json(specs: specs) + when nil + print_human(specs: specs) + else + raise InvalidOption, "Unknown option`--format=#{@format}`. Supported formats: `json`" + end + end + + private + + def print_json(specs:) + gems = if @options["name-only"] + specs.map {|s| { name: s.name } } + else + specs.map do |s| + { + name: s.name, + version: s.version.to_s, + git_version: s.git_version&.strip, + }.tap do |h| + h[:path] = s.full_gem_path if @options["paths"] + end + end + end + Bundler.ui.info({ gems: gems }.to_json) + end + + def print_human(specs:) return Bundler.ui.info "No gems in the Gemfile" if specs.empty? return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"] @@ -37,8 +70,6 @@ module Bundler Bundler.ui.info "Use `bundle info` to print more detailed information about a gem" end - private - def verify_group_exists(groups) (@without_group + @only_group).each do |group| raise InvalidOption, "`#{group}` group could not be found." unless groups.include?(group) diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index b60c82e3a1..2f78868936 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -35,11 +35,8 @@ module Bundler update = { bundler: bundler } end - file = options[:lockfile] - file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile - Bundler.settings.temporary(frozen: false) do - definition = Bundler.definition(update, file) + definition = Bundler.definition(update, Bundler.default_lockfile) definition.add_checksums if options["add-checksums"] Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update] @@ -71,8 +68,11 @@ module Bundler if print puts definition.to_lock else + file = options[:lockfile] + file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile + puts "Writing lockfile to #{file}" - definition.lock + definition.write_lock(file, false) end end diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index 1be44ff4b4..0c8ba3ebf7 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -155,7 +155,7 @@ module Bundler return active_spec if strict - active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version) + active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version) if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } end diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb index fd61ef0d95..32fa660fe0 100644 --- a/lib/bundler/cli/plugin.rb +++ b/lib/bundler/cli/plugin.rb @@ -10,11 +10,15 @@ module Bundler method_option "source", type: :string, default: nil, banner: "URL of the RubyGems source to fetch the plugin from" method_option "version", type: :string, default: nil, banner: "The version of the plugin to fetch" method_option "git", type: :string, default: nil, banner: "URL of the git repo to fetch from" - method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)" + method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (removed)" method_option "branch", type: :string, default: nil, banner: "The git branch to checkout" method_option "ref", type: :string, default: nil, banner: "The git revision to check out" method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use" def install(*plugins) + if options.key?(:local_git) + raise InvalidOption, "--local_git has been removed, use --git" + end + Bundler::Plugin.install(plugins, options) end diff --git a/lib/bundler/cli/pristine.rb b/lib/bundler/cli/pristine.rb index cfd4a995a3..b8545fe4c9 100644 --- a/lib/bundler/cli/pristine.rb +++ b/lib/bundler/cli/pristine.rb @@ -11,6 +11,7 @@ module Bundler definition = Bundler.definition definition.validate_runtime! installer = Bundler::Installer.new(Bundler.root, definition) + git_sources = [] ProcessLock.lock do installed_specs = definition.specs.reject do |spec| @@ -41,6 +42,9 @@ module Bundler end FileUtils.rm_rf spec.extension_dir FileUtils.rm_rf spec.full_gem_path + + next if git_sources.include?(source) + git_sources << source else Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.") next diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb index 13bb8b774b..67fdcc797e 100644 --- a/lib/bundler/cli/show.rb +++ b/lib/bundler/cli/show.rb @@ -6,7 +6,7 @@ module Bundler def initialize(options, gem_name) @options = options @gem_name = gem_name - @verbose = options[:verbose] || options[:outdated] + @verbose = options[:verbose] @latest_specs = fetch_latest_specs if @verbose end @@ -57,12 +57,8 @@ module Bundler def fetch_latest_specs definition = Bundler.definition(true) - if options[:outdated] - Bundler.ui.info "Fetching remote specs for outdated check...\n\n" - Bundler.ui.silence { definition.remotely! } - else - definition.with_cache! - end + Bundler.ui.info "Fetching remote specs for outdated check...\n\n" + Bundler.ui.silence { definition.remotely! } Bundler.reset! definition.specs end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 985e8db051..9cc90acc58 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -15,7 +15,7 @@ module Bundler Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] sources = Array(options[:source]) groups = Array(options[:group]).map(&:to_sym) @@ -23,10 +23,10 @@ module Bundler full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !update_bundler if full_update && !options[:all] - if Bundler.feature_flag.update_requires_all_flag? + if Bundler.settings[:update_requires_all_flag] raise InvalidOption, "To update everything, pass the `--all` flag." end - SharedHelpers.major_deprecation 3, "Pass --all to `bundle update` to update everything" + SharedHelpers.feature_deprecated! "Pass --all to `bundle update` to update everything" elsif !full_update && options[:all] raise InvalidOption, "Cannot specify --all along with specific options." end @@ -63,7 +63,7 @@ module Bundler opts = options.dup opts["update"] = true opts["local"] = options[:local] - opts["force"] = options[:redownload] + opts["force"] = options[:redownload] if options[:redownload] Bundler.settings.set_command_option_if_given :jobs, opts["jobs"] @@ -92,7 +92,7 @@ module Bundler locked_spec = locked_info[:spec] new_spec = Bundler.definition.specs[name].first unless new_spec - unless locked_spec.match_platform(Bundler.local_platform) + unless locked_spec.installable_on_platform?(Bundler.local_platform) Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one" end diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb deleted file mode 100644 index 5c09e00995..0000000000 --- a/lib/bundler/cli/viz.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class CLI::Viz - attr_reader :options, :gem_name - def initialize(options) - @options = options - end - - def run - # make sure we get the right `graphviz`. There is also a `graphviz` - # gem we're not built to support - gem "ruby-graphviz" - require "graphviz" - - options[:without] = options[:without].join(":").tr(" ", ":").split(":") - output_file = File.expand_path(options[:file]) - - graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without]) - graph.viz - rescue LoadError => e - Bundler.ui.error e.inspect - Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:" - Bundler.ui.warn "`gem install ruby-graphviz`" - rescue StandardError => e - raise unless e.message.to_s.include?("GraphViz not installed or dot not in PATH") - Bundler.ui.error e.message - Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`." - end - end -end diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb index 37e2ccced8..6865e30dbc 100644 --- a/lib/bundler/compact_index_client.rb +++ b/lib/bundler/compact_index_client.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "pathname" require "set" module Bundler diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index e7c872031f..17c7655adb 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -11,7 +11,7 @@ module Bundler end class CurrentRuby - ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..35).to_a).freeze + ALL_RUBY_VERSIONS = [*18..27, *30..34, *40..41].freeze KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze PLATFORM_MAP = { @@ -32,7 +32,7 @@ module Bundler end.freeze def ruby? - return true if Bundler::GemHelpers.generic_local_platform_is_ruby? + return true if Bundler::MatchPlatform.generic_local_platform_is_ruby? !windows? && (RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby") end @@ -50,19 +50,10 @@ module Bundler end def maglev? - message = - "`CurrentRuby#maglev?` is deprecated with no replacement. Please use the " \ - "built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on." removed_message = "`CurrentRuby#maglev?` was removed with no replacement. Please use the " \ "built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on." - internally_exempted = caller_locations(1, 1).first.path == __FILE__ - - unless internally_exempted - SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - end - - RUBY_ENGINE == "maglev" + SharedHelpers.feature_removed!(removed_message) end def truffleruby? @@ -90,14 +81,11 @@ module Bundler end define_method(:"maglev_#{trimmed_version}?") do - message = - "`CurrentRuby##{__method__}` is deprecated with no replacement. Please use the " \ - "built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check." removed_message = "`CurrentRuby##{__method__}` was removed with no replacement. Please use the " \ "built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check." - SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) + SharedHelpers.feature_removed!(removed_message) send(:"maglev?") && send(:"on_#{trimmed_version}?") end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index e9b67005a9..5ab577f504 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1,16 +1,17 @@ # frozen_string_literal: true require_relative "lockfile_parser" +require_relative "worker" module Bundler class Definition - include GemHelpers - class << self # Do not create or modify a lockfile (Makes #lock a noop) attr_accessor :no_lock end + attr_writer :lockfile + attr_reader( :dependencies, :locked_checksums, @@ -62,6 +63,7 @@ module Bundler if unlock == true @unlocking_all = true + strict = false @unlocking_bundler = false @unlocking = unlock @sources_to_unlock = [] @@ -70,6 +72,7 @@ module Bundler conservative = false else @unlocking_all = false + strict = unlock.delete(:strict) @unlocking_bundler = unlock.delete(:bundler) @unlocking = unlock.any? {|_k, v| !Array(v).empty? } @sources_to_unlock = unlock.delete(:sources) || [] @@ -99,7 +102,7 @@ module Bundler if lockfile_exists? @lockfile_contents = Bundler.read_file(lockfile) - @locked_gems = LockfileParser.new(@lockfile_contents) + @locked_gems = LockfileParser.new(@lockfile_contents, strict: strict) @locked_platforms = @locked_gems.platforms @most_specific_locked_platform = @locked_gems.most_specific_locked_platform @platforms = @locked_platforms.dup @@ -107,6 +110,7 @@ module Bundler @locked_ruby_version = @locked_gems.ruby_version @locked_deps = @locked_gems.dependencies @originally_locked_specs = SpecSet.new(@locked_gems.specs) + @originally_locked_sources = @locked_gems.sources @locked_checksums = @locked_gems.checksums if @unlocking_all @@ -114,7 +118,16 @@ module Bundler @locked_sources = [] else @locked_specs = @originally_locked_specs - @locked_sources = @locked_gems.sources + @locked_sources = @originally_locked_sources + end + + locked_gem_sources = @originally_locked_sources.select {|s| s.is_a?(Source::Rubygems) } + multisource_lockfile = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? + + if multisource_lockfile + msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." + + Bundler::SharedHelpers.feature_removed! msg end else @locked_gems = nil @@ -123,22 +136,10 @@ module Bundler @platforms = [] @locked_deps = {} @locked_specs = SpecSet.new([]) - @originally_locked_specs = @locked_specs @locked_sources = [] - @locked_checksums = Bundler.feature_flag.lockfile_checksums? - end - - locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } - @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle? - - if @multisource_allowed - unless sources.aggregate_global_source? - msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." - - Bundler::SharedHelpers.major_deprecation 2, msg - end - - @sources.merged_gem_lockfile_sections!(locked_gem_sources.first) + @originally_locked_specs = @locked_specs + @originally_locked_sources = @locked_sources + @locked_checksums = Bundler.settings[:lockfile_checksums] end @unlocking_ruby ||= if @ruby_version && locked_ruby_version_object @@ -189,12 +190,14 @@ module Bundler def setup_domain!(options = {}) prefer_local! if options[:"prefer-local"] + sources.cached! + if options[:add_checksums] || (!options[:local] && install_needed?) - remotely! + sources.remote! true else Bundler.settings.set_command_option(:jobs, 1) unless install_needed? # to avoid the overhead of Bundler::Worker - with_cache! + sources.local! false end end @@ -282,12 +285,17 @@ module Bundler end def filter_relevant(dependencies) - platforms_array = [generic_local_platform].freeze dependencies.select do |d| - d.should_include? && !d.gem_platforms(platforms_array).empty? + relevant_deps?(d) end end + def relevant_deps?(dep) + platforms_array = [Bundler.generic_local_platform].freeze + + dep.should_include? && !dep.gem_platforms(platforms_array).empty? + end + def locked_dependencies @locked_deps.values end @@ -367,12 +375,50 @@ module Bundler msg = "`Definition#lock` was passed a target file argument. #{suggestion}" - Bundler::SharedHelpers.major_deprecation 2, msg + Bundler::SharedHelpers.feature_removed! msg end write_lock(target_lockfile, preserve_unknown_sections) end + def write_lock(file, preserve_unknown_sections) + return if Definition.no_lock || !lockfile || file.nil? + + contents = to_lock + + # Convert to \r\n if the existing lock has them + # i.e., Windows with `git config core.autocrlf=true` + contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") + + if @locked_bundler_version + locked_major = @locked_bundler_version.segments.first + current_major = bundler_version_to_lock.segments.first + + updating_major = locked_major < current_major + end + + preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + + if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) + return if Bundler.frozen_bundle? + SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } + return + end + + if Bundler.frozen_bundle? + Bundler.ui.error "Cannot write a changed lockfile while frozen." + return + end + + begin + SharedHelpers.filesystem_access(file) do |p| + File.open(p, "wb") {|f| f.puts(contents) } + end + rescue ReadOnlyFileSystemError + raise ProductionError, lockfile_changes_summary("file system is read-only") + end + end + def locked_ruby_version return unless ruby_version if @unlocking_ruby || !@locked_ruby_version @@ -456,8 +502,8 @@ module Bundler return if current_platform_locked? || @platforms.include?(Gem::Platform::RUBY) raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \ - "but your local platform is #{local_platform}. " \ - "Add the current platform to the lockfile with\n`bundle lock --add-platform #{local_platform}` and try again." + "but your local platform is #{Bundler.local_platform}. " \ + "Add the current platform to the lockfile with\n`bundle lock --add-platform #{Bundler.local_platform}` and try again." end def normalize_platforms @@ -492,14 +538,25 @@ module Bundler @unlocking end - attr_writer :source_requirements - def add_checksums + require "rubygems/package" + @locked_checksums = true setup_domain!(add_checksums: true) - specs # force materialization to real specifications, so that checksums are fetched + # force materialization to real specifications, so that checksums are fetched + specs.each do |spec| + next unless spec.source.is_a?(Bundler::Source::Rubygems) + # Checksum was fetched from the compact index API. + next if !spec.source.checksum_store.missing?(spec) && !spec.source.checksum_store.empty?(spec) + # The gem isn't installed, can't compute the checksum. + next unless spec.loaded_from + + package = Gem::Package.new(spec.source.cached_built_in_gem(spec)) + checksum = Checksum.from_gem_package(package) + spec.source.checksum_store.register(spec, checksum) + end end private @@ -533,7 +590,7 @@ module Bundler return unless added.any? || deleted.any? || changed.any? || resolve_needed? - msg = String.new("#{change_reason.capitalize.strip}, but ") + msg = String.new("#{change_reason[0].upcase}#{change_reason[1..-1].strip}, but ") msg << "the lockfile " unless msg.start_with?("Your lockfile") msg << "can't be updated because #{update_refused_reason}" msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? @@ -559,6 +616,7 @@ module Bundler @missing_lockfile_dep || @unlocking_bundler || @locked_spec_with_missing_checksums || + @locked_spec_with_empty_checksums || @locked_spec_with_missing_deps || @locked_spec_with_invalid_deps end @@ -568,53 +626,15 @@ module Bundler end def should_add_extra_platforms? - !lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform] + !lockfile_exists? && Bundler::MatchPlatform.generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform] end def lockfile_exists? lockfile && File.exist?(lockfile) end - def write_lock(file, preserve_unknown_sections) - return if Definition.no_lock || file.nil? - - contents = to_lock - - # Convert to \r\n if the existing lock has them - # i.e., Windows with `git config core.autocrlf=true` - contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") - - if @locked_bundler_version - locked_major = @locked_bundler_version.segments.first - current_major = bundler_version_to_lock.segments.first - - updating_major = locked_major < current_major - end - - preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) - - if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) - return if Bundler.frozen_bundle? - SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } - return - end - - if Bundler.frozen_bundle? - Bundler.ui.error "Cannot write a changed lockfile while frozen." - return - end - - begin - SharedHelpers.filesystem_access(file) do |p| - File.open(p, "wb") {|f| f.puts(contents) } - end - rescue ReadOnlyFileSystemError - raise ProductionError, lockfile_changes_summary("file system is read-only") - end - end - def resolver - @resolver ||= Resolver.new(resolution_base, gem_version_promoter, @most_specific_locked_platform) + @resolver ||= new_resolver(resolution_base) end def expanded_dependencies @@ -632,8 +652,7 @@ module Bundler @resolution_base ||= begin last_resolve = converge_locked_specs remove_invalid_platforms! - new_resolution_platforms = @current_platform_missing ? @new_platforms + [local_platform] : @new_platforms - base = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @unlocking_all || @gems_to_unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms) + base = new_resolution_base(last_resolve: last_resolve, unlock: @unlocking_all || @gems_to_unlock) base = additional_base_requirements_to_prevent_downgrades(base) base = additional_base_requirements_to_force_updates(base) base @@ -645,20 +664,12 @@ module Bundler end def materialize(dependencies) - # Tracks potential endless loops trying to re-resolve. - # TODO: Remove as dead code if not reports are received in a while - incorrect_spec = nil - specs = begin resolve.materialize(dependencies) rescue IncorrectLockfileDependencies => e raise if Bundler.frozen_bundle? - spec = e.spec - raise "Infinite loop while fixing lockfile dependencies" if incorrect_spec == spec - - incorrect_spec = spec - reresolve_without([spec]) + reresolve_without([e.spec]) retry end @@ -738,9 +749,8 @@ module Bundler end def start_resolution - local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(local_platform) - @platforms << local_platform if local_platform_needed_for_resolvability - add_platform(Gem::Platform::RUBY) if RUBY_ENGINE == "truffleruby" + local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(Bundler.local_platform) + @platforms << Bundler.local_platform if local_platform_needed_for_resolvability result = SpecSet.new(resolver.start) @@ -758,7 +768,7 @@ module Bundler if result.incomplete_for_platform?(current_dependencies, @most_specific_non_local_locked_platform) @platforms.delete(@most_specific_non_local_locked_platform) elsif local_platform_needed_for_resolvability - @platforms.delete(local_platform) + @platforms.delete(Bundler.local_platform) end end @@ -772,22 +782,22 @@ module Bundler end def precompute_source_requirements_for_indirect_dependencies? - sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source? + sources.non_global_rubygems_sources.all?(&:dependency_api_available?) end def current_platform_locked? @platforms.any? do |bundle_platform| - generic_local_platform == bundle_platform || local_platform === bundle_platform + Bundler.generic_local_platform == bundle_platform || Bundler.local_platform === bundle_platform end end def add_current_platform - return if @platforms.include?(local_platform) + return if @platforms.include?(Bundler.local_platform) @most_specific_non_local_locked_platform = find_most_specific_locked_platform return if @most_specific_non_local_locked_platform - @platforms << local_platform + @platforms << Bundler.local_platform true end @@ -848,6 +858,7 @@ module Bundler [@missing_lockfile_dep, "your lockfile is missing \"#{@missing_lockfile_dep}\""], [@unlocking_bundler, "an update to the version of Bundler itself was requested"], [@locked_spec_with_missing_checksums, "your lockfile is missing a CHECKSUMS entry for \"#{@locked_spec_with_missing_checksums}\""], + [@locked_spec_with_empty_checksums, "your lockfile has an empty CHECKSUMS entry for \"#{@locked_spec_with_empty_checksums}\""], [@locked_spec_with_missing_deps, "your lockfile includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"], [@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""], ].select(&:first).map(&:last).join(", ") @@ -907,13 +918,23 @@ module Bundler @locked_spec_with_invalid_deps = nil @locked_spec_with_missing_deps = nil @locked_spec_with_missing_checksums = nil + @locked_spec_with_empty_checksums = nil missing_deps = [] missing_checksums = [] + empty_checksums = [] invalid = [] @locked_specs.each do |s| - missing_checksums << s if @locked_checksums && s.source.checksum_store.missing?(s) + if @locked_checksums + checksum_store = s.source.checksum_store + + if checksum_store.missing?(s) + missing_checksums << s + elsif checksum_store.empty?(s) + empty_checksums << s + end + end validation = @locked_specs.validate_deps(s) @@ -922,6 +943,7 @@ module Bundler end @locked_spec_with_missing_checksums = missing_checksums.first.name if missing_checksums.any? + @locked_spec_with_empty_checksums = empty_checksums.first.name if empty_checksums.any? if missing_deps.any? @locked_specs.delete(missing_deps) @@ -951,7 +973,7 @@ module Bundler sources.all_sources.each do |source| # has to be done separately, because we want to keep the locked checksum # store for a source, even when doing a full update - if @locked_checksums && @locked_gems && locked_source = @locked_gems.sources.find {|s| s == source && !s.equal?(source) } + if @locked_checksums && @locked_gems && locked_source = @originally_locked_sources.find {|s| s == source && !s.equal?(source) } source.checksum_store.merge!(locked_source.checksum_store) end # If the source is unlockable and the current command allows an unlock of @@ -972,10 +994,11 @@ module Bundler @missing_lockfile_dep = nil @changed_dependencies = [] - current_dependencies.each do |dep| + @dependencies.each do |dep| if dep.source dep.source = sources.get(dep.source) end + next unless relevant_deps?(dep) name = dep.name @@ -1033,26 +1056,43 @@ module Bundler specs.each do |s| name = s.name + next if @gems_to_unlock.include?(name) + dep = @dependencies.find {|d| s.satisfies?(d) } lockfile_source = s.source if dep - gemfile_source = dep.source || default_source + replacement_source = dep.source - deps << dep if !dep.source || lockfile_source.include?(dep.source) || new_deps.include?(dep) - - # Replace the locked dependency's source with the equivalent source from the Gemfile - s.source = gemfile_source + deps << dep if !replacement_source || lockfile_source.include?(replacement_source) || new_deps.include?(dep) else - # Replace the locked dependency's source with the default source, if the locked source is no longer in the Gemfile - s.source = default_source unless sources.get(lockfile_source) + parent_dep = @dependencies.find do |d| + next unless d.source && d.source != lockfile_source + next if d.source.is_a?(Source::Gemspec) + + parent_locked_specs = @originally_locked_specs[d.name] + + parent_locked_specs.any? do |parent_spec| + parent_spec.runtime_dependencies.any? {|rd| rd.name == s.name } + end + end + + if parent_dep + replacement_source = parent_dep.source + else + replacement_source = sources.get(lockfile_source) + end end + # Replace the locked dependency's source with the equivalent source from the Gemfile + s.source = replacement_source || default_source + next if s.source_changed? + source = s.source next if @sources_to_unlock.include?(source.name) # Path sources have special logic - if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || (source.instance_of?(Source::Git) && !@gems_to_unlock.include?(name) && deps.include?(dep)) + if source.is_a?(Source::Path) new_spec = source.specs[s].first if new_spec s.runtime_dependencies.replace(new_spec.runtime_dependencies) @@ -1080,7 +1120,23 @@ module Bundler @source_requirements ||= find_source_requirements end + def preload_git_source_worker + @preload_git_source_worker ||= Bundler::Worker.new(5, "Git source preloading", ->(source, _) { source.specs }) + end + + def preload_git_sources + sources.git_sources.each {|source| preload_git_source_worker.enq(source) } + ensure + preload_git_source_worker.stop + end + def find_source_requirements + if Gem.ruby_version >= Gem::Version.new("3.3") + # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to + # fetch git repositories one by one. + preload_git_sources + end + # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) @@ -1132,9 +1188,9 @@ module Bundler end def additional_base_requirements_to_prevent_downgrades(resolution_base) - return resolution_base unless @locked_gems && !sources.expired_sources?(@locked_gems.sources) + return resolution_base unless @locked_gems @originally_locked_specs.each do |locked_spec| - next if locked_spec.source.is_a?(Source::Path) + next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed? name = locked_spec.name next if @changed_dependencies.include?(name) @@ -1146,7 +1202,7 @@ module Bundler def additional_base_requirements_to_force_updates(resolution_base) return resolution_base if @explicit_unlocks.empty? - full_update = dup_for_full_unlock.resolve + full_update = SpecSet.new(new_resolver_for_full_update.start) @explicit_unlocks.each do |name| version = full_update.version_for(name) resolution_base.base_requirements[name] = Gem::Requirement.new("= #{version}") if version @@ -1154,21 +1210,10 @@ module Bundler resolution_base end - def dup_for_full_unlock - unlocked_definition = self.class.new(@lockfile, @dependencies, @sources, true, @ruby_version, @optional_groups, @gemfiles) - unlocked_definition.source_requirements = source_requirements - unlocked_definition.gem_version_promoter.tap do |gvp| - gvp.level = gem_version_promoter.level - gvp.strict = gem_version_promoter.strict - gvp.pre = gem_version_promoter.pre - end - unlocked_definition - end - def remove_invalid_platforms! return if Bundler.frozen_bundle? - skips = (@new_platforms + [local_platform]).uniq + skips = (@new_platforms + [Bundler.local_platform]).uniq # We should probably avoid removing non-ruby platforms, since that means # lockfile will no longer install on those platforms, so a error to give @@ -1183,5 +1228,22 @@ module Bundler def source_map @source_map ||= SourceMap.new(sources, dependencies, @locked_specs) end + + def new_resolver_for_full_update + new_resolver(unlocked_resolution_base) + end + + def unlocked_resolution_base + new_resolution_base(last_resolve: SpecSet.new([]), unlock: true) + end + + def new_resolution_base(last_resolve:, unlock:) + new_resolution_platforms = @current_platform_missing ? @new_platforms + [Bundler.local_platform] : @new_platforms + Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms) + end + + def new_resolver(base) + Resolver.new(base, gem_version_promoter, @most_specific_locked_platform) + end end end diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index e81696ff42..cb9c7a76ea 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -99,7 +99,7 @@ module Bundler return RUBY_PLATFORM_ARRAY if force_ruby_platform return valid_platforms if platforms.empty? - valid_platforms.select {|p| expanded_platforms.include?(GemHelpers.generic(p)) } + valid_platforms.select {|p| expanded_platforms.include?(Gem::Platform.generic(p)) } end def expanded_platforms diff --git a/lib/bundler/deployment.rb b/lib/bundler/deployment.rb index b432ae6ae1..3344449e82 100644 --- a/lib/bundler/deployment.rb +++ b/lib/bundler/deployment.rb @@ -1,69 +1,6 @@ # frozen_string_literal: true require_relative "shared_helpers" -Bundler::SharedHelpers.major_deprecation 2, "Bundler no longer integrates with " \ +Bundler::SharedHelpers.feature_removed! "Bundler no longer integrates with " \ "Capistrano, but Capistrano provides its own integration with " \ "Bundler via the capistrano-bundler gem. Use it instead." - -module Bundler - class Deployment - def self.define_task(context, task_method = :task, opts = {}) - if defined?(Capistrano) && context.is_a?(Capistrano::Configuration) - context_name = "capistrano" - role_default = "{:except => {:no_release => true}}" - error_type = ::Capistrano::CommandError - else - context_name = "vlad" - role_default = "[:app]" - error_type = ::Rake::CommandFailedError - end - - roles = context.fetch(:bundle_roles, false) - opts[:roles] = roles if roles - - context.send :namespace, :bundle do - send :desc, <<-DESC - Install the current Bundler environment. By default, gems will be \ - installed to the shared/bundle path. Gems in the development and \ - test group will not be installed. The install command is executed \ - with the --deployment and --quiet flags. If the bundle cmd cannot \ - be found then you can override the bundle_cmd variable to specify \ - which one it should use. The base path to the app is fetched from \ - the :latest_release variable. Set it for custom deploy layouts. - - You can override any of these defaults by setting the variables shown below. - - N.B. bundle_roles must be defined before you require 'bundler/#{context_name}' \ - in your deploy.rb file. - - set :bundle_gemfile, "Gemfile" - set :bundle_dir, File.join(fetch(:shared_path), 'bundle') - set :bundle_flags, "--deployment --quiet" - set :bundle_without, [:development, :test] - set :bundle_with, [:mysql] - set :bundle_cmd, "bundle" # e.g. "/opt/ruby/bin/bundle" - set :bundle_roles, #{role_default} # e.g. [:app, :batch] - DESC - send task_method, :install, opts do - bundle_cmd = context.fetch(:bundle_cmd, "bundle") - bundle_flags = context.fetch(:bundle_flags, "--deployment --quiet") - bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), "bundle")) - bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile") - bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact - bundle_with = [*context.fetch(:bundle_with, [])].compact - app_path = context.fetch(:latest_release) - if app_path.to_s.empty? - raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.") - end - args = ["--gemfile #{File.join(app_path, bundle_gemfile)}"] - args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty? - args << bundle_flags.to_s - args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty? - args << "--with #{bundle_with.join(" ")}" unless bundle_with.empty? - - run "cd #{app_path} && #{bundle_cmd} install #{args.join(" ")}" - end - end - end - end -end diff --git a/lib/bundler/digest.rb b/lib/bundler/digest.rb index 2c6d971f1b..158803033d 100644 --- a/lib/bundler/digest.rb +++ b/lib/bundler/digest.rb @@ -26,7 +26,7 @@ module Bundler end a, b, c, d, e = *words (16..79).each do |i| - w[i] = SHA1_MASK & rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1) + w[i] = SHA1_MASK & rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) end 0.upto(79) do |i| case i diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 32f45d97ec..6f06c4e918 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -9,8 +9,9 @@ module Bundler def self.evaluate(gemfile, lockfile, unlock) builder = new + builder.lockfile(lockfile) builder.eval_gemfile(gemfile) - builder.to_definition(lockfile, unlock) + builder.to_definition(builder.lockfile_path, unlock) end VALID_PLATFORMS = Bundler::CurrentRuby::PLATFORM_MAP.keys.freeze @@ -38,6 +39,7 @@ module Bundler @gemspecs = [] @gemfile = nil @gemfiles = [] + @lockfile = nil add_git_sources end @@ -73,7 +75,7 @@ module Bundler case specs_by_name_and_version.size when 1 specs = specs_by_name_and_version.values.first - spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first + spec = specs.find {|s| s.installable_on_platform?(Bundler.local_platform) } || specs.first @gemspecs << spec @@ -101,6 +103,15 @@ module Bundler add_dependency(name, version, options) end + # For usage in Dsl.evaluate, since lockfile is used as part of the Gemfile. + def lockfile_path + @lockfile + end + + def lockfile(file) + @lockfile = file + end + def source(source, *args, &blk) options = args.last.is_a?(Hash) ? args.pop.dup : {} options = normalize_hash(options) @@ -175,6 +186,7 @@ module Bundler def to_definition(lockfile, unlock) check_primary_source_safety + lockfile = @lockfile unless @lockfile.nil? Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles) end @@ -290,7 +302,7 @@ module Bundler @dependencies.delete(current) elsif dep.gemspec_dev_dep? return - elsif current.source != dep.source + elsif current.source.to_s != dep.source.to_s raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \ "You specified that #{name} (#{dep.requirement}) should come from " \ "#{current.source || "an unspecified source"} and #{dep.source}\n" @@ -411,7 +423,13 @@ module Bundler next if VALID_PLATFORMS.include?(p) raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}" end - deprecate_legacy_windows_platforms(platforms) + + windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) } + if windows_platforms.any? + windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ") + deprecated_message = "Platform #{windows_platforms} will be removed in the future. Please use platform :windows instead." + Bundler::SharedHelpers.feature_deprecated! deprecated_message + end # Save sources passed in a key if opts.key?("source") @@ -477,14 +495,10 @@ module Bundler def normalize_source(source) case source when :gemcutter, :rubygems, :rubyforge - message = - "The source :#{source} is deprecated because HTTP requests are insecure.\n" \ - "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." removed_message = "The source :#{source} is disallowed because HTTP requests are insecure.\n" \ "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - "http://rubygems.org" + Bundler::SharedHelpers.feature_removed! removed_message when String source else @@ -492,16 +506,6 @@ module Bundler end end - def deprecate_legacy_windows_platforms(platforms) - windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) } - return if windows_platforms.empty? - - windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ") - message = "Platform #{windows_platforms} is deprecated. Please use platform :windows instead." - removed_message = "Platform #{windows_platforms} has been removed. Please use platform :windows instead." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - end - def check_path_source_safety return if @sources.global_path_source.nil? @@ -513,7 +517,7 @@ module Bundler " gem 'rails'\n" \ " end\n\n" - SharedHelpers.major_deprecation(2, msg.strip) + SharedHelpers.feature_removed! msg.strip end def check_rubygems_source_safety @@ -521,24 +525,10 @@ module Bundler end def multiple_global_source_warning - if Bundler.feature_flag.bundler_3_mode? - msg = "This Gemfile contains multiple global sources. " \ - "Each source after the first must include a block to indicate which gems " \ - "should come from that source" - raise GemfileEvalError, msg - else - message = - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source." - removed_message = - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this error, use " \ - "a block to indicate which gems should come from the secondary source." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - end + msg = "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" + raise GemfileEvalError, msg end class DSLError < GemfileError diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb index 444ab6fd37..bf9478a299 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -6,6 +6,7 @@ module Bundler BUNDLER_KEYS = %w[ BUNDLE_BIN_PATH BUNDLE_GEMFILE + BUNDLE_LOCKFILE BUNDLER_VERSION BUNDLER_SETUP GEM_HOME diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 4d1bface51..d8df4d6ec5 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -25,6 +25,7 @@ module Bundler class GemNotFound < BundlerError; status_code(7); end class InstallHookError < BundlerError; status_code(8); end + class RemovedError < BundlerError; status_code(9); end class GemfileNotFound < BundlerError; status_code(10); end class GitError < BundlerError; status_code(11); end class DeprecatedError < BundlerError; status_code(12); end @@ -76,11 +77,6 @@ module Bundler def mismatch_resolution_instructions removable, remote = [@existing, @checksum].partition(&:removable?) case removable.size - when 0 - msg = +"Mismatched checksums each have an authoritative source:\n" - msg << " 1. #{@existing.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n" - msg << " 2. #{@checksum.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n" - msg << "You may need to alter your Gemfile sources to resolve this issue.\n" when 1 msg = +"If you trust #{remote.first.sources.first}, to resolve this issue you can:\n" msg << removable.first.removal_instructions @@ -135,7 +131,8 @@ module Bundler attr_reader :orig_exception def initialize(orig_exception, msg) - full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\ + full_message = msg + "\nGem Load Error is: + #{orig_exception.full_message(highlight: false)}\n"\ "Backtrace for gem load error is:\n"\ "#{orig_exception.backtrace.join("\n")}\n"\ "Bundler Error Backtrace:\n" @@ -225,7 +222,9 @@ module Bundler class DirectoryRemovalError < BundlerError def initialize(orig_exception, msg) full_message = "#{msg}.\n" \ - "The underlying error was #{orig_exception.class}: #{orig_exception.message}, with backtrace:\n" \ + "The underlying error was #{orig_exception.class}: + #{orig_exception.full_message(highlight: false)}, + with backtrace:\n" \ " #{orig_exception.backtrace.join("\n ")}\n\n" \ "Bundler Error Backtrace:" super(full_message) diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index b19cf42cc3..dea8abedba 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -2,45 +2,15 @@ module Bundler class FeatureFlag - def self.settings_flag(flag, &default) - unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s) - raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key" - end - - settings_method("#{flag}?", flag, &default) - end - private_class_method :settings_flag + (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } - def self.settings_option(key, &default) - settings_method(key, key, &default) + def removed_major?(target_major_version) + @major_version > target_major_version end - private_class_method :settings_option - def self.settings_method(name, key, &default) - define_method(name) do - value = Bundler.settings[key] - value = instance_eval(&default) if value.nil? - value - end + def deprecated_major?(target_major_version) + @major_version >= target_major_version end - private_class_method :settings_method - - (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } - - settings_flag(:allow_offline_install) { bundler_3_mode? } - settings_flag(:auto_clean_without_path) { bundler_3_mode? } - settings_flag(:cache_all) { bundler_3_mode? } - settings_flag(:default_install_uses_path) { bundler_3_mode? } - settings_flag(:forget_cli_options) { bundler_3_mode? } - settings_flag(:global_gem_cache) { bundler_3_mode? } - settings_flag(:lockfile_checksums) { bundler_3_mode? } - settings_flag(:path_relative_to_cwd) { bundler_3_mode? } - settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } - settings_flag(:print_only_version_number) { bundler_3_mode? } - settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? } - settings_flag(:update_requires_all_flag) { bundler_4_mode? } - - settings_option(:default_cli_command) { bundler_3_mode? ? :cli_help : :install } def initialize(bundler_version) @bundler_version = Gem::Version.create(bundler_version) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 9992b20c47..0b6ced6f39 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -72,19 +72,57 @@ module Bundler end end + HTTP_ERRORS = (Downloader::HTTP_RETRYABLE_ERRORS + Downloader::HTTP_NON_RETRYABLE_ERRORS).freeze + deprecate_constant :HTTP_ERRORS + + NET_ERRORS = [ + :HTTPBadGateway, + :HTTPBadRequest, + :HTTPFailedDependency, + :HTTPForbidden, + :HTTPInsufficientStorage, + :HTTPMethodNotAllowed, + :HTTPMovedPermanently, + :HTTPNoContent, + :HTTPNotFound, + :HTTPNotImplemented, + :HTTPPreconditionFailed, + :HTTPRequestEntityTooLarge, + :HTTPRequestURITooLong, + :HTTPUnauthorized, + :HTTPUnprocessableEntity, + :HTTPUnsupportedMediaType, + :HTTPVersionNotSupported, + ].freeze + deprecate_constant :NET_ERRORS + # Exceptions classes that should bypass retry attempts. If your password didn't work the # first time, it's not going to the third time. - NET_ERRORS = [:HTTPBadGateway, :HTTPBadRequest, :HTTPFailedDependency, - :HTTPForbidden, :HTTPInsufficientStorage, :HTTPMethodNotAllowed, - :HTTPMovedPermanently, :HTTPNoContent, :HTTPNotFound, - :HTTPNotImplemented, :HTTPPreconditionFailed, :HTTPRequestEntityTooLarge, - :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity, - :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze - FAIL_ERRORS = begin - fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError, SecurityError] - fail_errors << Gem::Requirement::BadRequirementError - fail_errors.concat(NET_ERRORS.map {|e| Gem::Net.const_get(e) }) - end.freeze + FAIL_ERRORS = [ + AuthenticationRequiredError, + BadAuthenticationError, + AuthenticationForbiddenError, + FallbackError, + SecurityError, + Gem::Requirement::BadRequirementError, + Gem::Net::HTTPBadGateway, + Gem::Net::HTTPBadRequest, + Gem::Net::HTTPFailedDependency, + Gem::Net::HTTPForbidden, + Gem::Net::HTTPInsufficientStorage, + Gem::Net::HTTPMethodNotAllowed, + Gem::Net::HTTPMovedPermanently, + Gem::Net::HTTPNoContent, + Gem::Net::HTTPNotFound, + Gem::Net::HTTPNotImplemented, + Gem::Net::HTTPPreconditionFailed, + Gem::Net::HTTPRequestEntityTooLarge, + Gem::Net::HTTPRequestURITooLong, + Gem::Net::HTTPUnauthorized, + Gem::Net::HTTPUnprocessableEntity, + Gem::Net::HTTPUnsupportedMediaType, + Gem::Net::HTTPVersionNotSupported, + ].freeze class << self attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries @@ -293,13 +331,6 @@ module Bundler paths.find {|path| File.file? path } end - HTTP_ERRORS = [ - Gem::Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH, - Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN, - Gem::Net::HTTPBadResponse, Gem::Net::HTTPHeaderSyntaxError, Gem::Net::ProtocolError, - Gem::Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH - ].freeze - def bundler_cert_store store = OpenSSL::X509::Store.new ssl_ca_cert = Bundler.settings[:ssl_ca_cert] || diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index 6c82d57011..52168111fe 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -110,7 +110,7 @@ module Bundler def call(path, headers) fetcher.downloader.fetch(fetcher.fetch_uri + path, headers) rescue NetworkDownError => e - raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"] + raise unless headers["If-None-Match"] ui.warn "Using the cached data for the new index because of a network error: #{e}" Gem::Net::HTTPNotModified.new(nil, nil, nil) end diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index e12269925d..994b415e9c 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true require_relative "base" -require "cgi/util" +require "cgi/escape" +require "cgi/util" unless defined?(CGI::EscapeExt) module Bundler class Fetcher diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 868b39b959..2eac6e7975 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -3,6 +3,28 @@ module Bundler class Fetcher class Downloader + HTTP_NON_RETRYABLE_ERRORS = [ + SocketError, + Errno::EADDRNOTAVAIL, + Errno::ENETDOWN, + Errno::ENETUNREACH, + Gem::Net::HTTP::Persistent::Error, + Errno::EHOSTUNREACH, + ].freeze + + HTTP_RETRYABLE_ERRORS = [ + Gem::Timeout::Error, + EOFError, + Errno::EINVAL, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + Errno::EAGAIN, + Gem::Net::HTTPBadResponse, + Gem::Net::HTTPHeaderSyntaxError, + Gem::Net::ProtocolError, + Zlib::BufError, + ].freeze + attr_reader :connection attr_reader :redirect_limit @@ -67,15 +89,19 @@ module Bundler connection.request(uri, req) rescue OpenSSL::SSL::SSLError raise CertificateFailureError.new(uri) - rescue *HTTP_ERRORS => e + rescue *HTTP_NON_RETRYABLE_ERRORS => e Bundler.ui.trace e - if e.is_a?(SocketError) || e.message.to_s.include?("host down:") - raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ - "connection and try again." - else - raise HTTPError, "Network error while fetching #{filtered_uri}" \ + + host = uri.host + host_port = "#{host}:#{uri.port}" + host = host_port if filtered_uri.to_s.include?(host_port) + raise NetworkDownError, "Could not reach host #{host}. Check your network " \ + "connection and try again." + rescue *HTTP_RETRYABLE_ERRORS => e + Bundler.ui.trace e + + raise HTTPError, "Network error while fetching #{filtered_uri}" \ " (#{e})" - end end private diff --git a/lib/bundler/fetcher/gem_remote_fetcher.rb b/lib/bundler/fetcher/gem_remote_fetcher.rb index 3fc7b68263..3c3c1826a1 100644 --- a/lib/bundler/fetcher/gem_remote_fetcher.rb +++ b/lib/bundler/fetcher/gem_remote_fetcher.rb @@ -5,6 +5,12 @@ require "rubygems/remote_fetcher" module Bundler class Fetcher class GemRemoteFetcher < Gem::RemoteFetcher + def initialize(*) + super + + @pool_size = 5 + end + def request(*args) super do |req| req.delete("User-Agent") if headers["User-Agent"] diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index 8903abf4a9..5e8eaee6bb 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -102,13 +102,14 @@ module Bundler def issues_url(exception) message = exception.message.lines.first.tr(":", " ").chomp message = message.split("-").first if exception.is_a?(Errno) - require "cgi/util" - "https://github.com/rubygems/rubygems/search?q=" \ + require "cgi/escape" + require "cgi/util" unless defined?(CGI::EscapeExt) + "https://github.com/ruby/rubygems/search?q=" \ "#{CGI.escape(message)}&type=Issues" end def new_issue_url - "https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" + "https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" end end diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb deleted file mode 100644 index ad12bf89a4..0000000000 --- a/lib/bundler/gem_helpers.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -module Bundler - module GemHelpers - GENERIC_CACHE = { Gem::Platform::RUBY => Gem::Platform::RUBY } # rubocop:disable Style/MutableConstant - GENERICS = [ - Gem::Platform::JAVA, - *Gem::Platform::WINDOWS, - ].freeze - - def generic(p) - GENERIC_CACHE[p] ||= begin - found = GENERICS.find do |match| - p === match - end - found || Gem::Platform::RUBY - end - end - module_function :generic - - def generic_local_platform - generic(local_platform) - end - module_function :generic_local_platform - - def local_platform - Bundler.local_platform - end - module_function :local_platform - - def generic_local_platform_is_ruby? - generic_local_platform == Gem::Platform::RUBY - end - module_function :generic_local_platform_is_ruby? - - def platform_specificity_match(spec_platform, user_platform) - spec_platform = Gem::Platform.new(spec_platform) - - PlatformMatch.specificity_score(spec_platform, user_platform) - end - module_function :platform_specificity_match - - def select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false) - matching = if force_ruby - specs.select {|spec| spec.match_platform(Gem::Platform::RUBY) && spec.force_ruby_platform! } - else - specs.select {|spec| spec.match_platform(platform) } - end - - if prefer_locked - locked_originally = matching.select {|spec| spec.is_a?(LazySpecification) } - return locked_originally if locked_originally.any? - end - - matching - end - module_function :select_all_platform_match - - def select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false) - matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked) - - sort_and_filter_best_platform_match(matching, platform) - end - module_function :select_best_platform_match - - def select_best_local_platform_match(specs, force_ruby: false) - matching = select_all_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialized_for_installation) - - sort_best_platform_match(matching, local_platform) - end - module_function :select_best_local_platform_match - - def sort_and_filter_best_platform_match(matching, platform) - return matching if matching.one? - - exact = matching.select {|spec| spec.platform == platform } - return exact if exact.any? - - sorted_matching = sort_best_platform_match(matching, platform) - exemplary_spec = sorted_matching.first - - sorted_matching.take_while {|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) } - end - module_function :sort_and_filter_best_platform_match - - def sort_best_platform_match(matching, platform) - matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) } - end - module_function :sort_best_platform_match - - class PlatformMatch - def self.specificity_score(spec_platform, user_platform) - return -1 if spec_platform == user_platform - return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY - - os_match(spec_platform, user_platform) + - cpu_match(spec_platform, user_platform) * 10 + - platform_version_match(spec_platform, user_platform) * 100 - end - - def self.os_match(spec_platform, user_platform) - if spec_platform.os == user_platform.os - 0 - else - 1 - end - end - - def self.cpu_match(spec_platform, user_platform) - if spec_platform.cpu == user_platform.cpu - 0 - elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") - 0 - elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" - 1 - else - 2 - end - end - - def self.platform_version_match(spec_platform, user_platform) - if spec_platform.version == user_platform.version - 0 - elsif spec_platform.version.nil? - 1 - else - 2 - end - end - end - - def same_specificity(platform, spec, exemplary_spec) - platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) - end - module_function :same_specificity - - def same_deps(spec, exemplary_spec) - same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort - same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version - same_runtime_deps && same_metadata_deps - end - module_function :same_deps - end -end diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb deleted file mode 100644 index b22b17a453..0000000000 --- a/lib/bundler/graph.rb +++ /dev/null @@ -1,152 +0,0 @@ -# frozen_string_literal: true - -require "set" -module Bundler - class Graph - GRAPH_NAME = :Gemfile - - def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = []) - @env = env - @output_file = output_file - @show_version = show_version - @show_requirements = show_requirements - @output_format = output_format - @without_groups = without.map(&:to_sym) - - @groups = [] - @relations = Hash.new {|h, k| h[k] = Set.new } - @node_options = {} - @edge_options = {} - - _populate_relations - end - - attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format - - def viz - GraphVizClient.new(self).run - end - - private - - def _populate_relations - parent_dependencies = _groups.values.to_set.flatten - loop do - break if parent_dependencies.empty? - - tmp = Set.new - parent_dependencies.each do |dependency| - child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set - @relations[dependency.name] += child_dependencies.map(&:name).to_set - tmp += child_dependencies - - @node_options[dependency.name] = _make_label(dependency, :node) - child_dependencies.each do |c_dependency| - @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge) - end - end - parent_dependencies = tmp - end - end - - def _groups - relations = Hash.new {|h, k| h[k] = Set.new } - @env.current_dependencies.each do |dependency| - dependency.groups.each do |group| - next if @without_groups.include?(group) - - relations[group.to_s].add(dependency) - @relations[group.to_s].add(dependency.name) - - @node_options[group.to_s] ||= _make_label(group, :node) - @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge) - end - end - @groups = relations.keys - relations - end - - def _make_label(symbol_or_string_or_dependency, element_type) - case element_type.to_sym - when :node - if symbol_or_string_or_dependency.is_a?(Gem::Dependency) - label = symbol_or_string_or_dependency.name.dup - label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version - else - label = symbol_or_string_or_dependency.to_s - end - when :edge - label = nil - if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements - tmp = symbol_or_string_or_dependency.requirements_list.join(", ") - label = tmp if tmp != ">= 0" - end - else - raise ArgumentError, "2nd argument is invalid" - end - label.nil? ? {} : { label: label } - end - - def spec_for_dependency(dependency) - @env.requested_specs.find {|s| s.name == dependency.name } - end - - class GraphVizClient - def initialize(graph_instance) - @graph_name = graph_instance.class::GRAPH_NAME - @groups = graph_instance.groups - @relations = graph_instance.relations - @node_options = graph_instance.node_options - @edge_options = graph_instance.edge_options - @output_file = graph_instance.output_file - @output_format = graph_instance.output_format - end - - def g - @g ||= ::GraphViz.digraph(@graph_name, concentrate: true, normalize: true, nodesep: 0.55) do |g| - g.edge[:weight] = 2 - g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif" - g.edge[:fontsize] = 12 - end - end - - def run - @groups.each do |group| - g.add_nodes( - group, { - style: "filled", - fillcolor: "#B9B9D5", - shape: "box3d", - fontsize: 16, - }.merge(@node_options[group]) - ) - end - - @relations.each do |parent, children| - children.each do |child| - if @groups.include?(parent) - g.add_nodes(child, { style: "filled", fillcolor: "#B9B9D5" }.merge(@node_options[child])) - g.add_edges(parent, child, { constraint: false }.merge(@edge_options["#{parent}_#{child}"])) - else - g.add_nodes(child, @node_options[child]) - g.add_edges(parent, child, @edge_options["#{parent}_#{child}"]) - end - end - end - - if @output_format.to_s == "debug" - $stdout.puts g.output none: String - Bundler.ui.info "debugging bundle viz..." - else - begin - g.output @output_format.to_sym => "#{@output_file}.#{@output_format}" - Bundler.ui.info "#{@output_file}.#{@output_format}" - rescue ArgumentError => e - warn "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb" - raise e - end - end - end - end - end -end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index df46facc88..9aef2dfa12 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -46,13 +46,6 @@ module Bundler true end - def search_all(name, &blk) - return enum_for(:search_all, name) unless blk - specs_by_name(name).each(&blk) - @duplicates[name]&.each(&blk) - @sources.each {|source| source.search_all(name, &blk) } - end - # Search this index's specs, and any source indexes that this index knows # about, returning all of the results. def search(query) @@ -131,6 +124,11 @@ module Bundler return unless other other.each do |spec| if existing = find_by_spec(spec) + unless dependencies_eql?(existing, spec) + Bundler.ui.warn "Local specification for #{spec.full_name} has different dependencies than the remote gem, ignoring it" + next + end + add_duplicate(existing) end add spec @@ -153,8 +151,8 @@ module Bundler end def dependencies_eql?(spec, other_spec) - deps = spec.dependencies.select {|d| d.type != :development } - other_deps = other_spec.dependencies.select {|d| d.type != :development } + deps = spec.runtime_dependencies + other_deps = other_spec.runtime_dependencies deps.sort == other_deps.sort end diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index f2f5b22cd3..c861bee149 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -44,14 +44,16 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile) raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty? old_gemfile = ENV["BUNDLE_GEMFILE"] + old_lockfile = ENV["BUNDLE_LOCKFILE"] Bundler.unbundle_env! begin Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir)) Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock" - Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? + Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins] builder = Bundler::Dsl.new builder.instance_eval(&gemfile) @@ -94,5 +96,11 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile) else ENV["BUNDLE_GEMFILE"] = "" end + + if old_lockfile + ENV["BUNDLE_LOCKFILE"] = old_lockfile + else + ENV["BUNDLE_LOCKFILE"] = "" + end end end diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index d41740a411..c5fd75431f 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -7,12 +7,6 @@ require_relative "installer/gem_installer" module Bundler class Installer - class << self - attr_accessor :ambiguous_gems - - Installer.ambiguous_gems = [] - end - attr_reader :post_install_messages, :definition # Begins the installation process for Bundler. diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb index 1da91857bd..5c4fa78253 100644 --- a/lib/bundler/installer/gem_installer.rb +++ b/lib/bundler/installer/gem_installer.rb @@ -16,7 +16,6 @@ module Bundler def install_from_spec post_install_message = install Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}" - generate_executable_stubs [true, post_install_message] rescue Bundler::InstallHookError, Bundler::SecurityError, Bundler::APIResponseMismatchError, Bundler::InsecureInstallPathError raise @@ -71,15 +70,5 @@ module Bundler def out_of_space_message "#{install_error_message}\nYour disk is out of space. Free some space to be able to install your bundle." end - - def generate_executable_stubs - return if Bundler.feature_flag.forget_cli_options? - return if Bundler.settings[:inline] - if Bundler.settings[:bin] && standalone - installer.generate_standalone_bundler_executable_stubs(spec) - elsif Bundler.settings[:bin] - installer.generate_bundler_executable_stubs(spec, force: true) - end - end end end diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 061e4bb91e..786dbcae65 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -33,7 +33,7 @@ module Bundler lazy_spec end - def initialize(name, version, platform, source = nil) + def initialize(name, version, platform, source = nil, **materialization_options) @name = name @version = version @dependencies = [] @@ -43,6 +43,7 @@ module Bundler @original_source = source @source = source + @materialization_options = materialization_options @force_ruby_platform = default_force_ruby_platform @most_specific_locked_platform = nil @@ -137,24 +138,16 @@ module Bundler source.local! if use_exact_resolved_specifications? - materialize(self) do |matching_specs| - choose_compatible(matching_specs) - end - else - materialize([name, version]) do |matching_specs| - target_platform = source.is_a?(Source::Path) ? platform : local_platform - - installable_candidates = GemHelpers.select_best_platform_match(matching_specs, target_platform) - - specification = choose_compatible(installable_candidates, fallback_to_non_installable: false) - return specification unless specification.nil? + spec = materialize(self) {|specs| choose_compatible(specs, fallback_to_non_installable: false) } + return spec if spec - if target_platform != platform - installable_candidates = GemHelpers.select_best_platform_match(matching_specs, platform) - end - - choose_compatible(installable_candidates) + # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant + # In non-frozen mode, return nil to trigger re-resolution and lockfile update + if Bundler.frozen_bundle? + materialize([name, version]) {|specs| resolve_best_platform(specs) } end + else + materialize([name, version]) {|specs| resolve_best_platform(specs) } end end @@ -189,8 +182,41 @@ module Bundler !source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform? end + # Try platforms in order of preference until finding a compatible spec. + # Used for legacy lockfiles and as a fallback when the exact locked spec + # is incompatible. Falls back to frozen bundle behavior if none match. + def resolve_best_platform(specs) + find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs) + end + + def find_compatible_platform_spec(specs) + candidate_platforms.each do |plat| + candidates = MatchPlatform.select_best_platform_match(specs, plat) + spec = choose_compatible(candidates, fallback_to_non_installable: false) + return spec if spec + end + nil + end + + # Platforms to try in order of preference. Ruby platform is last since it + # requires compilation, but works when precompiled gems are incompatible. + def candidate_platforms + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + [target, platform, Gem::Platform::RUBY].uniq + end + + # In frozen mode, accept any candidate. Will error at install time. + # When target differs from locked platform, prefer locked platform's candidates + # to preserve lockfile integrity. + def frozen_bundle_fallback(specs) + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + fallback_platform = target == platform ? target : platform + candidates = MatchPlatform.select_best_platform_match(specs, fallback_platform) + choose_compatible(candidates) + end + def ruby_platform_materializes_to_ruby_platform? - generic_platform = generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY + generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY (most_specific_locked_platform != generic_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform] end @@ -226,12 +252,13 @@ module Bundler # Validate dependencies of this locked spec are consistent with dependencies # of the actual spec that was materialized. # - # Note that we don't validate dependencies of locally installed gems but + # Note that unless we are in strict mode (which we set during installation) + # we don't validate dependencies of locally installed gems but # accept what's in the lockfile instead for performance, since loading # dependencies of locally installed gems would mean evaluating all gemspecs, # which would affect `bundler/setup` performance. def validate_dependencies(spec) - if spec.is_a?(StubSpecification) + if !@materialization_options[:strict] && spec.is_a?(StubSpecification) spec.dependencies = dependencies else if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb index 904552fa0c..6b6cf9d9ea 100644 --- a/lib/bundler/lockfile_generator.rb +++ b/lib/bundler/lockfile_generator.rb @@ -95,7 +95,7 @@ module Bundler out << " #{key}: #{val}\n" end when String - out << " #{value}\n" + out << " #{value}\n" else raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile" end diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 7d57ec724d..ac0ce1ef3d 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true +require_relative "shared_helpers" + module Bundler class LockfileParser - include GemHelpers - class Position attr_reader :line, :column def initialize(line, column) @@ -94,7 +94,7 @@ module Bundler lockfile_contents.split(BUNDLED).last.strip end - def initialize(lockfile) + def initialize(lockfile, strict: false) @platforms = [] @sources = [] @dependencies = {} @@ -106,6 +106,7 @@ module Bundler "Gemfile.lock" end @pos = Position.new(1, 1) + @strict = strict if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \ @@ -139,8 +140,13 @@ module Bundler end @pos.advance!(line) end + + if @platforms.include?(Gem::Platform::X64_MINGW_LEGACY) + SharedHelpers.feature_deprecated!("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.") + end + @most_specific_locked_platform = @platforms.min_by do |bundle_platform| - platform_specificity_match(bundle_platform, local_platform) + Gem::Platform.platform_specificity_match(bundle_platform, Bundler.local_platform) end @specs = @specs.values.sort_by!(&:full_name).each do |spec| spec.most_specific_locked_platform = @most_specific_locked_platform @@ -271,7 +277,7 @@ module Bundler version = Gem::Version.new(version) platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY - @current_spec = LazySpecification.new(name, version, platform, @current_source) + @current_spec = LazySpecification.new(name, version, platform, @current_source, strict: @strict) @current_source.add_dependency_names(name) @specs[@current_spec.full_name] = @current_spec diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index 176e8b117e..4474969db6 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ADD" "1" "March 2025" "" +.TH "BUNDLE\-ADD" "1" "September 2025" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index 146c1c021e..b8c153696b 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,24 +1,21 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-BINSTUBS" "1" "March 2025" "" +.TH "BUNDLE\-BINSTUBS" "1" "September 2025" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" -\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-path PATH] [\-\-standalone] [\-\-all\-platforms] +\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-standalone] [\-\-all\-platforms] .SH "DESCRIPTION" Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into \fBbin/\fR\. Binstubs are a shortcut\-or alternative\- to always using \fBbundle exec\fR\. This gives you a file that can be run directly, and one that will always run the correct gem version used by the application\. .P For example, if you run \fBbundle binstubs rspec\-core\fR, Bundler will create the file \fBbin/rspec\fR\. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec\. .P -This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the \fB\-\-path\fR directory if one has been set\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\. +This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the directory specified by \fBbin\fR setting if it has been configured\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\. .SH "OPTIONS" .TP \fB\-\-force\fR Overwrite existing binstubs if they exist\. .TP -\fB\-\-path[=PATH]\fR -The location to install the specified binstubs to\. This defaults to \fBbin\fR\. -.TP \fB\-\-standalone\fR Makes binstubs that can work without depending on Rubygems or Bundler at runtime\. .TP diff --git a/lib/bundler/man/bundle-binstubs.1.ronn b/lib/bundler/man/bundle-binstubs.1.ronn index ec2cfd80db..cbe5983f4d 100644 --- a/lib/bundler/man/bundle-binstubs.1.ronn +++ b/lib/bundler/man/bundle-binstubs.1.ronn @@ -3,7 +3,7 @@ bundle-binstubs(1) -- Install the binstubs of the listed gems ## SYNOPSIS -`bundle binstubs` <GEM_NAME> [--force] [--path PATH] [--standalone] [--all-platforms] +`bundle binstubs` <GEM_NAME> [--force] [--standalone] [--all-platforms] ## DESCRIPTION @@ -19,17 +19,15 @@ the file `bin/rspec`. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec. This command generates binstubs for executables in `GEM_NAME`. -Binstubs are put into `bin`, or the `--path` directory if one has been set. -Calling binstubs with [GEM [GEM]] will create binstubs for all given gems. +Binstubs are put into `bin`, or the directory specified by `bin` setting if it +has been configured. Calling binstubs with [GEM [GEM]] will create binstubs for +all given gems. ## OPTIONS * `--force`: Overwrite existing binstubs if they exist. -* `--path[=PATH]`: - The location to install the specified binstubs to. This defaults to `bin`. - * `--standalone`: Makes binstubs that can work without depending on Rubygems or Bundler at runtime. diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 64e806029b..c1dafbf070 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CACHE" "1" "March 2025" "" +.TH "BUNDLE\-CACHE" "1" "September 2025" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" @@ -11,9 +11,6 @@ alias: \fBpackage\fR, \fBpack\fR Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR, use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\. .SH "OPTIONS" .TP -\fB\-\-all\fR -Include all sources (including path and git)\. -.TP \fB\-\-all\-platforms\fR Include gems for all platforms present in the lockfile, not only the current one\. .TP @@ -26,19 +23,10 @@ Use the specified gemfile instead of Gemfile\. \fB\-\-no\-install\fR Don't install the gems, only update the cache\. .TP -\fB\-\-no\-prune\fR -Don't remove stale gems from the cache\. -.TP -\fB\-\-path=PATH\fR -Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME)\. -.TP \fB\-\-quiet\fR Only output warnings and errors\. -.TP -\fB\-\-frozen\fR -Do not allow the Gemfile\.lock to be updated after this bundle cache operation's install\. .SH "GIT AND PATH GEMS" -The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This needs to be explicitly enabled via the \fB\-\-all\fR option\. Once used, the \fB\-\-all\fR option will be remembered\. +The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This can be disabled setting \fBcache_all\fR to false\. .SH "SUPPORT FOR MULTIPLE PLATFORMS" When using gems that have different packages for different platforms, Bundler supports caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\. .SH "REMOTE FETCHING" diff --git a/lib/bundler/man/bundle-cache.1.ronn b/lib/bundler/man/bundle-cache.1.ronn index ffcc07c7b1..51846c96b4 100644 --- a/lib/bundler/man/bundle-cache.1.ronn +++ b/lib/bundler/man/bundle-cache.1.ronn @@ -15,9 +15,6 @@ use the gems in the cache in preference to the ones on `rubygems.org`. ## OPTIONS -* `--all`: - Include all sources (including path and git). - * `--all-platforms`: Include gems for all platforms present in the lockfile, not only the current one. @@ -30,23 +27,13 @@ use the gems in the cache in preference to the ones on `rubygems.org`. * `--no-install`: Don't install the gems, only update the cache. -* `--no-prune`: - Don't remove stale gems from the cache. - -* `--path=PATH`: - Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). - * `--quiet`: Only output warnings and errors. -* `--frozen`: - Do not allow the Gemfile.lock to be updated after this bundle cache operation's install. - ## GIT AND PATH GEMS The `bundle cache` command can also package `:git` and `:path` dependencies -besides .gem files. This needs to be explicitly enabled via the `--all` option. -Once used, the `--all` option will be remembered. +besides .gem files. This can be disabled setting `cache_all` to false. ## SUPPORT FOR MULTIPLE PLATFORMS diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index bf16a22461..f83af1eb55 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CHECK" "1" "March 2025" "" +.TH "BUNDLE\-CHECK" "1" "September 2025" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" -\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] [\-\-path=PATH] +\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] .SH "DESCRIPTION" \fBcheck\fR searches the local machine for each of the gems requested in the Gemfile\. If all gems are found, Bundler prints a success message and exits with a status of 0\. .P @@ -18,7 +18,4 @@ Locks the [\fBGemfile(5)\fR][Gemfile(5)] before running the command\. .TP \fB\-\-gemfile=GEMFILE\fR Use the specified gemfile instead of the [\fBGemfile(5)\fR][Gemfile(5)]\. -.TP -\fB\-\-path=PATH\fR -Specify a different path than the system default (\fB$BUNDLE_PATH\fR or \fB$GEM_HOME\fR)\. Bundler will remember this value for future installs on this machine\. diff --git a/lib/bundler/man/bundle-check.1.ronn b/lib/bundler/man/bundle-check.1.ronn index 23bb6f3eb8..92589159c9 100644 --- a/lib/bundler/man/bundle-check.1.ronn +++ b/lib/bundler/man/bundle-check.1.ronn @@ -5,7 +5,6 @@ bundle-check(1) -- Verifies if dependencies are satisfied by installed gems `bundle check` [--dry-run] [--gemfile=FILE] - [--path=PATH] ## DESCRIPTION @@ -25,7 +24,3 @@ installed on the local machine, if they satisfy the requirements. * `--gemfile=GEMFILE`: Use the specified gemfile instead of the [`Gemfile(5)`][Gemfile(5)]. - -* `--path=PATH`: - Specify a different path than the system default (`$BUNDLE_PATH` or `$GEM_HOME`). - Bundler will remember this value for future installs on this machine. diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 83f7661482..c4d148c5df 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CLEAN" "1" "March 2025" "" +.TH "BUNDLE\-CLEAN" "1" "September 2025" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 190177eb37..05c13e2d0f 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,16 +1,16 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONFIG" "1" "March 2025" "" +.TH "BUNDLE\-CONFIG" "1" "September 2025" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" -\fBbundle config\fR list +\fBbundle config\fR [list] .br -\fBbundle config\fR [get] NAME +\fBbundle config\fR [get [\-\-local|\-\-global]] NAME .br -\fBbundle config\fR [set] NAME VALUE +\fBbundle config\fR [set [\-\-local|\-\-global]] NAME VALUE .br -\fBbundle config\fR unset NAME +\fBbundle config\fR unset [\-\-local|\-\-global] NAME .SH "DESCRIPTION" This command allows you to interact with Bundler's configuration system\. .P @@ -25,47 +25,203 @@ Global config (\fB~/\.bundle/config\fR) Bundler default config .IP "" 0 .P +Executing \fBbundle\fR with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\. +.SH "SUB\-COMMANDS" +.SS "list (default command)" Executing \fBbundle config list\fR will print a list of all bundler configuration for the current bundle, and where that configuration was set\. +.SS "get" +Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and all locations where it was set\. .P -Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and where it was set\. -.P -Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\. See \fB\-\-local\fR and \fB\-\-global\fR options below\. +\fBOPTIONS\fR +.TP +\fB\-\-local\fR +Get configuration from configuration file for the local application, namely, \fB<project_root>/\.bundle/config\fR, or \fB$BUNDLE_APP_CONFIG/config\fR if \fBBUNDLE_APP_CONFIG\fR is set\. +.TP +\fB\-\-global\fR +Get configuration from configuration file global to all bundles executed as the current user, namely, from \fB~/\.bundle/config\fR\. +.SS "set" +Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\. .P +\fBOPTIONS\fR +.TP +\fB\-\-local\fR Executing \fBbundle config set \-\-local <name> <value>\fR will set that configuration in the directory for the local application\. The configuration will be stored in \fB<project_root>/\.bundle/config\fR\. If \fBBUNDLE_APP_CONFIG\fR is set, the configuration will be stored in \fB$BUNDLE_APP_CONFIG/config\fR\. -.P +.TP +\fB\-\-global\fR Executing \fBbundle config set \-\-global <name> <value>\fR will set that configuration to the value specified for all bundles executed as the current user\. The configuration will be stored in \fB~/\.bundle/config\fR\. If \fIname\fR already is set, \fIname\fR will be overridden and user will be warned\. -.P +.SS "unset" Executing \fBbundle config unset <name>\fR will delete the configuration in both local and global sources\. .P -Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\. -.P +\fBOPTIONS\fR +.TP +\fB\-\-local\fR Executing \fBbundle config unset \-\-local <name>\fR will delete the configuration only from the local application\. +.TP +\fB\-\-global\fR +Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\. +.SH "CONFIGURATION KEYS" +Configuration keys in bundler have two forms: the canonical form and the environment variable form\. .P -Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\. -.SH "REMEMBERING OPTIONS" -Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application's configuration (normally, \fB\./\.bundle/config\fR)\. +For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\. .P -However, this will be changed in bundler 3, so it's better not to rely on this behavior\. If these options must be remembered, it's better to set them using \fBbundle config\fR (e\.g\., \fBbundle config set \-\-local path foo\fR)\. +The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\. .P -The options that can be configured are: +Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\. +.SH "LIST OF AVAILABLE KEYS" +The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. +.TP +\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR) +Automatically run \fBbundle install\fR when gems are missing\. +.TP +\fBbin\fR (\fBBUNDLE_BIN\fR) +If configured, \fBbundle binstubs\fR will install executables from gems in the bundle to the specified directory\. Otherwise it will create them in a \fBbin\fR directory relative to the Gemfile directory\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, \fBbundle binstubs\fR will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. +.TP +\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR) +Cache all gems, including path and git gems\. This needs to be explicitly before bundler 4, but will be the default on bundler 4\. +.TP +\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR) +Cache gems for all platforms\. +.TP +\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR) +The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\. +.TP +\fBclean\fR (\fBBUNDLE_CLEAN\fR) +Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 4, as long as \fBpath\fR is not explicitly configured\. +.TP +\fBconsole\fR (\fBBUNDLE_CONSOLE\fR) +The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\. +.TP +\fBdefault_cli_command\fR (\fBBUNDLE_DEFAULT_CLI_COMMAND\fR) +The command that running \fBbundle\fR without arguments should run\. Defaults to \fBcli_help\fR since Bundler 4, but can also be \fBinstall\fR which was the previous default\. +.TP +\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR) +Equivalent to setting \fBfrozen\fR to \fBtrue\fR and \fBpath\fR to \fBvendor/bundle\fR\. +.TP +\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR) +Allow installing gems even if they do not match the checksum provided by RubyGems\. +.TP +\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR) +Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\. +.TP +\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR) +Allow Bundler to use a local git override without a branch specified in the Gemfile\. +.TP +\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR) +Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. +.TP +\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR) +Stop Bundler from accessing gems installed to RubyGems' normal location\. +.TP +\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR) +Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\. +.TP +\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR) +Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\. +.TP +\fBfrozen\fR (\fBBUNDLE_FROZEN\fR) +Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\. +.TP +\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR) +Sets a GitHub username or organization to be used in the \fBREADME\fR and \fB\.gemspec\fR files when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. +.TP +\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR) +Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. +.TP +\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR) +The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\. +.TP +\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR) +Whether Bundler should cache all gems and compiled extensions globally, rather than locally to the configured installation path\. +.TP +\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR) +When set, no funding requests will be printed\. +.TP +\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR) +When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. +.TP +\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR) +Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. +.TP +\fBjobs\fR (\fBBUNDLE_JOBS\fR) +The number of gems Bundler can install in parallel\. Defaults to the number of available processors\. +.TP +\fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR) +The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\. +.TP +\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR) +Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\. +.TP +\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR) +Whether \fBbundle package\fR should skip installing gems\. +.TP +\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR) +Whether Bundler should leave outdated gems unpruned when caching\. +.TP +\fBonly\fR (\fBBUNDLE_ONLY\fR) +A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, because they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\. +.TP +\fBpath\fR (\fBBUNDLE_PATH\fR) +The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 4, and to the default system path (\fBGem\.dir\fR) before Bundler 4\. That means that before Bundler 4, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 4\. +.TP +\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR) +Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\. +.TP +\fBplugins\fR (\fBBUNDLE_PLUGINS\fR) +Enable Bundler's experimental plugin system\. +.TP +\fBprefer_patch\fR (BUNDLE_PREFER_PATCH) +Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. +.TP +\fBredirect\fR (\fBBUNDLE_REDIRECT\fR) +The number of redirects allowed for network requests\. Defaults to \fB5\fR\. +.TP +\fBretry\fR (\fBBUNDLE_RETRY\fR) +The number of times to retry failed network requests\. Defaults to \fB3\fR\. +.TP +\fBshebang\fR (\fBBUNDLE_SHEBANG\fR) +The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\. +.TP +\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR) +Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\. +.TP +\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR) +Silence the warning Bundler prints when installing gems as root\. +.TP +\fBsimulate_version\fR (\fBBUNDLE_SIMULATE_VERSION\fR) +The virtual version Bundler should use for activating feature flags\. Can be used to simulate all the new functionality that will be enabled in a future major version\. +.TP +\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR) +Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. +.TP +\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR) +Path to a designated file containing a X\.509 client certificate and key in PEM format\. +.TP +\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR) +The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\. +.TP +\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR) +The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. +.TP +\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR) +The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. .TP -\fBbin\fR -Creates a directory (defaults to \fB~/bin\fR) and place any executables from the gem there\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. +\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR) +Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. .TP -\fBdeployment\fR -In deployment mode, Bundler will 'roll\-out' the bundle for \fBproduction\fR use\. Please check carefully if you want to have this option enabled in \fBdevelopment\fR or \fBtest\fR environments\. +\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR) +The custom user agent fragment Bundler includes in API requests\. .TP -\fBonly\fR -A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, cause they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\. +\fBverbose\fR (\fBBUNDLE_VERBOSE\fR) +Whether Bundler should print verbose output\. Defaults to \fBfalse\fR, unless the \fB\-\-verbose\fR CLI flag is used\. .TP -\fBpath\fR -The location to install the specified gems to\. This defaults to Rubygems' setting\. Bundler shares this location with Rubygems, \fBgem install \|\.\|\.\|\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \|\.\|\.\|\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\. +\fBversion\fR (\fBBUNDLE_VERSION\fR) +The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. .TP -\fBwithout\fR -A space\-separated or \fB:\fR\-separated list of groups referencing gems to skip during installation\. +\fBwith\fR (\fBBUNDLE_WITH\fR) +A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\. .TP -\fBwith\fR -A space\-separated or \fB:\fR\-separated list of \fBoptional\fR groups referencing gems to include during installation\. +\fBwithout\fR (\fBBUNDLE_WITHOUT\fR) +A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\. .SH "BUILD OPTIONS" You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\. .P @@ -84,123 +240,6 @@ bundle config set \-\-global build\.mysql \-\-with\-mysql\-config=/usr/local/mys .IP "" 0 .P After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\. -.SH "CONFIGURATION KEYS" -Configuration keys in bundler have two forms: the canonical form and the environment variable form\. -.P -For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\. -.P -The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\. -.P -Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\. -.SH "LIST OF AVAILABLE KEYS" -The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. -.IP "\(bu" 4 -\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\. -.IP "\(bu" 4 -\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\. -.IP "\(bu" 4 -\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\. -.IP "\(bu" 4 -\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\. -.IP "\(bu" 4 -\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\. -.IP "\(bu" 4 -\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\. -.IP "\(bu" 4 -\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\. -.IP "\(bu" 4 -\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. -.IP "\(bu" 4 -\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\. -.IP "\(bu" 4 -\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\. -.IP "\(bu" 4 -\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. -.IP "\(bu" 4 -\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\. -.IP "\(bu" 4 -\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\. -.IP "\(bu" 4 -\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\. -.IP "\(bu" 4 -\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. -.IP "\(bu" 4 -\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems' normal location\. -.IP "\(bu" 4 -\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\. -.IP "\(bu" 4 -\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\. -.IP "\(bu" 4 -\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\. -.IP "\(bu" 4 -\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. -.IP "\(bu" 4 -\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. -.IP "\(bu" 4 -\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\. -.IP "\(bu" 4 -\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\. -.IP "\(bu" 4 -\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\. -.IP "\(bu" 4 -\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. -.IP "\(bu" 4 -\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. -.IP "\(bu" 4 -\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to the number of available processors\. -.IP "\(bu" 4 -\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR): Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. -.IP "\(bu" 4 -\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\. -.IP "\(bu" 4 -\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\. -.IP "\(bu" 4 -\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\. -.IP "\(bu" 4 -\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\. -.IP "\(bu" 4 -\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\. -.IP "\(bu" 4 -\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\. -.IP "\(bu" 4 -\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\. -.IP "\(bu" 4 -\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. -.IP "\(bu" 4 -\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\. -.IP "\(bu" 4 -\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\. -.IP "\(bu" 4 -\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\. -.IP "\(bu" 4 -\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\. -.IP "\(bu" 4 -\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\. -.IP "\(bu" 4 -\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\. -.IP "\(bu" 4 -\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\. -.IP "\(bu" 4 -\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. -.IP "\(bu" 4 -\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\. -.IP "\(bu" 4 -\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\. -.IP "\(bu" 4 -\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. -.IP "\(bu" 4 -\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. -.IP "\(bu" 4 -\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. -.IP "\(bu" 4 -\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\. -.IP "\(bu" 4 -\fBversion\fR (\fBBUNDLE_VERSION\fR): The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. -.IP "\(bu" 4 -\fBwith\fR (\fBBUNDLE_WITH\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\. -.IP "\(bu" 4 -\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\. -.IP "" 0 .SH "LOCAL GIT REPOS" Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override: .IP "" 4 @@ -276,7 +315,7 @@ export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit" For gems with a git source with HTTP(S) URL you can specify credentials like so: .IP "" 4 .nf -bundle config set \-\-global https://github\.com/rubygems/rubygems\.git username:password +bundle config set \-\-global https://github\.com/ruby/rubygems\.git username:password .fi .IP "" 0 .P diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 44c31cd10d..7c34f1d1af 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -3,10 +3,10 @@ bundle-config(1) -- Set bundler configuration options ## SYNOPSIS -`bundle config` list<br> -`bundle config` [get] NAME<br> -`bundle config` [set] NAME VALUE<br> -`bundle config` unset NAME +`bundle config` [list]<br> +`bundle config` [get [--local|--global]] NAME<br> +`bundle config` [set [--local|--global]] NAME VALUE<br> +`bundle config` unset [--local|--global] NAME ## DESCRIPTION @@ -19,103 +19,67 @@ Bundler loads configuration settings in this order: 3. Global config (`~/.bundle/config`) 4. Bundler default config -Executing `bundle config list` will print a list of all bundler -configuration for the current bundle, and where that configuration -was set. - -Executing `bundle config get <name>` will print the value of that configuration -setting, and where it was set. - -Executing `bundle config set <name> <value>` defaults to setting `local` -configuration if executing from within a local application, otherwise it will -set `global` configuration. See `--local` and `--global` options below. - -Executing `bundle config set --local <name> <value>` will set that configuration -in the directory for the local application. The configuration will be stored in -`<project_root>/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration -will be stored in `$BUNDLE_APP_CONFIG/config`. - -Executing `bundle config set --global <name> <value>` will set that -configuration to the value specified for all bundles executed as the current -user. The configuration will be stored in `~/.bundle/config`. If <name> already -is set, <name> will be overridden and user will be warned. - -Executing `bundle config unset <name>` will delete the configuration in both -local and global sources. - -Executing `bundle config unset --global <name>` will delete the configuration -only from the user configuration. - -Executing `bundle config unset --local <name>` will delete the configuration -only from the local application. - -Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will +Executing `bundle` with the `BUNDLE_IGNORE_CONFIG` environment variable set will cause it to ignore all configuration. -## REMEMBERING OPTIONS +## SUB-COMMANDS -Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or -`--without production`, are remembered between commands and saved to your local -application's configuration (normally, `./.bundle/config`). +### list (default command) -However, this will be changed in bundler 3, so it's better not to rely on this -behavior. If these options must be remembered, it's better to set them using -`bundle config` (e.g., `bundle config set --local path foo`). +Executing `bundle config list` will print a list of all bundler +configuration for the current bundle, and where that configuration +was set. -The options that can be configured are: +### get -* `bin`: - Creates a directory (defaults to `~/bin`) and place any executables from the - gem there. These executables run in Bundler's context. If used, you might add - this directory to your environment's `PATH` variable. For instance, if the - `rails` gem comes with a `rails` executable, this flag will create a - `bin/rails` executable that ensures that all referred dependencies will be - resolved using the bundled gems. +Executing `bundle config get <name>` will print the value of that configuration +setting, and all locations where it was set. -* `deployment`: - In deployment mode, Bundler will 'roll-out' the bundle for - `production` use. Please check carefully if you want to have this option - enabled in `development` or `test` environments. +**OPTIONS** -* `only`: - A space-separated list of groups to install only gems of the specified groups. - Please check carefully if you want to install also gems without a group, cause - they get put inside `default` group. For example `only test:default` will install - all gems specified in test group and without one. +* `--local`: + Get configuration from configuration file for the local application, namely, + `<project_root>/.bundle/config`, or `$BUNDLE_APP_CONFIG/config` if + `BUNDLE_APP_CONFIG` is set. -* `path`: - The location to install the specified gems to. This defaults to Rubygems' - setting. Bundler shares this location with Rubygems, `gem install ...` will - have gem installed there, too. Therefore, gems installed without a - `--path ...` setting will show up by calling `gem list`. Accordingly, gems - installed to other locations will not get listed. +* `--global`: + Get configuration from configuration file global to all bundles executed as + the current user, namely, from `~/.bundle/config`. -* `without`: - A space-separated or `:`-separated list of groups referencing gems to skip during - installation. +### set -* `with`: - A space-separated or `:`-separated list of **optional** groups referencing gems to - include during installation. +Executing `bundle config set <name> <value>` defaults to setting `local` +configuration if executing from within a local application, otherwise it will +set `global` configuration. -## BUILD OPTIONS +**OPTIONS** -You can use `bundle config` to give Bundler the flags to pass to the gem -installer every time bundler tries to install a particular gem. +* `--local`: + Executing `bundle config set --local <name> <value>` will set that configuration + in the directory for the local application. The configuration will be stored in + `<project_root>/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration + will be stored in `$BUNDLE_APP_CONFIG/config`. -A very common example, the `mysql` gem, requires Snow Leopard users to -pass configuration flags to `gem install` to specify where to find the -`mysql_config` executable. +* `--global`: + Executing `bundle config set --global <name> <value>` will set that + configuration to the value specified for all bundles executed as the current + user. The configuration will be stored in `~/.bundle/config`. If <name> already + is set, <name> will be overridden and user will be warned. - gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config +### unset -Since the specific location of that executable can change from machine -to machine, you can specify these flags on a per-machine basis. +Executing `bundle config unset <name>` will delete the configuration in both +local and global sources. - bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config +**OPTIONS** -After running this command, every time bundler needs to install the -`mysql` gem, it will pass along the flags you specified. +* `--local`: + Executing `bundle config unset --local <name>` will delete the configuration + only from the local application. + +* `--global`: + Executing `bundle config unset --global <name>` will delete the configuration + only from the user configuration. ## CONFIGURATION KEYS @@ -142,19 +106,20 @@ the environment variable `BUNDLE_LOCAL__RACK`. The following is a list of all configuration keys and their purpose. You can learn more about their operation in [bundle install(1)](bundle-install.1.html). -* `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): - Allow Bundler to use cached data when installing without network access. -* `auto_clean_without_path` (`BUNDLE_AUTO_CLEAN_WITHOUT_PATH`): - Automatically run `bundle clean` after installing when an explicit `path` - has not been set and Bundler is not installing into the system gems. * `auto_install` (`BUNDLE_AUTO_INSTALL`): Automatically run `bundle install` when gems are missing. * `bin` (`BUNDLE_BIN`): - Install executables from gems in the bundle to the specified directory. - Defaults to `false`. + If configured, `bundle binstubs` will install executables from gems in the + bundle to the specified directory. Otherwise it will create them in a `bin` + directory relative to the Gemfile directory. These executables run in + Bundler's context. If used, you might add this directory to your + environment's `PATH` variable. For instance, if the `rails` gem comes with a + `rails` executable, `bundle binstubs` will create a `bin/rails` executable + that ensures that all referred dependencies will be resolved using the + bundled gems. * `cache_all` (`BUNDLE_CACHE_ALL`): Cache all gems, including path and git gems. This needs to be explicitly - configured on bundler 1 and bundler 2, but will be the default on bundler 3. + before bundler 4, but will be the default on bundler 4. * `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`): Cache gems for all platforms. * `cache_path` (`BUNDLE_CACHE_PATH`): @@ -163,15 +128,16 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Defaults to `vendor/cache`. * `clean` (`BUNDLE_CLEAN`): Whether Bundler should run `bundle clean` automatically after - `bundle install`. + `bundle install`. Defaults to `true` in Bundler 4, as long as `path` is not + explicitly configured. * `console` (`BUNDLE_CONSOLE`): The console that `bundle console` starts. Defaults to `irb`. -* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`): - Whether a `bundle install` without an explicit `--path` argument defaults - to installing gems in `.bundle`. +* `default_cli_command` (`BUNDLE_DEFAULT_CLI_COMMAND`): + The command that running `bundle` without arguments should run. Defaults to + `cli_help` since Bundler 4, but can also be `install` which was the previous + default. * `deployment` (`BUNDLE_DEPLOYMENT`): - Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the - lockfile has not been updated, running Bundler commands will be blocked. + Equivalent to setting `frozen` to `true` and `path` to `vendor/bundle`. * `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`): Allow installing gems even if they do not match the checksum provided by RubyGems. @@ -193,12 +159,13 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Ignore the current machine's platform and install only `ruby` platform gems. As a result, gems with native extensions will be compiled from source. * `frozen` (`BUNDLE_FROZEN`): - Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the - lockfile has not been updated, running Bundler commands will be blocked. - Defaults to `true` when `--deployment` is used. + Disallow any automatic changes to `Gemfile.lock`. Bundler commands will + be blocked unless the lockfile can be installed exactly as written. + Usually this will happen when changing the `Gemfile` manually and forgetting + to update the lockfile through `bundle lock` or `bundle install`. * `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`): - Sets a GitHub username or organization to be used in `README` file when you - create a new gem via `bundle gem` command. It can be overridden by passing an + Sets a GitHub username or organization to be used in the `README` and `.gemspec` files + when you create a new gem via `bundle gem` command. It can be overridden by passing an explicit `--github-username` flag to `bundle gem`. * `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`): Sets the `--key` parameter for `gem push` when using the `rake release` @@ -210,8 +177,8 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). will search up from the current working directory until it finds a `Gemfile`. * `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`): - Whether Bundler should cache all gems globally, rather than locally to the - installing Ruby installation. + Whether Bundler should cache all gems and compiled extensions globally, + rather than locally to the configured installation path. * `ignore_funding_requests` (`BUNDLE_IGNORE_FUNDING_REQUESTS`): When set, no funding requests will be printed. * `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): @@ -222,36 +189,41 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `jobs` (`BUNDLE_JOBS`): The number of gems Bundler can install in parallel. Defaults to the number of available processors. +* `lockfile` (`BUNDLE_LOCKFILE`): + The path to the lockfile that bundler should use. By default, Bundler adds + `.lock` to the end of the `gemfile` entry. Can be set to `false` in the + Gemfile to disable lockfile creation entirely (see gemfile(5)). * `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`): - Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. + Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true. * `no_install` (`BUNDLE_NO_INSTALL`): Whether `bundle package` should skip installing gems. * `no_prune` (`BUNDLE_NO_PRUNE`): Whether Bundler should leave outdated gems unpruned when caching. * `only` (`BUNDLE_ONLY`): A space-separated list of groups to install only gems of the specified groups. + Please check carefully if you want to install also gems without a group, because + they get put inside `default` group. For example `only test:default` will install + all gems specified in test group and without one. * `path` (`BUNDLE_PATH`): The location on disk where all gems in your bundle will be located regardless of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location - will be installed by `bundle install`. Defaults to `Gem.dir`. When --deployment - is used, defaults to vendor/bundle. + will be installed by `bundle install`. When not set, Bundler install by + default to a `.bundle` directory relative to repository root in Bundler 4, + and to the default system path (`Gem.dir`) before Bundler 4. That means that + before Bundler 4, Bundler shares this location with Rubygems, and `gem + install ...` will have gems installed in the same location and therefore, + gems installed without `path` set will show up by calling `gem list`. This + will not be the case in Bundler 4. * `path.system` (`BUNDLE_PATH__SYSTEM`): Whether Bundler will install gems into the default system path (`Gem.dir`). -* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`) - Makes `--path` relative to the CWD instead of the `Gemfile`. * `plugins` (`BUNDLE_PLUGINS`): Enable Bundler's experimental plugin system. * `prefer_patch` (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`. -* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`): - Print only version number from `bundler --version`. * `redirect` (`BUNDLE_REDIRECT`): The number of redirects allowed for network requests. Defaults to `5`. * `retry` (`BUNDLE_RETRY`): The number of times to retry failed network requests. Defaults to `3`. -* `setup_makes_kernel_gem_public` (`BUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC`): - Have `Bundler.setup` make the `Kernel#gem` method public, even though - RubyGems declares it as private. * `shebang` (`BUNDLE_SHEBANG`): The program name that should be invoked for generated binstubs. Defaults to the ruby install name used to generate the binstub. @@ -260,6 +232,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). be changed in the next major version. * `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`): Silence the warning Bundler prints when installing gems as root. +* `simulate_version` (`BUNDLE_SIMULATE_VERSION`): + The virtual version Bundler should use for activating feature flags. Can be + used to simulate all the new functionality that will be enabled in a future + major version. * `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format. @@ -278,6 +254,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). and disallow passing no options to `bundle update`. * `user_agent` (`BUNDLE_USER_AGENT`): The custom user agent fragment Bundler includes in API requests. +* `verbose` (`BUNDLE_VERBOSE`): + Whether Bundler should print verbose output. Defaults to `false`, unless the + `--verbose` CLI flag is used. * `version` (`BUNDLE_VERSION`): The version of Bundler to use when running under Bundler environment. Defaults to `lockfile`. You can also specify `system` or `x.y.z`. @@ -289,6 +268,25 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `without` (`BUNDLE_WITHOUT`): A space-separated or `:`-separated list of groups whose gems bundler should not install. +## BUILD OPTIONS + +You can use `bundle config` to give Bundler the flags to pass to the gem +installer every time bundler tries to install a particular gem. + +A very common example, the `mysql` gem, requires Snow Leopard users to +pass configuration flags to `gem install` to specify where to find the +`mysql_config` executable. + + gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config + +Since the specific location of that executable can change from machine +to machine, you can specify these flags on a per-machine basis. + + bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config + +After running this command, every time bundler needs to install the +`mysql` gem, it will pass along the flags you specified. + ## LOCAL GIT REPOS Bundler also allows you to work against a git repository locally @@ -366,7 +364,7 @@ Or you can set the credentials as an environment variable like this: For gems with a git source with HTTP(S) URL you can specify credentials like so: - bundle config set --global https://github.com/rubygems/rubygems.git username:password + bundle config set --global https://github.com/ruby/rubygems.git username:password Or you can set the credentials as an environment variable like so: diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index de9eeac907..5ab15668be 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONSOLE" "1" "March 2025" "" +.TH "BUNDLE\-CONSOLE" "1" "September 2025" "" .SH "NAME" \fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index cd831f2fee..a0329dfc48 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,14 +1,21 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-DOCTOR" "1" "March 2025" "" +.TH "BUNDLE\-DOCTOR" "1" "September 2025" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" -\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE] +\fBbundle doctor [diagnose]\fR [\-\-quiet] [\-\-gemfile=GEMFILE] [\-\-ssl] +.br +\fBbundle doctor ssl\fR [\-\-host=HOST] [\-\-tls\-version=TLS\-VERSION] [\-\-verify\-mode=VERIFY\-MODE] +.br +\fBbundle doctor\fR help [COMMAND] .SH "DESCRIPTION" +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue\. +.SH "SUB\-COMMANDS" +.SS "diagnose (default command)" Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\. .P -Examples of common problems caught by bundle\-doctor include: +Examples of common problems caught include: .IP "\(bu" 4 Invalid Bundler settings .IP "\(bu" 4 @@ -20,11 +27,43 @@ Uninstalled gems .IP "\(bu" 4 Missing dependencies .IP "" 0 -.SH "OPTIONS" +.P +\fBOPTIONS\fR .TP \fB\-\-quiet\fR Only output warnings and errors\. .TP \fB\-\-gemfile=GEMFILE\fR The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. +.TP +\fB\-\-ssl\fR +Diagnose common SSL problems when connecting to https://rubygems\.org\. +.IP +This flag runs the \fBbundle doctor ssl\fR subcommand with default values underneath\. +.SS "ssl" +If you've experienced issues related to SSL certificates and/or TLS versions while connecting to https://rubygems\.org, this command can help troubleshoot common problems\. The diagnostic will perform a few checks such as: +.IP "\(bu" 4 +Verify the Ruby OpenSSL version installed on your system\. +.IP "\(bu" 4 +Check the OpenSSL library version used for compilation\. +.IP "\(bu" 4 +Ensure CA certificates are correctly setup on your machine\. +.IP "\(bu" 4 +Open a TLS connection and verify the outcome\. +.IP "" 0 +.P +\fBOPTIONS\fR +.TP +\fB\-\-host=HOST\fR +Perform the diagnostic on HOST\. Defaults to \fBrubygems\.org\fR\. +.TP +\fB\-\-tls\-version=TLS\-VERSION\fR +Specify the TLS version when opening the connection to HOST\. +.IP +Accepted values are: \fB1\.1\fR or \fB1\.2\fR\. +.TP +\fB\-\-verify\-mode=VERIFY\-MODE\fR +Specify the TLS verify mode when opening the connection to HOST\. Defaults to \fBSSL_VERIFY_PEER\fR\. +.IP +Accepted values are: \fBCLIENT_ONCE\fR, \fBFAIL_IF_NO_PEER_CERT\fR, \fBNONE\fR, \fBPEER\fR\. diff --git a/lib/bundler/man/bundle-doctor.1.ronn b/lib/bundler/man/bundle-doctor.1.ronn index 5970f6188b..7495099ff5 100644 --- a/lib/bundler/man/bundle-doctor.1.ronn +++ b/lib/bundler/man/bundle-doctor.1.ronn @@ -3,16 +3,27 @@ bundle-doctor(1) -- Checks the bundle for common problems ## SYNOPSIS -`bundle doctor` [--quiet] - [--gemfile=GEMFILE] +`bundle doctor [diagnose]` [--quiet] + [--gemfile=GEMFILE] + [--ssl]<br> +`bundle doctor ssl` [--host=HOST] + [--tls-version=TLS-VERSION] + [--verify-mode=VERIFY-MODE]<br> +`bundle doctor` help [COMMAND] ## DESCRIPTION +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue. + +## SUB-COMMANDS + +### diagnose (default command) + Checks your Gemfile and gem environment for common problems. If issues are detected, Bundler prints them and exits status 1. Otherwise, Bundler prints a success message and exits status 0. -Examples of common problems caught by bundle-doctor include: +Examples of common problems caught include: * Invalid Bundler settings * Mismatched Ruby versions @@ -20,7 +31,7 @@ Examples of common problems caught by bundle-doctor include: * Uninstalled gems * Missing dependencies -## OPTIONS +**OPTIONS** * `--quiet`: Only output warnings and errors. @@ -31,3 +42,36 @@ Examples of common problems caught by bundle-doctor include: will assume that the location of the Gemfile(5) is also the project's root and will try to find `Gemfile.lock` and `vendor/cache` relative to this location. + +* `--ssl`: + Diagnose common SSL problems when connecting to https://rubygems.org. + + This flag runs the `bundle doctor ssl` subcommand with default values + underneath. + +### ssl + +If you've experienced issues related to SSL certificates and/or TLS versions while connecting +to https://rubygems.org, this command can help troubleshoot common problems. +The diagnostic will perform a few checks such as: + +* Verify the Ruby OpenSSL version installed on your system. +* Check the OpenSSL library version used for compilation. +* Ensure CA certificates are correctly setup on your machine. +* Open a TLS connection and verify the outcome. + +**OPTIONS** + +* `--host=HOST`: + Perform the diagnostic on HOST. Defaults to `rubygems.org`. + +* `--tls-version=TLS-VERSION`: + Specify the TLS version when opening the connection to HOST. + + Accepted values are: `1.1` or `1.2`. + +* `--verify-mode=VERIFY-MODE`: + Specify the TLS verify mode when opening the connection to HOST. + Defaults to `SSL_VERIFY_PEER`. + + Accepted values are: `CLIENT_ONCE`, `FAIL_IF_NO_PEER_CERT`, `NONE`, `PEER`. diff --git a/lib/bundler/man/bundle-env.1 b/lib/bundler/man/bundle-env.1 index c936294827..eee3ca05d0 100644 --- a/lib/bundler/man/bundle-env.1 +++ b/lib/bundler/man/bundle-env.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ENV" "1" "March 2025" "" +.TH "BUNDLE\-ENV" "1" "September 2025" "" .SH "NAME" \fBbundle\-env\fR \- Print information about the environment Bundler is running under .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 0ebbb4c198..24c84889b5 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-EXEC" "1" "March 2025" "" +.TH "BUNDLE\-EXEC" "1" "September 2025" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" -\fBbundle exec\fR [\-\-keep\-file\-descriptors] [\-\-gemfile=GEMFILE] \fIcommand\fR +\fBbundle exec\fR [\-\-gemfile=GEMFILE] \fIcommand\fR .SH "DESCRIPTION" This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\. .P @@ -13,9 +13,6 @@ Essentially, if you would normally have run something like \fBrspec spec/my_spec Note that \fBbundle exec\fR does not require that an executable is available on your shell's \fB$PATH\fR\. .SH "OPTIONS" .TP -\fB\-\-keep\-file\-descriptors\fR -Passes all file descriptors to the new processes\. Default is true from bundler version 2\.2\.26\. Setting it to false is now deprecated\. -.TP \fB\-\-gemfile=GEMFILE\fR Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\. .SH "BUNDLE INSTALL \-\-BINSTUBS" diff --git a/lib/bundler/man/bundle-exec.1.ronn b/lib/bundler/man/bundle-exec.1.ronn index 3d3f0eed7b..e51a66a084 100644 --- a/lib/bundler/man/bundle-exec.1.ronn +++ b/lib/bundler/man/bundle-exec.1.ronn @@ -3,7 +3,7 @@ bundle-exec(1) -- Execute a command in the context of the bundle ## SYNOPSIS -`bundle exec` [--keep-file-descriptors] [--gemfile=GEMFILE] <command> +`bundle exec` [--gemfile=GEMFILE] <command> ## DESCRIPTION @@ -20,10 +20,6 @@ available on your shell's `$PATH`. ## OPTIONS -* `--keep-file-descriptors`: - Passes all file descriptors to the new processes. Default is true from - bundler version 2.2.26. Setting it to false is now deprecated. - * `--gemfile=GEMFILE`: Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)]. diff --git a/lib/bundler/man/bundle-fund.1 b/lib/bundler/man/bundle-fund.1 index 641d8cf864..fe24a25ca1 100644 --- a/lib/bundler/man/bundle-fund.1 +++ b/lib/bundler/man/bundle-fund.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-FUND" "1" "March 2025" "" +.TH "BUNDLE\-FUND" "1" "September 2025" "" .SH "NAME" \fBbundle\-fund\fR \- Lists information about gems seeking funding assistance .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 65882afa4f..85c0f57674 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-GEM" "1" "March 2025" "" +.TH "BUNDLE\-GEM" "1" "September 2025" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" @@ -19,67 +19,87 @@ The generated project skeleton can be customized with OPTIONS, as explained belo \fBgem\.test\fR .IP "" 0 .SH "OPTIONS" -.IP "\(bu" 4 -\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR: Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\. -.IP "\(bu" 4 -\fB\-\-no\-exe\fR: Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-coc\fR: Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-coc\fR: Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-changelog\fR Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-changelog\fR: Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR: Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. -.IP "\(bu" 4 -\fB\-\-no\-ext\fR: Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-git\fR: Initialize a git repo inside your library\. -.IP "\(bu" 4 -\fB\-\-github\-username=GITHUB_USERNAME\fR: Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\. -.IP "\(bu" 4 -\fB\-\-mit\fR: Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-mit\fR: Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR: Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified: +.TP +\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR +Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\. +.TP +\fB\-\-no\-exe\fR +Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\. +.TP +\fB\-\-coc\fR +Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. +.TP +\fB\-\-no\-coc\fR +Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. +.TP +\fB\-\-changelog\fR +Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. Update the default with \fBbundle config set \-\-global gem\.changelog <true|false>\fR\. +.TP +\fB\-\-no\-changelog\fR +Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\. +.TP +\fB\-\-ext=c\fR, \fB\-\-ext=go\fR, \fB\-\-ext=rust\fR +Add boilerplate for C, Go (currently go\-gem\-wrapper \fIhttps://github\.com/ruby\-go\-gem/go\-gem\-wrapper\fR based) or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. +.TP +\fB\-\-no\-ext\fR +Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. +.TP +\fB\-\-git\fR +Initialize a git repo inside your library\. +.TP +\fB\-\-github\-username=GITHUB_USERNAME\fR +Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\. +.TP +\fB\-\-mit\fR +Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. +.TP +\fB\-\-no\-mit\fR +Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. +.TP +\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR +Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified: .IP When Bundler is configured to generate tests, this defaults to Bundler's global config setting \fBgem\.test\fR\. .IP When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-test\fR: Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-changelog\fR: Generate changelog file\. Set a default with \fBbundle config set \-\-global gem\.changelog true\fR\. -.IP "\(bu" 4 -\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR: Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +.TP +\fB\-\-no\-test\fR +Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\. +.TP +\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR +Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: .IP When Bundler is configured to generate CI files, this defaults to Bundler's global config setting \fBgem\.ci\fR\. .IP When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-ci\fR: Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR: Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +.TP +\fB\-\-no\-ci\fR +Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\. +.TP +\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR +Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified: .IP When Bundler is configured to add a linter, this defaults to Bundler's global config setting \fBgem\.linter\fR\. .IP When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-linter\fR: Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-rubocop\fR: Add rubocop to the generated Rakefile and gemspec\. Set a default with \fBbundle config set \-\-global gem\.rubocop true\fR\. -.IP "\(bu" 4 -\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR: Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. -.IP "" 0 +.TP +\fB\-\-no\-linter\fR +Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\. +.TP +\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR +Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. +.TP +\fB\-\-bundle\fR +Run \fBbundle install\fR after creating the gem\. +.TP +\fB\-\-no\-bundle\fR +Do not run \fBbundle install\fR after creating the gem\. .SH "SEE ALSO" .IP "\(bu" 4 bundle config(1) \fIbundle\-config\.1\.html\fR diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn index 13dc55c310..488c8113e4 100644 --- a/lib/bundler/man/bundle-gem.1.ronn +++ b/lib/bundler/man/bundle-gem.1.ronn @@ -41,17 +41,18 @@ configuration file using the following names: Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the global config). -* `--changelog` +* `--changelog`: Add a `CHANGELOG.md` file to the root of the generated project. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future `bundle gem` use. + Update the default with `bundle config set --global gem.changelog <true|false>`. * `--no-changelog`: Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the global config). -* `--ext=c`, `--ext=rust`: - Add boilerplate for C or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior +* `--ext=c`, `--ext=go`, `--ext=rust`: + Add boilerplate for C, Go (currently [go-gem-wrapper](https://github.com/ruby-go-gem/go-gem-wrapper) based) or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior is disabled by default. * `--no-ext`: @@ -95,9 +96,6 @@ configuration file using the following names: Do not use a test framework (overrides `--test` specified in the global config). -* `--changelog`: - Generate changelog file. Set a default with `bundle config set --global gem.changelog true`. - * `--ci`, `--ci=circle`, `--ci=github`, `--ci=gitlab`: Specify the continuous integration service that Bundler should use when generating the project. Acceptable values are `github`, `gitlab` @@ -137,13 +135,16 @@ configuration file using the following names: * `--no-linter`: Do not add a linter (overrides `--linter` specified in the global config). -* `--rubocop`: - Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`. - * `--edit=EDIT`, `-e=EDIT`: Open the resulting GEM_NAME.gemspec in EDIT, or the default editor if not specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`. +* `--bundle`: + Run `bundle install` after creating the gem. + +* `--no-bundle`: + Do not run `bundle install` after creating the gem. + ## SEE ALSO * [bundle config(1)](bundle-config.1.html) diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index d8dd4660dc..05fd5a7c48 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-HELP" "1" "March 2025" "" +.TH "BUNDLE\-HELP" "1" "September 2025" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 8124836519..96c7d876f6 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INFO" "1" "March 2025" "" +.TH "BUNDLE\-INFO" "1" "September 2025" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 2e4b99b28a..83dad5c050 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INIT" "1" "March 2025" "" +.TH "BUNDLE\-INIT" "1" "September 2025" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 deleted file mode 100644 index 54cacaa56d..0000000000 --- a/lib/bundler/man/bundle-inject.1 +++ /dev/null @@ -1,31 +0,0 @@ -.\" generated with Ronn-NG/v0.10.1 -.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INJECT" "1" "March 2025" "" -.SH "NAME" -\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile -.SH "SYNOPSIS" -\fBbundle inject\fR [GEM] [VERSION] [\-\-source=SOURCE] [\-\-group=GROUP] -.SH "DESCRIPTION" -Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\. -.P -This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn't listed yet\. -.P -Example: -.IP "" 4 -.nf -bundle install -bundle inject 'rack' '> 0' -.fi -.IP "" 0 -.P -This will inject the 'rack' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock\. -.P -The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 3\.0\. -.SH "OPTIONS" -.TP -\fB\-\-source=SOURCE\fR -Install gem from the given source\. -.TP -\fB\-\-group=GROUP\fR -Install gem into a bundler group\. - diff --git a/lib/bundler/man/bundle-inject.1.ronn b/lib/bundler/man/bundle-inject.1.ronn deleted file mode 100644 index e2a911b6d8..0000000000 --- a/lib/bundler/man/bundle-inject.1.ronn +++ /dev/null @@ -1,32 +0,0 @@ -bundle-inject(1) -- Add named gem(s) with version requirements to Gemfile -========================================================================= - -## SYNOPSIS - -`bundle inject` [GEM] [VERSION] [--source=SOURCE] [--group=GROUP] - -## DESCRIPTION - -Adds the named gem(s) with their version requirements to the resolved -[`Gemfile(5)`][Gemfile(5)]. - -This command will add the gem to both your [`Gemfile(5)`][Gemfile(5)] and Gemfile.lock if it -isn't listed yet. - -Example: - - bundle install - bundle inject 'rack' '> 0' - -This will inject the 'rack' gem with a version greater than 0 in your -[`Gemfile(5)`][Gemfile(5)] and Gemfile.lock. - -The `bundle inject` command was deprecated in Bundler 2.1 and will be removed in Bundler 3.0. - -## OPTIONS - -* `--source=SOURCE`: - Install gem from the given source. - -* `--group=GROUP`: - Install gem into a bundler group. diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 272f4187ed..68530f3ebb 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INSTALL" "1" "March 2025" "" +.TH "BUNDLE\-INSTALL" "1" "September 2025" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" -\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang=SHEBANG] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]] +\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-lockfile=LOCKFILE] [\-\-no\-cache] [\-\-no\-lock] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] .SH "DESCRIPTION" Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\. .P @@ -12,30 +12,9 @@ If a \fBGemfile\.lock\fR does exist, and you have not updated your Gemfile(5), B .P If a \fBGemfile\.lock\fR does exist, and you have updated your Gemfile(5), Bundler will use the dependencies in the \fBGemfile\.lock\fR for all gems that you did not update, but will re\-resolve the dependencies of gems that you did update\. You can find more information about this update process below under \fICONSERVATIVE UPDATING\fR\. .SH "OPTIONS" -The \fB\-\-clean\fR, \fB\-\-deployment\fR, \fB\-\-frozen\fR, \fB\-\-no\-prune\fR, \fB\-\-path\fR, \fB\-\-shebang\fR, \fB\-\-system\fR, \fB\-\-without\fR and \fB\-\-with\fR options are deprecated because they only make sense if they are applied to every subsequent \fBbundle install\fR run automatically and that requires \fBbundler\fR to silently remember them\. Since \fBbundler\fR will no longer remember CLI flags in future versions, \fBbundle config\fR (see bundle\-config(1)) should be used to apply them permanently\. .TP -\fB\-\-binstubs[=BINSTUBS]\fR -Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it in \fBbin/\fR\. This lets you link the binstub inside of an application to the exact gem version the application needs\. -.IP -Creates a directory (defaults to \fB~/bin\fR when the option is used without a value, or to the given \fB<BINSTUBS>\fR directory otherwise) and places any executables from the gem there\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. -.TP -\fB\-\-clean\fR -On finishing the installation Bundler is going to remove any gems not present in the current Gemfile(5)\. Don't worry, gems currently in use will not be removed\. -.IP -This option is deprecated in favor of the \fBclean\fR setting\. -.TP -\fB\-\-deployment\fR -In \fIdeployment mode\fR, Bundler will 'roll\-out' the bundle for production or CI use\. Please check carefully if you want to have this option enabled in your development environment\. -.IP -This option is deprecated in favor of the \fBdeployment\fR setting\. -.TP -\fB\-\-redownload\fR, \fB\-\-force\fR -Force download every gem, even if the required versions are already available locally\. -.TP -\fB\-\-frozen\fR -Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\. -.IP -This option is deprecated in favor of the \fBfrozen\fR setting\. +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. .TP \fB\-\-full\-index\fR Bundler will not call Rubygems' API endpoint (default) but download and cache a (currently big) index file of all gems\. Performance can be improved for large bundles that seldom change by enabling this option\. @@ -49,21 +28,19 @@ The maximum number of parallel download and install jobs\. The default is the nu \fB\-\-local\fR Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems' cache or in \fBvendor/cache\fR\. Note that if an appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\. .TP +\fB\-\-lockfile=LOCKFILE\fR +The location of the lockfile which Bundler should use\. This defaults to the Gemfile location with \fB\.lock\fR appended\. +.TP \fB\-\-prefer\-local\fR Force using locally installed gems, or gems already present in Rubygems' cache or in \fBvendor/cache\fR, when resolving, even if newer versions are available remotely\. Only attempt to connect to \fBrubygems\.org\fR for gems that are not present locally\. .TP \fB\-\-no\-cache\fR Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\. .TP -\fB\-\-no\-prune\fR -Don't remove stale gems from the cache when the installation finishes\. +\fB\-\-no\-lock\fR +Do not create a lockfile\. Useful if you want to install dependencies but not lock versions of gems\. Recommended for library development, and other situations where the code is expected to work with a range of dependency versions\. .IP -This option is deprecated in favor of the \fBno_prune\fR setting\. -.TP -\fB\-\-path=PATH\fR -The location to install the specified gems to\. This defaults to Rubygems' setting\. Bundler shares this location with Rubygems, \fBgem install \|\.\|\.\|\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \|\.\|\.\|\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\. -.IP -This option is deprecated in favor of the \fBpath\fR setting\. +This has the same effect as using \fBlockfile false\fR in the Gemfile\. See gemfile(5) for more information\. .TP \fB\-\-quiet\fR Do not print progress information to the standard output\. @@ -71,36 +48,16 @@ Do not print progress information to the standard output\. \fB\-\-retry=[<number>]\fR Retry failed network or git requests for \fInumber\fR times\. .TP -\fB\-\-shebang=SHEBANG\fR -Uses the specified ruby executable (usually \fBruby\fR) to execute the scripts created with \fB\-\-binstubs\fR\. In addition, if you use \fB\-\-binstubs\fR together with \fB\-\-shebang jruby\fR these executables will be changed to execute \fBjruby\fR instead\. -.IP -This option is deprecated in favor of the \fBshebang\fR setting\. -.TP \fB\-\-standalone[=<list>]\fR -Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install can be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler's own setup in the manner required\. Using this option implicitly sets \fBpath\fR, which is a [remembered option][REMEMBERED OPTIONS]\. -.TP -\fB\-\-system\fR -Installs the gems specified in the bundle to the system's Rubygems location\. This overrides any previous configuration of \fB\-\-path\fR\. -.IP -This option is deprecated in favor of the \fBsystem\fR setting\. +Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install can be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler's own setup in the manner required\. .TP \fB\-\-trust\-policy=TRUST\-POLICY\fR Apply the Rubygems security policy \fIpolicy\fR, where policy is one of \fBHighSecurity\fR, \fBMediumSecurity\fR, \fBLowSecurity\fR, \fBAlmostNoSecurity\fR, or \fBNoSecurity\fR\. For more details, please see the Rubygems signing documentation linked below in \fISEE ALSO\fR\. .TP \fB\-\-target\-rbconfig=TARGET\-RBCONFIG\fR Path to rbconfig\.rb for the deployment target platform\. -.TP -\fB\-\-with=<list>\fR -A space\-separated list of groups referencing gems to install\. If an optional group is given it is installed\. If a group is given that is in the remembered list of groups given to \-\-without, it is removed from that list\. -.IP -This option is deprecated in favor of the \fBwith\fR setting\. -.TP -\fB\-\-without=<list>\fR -A space\-separated list of groups referencing gems to skip during installation\. If a group is given that is in the remembered list of groups given to \-\-with, it is removed from that list\. -.IP -This option is deprecated in favor of the \fBwithout\fR setting\. .SH "DEPLOYMENT MODE" -Bundler's defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fB\-\-deployment\fR flag\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\. +Bundler's defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fBdeployment\fR setting\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\. .IP "1." 4 A \fBGemfile\.lock\fR is required\. .IP @@ -120,14 +77,14 @@ In development, it's convenient to share the gems used in your application with .IP In deployment, isolation is a more important default\. In addition, the user deploying the application may not have permission to install gems to the system, or the web server may not have permission to read them\. .IP -As a result, \fBbundle install \-\-deployment\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fB\-\-path\fR option\. +As a result, when \fBdeployment\fR is configured, \fBbundle install\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fBpath\fR setting\. .IP "" 0 .SH "INSTALLING GROUPS" By default, \fBbundle install\fR will install all gems in all groups in your Gemfile(5), except those declared for a different platform\. .P -However, you can explicitly tell Bundler to skip installing certain groups with the \fB\-\-without\fR option\. This option takes a space\-separated list of groups\. +However, you can explicitly tell Bundler to skip installing certain groups with the \fBwithout\fR setting\. This setting takes a space\-separated list of groups\. .P -While the \fB\-\-without\fR option will skip \fIinstalling\fR the gems in the specified groups, it will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\. +While the \fBwithout\fR setting will skip \fIinstalling\fR the gems in the specified groups, \fBbundle install\fR will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\. .P This is so that installing a different set of groups on another machine (such as a production server) will not change the gems and versions that you have already developed and tested against\. .P @@ -148,7 +105,7 @@ end .P In this case, \fBsinatra\fR depends on any version of Rack (\fB>= 1\.0\fR), while \fBrack\-perftools\-profiler\fR depends on 1\.x (\fB~> 1\.0\fR)\. .P -When you run \fBbundle install \-\-without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\. +When you configure \fBbundle config without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\. .P This should not cause any problems in practice, because we do not attempt to \fBinstall\fR the gems in the excluded groups, and only evaluate as part of the dependency resolution process\. .P diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn index a3606826df..c7d88bfb73 100644 --- a/lib/bundler/man/bundle-install.1.ronn +++ b/lib/bundler/man/bundle-install.1.ronn @@ -3,28 +3,20 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile ## SYNOPSIS -`bundle install` [--binstubs[=DIRECTORY]] - [--clean] - [--deployment] - [--frozen] +`bundle install` [--force] [--full-index] [--gemfile=GEMFILE] [--jobs=NUMBER] [--local] + [--lockfile=LOCKFILE] [--no-cache] - [--no-prune] - [--path PATH] + [--no-lock] [--prefer-local] [--quiet] - [--redownload] [--retry=NUMBER] - [--shebang=SHEBANG] [--standalone[=GROUP[ GROUP...]]] - [--system] [--trust-policy=TRUST-POLICY] [--target-rbconfig=TARGET-RBCONFIG] - [--with=GROUP[ GROUP...]] - [--without=GROUP[ GROUP...]] ## DESCRIPTION @@ -45,50 +37,8 @@ update process below under [CONSERVATIVE UPDATING][]. ## OPTIONS -The `--clean`, `--deployment`, `--frozen`, `--no-prune`, `--path`, `--shebang`, -`--system`, `--without` and `--with` options are deprecated because they only -make sense if they are applied to every subsequent `bundle install` run -automatically and that requires `bundler` to silently remember them. Since -`bundler` will no longer remember CLI flags in future versions, `bundle config` -(see bundle-config(1)) should be used to apply them permanently. - -* `--binstubs[=BINSTUBS]`: - Binstubs are scripts that wrap around executables. Bundler creates a small Ruby - file (a binstub) that loads Bundler, runs the command, and puts it in `bin/`. - This lets you link the binstub inside of an application to the exact gem - version the application needs. - - Creates a directory (defaults to `~/bin` when the option is used without a - value, or to the given `<BINSTUBS>` directory otherwise) and places any - executables from the gem there. These executables run in Bundler's context. If - used, you might add this directory to your environment's `PATH` variable. For - instance, if the `rails` gem comes with a `rails` executable, this flag will - create a `bin/rails` executable that ensures that all referred dependencies - will be resolved using the bundled gems. - -* `--clean`: - On finishing the installation Bundler is going to remove any gems not present - in the current Gemfile(5). Don't worry, gems currently in use will not be - removed. - - This option is deprecated in favor of the `clean` setting. - -* `--deployment`: - In [deployment mode][DEPLOYMENT MODE], Bundler will 'roll-out' the bundle for - production or CI use. Please check carefully if you want to have this option - enabled in your development environment. - - This option is deprecated in favor of the `deployment` setting. - -* `--redownload`, `--force`: - Force download every gem, even if the required versions are already available - locally. - -* `--frozen`: - Do not allow the Gemfile.lock to be updated after this install. Exits - non-zero if there are going to be changes to the Gemfile.lock. - - This option is deprecated in favor of the `frozen` setting. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. * `--full-index`: Bundler will not call Rubygems' API endpoint (default) but download and cache @@ -112,6 +62,10 @@ automatically and that requires `bundler` to silently remember them. Since appropriate platform-specific gem exists on `rubygems.org` it will not be found. +* `--lockfile=LOCKFILE`: + The location of the lockfile which Bundler should use. This defaults + to the Gemfile location with `.lock` appended. + * `--prefer-local`: Force using locally installed gems, or gems already present in Rubygems' cache or in `vendor/cache`, when resolving, even if newer versions are available @@ -123,19 +77,14 @@ automatically and that requires `bundler` to silently remember them. Since does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install. -* `--no-prune`: - Don't remove stale gems from the cache when the installation finishes. +* `--no-lock`: + Do not create a lockfile. Useful if you want to install dependencies but not + lock versions of gems. Recommended for library development, and other + situations where the code is expected to work with a range of dependency + versions. - This option is deprecated in favor of the `no_prune` setting. - -* `--path=PATH`: - The location to install the specified gems to. This defaults to Rubygems' - setting. Bundler shares this location with Rubygems, `gem install ...` will - have gem installed there, too. Therefore, gems installed without a - `--path ...` setting will show up by calling `gem list`. Accordingly, gems - installed to other locations will not get listed. - - This option is deprecated in favor of the `path` setting. + This has the same effect as using `lockfile false` in the Gemfile. + See gemfile(5) for more information. * `--quiet`: Do not print progress information to the standard output. @@ -143,27 +92,12 @@ automatically and that requires `bundler` to silently remember them. Since * `--retry=[<number>]`: Retry failed network or git requests for <number> times. -* `--shebang=SHEBANG`: - Uses the specified ruby executable (usually `ruby`) to execute the scripts - created with `--binstubs`. In addition, if you use `--binstubs` together with - `--shebang jruby` these executables will be changed to execute `jruby` - instead. - - This option is deprecated in favor of the `shebang` setting. - * `--standalone[=<list>]`: Makes a bundle that can work without depending on Rubygems or Bundler at runtime. A space separated list of groups to install can be specified. Bundler creates a directory named `bundle` and installs the bundle there. It also generates a `bundle/bundler/setup.rb` file to replace Bundler's own setup - in the manner required. Using this option implicitly sets `path`, which is a - [remembered option][REMEMBERED OPTIONS]. - -* `--system`: - Installs the gems specified in the bundle to the system's Rubygems location. - This overrides any previous configuration of `--path`. - - This option is deprecated in favor of the `system` setting. + in the manner required. * `--trust-policy=TRUST-POLICY`: Apply the Rubygems security policy <policy>, where policy is one of @@ -174,26 +108,11 @@ automatically and that requires `bundler` to silently remember them. Since * `--target-rbconfig=TARGET-RBCONFIG`: Path to rbconfig.rb for the deployment target platform. -* `--with=<list>`: - A space-separated list of groups referencing gems to install. If an - optional group is given it is installed. If a group is given that is - in the remembered list of groups given to --without, it is removed - from that list. - - This option is deprecated in favor of the `with` setting. - -* `--without=<list>`: - A space-separated list of groups referencing gems to skip during installation. - If a group is given that is in the remembered list of groups given - to --with, it is removed from that list. - - This option is deprecated in favor of the `without` setting. - ## DEPLOYMENT MODE Bundler's defaults are optimized for development. To switch to -defaults optimized for deployment and for CI, use the `--deployment` -flag. Do not activate deployment mode on development machines, as it +defaults optimized for deployment and for CI, use the `deployment` +setting. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified. 1. A `Gemfile.lock` is required. @@ -225,9 +144,9 @@ will cause an error when the Gemfile(5) is modified. gems to the system, or the web server may not have permission to read them. - As a result, `bundle install --deployment` installs gems to - the `vendor/bundle` directory in the application. This may be - overridden using the `--path` option. + As a result, when `deployment` is configured, `bundle install` installs gems + to the `vendor/bundle` directory in the application. This may be + overridden using the `path` setting. ## INSTALLING GROUPS @@ -235,12 +154,12 @@ By default, `bundle install` will install all gems in all groups in your Gemfile(5), except those declared for a different platform. However, you can explicitly tell Bundler to skip installing -certain groups with the `--without` option. This option takes +certain groups with the `without` setting. This setting takes a space-separated list of groups. -While the `--without` option will skip _installing_ the gems in the -specified groups, it will still _download_ those gems and use them to -resolve the dependencies of every gem in your Gemfile(5). +While the `without` setting will skip _installing_ the gems in the +specified groups, `bundle install` will still _download_ those gems and use them +to resolve the dependencies of every gem in your Gemfile(5). This is so that installing a different set of groups on another machine (such as a production server) will not change the @@ -266,7 +185,7 @@ For a simple illustration, consider the following Gemfile(5): In this case, `sinatra` depends on any version of Rack (`>= 1.0`), while `rack-perftools-profiler` depends on 1.x (`~> 1.0`). -When you run `bundle install --without production` in development, we +When you configure `bundle config without production` in development, we look at the dependencies of `rack-perftools-profiler` as well. That way, you do not spend all your time developing against Rack 2.0, using new APIs unavailable in Rack 1.x, only to have Bundler switch to Rack 1.2 diff --git a/lib/bundler/man/bundle-issue.1 b/lib/bundler/man/bundle-issue.1 index 02d28c91ba..394d6c5469 100644 --- a/lib/bundler/man/bundle-issue.1 +++ b/lib/bundler/man/bundle-issue.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ISSUE" "1" "March 2025" "" +.TH "BUNDLE\-ISSUE" "1" "September 2025" "" .SH "NAME" \fBbundle\-issue\fR \- Get help reporting Bundler issues .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-licenses.1 b/lib/bundler/man/bundle-licenses.1 index d0dbf3913c..2931e42dd7 100644 --- a/lib/bundler/man/bundle-licenses.1 +++ b/lib/bundler/man/bundle-licenses.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LICENSES" "1" "March 2025" "" +.TH "BUNDLE\-LICENSES" "1" "September 2025" "" .SH "NAME" \fBbundle\-licenses\fR \- Print the license of all gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index cd09ccab31..d8bcf20585 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LIST" "1" "March 2025" "" +.TH "BUNDLE\-LIST" "1" "September 2025" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" @@ -19,6 +19,8 @@ bundle list \-\-without\-group test bundle list \-\-only\-group dev .P bundle list \-\-only\-group dev test \-\-paths +.P +bundle list \-\-format json .SH "OPTIONS" .TP \fB\-\-name\-only\fR @@ -32,4 +34,7 @@ A space\-separated list of groups of gems to skip during printing\. .TP \fB\-\-only\-group=<list>\fR A space\-separated list of groups of gems to print\. +.TP +\fB\-\-format=FORMAT\fR +Format output ('json' is the only supported format) diff --git a/lib/bundler/man/bundle-list.1.ronn b/lib/bundler/man/bundle-list.1.ronn index 81bee0ac33..9ec2b13282 100644 --- a/lib/bundler/man/bundle-list.1.ronn +++ b/lib/bundler/man/bundle-list.1.ronn @@ -21,6 +21,8 @@ bundle list --only-group dev bundle list --only-group dev test --paths +bundle list --format json + ## OPTIONS * `--name-only`: @@ -34,3 +36,6 @@ bundle list --only-group dev test --paths * `--only-group=<list>`: A space-separated list of groups of gems to print. + +* `--format=FORMAT`: + Format output ('json' is the only supported format) diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index 8c9b94e8e2..478d173535 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LOCK" "1" "March 2025" "" +.TH "BUNDLE\-LOCK" "1" "September 2025" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index eb4ac2e859..2f13b1329f 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OPEN" "1" "March 2025" "" +.TH "BUNDLE\-OPEN" "1" "September 2025" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 4f8a2cc56f..7e10d202be 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OUTDATED" "1" "March 2025" "" +.TH "BUNDLE\-OUTDATED" "1" "September 2025" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index bdac52f937..6a3a08c3a9 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLATFORM" "1" "March 2025" "" +.TH "BUNDLE\-PLATFORM" "1" "September 2025" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index ded328dbd8..25da562475 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,12 +1,12 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLUGIN" "1" "March 2025" "" +.TH "BUNDLE\-PLUGIN" "1" "September 2025" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" -\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] [\-\-path=\fIpath\fR] +\fBbundle plugin\fR install PLUGINS [\-\-source=SOURCE] [\-\-version=VERSION] [\-\-git=GIT] [\-\-branch=BRANCH|\-\-ref=REF] [\-\-path=PATH] .br -\fBbundle plugin\fR uninstall PLUGINS +\fBbundle plugin\fR uninstall PLUGINS [\-\-all] .br \fBbundle plugin\fR list .br @@ -16,18 +16,23 @@ You can install, uninstall, and list plugin(s) with this command to extend funct .SH "SUB\-COMMANDS" .SS "install" Install the given plugin(s)\. +.P +For example, \fBbundle plugin install bundler\-graph\fR will install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. Note that the global source specified in Gemfile is ignored\. +.P +\fBOPTIONS\fR .TP -\fBbundle plugin install bundler\-graph\fR -Install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. The global source, specified in source in Gemfile is ignored\. -.TP -\fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR -Install bundler\-graph gem from example\.com\. The global source, specified in source in Gemfile is not considered\. +\fB\-\-source=SOURCE\fR +Install the plugin gem from a specific source, rather than from globally configured sources\. +.IP +Example: \fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR .TP -\fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR -You can specify the version of the gem via \fB\-\-version\fR\. +\fB\-\-version=VERSION\fR +Specify a version of the plugin gem to install via \fB\-\-version\fR\. +.IP +Example: \fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR .TP -\fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR -Install bundler\-graph gem from Git repository\. You can use standard Git URLs like: +\fB\-\-git=GIT\fR +Install the plugin gem from a Git repository\. You can use standard Git URLs like: .IP \fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR .br @@ -37,12 +42,25 @@ Install bundler\-graph gem from Git repository\. You can use standard Git URLs l .br \fBfile:///path/to/repo\fR .IP -When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. +Example: \fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR .TP -\fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR -Install bundler\-graph gem from a local path\. +\fB\-\-branch=BRANCH\fR +When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR to use\. +.TP +\fB\-\-ref=REF\fR +When you specify \fB\-\-git\fR, you can use \fB\-\-ref\fR to specify any tag, or commit hash (revision) to use\. +.TP +\fB\-\-path=PATH\fR +Install the plugin gem from a local path\. +.IP +Example: \fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR .SS "uninstall" Uninstall the plugin(s) specified in PLUGINS\. +.P +\fBOPTIONS\fR +.TP +\fB\-\-all\fR +Uninstall all the installed plugins\. If no plugin is installed, then it does nothing\. .SS "list" List the installed plugins and available commands\. .P diff --git a/lib/bundler/man/bundle-plugin.1.ronn b/lib/bundler/man/bundle-plugin.1.ronn index b0a34660ea..b54e0c08b4 100644 --- a/lib/bundler/man/bundle-plugin.1.ronn +++ b/lib/bundler/man/bundle-plugin.1.ronn @@ -3,10 +3,10 @@ bundle-plugin(1) -- Manage Bundler plugins ## SYNOPSIS -`bundle plugin` install PLUGINS [--source=<SOURCE>] [--version=<version>] - [--git=<git-url>] [--branch=<branch>|--ref=<rev>] - [--path=<path>]<br> -`bundle plugin` uninstall PLUGINS<br> +`bundle plugin` install PLUGINS [--source=SOURCE] [--version=VERSION] + [--git=GIT] [--branch=BRANCH|--ref=REF] + [--path=PATH]<br> +`bundle plugin` uninstall PLUGINS [--all]<br> `bundle plugin` list<br> `bundle plugin` help [COMMAND] @@ -20,32 +20,53 @@ You can install, uninstall, and list plugin(s) with this command to extend funct Install the given plugin(s). -* `bundle plugin install bundler-graph`: - Install bundler-graph gem from globally configured sources (defaults to RubyGems.org). The global source, specified in source in Gemfile is ignored. +For example, `bundle plugin install bundler-graph` will install bundler-graph +gem from globally configured sources (defaults to RubyGems.org). Note that the +global source specified in Gemfile is ignored. -* `bundle plugin install bundler-graph --source https://example.com`: - Install bundler-graph gem from example.com. The global source, specified in source in Gemfile is not considered. +**OPTIONS** -* `bundle plugin install bundler-graph --version 0.2.1`: - You can specify the version of the gem via `--version`. +* `--source=SOURCE`: + Install the plugin gem from a specific source, rather than from globally configured sources. -* `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph`: - Install bundler-graph gem from Git repository. You can use standard Git URLs like: + Example: `bundle plugin install bundler-graph --source https://example.com` + +* `--version=VERSION`: + Specify a version of the plugin gem to install via `--version`. + + Example: `bundle plugin install bundler-graph --version 0.2.1` + +* `--git=GIT`: + Install the plugin gem from a Git repository. You can use standard Git URLs like: `ssh://[user@]host.xz[:port]/path/to/repo.git`<br> `http[s]://host.xz[:port]/path/to/repo.git`<br> `/path/to/repo`<br> `file:///path/to/repo` - When you specify `--git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. + Example: `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph` + +* `--branch=BRANCH`: + When you specify `--git`, you can use `--branch` to use. -* `bundle plugin install bundler-graph --path ../bundler-graph`: - Install bundler-graph gem from a local path. +* `--ref=REF`: + When you specify `--git`, you can use `--ref` to specify any tag, or commit + hash (revision) to use. + +* `--path=PATH`: + Install the plugin gem from a local path. + + Example: `bundle plugin install bundler-graph --path ../bundler-graph` ### uninstall Uninstall the plugin(s) specified in PLUGINS. +**OPTIONS** + +* `--all`: + Uninstall all the installed plugins. If no plugin is installed, then it does nothing. + ### list List the installed plugins and available commands. diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 294ef179a7..a8316b5cca 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PRISTINE" "1" "March 2025" "" +.TH "BUNDLE\-PRISTINE" "1" "September 2025" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 2e42a12de3..4dc7a03b9f 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,21 +1,15 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-REMOVE" "1" "March 2025" "" +.TH "BUNDLE\-REMOVE" "1" "September 2025" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" -\fBbundle remove [GEM [GEM \|\.\|\.\|\.]] [\-\-install]\fR +`bundle remove [GEM [GEM \|\.\|\.\|\.]] .SH "DESCRIPTION" Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid\. If a gem cannot be removed, a warning is printed\. If a gem is already absent from the Gemfile, and error is raised\. -.SH "OPTIONS" -.TP -\fB\-\-install\fR -Runs \fBbundle install\fR after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s)\. .P Example: .P bundle remove rails .P bundle remove rails rack -.P -bundle remove rails rack \-\-install diff --git a/lib/bundler/man/bundle-remove.1.ronn b/lib/bundler/man/bundle-remove.1.ronn index ceb1a980be..49cb4dc1fd 100644 --- a/lib/bundler/man/bundle-remove.1.ronn +++ b/lib/bundler/man/bundle-remove.1.ronn @@ -3,21 +3,14 @@ bundle-remove(1) -- Removes gems from the Gemfile ## SYNOPSIS -`bundle remove [GEM [GEM ...]] [--install]` +`bundle remove [GEM [GEM ...]] ## DESCRIPTION Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If a gem cannot be removed, a warning is printed. If a gem is already absent from the Gemfile, and error is raised. -## OPTIONS - -* `--install`: - Runs `bundle install` after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s). - Example: bundle remove rails bundle remove rails rack - -bundle remove rails rack --install diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index d460e7a256..901460962c 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-SHOW" "1" "March 2025" "" +.TH "BUNDLE\-SHOW" "1" "September 2025" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" -\fBbundle show\fR [GEM] [\-\-paths] [\-\-outdated] +\fBbundle show\fR [GEM] [\-\-paths] .SH "DESCRIPTION" Without the [GEM] option, \fBshow\fR will print a list of the names and versions of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by name\. .P @@ -13,7 +13,4 @@ Calling show with [GEM] will list the exact location of that gem on your machine .TP \fB\-\-paths\fR List the paths of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by gem name\. -.TP -\fB\-\-outdated\fR -Show verbose output including whether gems are outdated\. diff --git a/lib/bundler/man/bundle-show.1.ronn b/lib/bundler/man/bundle-show.1.ronn index 6e80b04696..a6a59a1445 100644 --- a/lib/bundler/man/bundle-show.1.ronn +++ b/lib/bundler/man/bundle-show.1.ronn @@ -5,7 +5,6 @@ bundle-show(1) -- Shows all the gems in your bundle, or the path to a gem `bundle show` [GEM] [--paths] - [--outdated] ## DESCRIPTION @@ -20,6 +19,3 @@ machine. * `--paths`: List the paths of all gems that are required by your [`Gemfile(5)`][Gemfile(5)], sorted by gem name. - -* `--outdated`: - Show verbose output including whether gems are outdated. diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 855a5049aa..2f9932dc1e 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-UPDATE" "1" "March 2025" "" +.TH "BUNDLE\-UPDATE" "1" "September 2025" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" -\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-redownload] [\-\-strict] [\-\-conservative] +\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative] .SH "DESCRIPTION" Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\. .P @@ -29,6 +29,9 @@ Update the locked version of Ruby to the current version of Ruby\. \fB\-\-bundler[=BUNDLER]\fR Update the locked version of bundler to the invoked bundler version\. .TP +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. +.TP \fB\-\-full\-index\fR Fall back to using the single\-file index of all gems\. .TP @@ -44,9 +47,6 @@ Retry failed network or git requests for \fInumber\fR times\. \fB\-\-quiet\fR Only output warnings and errors\. .TP -\fB\-\-redownload\fR, \fB\-\-force\fR -Force downloading every gem\. -.TP \fB\-\-patch\fR Prefer updating only to next patch version\. .TP diff --git a/lib/bundler/man/bundle-update.1.ronn b/lib/bundler/man/bundle-update.1.ronn index 1b8b31951d..bfe381677c 100644 --- a/lib/bundler/man/bundle-update.1.ronn +++ b/lib/bundler/man/bundle-update.1.ronn @@ -9,13 +9,13 @@ bundle-update(1) -- Update your gems to the latest available versions [--local] [--ruby] [--bundler[=VERSION]] + [--force] [--full-index] [--gemfile=GEMFILE] [--jobs=NUMBER] [--quiet] [--patch|--minor|--major] [--pre] - [--redownload] [--strict] [--conservative] @@ -54,6 +54,9 @@ gem. * `--bundler[=BUNDLER]`: Update the locked version of bundler to the invoked bundler version. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. + * `--full-index`: Fall back to using the single-file index of all gems. @@ -70,9 +73,6 @@ gem. * `--quiet`: Only output warnings and errors. -* `--redownload`, `--force`: - Force downloading every gem. - * `--patch`: Prefer updating only to next patch version. diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 17add566d8..6462ec7958 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-VERSION" "1" "March 2025" "" +.TH "BUNDLE\-VERSION" "1" "September 2025" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 deleted file mode 100644 index 17e6f90cca..0000000000 --- a/lib/bundler/man/bundle-viz.1 +++ /dev/null @@ -1,30 +0,0 @@ -.\" generated with Ronn-NG/v0.10.1 -.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-VIZ" "1" "March 2025" "" -.SH "NAME" -\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile -.SH "SYNOPSIS" -\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP] -.SH "DESCRIPTION" -\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\. -.P -The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\. -.P -\fBviz\fR command was deprecated in Bundler 2\.2\. Use bundler\-graph plugin \fIhttps://github\.com/rubygems/bundler\-graph\fR instead\. -.SH "OPTIONS" -.TP -\fB\-\-file=FILE\fR, \fB\-f=FILE\fR -The name to use for the generated file\. See \fB\-\-format\fR option -.TP -\fB\-\-format=FORMAT\fR, \fB\-F=FORMAT\fR -This is output format option\. Supported format is png, jpg, svg, dot \|\.\|\.\|\. -.TP -\fB\-\-requirements\fR, \fB\-R\fR -Set to show the version of each required dependency\. -.TP -\fB\-\-version\fR, \fB\-v\fR -Set to show each gem version\. -.TP -\fB\-\-without=<list>\fR, \fB\-W=<list>\fR -Exclude gems that are part of the specified named group\. - diff --git a/lib/bundler/man/bundle-viz.1.ronn b/lib/bundler/man/bundle-viz.1.ronn deleted file mode 100644 index 730b0eed22..0000000000 --- a/lib/bundler/man/bundle-viz.1.ronn +++ /dev/null @@ -1,36 +0,0 @@ -bundle-viz(1) -- Generates a visual dependency graph for your Gemfile -===================================================================== - -## SYNOPSIS - -`bundle viz` [--file=FILE] - [--format=FORMAT] - [--requirements] - [--version] - [--without=GROUP GROUP] - -## DESCRIPTION - -`viz` generates a PNG file of the current `Gemfile(5)` as a dependency graph. -`viz` requires the ruby-graphviz gem (and its dependencies). - -The associated gems must also be installed via [`bundle install(1)`](bundle-install.1.html). - -`viz` command was deprecated in Bundler 2.2. Use [bundler-graph plugin](https://github.com/rubygems/bundler-graph) instead. - -## OPTIONS - -* `--file=FILE`, `-f=FILE`: - The name to use for the generated file. See `--format` option - -* `--format=FORMAT`, `-F=FORMAT`: - This is output format option. Supported format is png, jpg, svg, dot ... - -* `--requirements`, `-R`: - Set to show the version of each required dependency. - -* `--version`, `-v`: - Set to show each gem version. - -* `--without=<list>`, `-W=<list>`: - Exclude gems that are part of the specified named group. diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 3b40f58210..9f7feb4133 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE" "1" "March 2025" "" +.TH "BUNDLE" "1" "September 2025" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" @@ -66,9 +66,6 @@ Open an installed gem in the editor \fBbundle lock(1)\fR \fIbundle\-lock\.1\.html\fR Generate a lockfile for your dependencies .TP -\fBbundle viz(1)\fR \fIbundle\-viz\.1\.html\fR (deprecated) -Generate a visual representation of your dependencies -.TP \fBbundle init(1)\fR \fIbundle\-init\.1\.html\fR Generate a simple \fBGemfile\fR, placed in the current directory .TP @@ -94,9 +91,3 @@ Manage Bundler plugins Prints Bundler version information .SH "PLUGINS" When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named \fBbundler\-<command>\fR and execute it, passing down any extra arguments to it\. -.SH "OBSOLETE" -These commands are obsolete and should no longer be used: -.IP "\(bu" 4 -\fBbundle inject(1)\fR -.IP "" 0 - diff --git a/lib/bundler/man/bundle.1.ronn b/lib/bundler/man/bundle.1.ronn index 8245effabd..1c2b3df7af 100644 --- a/lib/bundler/man/bundle.1.ronn +++ b/lib/bundler/man/bundle.1.ronn @@ -76,9 +76,6 @@ We divide `bundle` subcommands into primary commands and utilities: * [`bundle lock(1)`](bundle-lock.1.html): Generate a lockfile for your dependencies -* [`bundle viz(1)`](bundle-viz.1.html) (deprecated): - Generate a visual representation of your dependencies - * [`bundle init(1)`](bundle-init.1.html): Generate a simple `Gemfile`, placed in the current directory @@ -108,9 +105,3 @@ We divide `bundle` subcommands into primary commands and utilities: When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named `bundler-<command>` and execute it, passing down any extra arguments to it. - -## OBSOLETE - -These commands are obsolete and should no longer be used: - -* `bundle inject(1)` diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index f52864a2bf..a8c055a0c1 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "GEMFILE" "5" "March 2025" "" +.TH "GEMFILE" "5" "September 2025" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" @@ -469,4 +469,35 @@ For implicit gems (dependencies of explicit gems), any source, git, or path repo .IP "3." 4 If neither of the above conditions are met, the global source will be used\. If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1\.13, so Bundler prints a warning and will abort with an error in the future\. .IP "" 0 +.SH "LOCKFILE" +By default, Bundler will create a lockfile by adding \fB\.lock\fR to the end of the Gemfile name\. To change this, use the \fBlockfile\fR method: +.IP "" 4 +.nf +lockfile "/path/to/lockfile\.lock" +.fi +.IP "" 0 +.P +This is useful when you want to use different lockfiles per ruby version or platform\. +.P +To avoid writing a lock file, use \fBfalse\fR as the argument: +.IP "" 4 +.nf +lockfile false +.fi +.IP "" 0 +.P +This is useful for library development and other situations where the code is expected to work with a range of dependency versions\. +.SS "LOCKFILE PRECEDENCE" +When determining path to the lockfile or whether to create a lockfile, the following precedence is used: +.IP "1." 4 +The \fBbundle install\fR \fB\-\-no\-lock\fR option (which disables lockfile creation)\. +.IP "2." 4 +The \fBbundle install\fR \fB\-\-lockfile\fR option\. +.IP "3." 4 +The \fBBUNDLE_LOCKFILE\fR environment variable\. +.IP "4." 4 +The \fBlockfile\fR method in the Gemfile\. +.IP "5." 4 +The default behavior of adding \fB\.lock\fR to the end of the Gemfile name\. +.IP "" 0 diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index 802549737e..18d7bb826e 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -556,3 +556,31 @@ bundler uses the following priority order: If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1.13, so Bundler prints a warning and will abort with an error in the future. + +## LOCKFILE + +By default, Bundler will create a lockfile by adding `.lock` to the end of the +Gemfile name. To change this, use the `lockfile` method: + + lockfile "/path/to/lockfile.lock" + +This is useful when you want to use different lockfiles per ruby version or +platform. + +To avoid writing a lock file, use `false` as the argument: + + lockfile false + +This is useful for library development and other situations where the code is +expected to work with a range of dependency versions. + +### LOCKFILE PRECEDENCE + +When determining path to the lockfile or whether to create a lockfile, the +following precedence is used: + +1. The `bundle install` `--no-lock` option (which disables lockfile creation). +1. The `bundle install` `--lockfile` option. +1. The `BUNDLE_LOCKFILE` environment variable. +1. The `lockfile` method in the Gemfile. +1. The default behavior of adding `.lock` to the end of the Gemfile name. diff --git a/lib/bundler/man/index.txt b/lib/bundler/man/index.txt index 3ea3495f1b..f610ba852a 100644 --- a/lib/bundler/man/index.txt +++ b/lib/bundler/man/index.txt @@ -15,7 +15,6 @@ bundle-gem(1) bundle-gem.1 bundle-help(1) bundle-help.1 bundle-info(1) bundle-info.1 bundle-init(1) bundle-init.1 -bundle-inject(1) bundle-inject.1 bundle-install(1) bundle-install.1 bundle-issue(1) bundle-issue.1 bundle-licenses(1) bundle-licenses.1 @@ -30,4 +29,3 @@ bundle-remove(1) bundle-remove.1 bundle-show(1) bundle-show.1 bundle-update(1) bundle-update.1 bundle-version(1) bundle-version.1 -bundle-viz(1) bundle-viz.1 diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb index ece9fb8679..479818e5ec 100644 --- a/lib/bundler/match_platform.rb +++ b/lib/bundler/match_platform.rb @@ -1,23 +1,42 @@ # frozen_string_literal: true -require_relative "gem_helpers" - module Bundler module MatchPlatform - include GemHelpers + def installable_on_platform?(target_platform) # :nodoc: + return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform) + return true if Gem::Platform.new(platform) === target_platform - def match_platform(p) - MatchPlatform.platforms_match?(platform, p) + false end - def self.platforms_match?(gemspec_platform, local_platform) - return true if gemspec_platform.nil? - return true if gemspec_platform == Gem::Platform::RUBY - return true if local_platform == gemspec_platform - gemspec_platform = Gem::Platform.new(gemspec_platform) - return true if gemspec_platform === local_platform + def self.select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false) + matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked) - false + Gem::Platform.sort_and_filter_best_platform_match(matching, platform) + end + + def self.select_best_local_platform_match(specs, force_ruby: false) + local = Bundler.local_platform + matching = select_all_platform_match(specs, local, force_ruby: force_ruby).filter_map(&:materialized_for_installation) + + Gem::Platform.sort_best_platform_match(matching, local) + end + + def self.select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false) + matching = specs.select {|spec| spec.installable_on_platform?(force_ruby ? Gem::Platform::RUBY : platform) } + + specs.each(&:force_ruby_platform!) if force_ruby + + if prefer_locked + locked_originally = matching.select {|spec| spec.is_a?(::Bundler::LazySpecification) } + return locked_originally if locked_originally.any? + end + + matching + end + + def self.generic_local_platform_is_ruby? + Bundler.generic_local_platform == Gem::Platform::RUBY end end end diff --git a/lib/bundler/materialization.rb b/lib/bundler/materialization.rb index 6542c07649..82e48464a7 100644 --- a/lib/bundler/materialization.rb +++ b/lib/bundler/materialization.rb @@ -22,14 +22,14 @@ module Bundler @specs ||= if @candidates.nil? [] elsif platform - GemHelpers.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform) + MatchPlatform.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform) else - GemHelpers.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform) + MatchPlatform.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform) end end def dependencies - specs.first.runtime_dependencies.map {|d| [d, platform] } + (materialized_spec || specs.first).runtime_dependencies.map {|d| [d, platform] } end def materialized_spec diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 44129cc0ff..fd6da6cf6d 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -220,7 +220,7 @@ module Bundler # # @param [String] event def hook(event, *args, &arg_blk) - return unless Bundler.feature_flag.plugins? + return unless Bundler.settings[:plugins] unless Events.defined_event?(event) raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events" end diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index ac3c3ea7f3..853ad9edca 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -43,16 +43,6 @@ module Bundler private def check_sources_consistency!(options) - if options.key?(:git) && options.key?(:local_git) - raise InvalidOption, "Remote and local plugin git sources can't be both specified" - end - - # back-compat; local_git is an alias for git - if options.key?(:local_git) - Bundler::SharedHelpers.major_deprecation(2, "--local_git is deprecated, use --git") - options[:git] = options.delete(:local_git) - end - if (options.keys & [:source, :git, :path]).length > 1 raise InvalidOption, "Only one of --source, --git, or --path may be specified" end diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb index 746996de55..d929ade29e 100644 --- a/lib/bundler/plugin/source_list.rb +++ b/lib/bundler/plugin/source_list.rb @@ -23,7 +23,7 @@ module Bundler private - def rubygems_aggregate_class + def source_class Plugin::Installer::Rubygems end end diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index f5d1c57a11..1dbf565d46 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -14,8 +14,6 @@ module Bundler require_relative "resolver/root" require_relative "resolver/strategy" - include GemHelpers - def initialize(base, gem_version_promoter, most_specific_locked_platform = nil) @source_requirements = base.source_requirements @base = base @@ -167,7 +165,7 @@ module Bundler PubGrub::VersionConstraint.new(package, range: range) end - def versions_for(package, range=VersionRange.any) + def versions_for(package, range = VersionRange.any) range.select_versions(@sorted_versions[package]) end @@ -273,7 +271,7 @@ module Bundler next groups if platform_specs.all?(&:empty?) end - ruby_specs = select_best_platform_match(specs, Gem::Platform::RUBY) + ruby_specs = MatchPlatform.select_best_platform_match(specs, Gem::Platform::RUBY) ruby_group = Resolver::SpecGroup.new(ruby_specs) unless ruby_group.empty? diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb index 0e86a4f84d..3906be3f57 100644 --- a/lib/bundler/resolver/package.rb +++ b/lib/bundler/resolver/package.rb @@ -21,6 +21,7 @@ module Bundler @locked_version = locked_specs.version_for(name) @unlock = unlock @dependency = dependency || Dependency.new(name, @locked_version) + @platforms |= [Gem::Platform::RUBY] if @dependency.default_force_ruby_platform @top_level = !dependency.nil? @prerelease = @dependency.prerelease? || @locked_version&.prerelease? || prerelease ? :consider_first : :ignore @prefer_local = prefer_local @@ -30,7 +31,7 @@ module Bundler def platform_specs(specs) platforms.map do |platform| prefer_locked = @new_platforms.include?(platform) ? false : !unlock? - GemHelpers.select_best_platform_match(specs, platform, prefer_locked: prefer_locked) + MatchPlatform.select_best_platform_match(specs, platform, prefer_locked: prefer_locked) end end diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb index cd88253f46..5e52f38c8f 100644 --- a/lib/bundler/ruby_dsl.rb +++ b/lib/bundler/ruby_dsl.rb @@ -42,21 +42,26 @@ module Bundler # Loads the file relative to the dirname of the Gemfile itself. def normalize_ruby_file(filename) file_content = Bundler.read_file(gemfile.dirname.join(filename)) - # match "ruby-3.2.2", ruby = "3.2.2" or "ruby 3.2.2" capturing version string up to the first space or comment - if /^ # Start of line - ruby # Literal "ruby" - [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) - (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) - "? # Optional opening quote - ( # Start capturing group - [^\s#"]+ # One or more chars that aren't spaces, #, or quotes - ) # End capturing group - "? # Optional closing quote - /x.match(file_content) - $1 + # match "ruby-3.2.2", ruby = "3.2.2", ruby = '3.2.2' or "ruby 3.2.2" capturing version string up to the first space or comment + version_match = /^ # Start of line + ruby # Literal "ruby" + [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) + (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) + (?: + "([^"]+)" # Double quoted version + | + '([^']+)' # Single quoted version + | + ([^\s#"']+) # Unquoted version + ) + /x.match(file_content) + if version_match + version_match[1] || version_match[2] || version_match[3] else file_content.strip end + rescue Errno::ENOENT + raise GemfileError, "Could not find version file #{filename}" end end end diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 0ed5cbc6ca..7f60dde476 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -43,7 +43,6 @@ module Bundler def to_s(versions = self.versions) output = String.new("ruby #{versions_string(versions)}") - output << "p#{patchlevel}" if patchlevel && patchlevel != "-1" output << " (#{engine} #{versions_string(engine_versions)})" unless engine == "ruby" output @@ -72,8 +71,7 @@ module Bundler def ==(other) versions == other.versions && engine == other.engine && - engine_versions == other.engine_versions && - patchlevel == other.patchlevel + engine_versions == other.engine_versions end def host diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 1f3fb0fdde..fedf44b0e6 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -13,15 +13,6 @@ require "rubygems" unless defined?(Gem) # `Gem::Source` from the redefined `Gem::Specification#source`. require "rubygems/source" -# Cherry-pick fixes to `Gem.ruby_version` to be useful for modern Bundler -# versions and ignore patchlevels -# (https://github.com/rubygems/rubygems/pull/5472, -# https://github.com/rubygems/rubygems/pull/5486). May be removed once RubyGems -# 3.3.12 support is dropped. -unless Gem.ruby_version.to_s == RUBY_VERSION || RUBY_PATCHLEVEL == -1 - Gem.instance_variable_set(:@ruby_version, Gem::Version.new(RUBY_VERSION)) -end - module Gem # Can be removed once RubyGems 3.5.11 support is dropped unless Gem.respond_to?(:freebsd_platform?) @@ -61,81 +52,122 @@ module Gem require "rubygems/platform" class Platform - JAVA = Gem::Platform.new("java") - MSWIN = Gem::Platform.new("mswin32") - MSWIN64 = Gem::Platform.new("mswin64") - MINGW = Gem::Platform.new("x86-mingw32") - X64_MINGW = [Gem::Platform.new("x64-mingw32"), - Gem::Platform.new("x64-mingw-ucrt")].freeze - UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") - WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].flatten.freeze - X64_LINUX = Gem::Platform.new("x86_64-linux") - X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") - - if X64_LINUX === X64_LINUX_MUSL - remove_method :=== - - def ===(other) - return nil unless Gem::Platform === other - - # universal-mingw32 matches x64-mingw-ucrt - return true if (@cpu == "universal" || other.cpu == "universal") && - @os.start_with?("mingw") && other.os.start_with?("mingw") - - # cpu - ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu || - (@cpu == "arm" && other.cpu.start_with?("armv"))) && - - # os - @os == other.os && - - # version - ( - (@os != "linux" && (@version.nil? || other.version.nil?)) || - (@os == "linux" && (normalized_linux_version_ext == other.normalized_linux_version_ext || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) || - @version == other.version - ) - end + # Can be removed once RubyGems 3.6.9 support is dropped + unless respond_to?(:generic) + JAVA = Gem::Platform.new("java") # :nodoc: + MSWIN = Gem::Platform.new("mswin32") # :nodoc: + MSWIN64 = Gem::Platform.new("mswin64") # :nodoc: + MINGW = Gem::Platform.new("x86-mingw32") # :nodoc: + X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc: + X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc: + UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc: + WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc: + X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc: + X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc: + + GENERICS = [JAVA, *WINDOWS].freeze # :nodoc: + private_constant :GENERICS + + GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc: + private_constant :GENERIC_CACHE - # This is a copy of RubyGems 3.3.23 or higher `normalized_linux_method`. - # Once only 3.3.23 is supported, we can use the method in RubyGems. - def normalized_linux_version_ext - return nil unless @version + class << self + ## + # Returns the generic platform for the given platform. - without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "") - return nil if without_gnu_nor_abi_modifiers.empty? + def generic(platform) + return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY - without_gnu_nor_abi_modifiers - end - end - end + GENERIC_CACHE[platform] ||= begin + found = GENERICS.find do |match| + platform === match + end + found || Gem::Platform::RUBY + end + end - Platform.singleton_class.module_eval do - unless Platform.singleton_methods.include?(:match_spec?) - def match_spec?(spec) - match_gem?(spec.platform, spec.name) - end + ## + # Returns the platform specificity match for the given spec platform and user platform. - def match_gem?(platform, gem_name) - match_platforms?(platform, Gem.platforms) - end - end + def platform_specificity_match(spec_platform, user_platform) + return -1 if spec_platform == user_platform + return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY + + os_match(spec_platform, user_platform) + + cpu_match(spec_platform, user_platform) * 10 + + version_match(spec_platform, user_platform) * 100 + end + + ## + # Sorts and filters the best platform match for the given matching specs and platform. - match_platforms_defined = Gem::Platform.respond_to?(:match_platforms?, true) + def sort_and_filter_best_platform_match(matching, platform) + return matching if matching.one? - if !match_platforms_defined || Gem::Platform.send(:match_platforms?, Gem::Platform::X64_LINUX_MUSL, [Gem::Platform::X64_LINUX]) + exact = matching.select {|spec| spec.platform == platform } + return exact if exact.any? - private + sorted_matching = sort_best_platform_match(matching, platform) + exemplary_spec = sorted_matching.first - remove_method :match_platforms? if match_platforms_defined + sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) } + end + + ## + # Sorts the best platform match for the given matching specs and platform. + + def sort_best_platform_match(matching, platform) + matching.sort_by.with_index do |spec, i| + [ + platform_specificity_match(spec.platform, platform), + i, # for stable sort + ] + end + end + + private + + def same_specificity?(platform, spec, exemplary_spec) + platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) + end - def match_platforms?(platform, platforms) - platforms.any? do |local_platform| - platform.nil? || - local_platform == platform || - (local_platform != Gem::Platform::RUBY && platform =~ local_platform) + def same_deps?(spec, exemplary_spec) + spec.required_ruby_version == exemplary_spec.required_ruby_version && + spec.required_rubygems_version == exemplary_spec.required_rubygems_version && + spec.dependencies.sort == exemplary_spec.dependencies.sort + end + + def os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end end end + end end @@ -144,9 +176,6 @@ module Gem # Can be removed once RubyGems 3.5.14 support is dropped VALIDATES_FOR_RESOLUTION = Specification.new.respond_to?(:validate_for_resolution).freeze - # Can be removed once RubyGems 3.3.15 support is dropped - FLATTENS_REQUIRED_PATHS = Specification.new.respond_to?(:flatten_require_paths).freeze - class Specification # Can be removed once RubyGems 3.5.15 support is dropped correct_array_attributes = @@default_value.select {|_k,v| v.is_a?(Array) }.keys @@ -158,7 +187,6 @@ module Gem require_relative "match_platform" include ::Bundler::MatchMetadata - include ::Bundler::MatchPlatform attr_accessor :remote, :relative_loaded_from @@ -214,23 +242,6 @@ module Gem full_gem_path end - unless const_defined?(:LATEST_RUBY_WITHOUT_PATCH_VERSIONS) - LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1") - - alias_method :rg_required_ruby_version=, :required_ruby_version= - def required_ruby_version=(req) - self.rg_required_ruby_version = req - - @required_ruby_version.requirements.map! do |op, v| - if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4 - [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))] - else - [op, v] - end - end - end - end - def insecurely_materialized? false end @@ -272,25 +283,16 @@ module Gem end end - unless FLATTENS_REQUIRED_PATHS - def flatten_require_paths - return unless raw_require_paths.first.is_a?(Array) - - warn "#{name} #{version} includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this" - raw_require_paths.flatten! - end + if Gem.rubygems_version < Gem::Version.new("3.5.22") + module FixPathSourceMissingExtensions + def missing_extensions? + return false if %w[Bundler::Source::Path Bundler::Source::Gemspec].include?(source.class.name) - class << self - module RequirePathFlattener - def from_yaml(input) - spec = super(input) - spec.flatten_require_paths - spec - end + super end - - prepend RequirePathFlattener end + + prepend FixPathSourceMissingExtensions end private @@ -401,6 +403,11 @@ module Gem @ignored = missing_extensions? end end + + # Can be removed once RubyGems 3.6.9 support is dropped + unless new.respond_to?(:installable_on_platform?) + include(::Bundler::MatchPlatform) + end end require "rubygems/name_tuple" @@ -410,7 +417,7 @@ module Gem unless Gem::NameTuple.new("a", Gem::Version.new("1"), Gem::Platform.new("x86_64-linux")).platform.is_a?(String) alias_method :initialize_with_platform, :initialize - def initialize(name, version, platform=Gem::Platform::RUBY) + def initialize(name, version, platform = Gem::Platform::RUBY) if Gem::Platform === platform initialize_with_platform(name, version, platform.to_s) else @@ -471,15 +478,4 @@ module Gem Package::TarReader::Entry.prepend(FixFullNameEncoding) end - - require "rubygems/uri" - - # Can be removed once RubyGems 3.3.15 support is dropped - unless Gem::Uri.respond_to?(:redact) - class Uri - def self.redact(uri) - new(uri).redacted - end - end - end end diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 1af1b85ff0..64ce6193d3 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -69,7 +69,7 @@ module Bundler end def generate_plugins - return unless Gem::Installer.instance_methods(false).include?(:generate_plugins) + return unless Gem::Installer.method_defined?(:generate_plugins, false) latest = Gem::Specification.stubs_for(spec.name).first return if latest && latest.version > spec.version @@ -103,6 +103,10 @@ module Bundler end end + def build_jobs + Bundler.settings[:jobs] || super + end + def build_extensions extension_cache_path = options[:bundler_extension_cache_path] extension_dir = spec.extension_dir diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index eddf36278c..e04ef23259 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -214,16 +214,11 @@ module Bundler e.requirement = dep.requirement raise e end - - # backwards compatibility shim, see https://github.com/rubygems/bundler/issues/5102 - kernel_class.send(:public, :gem) if Bundler.feature_flag.setup_makes_kernel_gem_public? end end # Used to give better error messages when activating specs outside of the current bundle def replace_bin_path(specs_by_name) - gem_class = (class << Gem; self; end) - redefine_method(gem_class, :find_spec_for_exe) do |gem_name, *args| exec_name = args.first raise ArgumentError, "you must supply exec_name" unless exec_name @@ -345,9 +340,13 @@ module Bundler Gem::Specification.all = specs end - redefine_method((class << Gem; self; end), :finish_resolve) do |*| + redefine_method(gem_class, :finish_resolve) do |*| [] end + + redefine_method(gem_class, :load_plugins) do |*| + load_plugin_files specs.flat_map(&:plugins) + end end def plain_specs @@ -417,11 +416,7 @@ module Bundler end def all_specs - SharedHelpers.major_deprecation 2, "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs" - - Gem::Specification.stubs.map do |stub| - StubSpecification.from_stub(stub) - end + SharedHelpers.feature_removed! "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs" end def installed_specs @@ -437,7 +432,7 @@ module Bundler end def find_bundler(version) - find_name("bundler").find {|s| s.version.to_s == version } + find_name("bundler").find {|s| s.version.to_s == version.to_s } end def find_name(name) @@ -447,6 +442,12 @@ module Bundler def default_stubs Gem::Specification.default_stubs("*.gemspec") end + + private + + def gem_class + class << Gem; self; end + end end def self.rubygems diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 444b085bed..5280e72aa2 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -71,7 +71,7 @@ module Bundler raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end - rescue RuntimeError => e + rescue StandardError => e raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end @@ -174,7 +174,14 @@ module Bundler spec_cache_paths = [] spec_gemspec_paths = [] spec_extension_paths = [] - Bundler.rubygems.add_default_gems_to(specs).values.each do |spec| + specs_to_keep = Bundler.rubygems.add_default_gems_to(specs).values + + current_bundler = Bundler.rubygems.find_bundler(Bundler.gem_version) + if current_bundler + specs_to_keep << current_bundler + end + + specs_to_keep.each do |spec| spec_gem_paths << spec.full_gem_path # need to check here in case gems are nested like for the rails git repo md = %r{(.+bundler/gems/.+-[a-f0-9]{7,12})}.match(spec.full_gem_path) @@ -240,7 +247,11 @@ module Bundler cached.each do |path| Bundler.ui.info " * #{File.basename(path)}" - File.delete(path) + + begin + File.delete(path) + rescue Errno::ENOENT + end end end end diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb index 2aeac6be52..1db77fd46b 100644 --- a/lib/bundler/self_manager.rb +++ b/lib/bundler/self_manager.rb @@ -7,13 +7,15 @@ module Bundler # class SelfManager def restart_with_locked_bundler_if_needed - return unless needs_switching? && installed? + restart_version = find_restart_version + return unless restart_version && installed?(restart_version) restart_with(restart_version) end def install_locked_bundler_and_restart_with_it_if_needed - return unless needs_switching? + restart_version = find_restart_version + return unless restart_version if restart_version == lockfile_version Bundler.ui.info \ @@ -29,8 +31,6 @@ module Bundler end def update_bundler_and_restart_with_it_if_needed(target) - return unless autoswitching_applies? - spec = resolve_update_version_from(target) return unless spec @@ -38,7 +38,7 @@ module Bundler Bundler.ui.info "Updating bundler to #{version}." - install(spec) + install(spec) unless installed?(version) restart_with(version) end @@ -68,47 +68,37 @@ module Bundler def restart_with(version) configured_gem_home = ENV["GEM_HOME"] + configured_orig_gem_home = ENV["BUNDLER_ORIG_GEM_HOME"] configured_gem_path = ENV["GEM_PATH"] + configured_orig_gem_path = ENV["BUNDLER_ORIG_GEM_PATH"] - # Bundler specs need some stuff to be required before Bundler starts - # running, for example, for faking the compact index API. However, these - # flags are lost when we reexec to a different version of Bundler. In the - # future, we may be able to properly reconstruct the original Ruby - # invocation (see https://bugs.ruby-lang.org/issues/6648), but for now - # there's no way to do it, so we need to be explicit about how to re-exec. - # This may be a feature end users request at some point, but maybe by that - # time, we have builtin tools to do. So for now, we use an undocumented - # ENV variable only for our specs. - bundler_spec_original_cmd = ENV["BUNDLER_SPEC_ORIGINAL_CMD"] - if bundler_spec_original_cmd - require "shellwords" - cmd = [*Shellwords.shellsplit(bundler_spec_original_cmd), *ARGV] - else - argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0 - cmd = [argv0, *ARGV] - cmd.unshift(Gem.ruby) unless File.executable?(argv0) - end + argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0 + cmd = [argv0, *ARGV] + cmd.unshift(Gem.ruby) unless File.executable?(argv0) Bundler.with_original_env do Kernel.exec( - { "GEM_HOME" => configured_gem_home, "GEM_PATH" => configured_gem_path, "BUNDLER_VERSION" => version.to_s }, + { + "GEM_HOME" => configured_gem_home, + "BUNDLER_ORIG_GEM_HOME" => configured_orig_gem_home, + "GEM_PATH" => configured_gem_path, + "BUNDLER_ORIG_GEM_PATH" => configured_orig_gem_path, + "BUNDLER_VERSION" => version.to_s, + }, *cmd ) end end - def needs_switching? + def needs_switching?(restart_version) autoswitching_applies? && - Bundler.settings[:version] != "system" && released?(restart_version) && - !running?(restart_version) && - !updating? + !running?(restart_version) end def autoswitching_applies? - ENV["BUNDLER_VERSION"].nil? && + (ENV["BUNDLER_VERSION"].nil? || ENV["BUNDLER_VERSION"].empty?) && ruby_can_restart_with_same_arguments? && - SharedHelpers.in_bundle? && lockfile_version end @@ -142,6 +132,7 @@ module Bundler end def find_latest_matching_spec(requirement) + Bundler.configure local_result = find_latest_matching_spec_from_collection(local_specs, requirement) return local_result if local_result && requirement.specific? @@ -171,18 +162,14 @@ module Bundler $PROGRAM_NAME != "-e" end - def updating? - "update".start_with?(ARGV.first || " ") && ARGV[1..-1].any? {|a| a.start_with?("--bundler") } - end - - def installed? + def installed?(restart_version) Bundler.configure Bundler.rubygems.find_bundler(restart_version.to_s) end def current_version - @current_version ||= Gem::Version.new(Bundler::VERSION) + @current_version ||= Bundler.gem_version end def lockfile_version @@ -194,13 +181,16 @@ module Bundler @lockfile_version = nil end - def restart_version - return @restart_version if defined?(@restart_version) - # BUNDLE_VERSION=x.y.z - @restart_version = Gem::Version.new(Bundler.settings[:version]) - rescue ArgumentError - # BUNDLE_VERSION=lockfile - @restart_version = lockfile_version + def find_restart_version + return unless SharedHelpers.in_bundle? + + configured_version = Bundler.settings[:version] + return if configured_version == "system" + + restart_version = configured_version == "lockfile" ? lockfile_version : Gem::Version.new(configured_version) + return unless needs_switching?(restart_version) + + restart_version end end end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index cde01e0181..d00a4bb916 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -7,13 +7,10 @@ module Bundler autoload :Validator, File.expand_path("settings/validator", __dir__) BOOL_KEYS = %w[ - allow_offline_install - auto_clean_without_path auto_install cache_all cache_all_platforms clean - default_install_uses_path deployment disable_checksum_validation disable_exec_load @@ -22,11 +19,11 @@ module Bundler disable_shared_gems disable_version_check force_ruby_platform - forget_cli_options frozen gem.changelog gem.coc gem.mit + gem.bundle git.allow_insecure global_gem_cache ignore_messages @@ -35,29 +32,13 @@ module Bundler lockfile_checksums no_install no_prune - path_relative_to_cwd path.system plugins prefer_patch - print_only_version_number - setup_makes_kernel_gem_public silence_deprecations silence_root_warning update_requires_all_flag - ].freeze - - REMEMBERED_KEYS = %w[ - bin - cache_all - clean - deployment - frozen - no_prune - path - shebang - path.system - without - with + verbose ].freeze NUMBER_KEYS = %w[ @@ -84,8 +65,10 @@ module Bundler gem.rubocop gem.test gemfile + lockfile path shebang + simulate_version system_bindir trust-policy version @@ -99,6 +82,11 @@ module Bundler "BUNDLE_RETRY" => 3, "BUNDLE_TIMEOUT" => 10, "BUNDLE_VERSION" => "lockfile", + "BUNDLE_LOCKFILE_CHECKSUMS" => true, + "BUNDLE_CACHE_ALL" => true, + "BUNDLE_PLUGINS" => true, + "BUNDLE_GLOBAL_GEM_CACHE" => false, + "BUNDLE_UPDATE_REQUIRES_ALL_FLAG" => false, }.freeze def initialize(root = nil) @@ -130,12 +118,8 @@ module Bundler end def set_command_option(key, value) - if !is_remembered(key) || Bundler.feature_flag.forget_cli_options? - temporary(key => value) - value - else - set_local(key, value) - end + temporary(key => value) + value end def set_command_option_if_given(key, value) @@ -274,7 +258,7 @@ module Bundler def use_system_gems? return true if system_path return false if explicit_path - !Bundler.feature_flag.default_install_uses_path? + !Bundler.feature_flag.bundler_5_mode? end def base_path @@ -389,10 +373,6 @@ module Bundler ARRAY_KEYS.include?(self.class.key_to_s(key)) end - def is_remembered(key) - REMEMBERED_KEYS.include?(self.class.key_to_s(key)) - end - def is_credential(key) key == "gem.push_key" end diff --git a/lib/bundler/settings/validator.rb b/lib/bundler/settings/validator.rb index 0a57ea7f03..9aa1627fb2 100644 --- a/lib/bundler/settings/validator.rb +++ b/lib/bundler/settings/validator.rb @@ -74,29 +74,6 @@ module Bundler fail!(key, value, "`#{other_key}` is current set to #{other_setting.inspect}", "the `#{conflicting.join("`, `")}` groups conflict") end end - - rule %w[path], "relative paths are expanded relative to the current working directory" do |key, value, settings| - next if value.nil? - - path = Pathname.new(value) - next if !path.relative? || !Bundler.feature_flag.path_relative_to_cwd? - - path = path.expand_path - - root = begin - Bundler.root - rescue GemfileNotFound - Pathname.pwd.expand_path - end - - path = begin - path.relative_path_from(root) - rescue ArgumentError - path - end - - set(settings, key, path.to_s) - end end end end diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 1ef9d61361..2aa8abe0a0 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -4,8 +4,6 @@ require_relative "version" require_relative "rubygems_integration" require_relative "current_ruby" -autoload :Pathname, "pathname" - module Bundler autoload :WINDOWS, File.expand_path("constants", __dir__) autoload :FREEBSD, File.expand_path("constants", __dir__) @@ -25,6 +23,9 @@ module Bundler end def default_lockfile + given = ENV["BUNDLE_LOCKFILE"] + return Pathname.new(given) if given && !given.empty? + gemfile = default_gemfile case gemfile.basename.to_s @@ -57,7 +58,7 @@ module Bundler def pwd Bundler.rubygems.ext_lock.synchronize do - Pathname.pwd + Dir.pwd end end @@ -104,7 +105,8 @@ module Bundler def filesystem_access(path, action = :write, &block) yield(path.dup) rescue Errno::EACCES => e - raise unless e.message.include?(path.to_s) || action == :create + path_basename = File.basename(path.to_s) + raise unless e.message.include?(path_basename) || action == :create raise PermissionError.new(path, action) rescue Errno::EAGAIN @@ -125,24 +127,17 @@ module Bundler raise GenericSystemCallError.new(e, "There was an error #{[:create, :write].include?(action) ? "creating" : "accessing"} `#{path}`.") end - def major_deprecation(major_version, message, removed_message: nil, print_caller_location: false) - if print_caller_location - caller_location = caller_locations(2, 2).first - suffix = " (called at #{caller_location.path}:#{caller_location.lineno})" - message += suffix - removed_message += suffix if removed_message - end + def feature_deprecated!(message) + return unless prints_major_deprecations? - bundler_major_version = Bundler.bundler_major_version - if bundler_major_version > major_version - require_relative "errors" - raise DeprecatedError, "[REMOVED] #{removed_message || message}" - end - - return unless bundler_major_version >= major_version && prints_major_deprecations? Bundler.ui.warn("[DEPRECATED] #{message}") end + def feature_removed!(message) + require_relative "errors" + raise RemovedError, "[REMOVED] #{message}" + end + def print_major_deprecations! multiple_gemfiles = search_up(".") do |dir| gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) } @@ -306,6 +301,7 @@ module Bundler def set_bundle_variables Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", bundle_bin_path Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", default_lockfile.to_s Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__) end @@ -386,7 +382,6 @@ module Bundler end def prints_major_deprecations? - require_relative "../bundler" return false if Bundler.settings[:silence_deprecations] require_relative "deprecate" return false if Bundler::Deprecate.skip diff --git a/lib/bundler/similarity_detector.rb b/lib/bundler/similarity_detector.rb deleted file mode 100644 index 50e66b9cab..0000000000 --- a/lib/bundler/similarity_detector.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class SimilarityDetector - SimilarityScore = Struct.new(:string, :distance) - - # initialize with an array of words to be matched against - def initialize(corpus) - @corpus = corpus - end - - # return an array of words similar to 'word' from the corpus - def similar_words(word, limit = 3) - words_by_similarity = @corpus.map {|w| SimilarityScore.new(w, levenshtein_distance(word, w)) } - words_by_similarity.select {|s| s.distance <= limit }.sort_by(&:distance).map(&:string) - end - - # return the result of 'similar_words', concatenated into a list - # (eg "a, b, or c") - def similar_word_list(word, limit = 3) - words = similar_words(word, limit) - if words.length == 1 - words[0] - elsif words.length > 1 - [words[0..-2].join(", "), words[-1]].join(" or ") - end - end - - protected - - # https://www.informit.com/articles/article.aspx?p=683059&seqNum=36 - def levenshtein_distance(this, that, ins = 2, del = 2, sub = 1) - # ins, del, sub are weighted costs - return nil if this.nil? - return nil if that.nil? - dm = [] # distance matrix - - # Initialize first row values - dm[0] = (0..this.length).collect {|i| i * ins } - fill = [0] * (this.length - 1) - - # Initialize first column values - (1..that.length).each do |i| - dm[i] = [i * del, fill.flatten] - end - - # populate matrix - (1..that.length).each do |i| - (1..this.length).each do |j| - # critical comparison - dm[i][j] = [ - dm[i - 1][j - 1] + (this[j - 1] == that[i - 1] ? 0 : sub), - dm[i][j - 1] + ins, - dm[i - 1][j] + del, - ].min - end - end - - # The last value in matrix is the Levenshtein distance between the strings - dm[that.length][this.length] - end - end -end diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 232873503b..2b90a0eff1 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -79,7 +79,7 @@ module Bundler end def extension_cache_path(spec) - return unless Bundler.feature_flag.global_gem_cache? + return unless Bundler.settings[:global_gem_cache] return unless source_slug = extension_cache_slug(spec) Bundler.user_cache.join( "extensions", Gem::Platform.local.to_s, Bundler.ruby_scope, diff --git a/lib/bundler/source/gemspec.rb b/lib/bundler/source/gemspec.rb index b59dce1d09..ed766dbe74 100644 --- a/lib/bundler/source/gemspec.rb +++ b/lib/bundler/source/gemspec.rb @@ -10,6 +10,10 @@ module Bundler super @gemspec = options["gemspec"] end + + def to_s + "gemspec at `#{@path}`" + end end end end diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index d57944ee12..bb669ebba3 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -238,7 +238,7 @@ module Bundler # across different projects, this cache will be shared. # When using local git repos, this is set to the local repo. def cache_path - @cache_path ||= if Bundler.feature_flag.global_gem_cache? + @cache_path ||= if Bundler.settings[:global_gem_cache] Bundler.user_cache else Bundler.bundle_path.join("cache", "bundler") @@ -268,7 +268,7 @@ module Bundler private def cache_to(custom_path, try_migrate: false) - return unless Bundler.feature_flag.cache_all? + return unless Bundler.settings[:cache_all] app_cache_path = app_cache_path(custom_path) @@ -416,7 +416,6 @@ module Bundler def fetch git_proxy.checkout rescue GitError => e - raise unless Bundler.feature_flag.allow_offline_install? Bundler.ui.warn "Using cached git data because of network errors:\n#{e}" end diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 8230584260..cd352c22a7 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -16,7 +16,7 @@ module Bundler def initialize(command) msg = String.new msg << "Bundler is trying to run `#{command}` at runtime. You probably need to run `bundle install`. However, " - msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md " + msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md " msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}" super msg end @@ -121,7 +121,7 @@ module Bundler FileUtils.rm_rf(p) end git "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s - File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination) + File.chmod((File.stat(destination).mode | 0o777) & ~File.umask, destination) rescue Errno::EEXIST => e file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1] raise GitError, "Bundler could not install a gem because it needs to " \ @@ -305,8 +305,8 @@ module Bundler end def has_revision_cached? - return unless @revision && path.exist? - git("cat-file", "-e", @revision, dir: path) + return unless commit && path.exist? + git("cat-file", "-e", commit, dir: path) true rescue GitError false @@ -408,11 +408,7 @@ module Bundler def capture3_args_for(cmd, dir) return ["git", *cmd] unless dir - if Bundler.feature_flag.bundler_3_mode? || supports_minus_c? - ["git", "-C", dir.to_s, *cmd] - else - ["git", *cmd, { chdir: dir.to_s }] - end + ["git", "-C", dir.to_s, *cmd] end def extra_clone_args @@ -451,10 +447,6 @@ module Bundler depth.nil? end - def supports_minus_c? - @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5") - end - def needs_allow_any_sha1_in_want? @needs_allow_any_sha1_in_want ||= Gem::Version.new(version) <= Gem::Version.new("2.13.7") end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 885dd96d85..82e782ba25 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -24,7 +24,7 @@ module Bundler @path = Pathname.new(options["path"]) expanded_path = expand(@path) @path = if @path.relative? - expanded_path.relative_path_from(root_path.expand_path) + expanded_path.relative_path_from(File.expand_path(root_path)) else expanded_path end @@ -53,6 +53,8 @@ module Bundler "source at `#{@path}`" end + alias_method :identifier, :to_s + alias_method :to_gemfile, :path def hash @@ -81,7 +83,7 @@ module Bundler def cache(spec, custom_path = nil) app_cache_path = app_cache_path(custom_path) - return unless Bundler.feature_flag.cache_all? + return unless Bundler.settings[:cache_all] return if expand(@original_path).to_s.index(root_path.to_s + "/") == 0 unless @original_path.exist? @@ -124,11 +126,7 @@ module Bundler end def expand(somepath) - if Bundler.current_ruby.jruby? # TODO: Unify when https://github.com/rubygems/bundler/issues/7598 fixed upstream and all supported jrubies include the fix - somepath.expand_path(root_path).expand_path - else - somepath.expand_path(root_path) - end + somepath.expand_path(root_path) rescue ArgumentError => e Bundler.ui.debug(e) raise PathError, "There was an error while trying to use the path " \ @@ -167,6 +165,13 @@ module Bundler next unless spec = load_gemspec(file) spec.source = self + # The ignore attribute is for ignoring installed gems that don't + # have extensions correctly compiled for activation. In the case of + # path sources, there's a single version of each gem in the path + # source available to Bundler, so we always certainly want to + # consider that for activation and never makes sense to ignore it. + spec.ignored = false + # Validation causes extension_dir to be calculated, which depends # on #source, so we validate here instead of load_gemspec validate_spec(spec) diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 19800e9c58..e1e030ffc8 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -8,7 +8,7 @@ module Bundler autoload :Remote, File.expand_path("rubygems/remote", __dir__) # Ask for X gems per API request - API_REQUEST_SIZE = 50 + API_REQUEST_SIZE = 100 attr_accessor :remotes @@ -168,12 +168,6 @@ module Bundler return nil # no post-install message end - if spec.remote - # Check for this spec from other sources - uris = [spec.remote, *remotes_for_spec(spec)].map(&:anonymized_uri).uniq - Installer.ambiguous_gems << [spec.name, *uris] if uris.length > 1 - end - path = fetch_gem_if_possible(spec, options[:previous_spec]) raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path @@ -217,7 +211,11 @@ module Bundler message += " with native extensions" if spec.extensions.any? Bundler.ui.confirm message - installed_spec = installer.install + installed_spec = nil + + Gem.time("Installed #{spec.name} in", 0, true) do + installed_spec = installer.install + end spec.full_gem_path = installed_spec.full_gem_path spec.loaded_from = installed_spec.loaded_from @@ -332,13 +330,6 @@ module Bundler remotes.map(&method(:remove_auth)) end - def remotes_for_spec(spec) - specs.search_all(spec.name).inject([]) do |uris, s| - uris << s.remote if s.remote - uris - end - end - def cached_gem(spec) global_cache_path = download_cache_path(spec) caches << global_cache_path if global_cache_path @@ -491,7 +482,10 @@ module Bundler uri = spec.remote.uri Bundler.ui.confirm("Fetching #{version_message(spec, previous_spec)}") gem_remote_fetcher = remote_fetchers.fetch(spec.remote).gem_remote_fetcher - Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher) + + Gem.time("Downloaded #{spec.name} in", 0, true) do + Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher) + end end # Returns the global cache path of the calling Rubygems::Source object. @@ -506,7 +500,7 @@ module Bundler # @return [Pathname] The global cache path. # def download_cache_path(spec) - return unless Bundler.feature_flag.global_gem_cache? + return unless Bundler.settings[:global_gem_cache] return unless remote = spec.remote return unless cache_slug = remote.cache_slug diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index d1308b1dfb..38fa0972e6 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -9,7 +9,7 @@ module Bundler :metadata_source def global_rubygems_source - @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true) + @global_rubygems_source ||= source_class.new("allow_local" => true) end def initialize @@ -21,19 +21,9 @@ module Bundler @rubygems_sources = [] @metadata_source = Source::Metadata.new - @merged_gem_lockfile_sections = false @local_mode = true end - def merged_gem_lockfile_sections? - @merged_gem_lockfile_sections - end - - def merged_gem_lockfile_sections!(replacement_source) - @merged_gem_lockfile_sections = true - @global_rubygems_source = replacement_source - end - def aggregate_global_source? global_rubygems_source.multiple_remotes? end @@ -90,10 +80,6 @@ module Bundler @rubygems_sources end - def rubygems_remotes - rubygems_sources.flat_map(&:remotes).uniq - end - def all_sources path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source] end @@ -103,7 +89,7 @@ module Bundler end def get(source) - source_list_for(source).find {|s| equivalent_source?(source, s) } + source_list_for(source).find {|s| s.include?(source) } end def lock_sources @@ -115,11 +101,7 @@ module Bundler end def lock_rubygems_sources - if merged_gem_lockfile_sections? - [combine_rubygems_sources] - else - rubygems_sources.sort_by(&:identifier) - end + rubygems_sources.sort_by(&:identifier) end # Returns true if there are changes @@ -129,16 +111,7 @@ module Bundler @rubygems_sources, @path_sources, @git_sources, @plugin_sources = map_sources(replacement_sources) @global_rubygems_source = global_replacement_source(replacement_sources) - different_sources?(lock_sources, replacement_sources) - end - - # Returns true if there are changes - def expired_sources?(replacement_sources) - return false if replacement_sources.empty? - - lock_sources = dup_with_replaced_sources(replacement_sources).lock_sources - - different_sources?(lock_sources, replacement_sources) + !equivalent_sources?(lock_sources, replacement_sources) end def prefer_local! @@ -165,12 +138,6 @@ module Bundler private - def dup_with_replaced_sources(replacement_sources) - new_source_list = dup - new_source_list.replace_sources!(replacement_sources) - new_source_list - end - def map_sources(replacement_sources) rubygems = @rubygems_sources.map do |source| replace_rubygems_source(replacement_sources, source) @@ -224,11 +191,7 @@ module Bundler end end - def different_sources?(lock_sources, replacement_sources) - !equivalent_sources?(lock_sources, replacement_sources) - end - - def rubygems_aggregate_class + def source_class Source::Rubygems end @@ -247,10 +210,6 @@ module Bundler end end - def combine_rubygems_sources - Source::Rubygems.new("remotes" => rubygems_remotes) - end - def warn_on_git_protocol(source) return if Bundler.settings["git.allow_insecure"] @@ -265,9 +224,5 @@ module Bundler def equivalent_sources?(lock_sources, replacement_sources) lock_sources.sort_by(&:identifier) == replacement_sources.sort_by(&:identifier) end - - def equivalent_source?(source, other_source) - source == other_source - end end end diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb index ca73e01f9d..cb88caf1bd 100644 --- a/lib/bundler/source_map.rb +++ b/lib/bundler/source_map.rb @@ -23,15 +23,12 @@ module Bundler if previous_source.nil? requirements[indirect_dependency_name] = source else - no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode? - msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."] msg.concat [previous_source, source].map {|s| " * #{s}" }.sort - msg << "You #{no_ambiguous_sources ? :must : :should} add this gem to the source block for the source you wish it to be installed from." + msg << "You must add this gem to the source block for the source you wish it to be installed from." msg = msg.join("\n") - raise SecurityError, msg if no_ambiguous_sources - Bundler.ui.warn "Warning: #{msg}" + raise SecurityError, msg end end diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 5fa179b978..f9179e7a06 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -11,16 +11,11 @@ module Bundler @specs = specs end - def for(dependencies, platforms_or_legacy_check = [nil], legacy_platforms = [nil], skips: []) - platforms = if [true, false].include?(platforms_or_legacy_check) - Bundler::SharedHelpers.major_deprecation 2, + def for(dependencies, platforms = [nil], legacy_platforms = [nil], skips: []) + if [true, false].include?(platforms) + Bundler::SharedHelpers.feature_removed! \ "SpecSet#for received a `check` parameter, but that's no longer used and deprecated. " \ - "SpecSet#for always implicitly performs validation. Please remove this parameter", - print_caller_location: true - - legacy_platforms - else - platforms_or_legacy_check + "SpecSet#for always implicitly performs validation. Please remove this parameter" end materialize_dependencies(dependencies, platforms, skips: skips) @@ -76,7 +71,7 @@ module Bundler new_platforms = all_platforms.select do |platform| next if platforms.include?(platform) - next unless GemHelpers.generic(platform) == Gem::Platform::RUBY + next unless Gem::Platform.generic(platform) == Gem::Platform::RUBY complete_platform(platform) end @@ -179,11 +174,11 @@ module Bundler end def -(other) - SpecSet.new(to_a - other.to_a) + SharedHelpers.feature_removed! "SpecSet#- has been removed with no replacement" end def find_by_name_and_platform(name, platform) - @specs.detect {|spec| spec.name == name && spec.match_platform(platform) } + @specs.detect {|spec| spec.name == name && spec.installable_on_platform?(platform) } end def specs_with_additional_variants_from(other) @@ -210,7 +205,7 @@ module Bundler end def <<(spec) - @specs << spec + SharedHelpers.feature_removed! "SpecSet#<< has been removed with no replacement" end def length @@ -280,7 +275,7 @@ module Bundler valid_platform = lookup.all? do |_, specs| spec = specs.first matching_specs = spec.source.specs.search([spec.name, spec.version]) - platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s| + platform_spec = MatchPlatform.select_best_platform_match(matching_specs, platform).find do |s| valid?(s) end diff --git a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt index 67fe8cee79..633baebdd5 100644 --- a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +++ b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt @@ -1,132 +1,10 @@ -# Contributor Covenant Code of Conduct +# Code of Conduct -## Our Pledge +<%= config[:name].inspect %> follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.): -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. +* Participants will be tolerant of opposing views. +* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks. +* When interpreting the words and actions of others, participants should always assume good intentions. +* Behaviour which can be reasonably considered harassment will not be tolerated. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official email address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations +If you have any concerns about behaviour within this project, please contact us at [<%= config[:email].inspect %>](mailto:<%= config[:email].inspect %>). diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index 172183d4b4..83f10009c7 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -59,6 +59,11 @@ Rake::ExtensionTask.new("<%= config[:underscored_name] %>", GEMSPEC) do |ext| end <% end -%> +<% if config[:ext] == "go" -%> +require "go_gem/rake_task" + +GoGem::RakeTask.new("<%= config[:underscored_name] %>") +<% end -%> <% end -%> <% if default_task_names.size == 1 -%> task default: <%= default_task_names.first.inspect %> diff --git a/lib/bundler/templates/newgem/circleci/config.yml.tt b/lib/bundler/templates/newgem/circleci/config.yml.tt index f40f029bf1..c4dd9d0647 100644 --- a/lib/bundler/templates/newgem/circleci/config.yml.tt +++ b/lib/bundler/templates/newgem/circleci/config.yml.tt @@ -7,6 +7,10 @@ jobs: environment: RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' <%- end -%> +<%- if config[:ext] == 'go' -%> + environment: + GO_VERSION: '1.23.0' +<%- end -%> steps: - checkout <%- if config[:ext] == 'rust' -%> @@ -17,6 +21,14 @@ jobs: name: Install a RubyGems version that can compile rust extensions command: gem update --system '<%= ::Gem.rubygems_version %>' <%- end -%> +<%- if config[:ext] == 'go' -%> + - run: + name: Install Go + command: | + wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + tar -C /usr/local -xzf /tmp/go.tar.gz + echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV" +<%- end -%> - run: name: Run the default task command: | diff --git a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt index 0ebce0e4a0..c0dc63fbfa 100644 --- a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt +++ b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt @@ -12,4 +12,4 @@ publish = false crate-type = ["cdylib"] [dependencies] -magnus = { version = "0.6.2" } +magnus = { version = "0.8.2" } diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt new file mode 100644 index 0000000000..a689e21ebe --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "mkmf" +require "go_gem/mkmf" + +# Makes all symbols private by default to avoid unintended conflict +# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED +# selectively, or entirely remove this flag. +append_cflags("-fvisibility=hidden") + +create_go_makefile(<%= config[:makefile_path].inspect %>) diff --git a/lib/bundler/templates/newgem/ext/newgem/go.mod.tt b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt new file mode 100644 index 0000000000..3f4819d004 --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt @@ -0,0 +1,5 @@ +module github.com/<%= config[:go_module_username] %>/<%= config[:underscored_name] %> + +go 1.23 + +require github.com/ruby-go-gem/go-gem-wrapper latest diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt new file mode 100644 index 0000000000..119c0c96ea --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt @@ -0,0 +1,2 @@ +#include "<%= config[:underscored_name] %>.h" +#include "_cgo_export.h" diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt new file mode 100644 index 0000000000..f19b750e58 --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt @@ -0,0 +1,31 @@ +package main + +/* +#include "<%= config[:underscored_name] %>.h" + +VALUE rb_<%= config[:underscored_name] %>_sum(VALUE self, VALUE a, VALUE b); +*/ +import "C" + +import ( + "github.com/ruby-go-gem/go-gem-wrapper/ruby" +) + +//export rb_<%= config[:underscored_name] %>_sum +func rb_<%= config[:underscored_name] %>_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE { + longA := ruby.NUM2LONG(ruby.VALUE(a)) + longB := ruby.NUM2LONG(ruby.VALUE(b)) + + sum := longA + longB + + return C.VALUE(ruby.LONG2NUM(sum)) +} + +//export Init_<%= config[:underscored_name] %> +func Init_<%= config[:underscored_name] %>() { + rb_m<%= config[:constant_array].join %> := ruby.RbDefineModule(<%= config[:constant_name].inspect %>) + ruby.RbDefineSingletonMethod(rb_m<%= config[:constant_array].join %>, "sum", C.rb_<%= config[:underscored_name] %>_sum, 2) +} + +func main() { +} diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt index d1b5ae0534..7f3e3a5b66 100644 --- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt +++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt @@ -18,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false <%- if config[:ext] == 'rust' -%> - name: Set up Ruby & Rust uses: oxidize-rb/actions/setup-ruby-and-rust@v1 @@ -33,5 +35,11 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true <%- end -%> +<%- if config[:ext] == 'go' -%> + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: ext/<%= config[:underscored_name] %>/go.mod +<%- end -%> - name: Run the default task run: bundle exec rake diff --git a/lib/bundler/templates/newgem/gitlab-ci.yml.tt b/lib/bundler/templates/newgem/gitlab-ci.yml.tt index d2e1f33736..adbd70cbc0 100644 --- a/lib/bundler/templates/newgem/gitlab-ci.yml.tt +++ b/lib/bundler/templates/newgem/gitlab-ci.yml.tt @@ -6,6 +6,11 @@ default: - apt-get update && apt-get install -y clang - gem update --system '<%= ::Gem.rubygems_version %>' <%- end -%> +<%- if config[:ext] == 'go' -%> + - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + - tar -C /usr/local -xzf /tmp/go.tar.gz + - export PATH=/usr/local/go/bin:$PATH +<%- end -%> - gem install bundler -v <%= Bundler::VERSION %> - bundle install @@ -14,5 +19,9 @@ example_job: variables: RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' <%- end -%> +<%- if config[:ext] == 'go' -%> + variables: + GO_VERSION: '1.23.0' +<%- end -%> script: - bundle exec rake diff --git a/lib/bundler/templates/newgem/lib/newgem.rb.tt b/lib/bundler/templates/newgem/lib/newgem.rb.tt index caf6e32f4a..3aedee0d25 100644 --- a/lib/bundler/templates/newgem/lib/newgem.rb.tt +++ b/lib/bundler/templates/newgem/lib/newgem.rb.tt @@ -2,7 +2,7 @@ require_relative "<%= File.basename(config[:namespaced_path]) %>/version" <%- if config[:ext] -%> -require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" +require "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" <%- end -%> <%- config[:constant_array].each_with_index do |c, i| -%> diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index ced300f379..513875fd63 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.summary = "TODO: Write a short summary, because RubyGems requires one." spec.description = "TODO: Write a longer description or delete this line." - spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.homepage = "<%= config[:homepage_uri] %>" <%- if config[:mit] -%> spec.license = "MIT" <%- end -%> @@ -20,10 +20,11 @@ Gem::Specification.new do |spec| <%- end -%> spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + spec.metadata["source_code_uri"] = "<%= config[:source_code_uri] %>" +<%- if config[:changelog] -%> + spec.metadata["changelog_uri"] = "<%= config[:changelog_uri] %>" +<%- end -%> # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. @@ -31,13 +32,13 @@ Gem::Specification.new do |spec| spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| ls.readlines("\x0", chomp: true).reject do |f| (f == gemspec) || - f.start_with?(*%w[bin/ test/ spec/ features/ .git <%= config[:ci_config_path] %>appveyor Gemfile]) + f.start_with?(*%w[<%= config[:ignore_paths].join(" ") %>]) end end spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] -<%- if config[:ext] == 'c' || config[:ext] == 'rust' -%> +<%- if %w(c rust go).include?(config[:ext]) -%> spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] <%- end -%> @@ -46,6 +47,9 @@ Gem::Specification.new do |spec| <%- if config[:ext] == 'rust' -%> spec.add_dependency "rb_sys", "~> 0.9.91" <%- end -%> +<%- if config[:ext] == 'go' -%> + spec.add_dependency "go_gem", "~> 0.2" +<%- end -%> # For more information and examples about making a new gem, check out our # guide at: https://bundler.io/guides/creating_gem.html diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb index 6df1512a5b..b836208da8 100644 --- a/lib/bundler/ui/shell.rb +++ b/lib/bundler/ui/shell.rb @@ -17,6 +17,7 @@ module Bundler @level = ENV["DEBUG"] ? "debug" : "info" @warning_history = [] @output_stream = :stdout + @thread_safe_logger_key = "logger_level_#{object_id}" end def add_color(string, *color) @@ -80,11 +81,11 @@ module Bundler end def ask(msg) - @shell.ask(msg) + @shell.ask(msg, :green) end def yes?(msg) - @shell.yes?(msg) + @shell.yes?(msg, :green) end def no?(msg) @@ -97,11 +98,13 @@ module Bundler end def level(name = nil) - return @level unless name + current_level = Thread.current.thread_variable_get(@thread_safe_logger_key) || @level + return current_level unless name + unless index = LEVELS.index(name) raise "#{name.inspect} is not a valid level" end - index <= LEVELS.index(@level) + index <= LEVELS.index(current_level) end def output_stream=(symbol) @@ -167,12 +170,13 @@ module Bundler end * "\n" end - def with_level(level) - original = @level - @level = level + def with_level(desired_level) + old_level = level + Thread.current.thread_variable_set(@thread_safe_logger_key, desired_level) + yield ensure - @level = original + Thread.current.thread_variable_set(@thread_safe_logger_key, old_level) end def with_output_stream(symbol) diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb index cb4485e463..e8aaf70016 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -39,7 +39,7 @@ end # - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true # class Bundler::ConnectionPool - DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true} + DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}.freeze def self.wrap(options, &block) Wrapper.new(options, &block) @@ -99,10 +99,14 @@ class Bundler::ConnectionPool @available = TimedStack.new(@size, &block) @key = :"pool-#{@available.object_id}" @key_count = :"pool-#{@available.object_id}-count" - INSTANCES[self] = self if INSTANCES + @discard_key = :"pool-#{@available.object_id}-discard" + INSTANCES[self] = self if @auto_reload_after_fork && INSTANCES end def with(options = {}) + # We need to manage exception handling manually here in order + # to work correctly with `Gem::Timeout.timeout` and `Thread#raise`. + # Otherwise an interrupted Thread can leak connections. Thread.handle_interrupt(Exception => :never) do conn = checkout(options) begin @@ -116,20 +120,65 @@ class Bundler::ConnectionPool end alias_method :then, :with + ## + # Marks the current thread's checked-out connection for discard. + # + # When a connection is marked for discard, it will not be returned to the pool + # when checked in. Instead, the connection will be discarded. + # This is useful when a connection has become invalid or corrupted + # and should not be reused. + # + # Takes an optional block that will be called with the connection to be discarded. + # The block should perform any necessary clean-up on the connection. + # + # @yield [conn] + # @yieldparam conn [Object] The connection to be discarded. + # @yieldreturn [void] + # + # + # Note: This only affects the connection currently checked out by the calling thread. + # The connection will be discarded when +checkin+ is called. + # + # @return [void] + # + # @example + # pool.with do |conn| + # begin + # conn.execute("SELECT 1") + # rescue SomeConnectionError + # pool.discard_current_connection # Mark connection as bad + # raise + # end + # end + def discard_current_connection(&block) + ::Thread.current[@discard_key] = block || proc { |conn| conn } + end + def checkout(options = {}) if ::Thread.current[@key] ::Thread.current[@key_count] += 1 ::Thread.current[@key] else ::Thread.current[@key_count] = 1 - ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout) + ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout, options) end end def checkin(force: false) if ::Thread.current[@key] if ::Thread.current[@key_count] == 1 || force - @available.push(::Thread.current[@key]) + if ::Thread.current[@discard_key] + begin + @available.decrement_created + ::Thread.current[@discard_key].call(::Thread.current[@key]) + rescue + nil + ensure + ::Thread.current[@discard_key] = nil + end + else + @available.push(::Thread.current[@key]) + end ::Thread.current[@key] = nil ::Thread.current[@key_count] = nil else @@ -146,7 +195,6 @@ class Bundler::ConnectionPool # Shuts down the Bundler::ConnectionPool by passing each connection to +block+ and # then removing it from the pool. Attempting to checkout a connection after # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+. - def shutdown(&block) @available.shutdown(&block) end @@ -155,7 +203,6 @@ class Bundler::ConnectionPool # Reloads the Bundler::ConnectionPool by passing each connection to +block+ and then # removing it the pool. Subsequent checkouts will create new connections as # needed. - def reload(&block) @available.shutdown(reload: true, &block) end 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 02e4485eb2..026d2c5be2 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 @@ -1,8 +1,8 @@ ## # The TimedStack manages a pool of homogeneous connections (or any resource -# you wish to manage). Connections are created lazily up to a given maximum +# you wish to manage). Connections are created lazily up to a given maximum # number. - +# # Examples: # # ts = TimedStack.new(1) { MyConnection.new } @@ -16,14 +16,12 @@ # conn = ts.pop # ts.pop timeout: 5 # #=> raises Bundler::ConnectionPool::TimeoutError after 5 seconds - class Bundler::ConnectionPool::TimedStack attr_reader :max ## # Creates a new pool with +size+ connections that are created from the given # +block+. - def initialize(size = 0, &block) @create_block = block @created = 0 @@ -35,9 +33,8 @@ class Bundler::ConnectionPool::TimedStack end ## - # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be + # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be # used by subclasses that extend TimedStack. - def push(obj, options = {}) @mutex.synchronize do if @shutdown_block @@ -53,14 +50,16 @@ class Bundler::ConnectionPool::TimedStack alias_method :<<, :push ## - # Retrieves a connection from the stack. If a connection is available it is - # immediately returned. If no connection is available within the given + # Retrieves a connection from the stack. If a connection is available it is + # immediately returned. If no connection is available within the given # timeout a Bundler::ConnectionPool::TimeoutError is raised. # - # +:timeout+ is the only checked entry in +options+ and is preferred over - # the +timeout+ argument (which will be removed in a future release). Other - # options may be used by subclasses that extend TimedStack. - + # @option options [Float] :timeout (0.5) Wait this many seconds for an available entry + # @option options [Class] :exception (Bundler::ConnectionPool::TimeoutError) Exception class to raise + # if an entry was not available within the timeout period. Use `exception: false` to return nil. + # + # The +timeout+ argument will be removed in 3.0. + # Other options may be used by subclasses that extend TimedStack. def pop(timeout = 0.5, options = {}) options, timeout = timeout, 0.5 if Hash === timeout timeout = options.fetch :timeout, timeout @@ -69,13 +68,22 @@ class Bundler::ConnectionPool::TimedStack @mutex.synchronize do loop do raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block - return fetch_connection(options) if connection_stored?(options) + if (conn = try_fetch_connection(options)) + return conn + end connection = try_create(options) return connection if connection to_wait = deadline - current_time - raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0 + if to_wait <= 0 + exc = options.fetch(:exception, Bundler::ConnectionPool::TimeoutError) + if exc + raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" + else + return nil + end + end @resource.wait(@mutex, to_wait) end end @@ -86,7 +94,6 @@ class Bundler::ConnectionPool::TimedStack # removing it from the pool. Attempting to checkout a connection after # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+ unless # +:reload+ is +true+. - def shutdown(reload: false, &block) raise ArgumentError, "shutdown must receive a block" unless block @@ -121,14 +128,12 @@ class Bundler::ConnectionPool::TimedStack ## # Returns +true+ if there are no available connections. - def empty? (@created - @que.length) >= @max end ## # The number of connections available on the stack. - def length @max - @created + @que.length end @@ -139,6 +144,12 @@ class Bundler::ConnectionPool::TimedStack @que.length end + ## + # Reduce the created count + def decrement_created + @created -= 1 unless @created == 0 + end + private def current_time @@ -148,8 +159,17 @@ class Bundler::ConnectionPool::TimedStack ## # This is an extension point for TimedStack and is called with a mutex. # - # This method must returns true if a connection is available on the stack. + # This method must returns a connection from the stack if one exists. Allows + # subclasses with expensive match/search algorithms to avoid double-handling + # their stack. + def try_fetch_connection(options = nil) + connection_stored?(options) && fetch_connection(options) + end + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must returns true if a connection is available on the stack. def connection_stored?(options = nil) !@que.empty? end @@ -158,7 +178,6 @@ class Bundler::ConnectionPool::TimedStack # This is an extension point for TimedStack and is called with a mutex. # # This method must return a connection from the stack. - def fetch_connection(options = nil) @que.pop&.first end @@ -167,10 +186,8 @@ class Bundler::ConnectionPool::TimedStack # This is an extension point for TimedStack and is called with a mutex. # # This method must shut down all connections on the stack. - def shutdown_connections(options = nil) - while connection_stored?(options) - conn = fetch_connection(options) + while (conn = try_fetch_connection(options)) @created -= 1 unless @created == 0 @shutdown_block.call(conn) end @@ -181,7 +198,6 @@ class Bundler::ConnectionPool::TimedStack # # This method returns the oldest idle connection if it has been idle for more than idle_seconds. # This requires that the stack is kept in order of checked in time (oldest first). - def reserve_idle_connection(idle_seconds) return unless idle_connections?(idle_seconds) @@ -194,7 +210,6 @@ class Bundler::ConnectionPool::TimedStack # This is an extension point for TimedStack and is called with a mutex. # # Returns true if the first connection in the stack has been idle for more than idle_seconds - def idle_connections?(idle_seconds) connection_stored? && (current_time - @que.first.last > idle_seconds) end @@ -203,7 +218,6 @@ class Bundler::ConnectionPool::TimedStack # This is an extension point for TimedStack and is called with a mutex. # # This method must return +obj+ to the stack. - def store_connection(obj, options = nil) @que.push [obj, current_time] end @@ -213,7 +227,6 @@ class Bundler::ConnectionPool::TimedStack # # This method must create a connection if and only if the total number of # connections allowed has not been met. - def try_create(options = nil) unless @created == @max object = @create_block.call 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 88a3714fb8..2e9eebdbb6 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.5.0" + VERSION = "2.5.5" end diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb index 4d7619ddcf..a11fdc7176 100644 --- a/lib/bundler/vendor/fileutils/lib/fileutils.rb +++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb @@ -181,7 +181,7 @@ end # module Bundler::FileUtils # The version number. - VERSION = "1.7.3" + VERSION = "1.8.0" def self.private_module_function(name) #:nodoc: module_function name @@ -706,11 +706,12 @@ module Bundler::FileUtils # 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) + return ln_sr(src, dest, force: force, target_directory: target_directory, noop: noop, verbose: verbose) end - fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose + fu_output_message "ln -s#{force ? 'f' : ''}#{ + target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose return if noop - fu_each_src_dest0(src, dest) do |s,d| + fu_each_src_dest0(src, dest, target_directory) do |s,d| remove_file d, true if force File.symlink s, d end @@ -730,42 +731,37 @@ module Bundler::FileUtils # Like Bundler::FileUtils.ln_s, but create links relative to +dest+. # 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)) + cmd = "ln -s#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" if verbose + fu_each_src_dest0(src, dest, target_directory) do |s,d| + if target_directory + parent = File.dirname(d) + destdirs = fu_split_path(parent) + real_ddirs = fu_split_path(File.realpath(parent)) else - destdirs = File.dirname(d = dest) + destdirs ||= fu_split_path(dest) + real_ddirs ||= fu_split_path(File.realdirpath(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) + srcdirs = fu_split_path(s) + i = fu_common_components(srcdirs, destdirs) + n = destdirs.size - i + n -= 1 unless target_directory + link1 = fu_clean_components(*Array.new([n, 0].max, '..'), *srcdirs[i..-1]) + begin + real_sdirs = fu_split_path(File.realdirpath(s)) rescue nil + rescue 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) + i = fu_common_components(real_sdirs, real_ddirs) + n = real_ddirs.size - i + n -= 1 unless target_directory + link2 = fu_clean_components(*Array.new([n, 0].max, '..'), *real_sdirs[i..-1]) + link1 = link2 if link1.size > link2.size end - fu_output_message "ln -s#{options} #{s} #{d}" if verbose + s = File.join(link1) + fu_output_message [cmd, s, d].flatten.join(' ') 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 @@ -800,13 +796,13 @@ module Bundler::FileUtils # File.file?('dest1/dir1/t2.txt') # => true # File.file?('dest1/dir1/t3.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link. - # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default). + # - +remove_destination+ - removes +dest+ before creating links (+false+ by default). # # 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. + # and optional argument +remove_destination+ is not given. # # Related: Bundler::FileUtils.ln (has different options). # @@ -1029,12 +1025,12 @@ module Bundler::FileUtils # directories, and symbolic links; # other file types (FIFO streams, device files, etc.) are not supported. # - # Keyword arguments: + # Optional arguments: # - # - <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. + # - +dereference_root+ - if +src+ is a symbolic link, + # follows the link (+false+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1065,12 +1061,12 @@ module Bundler::FileUtils # Bundler::FileUtils.copy_file('src0.txt', 'dest0.txt') # File.file?('dest0.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <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. + # - +dereference+ - if +src+ is a symbolic link, + # follows the link (+true+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1491,7 +1487,8 @@ module Bundler::FileUtils # 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 + raise Errno::ENOTDIR, path unless force or File.directory?(path) + remove_entry path, force end module_function :remove_dir @@ -2475,6 +2472,10 @@ module Bundler::FileUtils def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc: if tmp = Array.try_convert(src) + unless target_directory or tmp.size <= 1 + tmp = tmp.map {|f| File.path(f)} # A workaround for RBS + raise ArgumentError, "extra target #{tmp}" + end tmp.each do |s| s = File.path(s) yield s, (target_directory ? File.join(dest, File.basename(s)) : dest) @@ -2509,7 +2510,11 @@ module Bundler::FileUtils path = File.path(path) list = [] until (parent, base = File.split(path); parent == path or parent == ".") - list << base + if base != '..' and list.last == '..' and !(fu_have_symlink? && File.symlink?(path)) + list.pop + else + list << base + end path = parent end list << path @@ -2517,14 +2522,14 @@ module Bundler::FileUtils end private_module_function :fu_split_path - def fu_relative_components_from(target, base) #:nodoc: + def fu_common_components(target, base) #:nodoc: i = 0 while target[i]&.== base[i] i += 1 end - Array.new(base.size-i, '..').concat(target[i..-1]) + i end - private_module_function :fu_relative_components_from + private_module_function :fu_common_components def fu_clean_components(*comp) #:nodoc: comp.shift while comp.first == "." @@ -2534,7 +2539,7 @@ module Bundler::FileUtils while c = comp.shift if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path)) clean.pop - path.chomp!(%r((?<=\A|/)[^/]+/\z), "") + path.sub!(%r((?<=\A|/)[^/]+/\z), "") else clean << c path << c << "/" 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 cfc0f48197..93e403a5bb 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,6 +1,10 @@ require_relative '../../../../../vendored_net_http' require_relative '../../../../../vendored_uri' -require 'cgi' # for escaping +begin + require 'cgi/escape' +rescue LoadError + require 'cgi/util' # for escaping +end require_relative '../../../../connection_pool/lib/connection_pool' autoload :OpenSSL, 'openssl' @@ -42,9 +46,8 @@ autoload :OpenSSL, 'openssl' # # 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 Gem::URI#request_uri not Gem::URI#path. The request_uri contains the query -# params which are sent in the body for other requests. +# âš Note that for GET, HEAD and other requests that do not have a body, +# it uses Gem::URI#request_uri as default to send query params # # == TLS/SSL # @@ -60,6 +63,7 @@ autoload :OpenSSL, 'openssl' # #ca_path :: Directory with certificate-authorities # #cert_store :: An SSL certificate store # #ciphers :: List of SSl ciphers allowed +# #extra_chain_cert :: Extra certificates to be added to the certificate chain # #private_key :: The client's SSL private key # #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new # connection @@ -176,7 +180,7 @@ class Gem::Net::HTTP::Persistent ## # The version of Gem::Net::HTTP::Persistent you are using - VERSION = '4.0.4' + VERSION = '4.0.6' ## # Error class for errors raised by Gem::Net::HTTP::Persistent. Various @@ -268,6 +272,11 @@ class Gem::Net::HTTP::Persistent attr_reader :ciphers ## + # Extra certificates to be added to the certificate chain + + attr_reader :extra_chain_cert + + ## # 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 @@ -587,6 +596,21 @@ class Gem::Net::HTTP::Persistent reconnect_ssl end + if Gem::Net::HTTP.method_defined?(:extra_chain_cert=) + ## + # Extra certificates to be added to the certificate chain. + # It is only supported starting from Gem::Net::HTTP version 0.1.1 + def extra_chain_cert= extra_chain_cert + @extra_chain_cert = extra_chain_cert + + reconnect_ssl + end + else + def extra_chain_cert= _extra_chain_cert + raise "extra_chain_cert= is not supported by this version of Gem::Net::HTTP" + end + end + ## # Creates a new connection for +uri+ @@ -605,47 +629,49 @@ class Gem::Net::HTTP::Persistent connection = @pool.checkout net_http_args - http = connection.http + begin + http = connection.http - connection.ressl @ssl_generation if - connection.ssl_generation != @ssl_generation + connection.ressl @ssl_generation if + connection.ssl_generation != @ssl_generation - if not http.started? then - ssl http if use_ssl - start http - elsif expired? connection then - reset connection - end + if not http.started? then + ssl http if use_ssl + start http + elsif expired? connection then + reset connection + end - http.keep_alive_timeout = @idle_timeout if @idle_timeout - http.max_retries = @max_retries if http.respond_to?(:max_retries=) - http.read_timeout = @read_timeout if @read_timeout - http.write_timeout = @write_timeout if - @write_timeout && http.respond_to?(:write_timeout=) + http.keep_alive_timeout = @idle_timeout if @idle_timeout + http.max_retries = @max_retries if http.respond_to?(:max_retries=) + http.read_timeout = @read_timeout if @read_timeout + http.write_timeout = @write_timeout if + @write_timeout && http.respond_to?(:write_timeout=) + + return yield connection + rescue Errno::ECONNREFUSED + if http.proxy? + address = http.proxy_address + port = http.proxy_port + else + address = http.address + port = http.port + end - return yield connection - rescue Errno::ECONNREFUSED - if http.proxy? - address = http.proxy_address - port = http.proxy_port - else - address = http.address - port = http.port - end + raise Error, "connection refused: #{address}:#{port}" + rescue Errno::EHOSTDOWN + if http.proxy? + address = http.proxy_address + port = http.proxy_port + else + address = http.address + port = http.port + end - raise Error, "connection refused: #{address}:#{port}" - rescue Errno::EHOSTDOWN - if http.proxy? - address = http.proxy_address - port = http.proxy_port - else - address = http.address - port = http.port + raise Error, "host down: #{address}:#{port}" + ensure + @pool.checkin net_http_args end - - raise Error, "host down: #{address}:#{port}" - ensure - @pool.checkin net_http_args end ## @@ -782,7 +808,7 @@ class Gem::Net::HTTP::Persistent @proxy_connection_id = [nil, *@proxy_args].join ':' if @proxy_uri.query then - @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? } + @no_proxy = Gem::URI.decode_www_form(@proxy_uri.query).filter_map { |k, v| v if k == 'no_proxy' }.join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? } end end @@ -953,7 +979,8 @@ class Gem::Net::HTTP::Persistent end ## - # Shuts down all connections + # Shuts down all connections. Attempting to checkout a connection after + # shutdown will raise an error. # # *NOTE*: Calling shutdown for can be dangerous! # @@ -965,6 +992,17 @@ class Gem::Net::HTTP::Persistent end ## + # Discard all existing connections. Subsequent checkouts will create + # new connections as needed. + # + # If any thread is still using a connection it may cause an error! Call + # #reload when you are completely done making requests! + + def reload + @pool.reload { |http| http.finish } + end + + ## # Enables SSL on +connection+ def ssl connection @@ -1021,6 +1059,10 @@ application: connection.key = @private_key end + if defined?(@extra_chain_cert) and @extra_chain_cert + connection.extra_chain_cert = @extra_chain_cert + end + connection.cert_store = if @cert_store then @cert_store else 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 214804fcd9..034fbe39b8 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 @@ -63,7 +63,8 @@ class Gem::Net::HTTP::Persistent::TimedStackMulti < Bundler::ConnectionPool::Tim if @created >= @max && @enqueued >= 1 oldest, = @lru.first @lru.delete oldest - @ques[oldest].pop + connection = @ques[oldest].pop + connection.close if connection.respond_to?(:close) @created -= 1 end diff --git a/lib/bundler/vendor/thor/lib/thor.rb b/lib/bundler/vendor/thor/lib/thor.rb index bfd9f5c914..945bdbd551 100644 --- a/lib/bundler/vendor/thor/lib/thor.rb +++ b/lib/bundler/vendor/thor/lib/thor.rb @@ -625,7 +625,7 @@ class Bundler::Thor # alias name. def find_command_possibilities(meth) len = meth.to_s.length - possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort + possibilities = all_commands.reject { |_k, c| c.hidden? }.merge(map).keys.select { |n| meth == n[0, len] }.sort unique_possibilities = possibilities.map { |k| map[k] || k }.uniq if possibilities.include?(meth) 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 bccfbb6b85..d8c9863054 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -242,6 +242,35 @@ class Bundler::Thor insert_into_file(path, *(args << config), &block) end + # Run a regular expression replacement on a file, raising an error if the + # contents of the file are not changed. + # + # ==== Parameters + # path<String>:: path of the file to be changed + # flag<Regexp|String>:: the regexp or string to be replaced + # replacement<String>:: the replacement, can be also given as a block + # config<Hash>:: give :verbose => false to not log the status, and + # :force => true, to force the replacement regardless of runner behavior. + # + # ==== Example + # + # gsub_file! 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' + # + # gsub_file! 'README', /rake/, :green do |match| + # match << " no more. Use thor!" + # end + # + def gsub_file!(path, flag, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + + return unless behavior == :invoke || config.fetch(:force, false) + + path = File.expand_path(path, destination_root) + say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) + + actually_gsub_file(path, flag, args, true, &block) unless options[:pretend] + end + # Run a regular expression replacement on a file. # # ==== Parameters @@ -267,11 +296,7 @@ class Bundler::Thor path = File.expand_path(path, destination_root) say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) - unless options[:pretend] - content = File.binread(path) - content.gsub!(flag, *args, &block) - File.open(path, "wb") { |file| file.write(content) } - end + actually_gsub_file(path, flag, args, false, &block) unless options[:pretend] end # Uncomment all lines matching a given regex. Preserves indentation before @@ -348,7 +373,7 @@ class Bundler::Thor end def with_output_buffer(buf = "".dup) #:nodoc: - raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen? + raise ArgumentError, "Buffer cannot be a frozen object" if buf.frozen? old_buffer = output_buffer self.output_buffer = buf yield @@ -357,6 +382,17 @@ class Bundler::Thor self.output_buffer = old_buffer end + def actually_gsub_file(path, flag, args, error_on_no_change, &block) + content = File.binread(path) + success = content.gsub!(flag, *args, &block) + + if success.nil? && error_on_no_change + raise Bundler::Thor::Error, "The content of #{path} did not change" + end + + File.open(path, "wb") { |file| file.write(content) } + end + # Bundler::Thor::Actions#capture depends on what kind of buffer is used in ERB. # Thus CapturableERB fixes ERB to use String buffer. class CapturableERB < ERB diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb index 734f5fe7e3..fe22d989e5 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb @@ -144,7 +144,7 @@ class Bundler::Thor 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. + # the difference 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}'"} diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index c7cc873131..f0ce6df96c 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -1,9 +1,8 @@ require_relative "../thor" require_relative "group" -require "yaml" require "digest/sha2" -require "pathname" +require "pathname" unless defined?(Pathname) class Bundler::Thor::Runner < Bundler::Thor #:nodoc: map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version @@ -195,6 +194,7 @@ private def thor_yaml @thor_yaml ||= begin yaml_file = File.join(thor_root, "thor.yml") + require "yaml" yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file) yaml || {} end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb index b3e85733cb..da02b94227 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -314,7 +314,7 @@ class Bundler::Thor diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u" require "tempfile" - Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp| + Tempfile.open(File.basename(destination), File.dirname(destination), binmode: true) do |temp| temp.write content temp.rewind system %(#{diff_cmd} "#{destination}" "#{temp.path}") @@ -372,16 +372,12 @@ class Bundler::Thor Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp| temp.write content temp.rewind - system %(#{merge_tool} "#{temp.path}" "#{destination}") + system(merge_tool, temp.path, destination) end end def merge_tool #:nodoc: - @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool - end - - def git_merge_tool #:nodoc: - `git config merge.tool`.rstrip rescue "" + @merge_tool ||= ENV["THOR_MERGE"] || "git difftool --no-index" end end end diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb index cd7b4f060e..5474a2f71b 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.3.2" + VERSION = "1.4.0" end diff --git a/lib/bundler/vendor/uri/lib/uri/common.rb b/lib/bundler/vendor/uri/lib/uri/common.rb index 186da24fa8..38339119c5 100644 --- a/lib/bundler/vendor/uri/lib/uri/common.rb +++ b/lib/bundler/vendor/uri/lib/uri/common.rb @@ -30,6 +30,9 @@ module Bundler::URI remove_const(:Parser) if defined?(::Bundler::URI::Parser) const_set("Parser", parser.class) + remove_const(:PARSER) if defined?(::Bundler::URI::PARSER) + const_set("PARSER", parser) + remove_const(:REGEXP) if defined?(::Bundler::URI::REGEXP) remove_const(:PATTERN) if defined?(::Bundler::URI::PATTERN) if Parser == RFC2396_Parser @@ -49,10 +52,10 @@ module Bundler::URI warn "Bundler::URI::REGEXP is obsolete. Use Bundler::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE Bundler::URI::RFC2396_REGEXP elsif value = RFC2396_PARSER.regexp[const] - warn "Bundler::URI::#{const} is obsolete. Use RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE + warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE value elsif value = RFC2396_Parser.const_get(const) - warn "Bundler::URI::#{const} is obsolete. Use RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE + warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE value else super @@ -92,6 +95,40 @@ module Bundler::URI end module Schemes # :nodoc: + class << self + ReservedChars = ".+-" + EscapedChars = "\u01C0\u01C1\u01C2" + # Use Lo category chars as escaped chars for TruffleRuby, which + # does not allow Symbol categories as identifiers. + + def escape(name) + unless name and name.ascii_only? + return nil + end + name.upcase.tr(ReservedChars, EscapedChars) + end + + def unescape(name) + name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase + end + + def find(name) + const_get(name, false) if name and const_defined?(name, false) + end + + def register(name, klass) + unless scheme = escape(name) + raise ArgumentError, "invalid character as scheme - #{name}" + end + const_set(scheme, klass) + end + + def list + constants.map { |name| + [unescape(name.to_s), const_get(name)] + }.to_h + end + end end private_constant :Schemes @@ -104,7 +141,7 @@ module Bundler::URI # 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) + Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: @@ -122,14 +159,14 @@ module Bundler::URI # # Related: Bundler::URI.register_scheme. def self.scheme_list - Schemes.constants.map { |name| - [name.to_s.upcase, Schemes.const_get(name)] - }.to_h + Schemes.list end + # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: @@ -148,12 +185,10 @@ module Bundler::URI # # => #<Bundler::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # def self.for(scheme, *arguments, default: Generic) - const_name = scheme.to_s.upcase + const_name = Schemes.escape(scheme) 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 ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) @@ -195,7 +230,7 @@ module Bundler::URI # ["fragment", "top"]] # def self.split(uri) - DEFAULT_PARSER.split(uri) + PARSER.split(uri) end # Returns a new \Bundler::URI object constructed from the given string +uri+: @@ -205,11 +240,11 @@ module Bundler::URI # 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> # - # It's recommended to first ::escape string +uri+ + # It's recommended to first Bundler::URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid Bundler::URI characters. # def self.parse(uri) - DEFAULT_PARSER.parse(uri) + PARSER.parse(uri) end # Merges the given Bundler::URI strings +str+ @@ -265,7 +300,7 @@ module Bundler::URI # def self.extract(str, schemes = nil, &block) # :nodoc: warn "Bundler::URI.extract is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.extract(str, schemes, &block) + PARSER.extract(str, schemes, &block) end # @@ -302,7 +337,7 @@ module Bundler::URI # def self.regexp(schemes = nil)# :nodoc: warn "Bundler::URI.regexp is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.make_regexp(schemes) + PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: @@ -407,6 +442,8 @@ module Bundler::URI _decode_uri_component(/%\h\h/, str, enc) end + # Returns a string derived from the given string +str+ with + # Bundler::URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT @@ -421,6 +458,8 @@ module Bundler::URI end private_class_method :_encode_uri_component + # Returns a string decoding characters matching +regexp+ from the + # given \URL-encoded string +str+. 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) @@ -859,6 +898,7 @@ module Bundler # Returns a \Bundler::URI object derived from the given +uri+, # which may be a \Bundler::URI string or an existing \Bundler::URI object: # + # require 'bundler/vendor/uri/lib/uri' # # Returns a new Bundler::URI. # uri = Bundler::URI('http://github.com/ruby/ruby') # # => #<Bundler::URI::HTTP http://github.com/ruby/ruby> @@ -866,6 +906,8 @@ module Bundler # Bundler::URI(uri) # # => #<Bundler::URI::HTTP http://github.com/ruby/ruby> # + # You must require 'bundler/vendor/uri/lib/uri' to use this method. + # def URI(uri) if uri.is_a?(Bundler::URI::Generic) uri diff --git a/lib/bundler/vendor/uri/lib/uri/file.rb b/lib/bundler/vendor/uri/lib/uri/file.rb index de347af6eb..21dd9ee535 100644 --- a/lib/bundler/vendor/uri/lib/uri/file.rb +++ b/lib/bundler/vendor/uri/lib/uri/file.rb @@ -47,7 +47,7 @@ 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 = Bundler::URI::File.build({:path => Bundler::URI::RFC2396_PARSER.escape('/path/my file.txt')}) # uri3.to_s # => "file:///path/my%20file.txt" # def self.build(args) diff --git a/lib/bundler/vendor/uri/lib/uri/generic.rb b/lib/bundler/vendor/uri/lib/uri/generic.rb index 6abb171d14..30dab60903 100644 --- a/lib/bundler/vendor/uri/lib/uri/generic.rb +++ b/lib/bundler/vendor/uri/lib/uri/generic.rb @@ -73,7 +73,7 @@ module Bundler::URI # # At first, tries to create a new Bundler::URI::Generic instance using # Bundler::URI::Generic::build. But, if exception Bundler::URI::InvalidComponentError is raised, - # then it does Bundler::URI::Escape.escape all Bundler::URI components and tries again. + # then it does Bundler::URI::RFC2396_PARSER.escape all Bundler::URI components and tries again. # def self.build2(args) begin @@ -126,9 +126,9 @@ module Bundler::URI end end else - component = self.class.component rescue ::Bundler::URI::Generic::COMPONENT + component = self.component rescue ::Bundler::URI::Generic::COMPONENT raise ArgumentError, - "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil @@ -186,18 +186,18 @@ module Bundler::URI if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -284,7 +284,7 @@ module Bundler::URI # Returns the parser to be used. # - # Unless a Bundler::URI::Parser is defined, DEFAULT_PARSER is used. + # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser @@ -315,7 +315,7 @@ module Bundler::URI end # - # Checks the scheme +v+ component against the Bundler::URI::Parser Regexp for :SCHEME. + # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v @@ -385,7 +385,7 @@ module Bundler::URI # # Checks the user +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -409,7 +409,7 @@ module Bundler::URI # # Checks the password +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -511,7 +511,7 @@ module Bundler::URI user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ module Bundler::URI # See also Bundler::URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ module Bundler::URI @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after Bundler::URI decoding. def decoded_user Bundler::URI.decode_uri_component(@user) if @user @@ -586,7 +592,7 @@ module Bundler::URI # # Checks the host +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :HOST. + # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. @@ -615,6 +621,13 @@ module Bundler::URI end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ module Bundler::URI def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -675,7 +689,7 @@ module Bundler::URI # # Checks the port +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :PORT. + # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. @@ -729,6 +743,7 @@ module Bundler::URI def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end @@ -748,7 +763,7 @@ module Bundler::URI # # Checks the path +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp + # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, @@ -853,7 +868,7 @@ module Bundler::URI # # Checks the opaque +v+ component for RFC2396 compliance and - # against the Bundler::URI::Parser Regexp for :OPAQUE. + # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. @@ -905,7 +920,7 @@ module Bundler::URI end # - # Checks the fragment +v+ component against the Bundler::URI::Parser Regexp for :FRAGMENT. + # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args @@ -1121,7 +1136,7 @@ module Bundler::URI base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1134,9 +1149,7 @@ module Bundler::URI # RFC2396, Section 5.2, 4) if authority - base.set_userinfo(rel.userinfo) - base.set_host(rel.host) - base.set_port(rel.port || base.default_port) + base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) @@ -1527,7 +1540,7 @@ module Bundler::URI 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 + warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end diff --git a/lib/bundler/vendor/uri/lib/uri/http.rb b/lib/bundler/vendor/uri/lib/uri/http.rb index 6c34a469b7..9b217ee266 100644 --- a/lib/bundler/vendor/uri/lib/uri/http.rb +++ b/lib/bundler/vendor/uri/lib/uri/http.rb @@ -61,6 +61,18 @@ module Bundler::URI super(tmp) end + # Do not allow empty host names, as they are not allowed by RFC 3986. + def check_host(v) + ret = super + + if ret && v.empty? + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + ret + end + # # == Description # diff --git a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb index 229971f73c..522113fe67 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb @@ -67,7 +67,7 @@ module Bundler::URI # # == Synopsis # - # Bundler::URI::Parser.new([opts]) + # Bundler::URI::RFC2396_Parser.new([opts]) # # == Args # @@ -86,7 +86,7 @@ module Bundler::URI # # == Examples # - # p = Bundler::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # p = Bundler::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> #<Bundler::URI::HTTP http://example.jp/%uABCD> # Bundler::URI.parse(u.to_s) #=> raises Bundler::URI::InvalidURIError # @@ -108,12 +108,12 @@ module Bundler::URI # The Hash of patterns. # - # See also Bundler::URI::Parser.initialize_pattern. + # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # - # See also Bundler::URI::Parser.initialize_regexp. + # See also #initialize_regexp. attr_reader :regexp # Returns a split Bundler::URI against +regexp[:ABS_URI]+. @@ -202,8 +202,7 @@ module Bundler::URI # # == Usage # - # p = Bundler::URI::Parser.new - # p.parse("ldap://ldap.example.com/dc=example?user=john") + # Bundler::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> #<Bundler::URI::LDAP ldap://ldap.example.com/dc=example?user=john> # def parse(uri) @@ -244,7 +243,7 @@ module Bundler::URI # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # - # See also Bundler::URI::Parser.make_regexp. + # See also #make_regexp. # def extract(str, schemes = nil) if block_given? @@ -263,7 +262,7 @@ module Bundler::URI unless schemes @regexp[:ABS_URI_REF] else - /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end @@ -524,6 +523,8 @@ module Bundler::URI ret end + # Returns +uri+ as-is if it is Bundler::URI, or convert it to Bundler::URI if it is + # a String. def convert_to_uri(uri) if uri.is_a?(Bundler::URI::Generic) uri diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb index d4996a12e2..ad76308e81 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 = '010003'.freeze - VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + VERSION = '1.1.1'.freeze + VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index fa24b4966e..ca7bb0719a 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,13 +1,21 @@ # frozen_string_literal: false module Bundler - VERSION = "2.7.0.dev".freeze + VERSION = "4.1.0.dev".freeze def self.bundler_major_version - @bundler_major_version ||= VERSION.split(".").first.to_i + @bundler_major_version ||= gem_version.segments.first end def self.gem_version @gem_version ||= Gem::Version.create(VERSION) end + + def self.verbose_version + @verbose_version ||= "#{VERSION}#{simulated_version ? " (simulating Bundler #{simulated_version})" : ""}" + end + + def self.simulated_version + @simulated_version ||= Bundler.settings[:simulate_version] + end end diff --git a/lib/bundler/vlad.rb b/lib/bundler/vlad.rb index 6179d0e4eb..c3a3d949a6 100644 --- a/lib/bundler/vlad.rb +++ b/lib/bundler/vlad.rb @@ -1,17 +1,4 @@ # frozen_string_literal: true require_relative "shared_helpers" -Bundler::SharedHelpers.major_deprecation 2, - "The Bundler task for Vlad" - -# Vlad task for Bundler. -# -# Add "require 'bundler/vlad'" in your Vlad deploy.rb, and -# include the vlad:bundle:install task in your vlad:deploy task. -require_relative "deployment" - -include Rake::DSL if defined? Rake::DSL - -namespace :vlad do - Bundler::Deployment.define_task(Rake::RemoteTask, :remote_task, roles: :app) -end +Bundler::SharedHelpers.feature_removed! "The Bundler task for Vlad" diff --git a/lib/cgi.rb b/lib/cgi.rb index 69c3c4fd24..bb306d2e06 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -1,297 +1,7 @@ # frozen_string_literal: true -# -# cgi.rb - cgi support library -# -# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. -# -# Copyright (C) 2000 Information-technology Promotion Agency, Japan -# -# Author: Wakou Aoyama <wakou@ruby-lang.org> -# -# Documentation: Wakou Aoyama (RDoc'd and embellished by William Webber) -# -# == Overview -# -# The Common Gateway Interface (CGI) is a simple protocol for passing an HTTP -# request from a web server to a standalone program, and returning the output -# to the web browser. Basically, a CGI program is called with the parameters -# of the request passed in either in the environment (GET) or via $stdin -# (POST), and everything it prints to $stdout is returned to the client. -# -# This file holds the CGI class. This class provides functionality for -# retrieving HTTP request parameters, managing cookies, and generating HTML -# output. -# -# The file CGI::Session provides session management functionality; see that -# class for more details. -# -# See http://www.w3.org/CGI/ for more information on the CGI protocol. -# -# == Introduction -# -# CGI is a large class, providing several categories of methods, many of which -# are mixed in from other modules. Some of the documentation is in this class, -# some in the modules CGI::QueryExtension and CGI::HtmlExtension. See -# CGI::Cookie for specific information on handling cookies, and cgi/session.rb -# (CGI::Session) for information on sessions. -# -# For queries, CGI provides methods to get at environmental variables, -# parameters, cookies, and multipart request data. For responses, CGI provides -# methods for writing output and generating HTML. -# -# Read on for more details. Examples are provided at the bottom. -# -# == Queries -# -# The CGI class dynamically mixes in parameter and cookie-parsing -# functionality, environmental variable access, and support for -# parsing multipart requests (including uploaded files) from the -# CGI::QueryExtension module. -# -# === Environmental Variables -# -# The standard CGI environmental variables are available as read-only -# attributes of a CGI object. The following is a list of these variables: -# -# -# AUTH_TYPE HTTP_HOST REMOTE_IDENT -# CONTENT_LENGTH HTTP_NEGOTIATE REMOTE_USER -# CONTENT_TYPE HTTP_PRAGMA REQUEST_METHOD -# GATEWAY_INTERFACE HTTP_REFERER SCRIPT_NAME -# HTTP_ACCEPT HTTP_USER_AGENT SERVER_NAME -# HTTP_ACCEPT_CHARSET PATH_INFO SERVER_PORT -# HTTP_ACCEPT_ENCODING PATH_TRANSLATED SERVER_PROTOCOL -# HTTP_ACCEPT_LANGUAGE QUERY_STRING SERVER_SOFTWARE -# HTTP_CACHE_CONTROL REMOTE_ADDR -# HTTP_FROM REMOTE_HOST -# -# -# For each of these variables, there is a corresponding attribute with the -# same name, except all lower case and without a preceding HTTP_. -# +content_length+ and +server_port+ are integers; the rest are strings. -# -# === Parameters -# -# The method #params() returns a hash of all parameters in the request as -# name/value-list pairs, where the value-list is an Array of one or more -# values. The CGI object itself also behaves as a hash of parameter names -# to values, but only returns a single value (as a String) for each -# parameter name. -# -# For instance, suppose the request contains the parameter -# "favourite_colours" with the multiple values "blue" and "green". The -# following behavior would occur: -# -# cgi.params["favourite_colours"] # => ["blue", "green"] -# cgi["favourite_colours"] # => "blue" -# -# If a parameter does not exist, the former method will return an empty -# array, the latter an empty string. The simplest way to test for existence -# of a parameter is by the #has_key? method. -# -# === Cookies -# -# HTTP Cookies are automatically parsed from the request. They are available -# from the #cookies() accessor, which returns a hash from cookie name to -# CGI::Cookie object. -# -# === Multipart requests -# -# If a request's method is POST and its content type is multipart/form-data, -# then it may contain uploaded files. These are stored by the QueryExtension -# module in the parameters of the request. The parameter name is the name -# attribute of the file input field, as usual. However, the value is not -# a string, but an IO object, either an IOString for small files, or a -# Tempfile for larger ones. This object also has the additional singleton -# methods: -# -# #local_path():: the path of the uploaded file on the local filesystem -# #original_filename():: the name of the file on the client computer -# #content_type():: the content type of the file -# -# == Responses -# -# The CGI class provides methods for sending header and content output to -# the HTTP client, and mixes in methods for programmatic HTML generation -# from CGI::HtmlExtension and CGI::TagMaker modules. The precise version of HTML -# to use for HTML generation is specified at object creation time. -# -# === Writing output -# -# The simplest way to send output to the HTTP client is using the #out() method. -# This takes the HTTP headers as a hash parameter, and the body content -# via a block. The headers can be generated as a string using the #http_header() -# method. The output stream can be written directly to using the #print() -# method. -# -# === Generating HTML -# -# Each HTML element has a corresponding method for generating that -# element as a String. The name of this method is the same as that -# of the element, all lowercase. The attributes of the element are -# passed in as a hash, and the body as a no-argument block that evaluates -# to a String. The HTML generation module knows which elements are -# always empty, and silently drops any passed-in body. It also knows -# which elements require matching closing tags and which don't. However, -# it does not know what attributes are legal for which elements. -# -# There are also some additional HTML generation methods mixed in from -# the CGI::HtmlExtension module. These include individual methods for the -# different types of form inputs, and methods for elements that commonly -# take particular attributes where the attributes can be directly specified -# as arguments, rather than via a hash. -# -# === Utility HTML escape and other methods like a function. -# -# There are some utility tool defined in cgi/util.rb . -# And when include, you can use utility methods like a function. -# -# == Examples of use -# -# === Get form values -# -# require "cgi" -# cgi = CGI.new -# value = cgi['field_name'] # <== value string for 'field_name' -# # if not 'field_name' included, then return "". -# fields = cgi.keys # <== array of field names -# -# # returns true if form has 'field_name' -# cgi.has_key?('field_name') -# cgi.has_key?('field_name') -# cgi.include?('field_name') -# -# CAUTION! <code>cgi['field_name']</code> returned an Array with the old -# cgi.rb(included in Ruby 1.6) -# -# === Get form values as hash -# -# require "cgi" -# cgi = CGI.new -# params = cgi.params -# -# cgi.params is a hash. -# -# cgi.params['new_field_name'] = ["value"] # add new param -# cgi.params['field_name'] = ["new_value"] # change value -# cgi.params.delete('field_name') # delete param -# cgi.params.clear # delete all params -# -# -# === Save form values to file -# -# require "pstore" -# db = PStore.new("query.db") -# db.transaction do -# db["params"] = cgi.params -# end -# -# -# === Restore form values from file -# -# require "pstore" -# db = PStore.new("query.db") -# db.transaction do -# cgi.params = db["params"] -# end -# -# -# === Get multipart form values -# -# require "cgi" -# cgi = CGI.new -# value = cgi['field_name'] # <== value string for 'field_name' -# value.read # <== body of value -# value.local_path # <== path to local file of value -# value.original_filename # <== original filename of value -# value.content_type # <== content_type of value -# -# and value has StringIO or Tempfile class methods. -# -# === Get cookie values -# -# require "cgi" -# cgi = CGI.new -# values = cgi.cookies['name'] # <== array of 'name' -# # if not 'name' included, then return []. -# names = cgi.cookies.keys # <== array of cookie names -# -# and cgi.cookies is a hash. -# -# === Get cookie objects -# -# require "cgi" -# cgi = CGI.new -# for name, cookie in cgi.cookies -# cookie.expires = Time.now + 30 -# end -# cgi.out("cookie" => cgi.cookies) {"string"} -# -# cgi.cookies # { "name1" => cookie1, "name2" => cookie2, ... } -# -# require "cgi" -# cgi = CGI.new -# cgi.cookies['name'].expires = Time.now + 30 -# cgi.out("cookie" => cgi.cookies['name']) {"string"} -# -# === Print http header and html string to $DEFAULT_OUTPUT ($>) -# -# require "cgi" -# cgi = CGI.new("html4") # add HTML generation methods -# cgi.out do -# cgi.html do -# cgi.head do -# cgi.title { "TITLE" } -# end + -# cgi.body do -# cgi.form("ACTION" => "uri") do -# cgi.p do -# cgi.textarea("get_text") + -# cgi.br + -# cgi.submit -# end -# end + -# cgi.pre do -# CGI.escapeHTML( -# "params: #{cgi.params.inspect}\n" + -# "cookies: #{cgi.cookies.inspect}\n" + -# ENV.collect do |key, value| -# "#{key} --> #{value}\n" -# end.join("") -# ) -# end -# end -# end -# end -# -# # add HTML generation methods -# CGI.new("html3") # html3.2 -# CGI.new("html4") # html4.01 (Strict) -# CGI.new("html4Tr") # html4.01 Transitional -# CGI.new("html4Fr") # html4.01 Frameset -# CGI.new("html5") # html5 -# -# === Some utility methods -# -# require 'cgi/util' -# CGI.escapeHTML('Usage: foo "bar" <baz>') -# -# -# === Some utility methods like a function -# -# require 'cgi/util' -# include CGI::Util -# escapeHTML('Usage: foo "bar" <baz>') -# h('Usage: foo "bar" <baz>') # alias -# -# - -class CGI - VERSION = "0.4.2" -end - -require 'cgi/core' -require 'cgi/cookie' -require 'cgi/util' -CGI.autoload(:HtmlExtension, 'cgi/html') +require "cgi/escape" +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE +CGI library is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you need to use the full features of CGI library, Please install cgi gem. +WARNING diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec deleted file mode 100644 index 5ef00d591d..0000000000 --- a/lib/cgi/cgi.gemspec +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Yukihiro Matsumoto"] - spec.email = ["matz@ruby-lang.org"] - - spec.summary = %q{Support for the Common Gateway Interface protocol.} - spec.description = %q{Support for the Common Gateway Interface protocol.} - spec.homepage = "https://github.com/ruby/cgi" - spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = ">= 2.5.0" - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - spec.executables = [] - - spec.files = [ - "COPYING", - "BSDL", - "README.md", - *Dir["lib{.rb,/**/*.rb}", "bin/*"] ] - - spec.require_paths = ["lib"] - - if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby' - spec.platform = 'java' - spec.require_paths << "ext/java/org/jruby/ext/cgi/escape/lib" - spec.files += Dir["ext/java/**/*.{rb}", "lib/cgi/escape.jar"] - else - spec.files += Dir["ext/cgi/**/*.{rb,c,h,sh}", "ext/cgi/escape/depend", "lib/cgi/escape.so"] - spec.extensions = ["ext/cgi/escape/extconf.rb"] - end -end diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb deleted file mode 100644 index 1c4ef6a600..0000000000 --- a/lib/cgi/cookie.rb +++ /dev/null @@ -1,210 +0,0 @@ -# frozen_string_literal: true -require_relative 'util' -class CGI - # Class representing an HTTP cookie. - # - # In addition to its specific fields and methods, a Cookie instance - # is a delegator to the array of its values. - # - # See RFC 2965. - # - # == Examples of use - # cookie1 = CGI::Cookie.new("name", "value1", "value2", ...) - # cookie1 = CGI::Cookie.new("name" => "name", "value" => "value") - # cookie1 = CGI::Cookie.new('name' => 'name', - # 'value' => ['value1', 'value2', ...], - # 'path' => 'path', # optional - # 'domain' => 'domain', # optional - # 'expires' => Time.now, # optional - # 'secure' => true, # optional - # 'httponly' => true # optional - # ) - # - # cgi.out("cookie" => [cookie1, cookie2]) { "string" } - # - # name = cookie1.name - # values = cookie1.value - # path = cookie1.path - # domain = cookie1.domain - # expires = cookie1.expires - # secure = cookie1.secure - # httponly = cookie1.httponly - # - # cookie1.name = 'name' - # cookie1.value = ['value1', 'value2', ...] - # cookie1.path = 'path' - # cookie1.domain = 'domain' - # cookie1.expires = Time.now + 30 - # cookie1.secure = true - # cookie1.httponly = true - class Cookie < Array - @@accept_charset="UTF-8" unless defined?(@@accept_charset) - - TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z" - PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z" - DOMAIN_VALUE_RE = %r"\A\.?(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z" - - # Create a new CGI::Cookie object. - # - # :call-seq: - # Cookie.new(name_string,*value) - # Cookie.new(options_hash) - # - # +name_string+:: - # The name of the cookie; in this form, there is no #domain or - # #expiration. The #path is gleaned from the +SCRIPT_NAME+ environment - # variable, and #secure is false. - # <tt>*value</tt>:: - # value or list of values of the cookie - # +options_hash+:: - # A Hash of options to initialize this Cookie. Possible options are: - # - # name:: the name of the cookie. Required. - # value:: the cookie's value or list of values. - # path:: the path for which this cookie applies. Defaults to - # the value of the +SCRIPT_NAME+ environment variable. - # domain:: the domain for which this cookie applies. - # expires:: the time at which this cookie expires, as a +Time+ object. - # secure:: whether this cookie is a secure cookie or not (default to - # false). Secure cookies are only transmitted to HTTPS - # servers. - # httponly:: whether this cookie is a HttpOnly cookie or not (default to - # false). HttpOnly cookies are not available to javascript. - # - # These keywords correspond to attributes of the cookie object. - def initialize(name = "", *value) - @domain = nil - @expires = nil - if name.kind_of?(String) - self.name = name - self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - @secure = false - @httponly = false - return super(value) - end - - options = name - unless options.has_key?("name") - raise ArgumentError, "`name' required" - end - - self.name = options["name"] - value = Array(options["value"]) - # simple support for IE - self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - self.domain = options["domain"] - @expires = options["expires"] - @secure = options["secure"] == true - @httponly = options["httponly"] == true - - super(value) - end - - # Name of this cookie, as a +String+ - attr_reader :name - # Set name of this cookie - def name=(str) - if str and !TOKEN_RE.match?(str) - raise ArgumentError, "invalid name: #{str.dump}" - end - @name = str - end - - # Path for which this cookie applies, as a +String+ - attr_reader :path - # Set path for which this cookie applies - def path=(str) - if str and !PATH_VALUE_RE.match?(str) - raise ArgumentError, "invalid path: #{str.dump}" - end - @path = str - end - - # Domain for which this cookie applies, as a +String+ - attr_reader :domain - # Set domain for which this cookie applies - def domain=(str) - if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str)) - raise ArgumentError, "invalid domain: #{str.dump}" - end - @domain = str - end - - # Time at which this cookie expires, as a +Time+ - attr_accessor :expires - # True if this cookie is secure; false otherwise - attr_reader :secure - # True if this cookie is httponly; false otherwise - attr_reader :httponly - - # Returns the value or list of values for this cookie. - def value - self - end - - # Replaces the value of this cookie with a new value or list of values. - def value=(val) - replace(Array(val)) - end - - # Set whether the Cookie is a secure cookie or not. - # - # +val+ must be a boolean. - def secure=(val) - @secure = val if val == true or val == false - @secure - end - - # Set whether the Cookie is a httponly cookie or not. - # - # +val+ must be a boolean. - def httponly=(val) - @httponly = !!val - end - - # Convert the Cookie to its string representation. - def to_s - val = collect{|v| CGI.escape(v) }.join("&") - buf = "#{@name}=#{val}".dup - buf << "; domain=#{@domain}" if @domain - buf << "; path=#{@path}" if @path - buf << "; expires=#{CGI.rfc1123_date(@expires)}" if @expires - buf << "; secure" if @secure - buf << "; HttpOnly" if @httponly - buf - end - - # Parse a raw cookie string into a hash of cookie-name=>Cookie - # pairs. - # - # cookies = CGI::Cookie.parse("raw_cookie_string") - # # { "name1" => cookie1, "name2" => cookie2, ... } - # - def self.parse(raw_cookie) - cookies = Hash.new([]) - return cookies unless raw_cookie - - raw_cookie.split(/;\s?/).each do |pairs| - name, values = pairs.split('=',2) - next unless name and values - values ||= "" - values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) } - if cookies.has_key?(name) - cookies[name].concat(values) - else - cookies[name] = Cookie.new(name, *values) - end - end - - cookies - end - - # A summary of cookie string. - def inspect - "#<CGI::Cookie: #{self.to_s.inspect}>" - end - - end # class Cookie -end - - diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb deleted file mode 100644 index 62e606837a..0000000000 --- a/lib/cgi/core.rb +++ /dev/null @@ -1,900 +0,0 @@ -# frozen_string_literal: true -#-- -# Methods for generating HTML, parsing CGI-related parameters, and -# generating HTTP responses. -#++ -class CGI - unless const_defined?(:Util) - module Util - @@accept_charset = "UTF-8" # :nodoc: - end - include Util - extend Util - end - - $CGI_ENV = ENV # for FCGI support - - # String for carriage return - CR = "\015" - - # String for linefeed - LF = "\012" - - # Standard internet newline sequence - EOL = CR + LF - - REVISION = '$Id$' #:nodoc: - - # Whether processing will be required in binary vs text - NEEDS_BINMODE = File::BINARY != 0 - - # Path separators in different environments. - PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'} - - # HTTP status codes. - HTTP_STATUS = { - "OK" => "200 OK", - "PARTIAL_CONTENT" => "206 Partial Content", - "MULTIPLE_CHOICES" => "300 Multiple Choices", - "MOVED" => "301 Moved Permanently", - "REDIRECT" => "302 Found", - "NOT_MODIFIED" => "304 Not Modified", - "BAD_REQUEST" => "400 Bad Request", - "AUTH_REQUIRED" => "401 Authorization Required", - "FORBIDDEN" => "403 Forbidden", - "NOT_FOUND" => "404 Not Found", - "METHOD_NOT_ALLOWED" => "405 Method Not Allowed", - "NOT_ACCEPTABLE" => "406 Not Acceptable", - "LENGTH_REQUIRED" => "411 Length Required", - "PRECONDITION_FAILED" => "412 Precondition Failed", - "SERVER_ERROR" => "500 Internal Server Error", - "NOT_IMPLEMENTED" => "501 Method Not Implemented", - "BAD_GATEWAY" => "502 Bad Gateway", - "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates" - } - - # :startdoc: - - # Synonym for ENV. - def env_table - ENV - end - - # Synonym for $stdin. - def stdinput - $stdin - end - - # Synonym for $stdout. - def stdoutput - $stdout - end - - private :env_table, :stdinput, :stdoutput - - # Create an HTTP header block as a string. - # - # :call-seq: - # http_header(content_type_string="text/html") - # http_header(headers_hash) - # - # Includes the empty line that ends the header block. - # - # +content_type_string+:: - # If this form is used, this string is the <tt>Content-Type</tt> - # +headers_hash+:: - # A Hash of header values. The following header keys are recognized: - # - # type:: The Content-Type header. Defaults to "text/html" - # charset:: The charset of the body, appended to the Content-Type header. - # nph:: A boolean value. If true, prepend protocol string and status - # code, and date; and sets default values for "server" and - # "connection" if not explicitly set. - # status:: - # The HTTP status code as a String, returned as the Status header. The - # values are: - # - # OK:: 200 OK - # PARTIAL_CONTENT:: 206 Partial Content - # MULTIPLE_CHOICES:: 300 Multiple Choices - # MOVED:: 301 Moved Permanently - # REDIRECT:: 302 Found - # NOT_MODIFIED:: 304 Not Modified - # BAD_REQUEST:: 400 Bad Request - # AUTH_REQUIRED:: 401 Authorization Required - # FORBIDDEN:: 403 Forbidden - # NOT_FOUND:: 404 Not Found - # METHOD_NOT_ALLOWED:: 405 Method Not Allowed - # NOT_ACCEPTABLE:: 406 Not Acceptable - # LENGTH_REQUIRED:: 411 Length Required - # PRECONDITION_FAILED:: 412 Precondition Failed - # SERVER_ERROR:: 500 Internal Server Error - # NOT_IMPLEMENTED:: 501 Method Not Implemented - # BAD_GATEWAY:: 502 Bad Gateway - # VARIANT_ALSO_VARIES:: 506 Variant Also Negotiates - # - # server:: The server software, returned as the Server header. - # connection:: The connection type, returned as the Connection header (for - # instance, "close". - # length:: The length of the content that will be sent, returned as the - # Content-Length header. - # language:: The language of the content, returned as the Content-Language - # header. - # expires:: The time on which the current content expires, as a +Time+ - # object, returned as the Expires header. - # cookie:: - # A cookie or cookies, returned as one or more Set-Cookie headers. The - # value can be the literal string of the cookie; a CGI::Cookie object; - # an Array of literal cookie strings or Cookie objects; or a hash all of - # whose values are literal cookie strings or Cookie objects. - # - # These cookies are in addition to the cookies held in the - # @output_cookies field. - # - # Other headers can also be set; they are appended as key: value. - # - # Examples: - # - # http_header - # # Content-Type: text/html - # - # http_header("text/plain") - # # Content-Type: text/plain - # - # http_header("nph" => true, - # "status" => "OK", # == "200 OK" - # # "status" => "200 GOOD", - # "server" => ENV['SERVER_SOFTWARE'], - # "connection" => "close", - # "type" => "text/html", - # "charset" => "iso-2022-jp", - # # Content-Type: text/html; charset=iso-2022-jp - # "length" => 103, - # "language" => "ja", - # "expires" => Time.now + 30, - # "cookie" => [cookie1, cookie2], - # "my_header1" => "my_value", - # "my_header2" => "my_value") - # - # This method does not perform charset conversion. - def http_header(options='text/html') - if options.is_a?(String) - content_type = options - buf = _header_for_string(content_type) - elsif options.is_a?(Hash) - if options.size == 1 && options.has_key?('type') - content_type = options['type'] - buf = _header_for_string(content_type) - else - buf = _header_for_hash(options.dup) - end - else - raise ArgumentError.new("expected String or Hash but got #{options.class}") - end - if defined?(MOD_RUBY) - _header_for_modruby(buf) - return '' - else - buf << EOL # empty line of separator - return buf - end - end # http_header() - - # This method is an alias for #http_header, when HTML5 tag maker is inactive. - # - # NOTE: use #http_header to create HTTP header blocks, this alias is only - # provided for backwards compatibility. - # - # Using #header with the HTML5 tag maker will create a <header> element. - alias :header :http_header - - def _no_crlf_check(str) - if str - str = str.to_s - raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/ - str - else - nil - end - end - private :_no_crlf_check - - def _header_for_string(content_type) #:nodoc: - buf = ''.dup - if nph?() - buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}" - buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" - buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}" - buf << "Connection: close#{EOL}" - end - buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}" - if @output_cookies - @output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" } - end - return buf - end # _header_for_string - private :_header_for_string - - def _header_for_hash(options) #:nodoc: - buf = ''.dup - ## add charset to option['type'] - options['type'] ||= 'text/html' - charset = options.delete('charset') - options['type'] += "; charset=#{charset}" if charset - ## NPH - options.delete('nph') if defined?(MOD_RUBY) - if options.delete('nph') || nph?() - protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0' - status = options.delete('status') - status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK' - buf << "#{protocol} #{status}#{EOL}" - buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" - options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || '' - options['connection'] ||= 'close' - end - ## common headers - status = options.delete('status') - buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status - server = options.delete('server') - buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server - connection = options.delete('connection') - buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection - type = options.delete('type') - buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type - length = options.delete('length') - buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length - language = options.delete('language') - buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language - expires = options.delete('expires') - buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires - ## cookie - if cookie = options.delete('cookie') - case cookie - when String, Cookie - buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" - when Array - arr = cookie - arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - when Hash - hash = cookie - hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - end - end - if @output_cookies - @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - end - ## other headers - options.each do |key, value| - buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}" - end - return buf - end # _header_for_hash - private :_header_for_hash - - def nph? #:nodoc: - return /IIS\/(\d+)/ =~ $CGI_ENV['SERVER_SOFTWARE'] && $1.to_i < 5 - end - - def _header_for_modruby(buf) #:nodoc: - request = Apache::request - buf.scan(/([^:]+): (.+)#{EOL}/o) do |name, value| - $stderr.printf("name:%s value:%s\n", name, value) if $DEBUG - case name - when 'Set-Cookie' - request.headers_out.add(name, value) - when /^status$/i - request.status_line = value - request.status = value.to_i - when /^content-type$/i - request.content_type = value - when /^content-encoding$/i - request.content_encoding = value - when /^location$/i - request.status = 302 if request.status == 200 - request.headers_out[name] = value - else - request.headers_out[name] = value - end - end - request.send_http_header - return '' - end - private :_header_for_modruby - - # Print an HTTP header and body to $DEFAULT_OUTPUT ($>) - # - # :call-seq: - # cgi.out(content_type_string='text/html') - # cgi.out(headers_hash) - # - # +content_type_string+:: - # If a string is passed, it is assumed to be the content type. - # +headers_hash+:: - # This is a Hash of headers, similar to that used by #http_header. - # +block+:: - # A block is required and should evaluate to the body of the response. - # - # <tt>Content-Length</tt> is automatically calculated from the size of - # the String returned by the content block. - # - # If <tt>ENV['REQUEST_METHOD'] == "HEAD"</tt>, then only the header - # is output (the content block is still required, but it is ignored). - # - # If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then the - # content is converted to this charset, and the language is set to "ja". - # - # Example: - # - # cgi = CGI.new - # cgi.out{ "string" } - # # Content-Type: text/html - # # Content-Length: 6 - # # - # # string - # - # cgi.out("text/plain") { "string" } - # # Content-Type: text/plain - # # Content-Length: 6 - # # - # # string - # - # cgi.out("nph" => true, - # "status" => "OK", # == "200 OK" - # "server" => ENV['SERVER_SOFTWARE'], - # "connection" => "close", - # "type" => "text/html", - # "charset" => "iso-2022-jp", - # # Content-Type: text/html; charset=iso-2022-jp - # "language" => "ja", - # "expires" => Time.now + (3600 * 24 * 30), - # "cookie" => [cookie1, cookie2], - # "my_header1" => "my_value", - # "my_header2" => "my_value") { "string" } - # # HTTP/1.1 200 OK - # # Date: Sun, 15 May 2011 17:35:54 GMT - # # Server: Apache 2.2.0 - # # Connection: close - # # Content-Type: text/html; charset=iso-2022-jp - # # Content-Length: 6 - # # Content-Language: ja - # # Expires: Tue, 14 Jun 2011 17:35:54 GMT - # # Set-Cookie: foo - # # Set-Cookie: bar - # # my_header1: my_value - # # my_header2: my_value - # # - # # string - def out(options = "text/html") # :yield: - - options = { "type" => options } if options.kind_of?(String) - content = yield - options["length"] = content.bytesize.to_s - output = stdoutput - output.binmode if defined? output.binmode - output.print http_header(options) - output.print content unless "HEAD" == env_table['REQUEST_METHOD'] - end - - - # Print an argument or list of arguments to the default output stream - # - # cgi = CGI.new - # cgi.print # default: cgi.print == $DEFAULT_OUTPUT.print - def print(*options) - stdoutput.print(*options) - end - - # Parse an HTTP query string into a hash of key=>value pairs. - # - # params = CGI.parse("query_string") - # # {"name1" => ["value1", "value2", ...], - # # "name2" => ["value1", "value2", ...], ... } - # - def self.parse(query) - params = {} - query.split(/[&;]/).each do |pairs| - key, value = pairs.split('=',2).collect{|v| CGI.unescape(v) } - - next unless key - - params[key] ||= [] - params[key].push(value) if value - end - - params.default=[].freeze - params - end - - # Maximum content length of post data - ##MAX_CONTENT_LENGTH = 2 * 1024 * 1024 - - # Maximum number of request parameters when multipart - MAX_MULTIPART_COUNT = 128 - - # Mixin module that provides the following: - # - # 1. Access to the CGI environment variables as methods. See - # documentation to the CGI class for a list of these variables. The - # methods are exposed by removing the leading +HTTP_+ (if it exists) and - # downcasing the name. For example, +auth_type+ will return the - # environment variable +AUTH_TYPE+, and +accept+ will return the value - # for +HTTP_ACCEPT+. - # - # 2. Access to cookies, including the cookies attribute. - # - # 3. Access to parameters, including the params attribute, and overloading - # #[] to perform parameter value lookup by key. - # - # 4. The initialize_query method, for initializing the above - # mechanisms, handling multipart forms, and allowing the - # class to be used in "offline" mode. - # - module QueryExtension - - %w[ CONTENT_LENGTH SERVER_PORT ].each do |env| - define_method(env.delete_prefix('HTTP_').downcase) do - (val = env_table[env]) && Integer(val) - end - end - - %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO - PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST - REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME - SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE - - HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING - HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST - HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| - define_method(env.delete_prefix('HTTP_').downcase) do - env_table[env] - end - end - - # Get the raw cookies as a string. - def raw_cookie - env_table["HTTP_COOKIE"] - end - - # Get the raw RFC2965 cookies as a string. - def raw_cookie2 - env_table["HTTP_COOKIE2"] - end - - # Get the cookies as a hash of cookie-name=>Cookie pairs. - attr_accessor :cookies - - # Get the parameters as a hash of name=>values pairs, where - # values is an Array. - attr_reader :params - - # Get the uploaded files as a hash of name=>values pairs - attr_reader :files - - # Set all the parameters. - def params=(hash) - @params.clear - @params.update(hash) - end - - ## - # Parses multipart form elements according to - # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 - # - # Returns a hash of multipart form parameters with bodies of type StringIO or - # Tempfile depending on whether the multipart form element exceeds 10 KB - # - # params[name => body] - # - def read_multipart(boundary, content_length) - ## read first boundary - stdin = stdinput - first_line = "--#{boundary}#{EOL}" - content_length -= first_line.bytesize - status = stdin.read(first_line.bytesize) - raise EOFError.new("no content body") unless status - raise EOFError.new("bad content body") unless first_line == status - ## parse and set params - params = {} - @files = {} - boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/ - boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize - buf = ''.dup - bufsize = 10 * 1024 - max_count = MAX_MULTIPART_COUNT - n = 0 - tempfiles = [] - while true - (n += 1) < max_count or raise StandardError.new("too many parameters.") - ## create body (StringIO or Tempfile) - body = create_body(bufsize < content_length) - tempfiles << body if defined?(Tempfile) && body.kind_of?(Tempfile) - class << body - if method_defined?(:path) - alias local_path path - else - def local_path - nil - end - end - attr_reader :original_filename, :content_type - end - ## find head and boundary - head = nil - separator = EOL * 2 - until head && matched = boundary_rexp.match(buf) - if !head && pos = buf.index(separator) - len = pos + EOL.bytesize - head = buf[0, len] - buf = buf[(pos+separator.bytesize)..-1] - else - if head && buf.size > boundary_size - len = buf.size - boundary_size - body.print(buf[0, len]) - buf[0, len] = '' - end - c = stdin.read(bufsize < content_length ? bufsize : content_length) - raise EOFError.new("bad content body") if c.nil? || c.empty? - buf << c - content_length -= c.bytesize - end - end - ## read to end of boundary - m = matched - len = m.begin(0) - s = buf[0, len] - if s =~ /(\r?\n)\z/ - s = buf[0, len - $1.bytesize] - end - body.print(s) - buf = buf[m.end(0)..-1] - boundary_end = m[1] - content_length = -1 if boundary_end == '--' - ## reset file cursor position - body.rewind - ## original filename - /Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head) - filename = $1 || $2 || ''.dup - filename = CGI.unescape(filename) if unescape_filename?() - body.instance_variable_set(:@original_filename, filename) - ## content type - /Content-Type: (.*)/i.match(head) - (content_type = $1 || ''.dup).chomp! - body.instance_variable_set(:@content_type, content_type) - ## query parameter name - /Content-Disposition:.* name=(?:"(.*?)"|([^;\r\n]*))/i.match(head) - name = $1 || $2 || '' - if body.original_filename.empty? - value=body.read.dup.force_encoding(@accept_charset) - body.close! if defined?(Tempfile) && body.kind_of?(Tempfile) - (params[name] ||= []) << value - unless value.valid_encoding? - if @accept_charset_error_block - @accept_charset_error_block.call(name,value) - else - raise InvalidEncoding,"Accept-Charset encoding error" - end - end - class << params[name].last;self;end.class_eval do - define_method(:read){self} - define_method(:original_filename){""} - define_method(:content_type){""} - end - else - (params[name] ||= []) << body - @files[name]=body - end - ## break loop - break if content_length == -1 - end - raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/ - params.default = [] - params - rescue Exception - if tempfiles - tempfiles.each {|t| - if t.path - t.close! - end - } - end - raise - end # read_multipart - private :read_multipart - def create_body(is_large) #:nodoc: - if is_large - require 'tempfile' - body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT) - else - begin - require 'stringio' - body = StringIO.new("".b) - rescue LoadError - require 'tempfile' - body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT) - end - end - body.binmode if defined? body.binmode - return body - end - def unescape_filename? #:nodoc: - user_agent = $CGI_ENV['HTTP_USER_AGENT'] - return false unless user_agent - return /Mac/i.match(user_agent) && /Mozilla/i.match(user_agent) && !/MSIE/i.match(user_agent) - end - - # offline mode. read name=value pairs on standard input. - def read_from_cmdline - require "shellwords" - - string = unless ARGV.empty? - ARGV.join(' ') - else - if STDIN.tty? - STDERR.print( - %|(offline mode: enter name=value pairs on standard input)\n| - ) - end - array = readlines rescue nil - if not array.nil? - array.join(' ').gsub(/\n/n, '') - else - "" - end - end.gsub(/\\=/n, '%3D').gsub(/\\&/n, '%26') - - words = Shellwords.shellwords(string) - - if words.find{|x| /=/n.match(x) } - words.join('&') - else - words.join('+') - end - end - private :read_from_cmdline - - # A wrapper class to use a StringIO object as the body and switch - # to a TempFile when the passed threshold is passed. - # Initialize the data from the query. - # - # Handles multipart forms (in particular, forms that involve file uploads). - # Reads query parameters in the @params field, and cookies into @cookies. - def initialize_query() - if ("POST" == env_table['REQUEST_METHOD']) and - %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?| =~ env_table['CONTENT_TYPE'] - current_max_multipart_length = @max_multipart_length.respond_to?(:call) ? @max_multipart_length.call : @max_multipart_length - raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > current_max_multipart_length - boundary = $1.dup - @multipart = true - @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH'])) - else - @multipart = false - @params = CGI.parse( - case env_table['REQUEST_METHOD'] - when "GET", "HEAD" - if defined?(MOD_RUBY) - Apache::request.args or "" - else - env_table['QUERY_STRING'] or "" - end - when "POST" - stdinput.binmode if defined? stdinput.binmode - stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or '' - else - read_from_cmdline - end.dup.force_encoding(@accept_charset) - ) - unless Encoding.find(@accept_charset) == Encoding::ASCII_8BIT - @params.each do |key,values| - values.each do |value| - unless value.valid_encoding? - if @accept_charset_error_block - @accept_charset_error_block.call(key,value) - else - raise InvalidEncoding,"Accept-Charset encoding error" - end - end - end - end - end - end - - @cookies = CGI::Cookie.parse((env_table['HTTP_COOKIE'] or env_table['COOKIE'])) - end - private :initialize_query - - # Returns whether the form contained multipart/form-data - def multipart? - @multipart - end - - # Get the value for the parameter with a given key. - # - # If the parameter has multiple values, only the first will be - # retrieved; use #params to get the array of values. - def [](key) - params = @params[key] - return '' unless params - value = params[0] - if @multipart - if value - return value - elsif defined? StringIO - StringIO.new("".b) - else - Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT) - end - else - str = if value then value.dup else "" end - str - end - end - - # Return all query parameter names as an array of String. - def keys(*args) - @params.keys(*args) - end - - # Returns true if a given query string parameter exists. - def has_key?(*args) - @params.has_key?(*args) - end - alias key? has_key? - alias include? has_key? - - end # QueryExtension - - # Exception raised when there is an invalid encoding detected - class InvalidEncoding < Exception; end - - # @@accept_charset is default accept character set. - # This default value default is "UTF-8" - # If you want to change the default accept character set - # when create a new CGI instance, set this: - # - # CGI.accept_charset = "EUC-JP" - # - @@accept_charset="UTF-8" if false # needed for rdoc? - - # Return the accept character set for all new CGI instances. - def self.accept_charset - @@accept_charset - end - - # Set the accept character set for all new CGI instances. - def self.accept_charset=(accept_charset) - @@accept_charset=accept_charset - end - - # Return the accept character set for this CGI instance. - attr_reader :accept_charset - - # @@max_multipart_length is the maximum length of multipart data. - # The default value is 128 * 1024 * 1024 bytes - # - # The default can be set to something else in the CGI constructor, - # via the :max_multipart_length key in the option hash. - # - # See CGI.new documentation. - # - @@max_multipart_length= 128 * 1024 * 1024 - - # Create a new CGI instance. - # - # :call-seq: - # CGI.new(tag_maker) { block } - # CGI.new(options_hash = {}) { block } - # - # - # <tt>tag_maker</tt>:: - # This is the same as using the +options_hash+ form with the value <tt>{ - # :tag_maker => tag_maker }</tt> Note that it is recommended to use the - # +options_hash+ form, since it also allows you specify the charset you - # will accept. - # <tt>options_hash</tt>:: - # A Hash that recognizes three options: - # - # <tt>:accept_charset</tt>:: - # specifies encoding of received query string. If omitted, - # <tt>@@accept_charset</tt> is used. If the encoding is not valid, a - # CGI::InvalidEncoding will be raised. - # - # Example. Suppose <tt>@@accept_charset</tt> is "UTF-8" - # - # when not specified: - # - # cgi=CGI.new # @accept_charset # => "UTF-8" - # - # when specified as "EUC-JP": - # - # cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP" - # - # <tt>:tag_maker</tt>:: - # String that specifies which version of the HTML generation methods to - # use. If not specified, no HTML generation methods will be loaded. - # - # The following values are supported: - # - # "html3":: HTML 3.x - # "html4":: HTML 4.0 - # "html4Tr":: HTML 4.0 Transitional - # "html4Fr":: HTML 4.0 with Framesets - # "html5":: HTML 5 - # - # <tt>:max_multipart_length</tt>:: - # Specifies maximum length of multipart data. Can be an Integer scalar or - # a lambda, that will be evaluated when the request is parsed. This - # allows more complex logic to be set when determining whether to accept - # multipart data (e.g. consult a registered users upload allowance) - # - # Default is 128 * 1024 * 1024 bytes - # - # cgi=CGI.new(:max_multipart_length => 268435456) # simple scalar - # - # cgi=CGI.new(:max_multipart_length => -> {check_filesystem}) # lambda - # - # <tt>block</tt>:: - # If provided, the block is called when an invalid encoding is - # encountered. For example: - # - # encoding_errors={} - # cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value| - # encoding_errors[name] = value - # end - # - # Finally, if the CGI object is not created in a standard CGI call - # environment (that is, it can't locate REQUEST_METHOD in its environment), - # then it will run in "offline" mode. In this mode, it reads its parameters - # from the command line or (failing that) from standard input. Otherwise, - # cookies and other parameters are parsed automatically from the standard - # CGI locations, which varies according to the REQUEST_METHOD. - def initialize(options = {}, &block) # :yields: name, value - @accept_charset_error_block = block_given? ? block : nil - @options={ - :accept_charset=>@@accept_charset, - :max_multipart_length=>@@max_multipart_length - } - case options - when Hash - @options.merge!(options) - when String - @options[:tag_maker]=options - end - @accept_charset=@options[:accept_charset] - @max_multipart_length=@options[:max_multipart_length] - if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE") - Apache.request.setup_cgi_env - end - - extend QueryExtension - @multipart = false - - initialize_query() # set @params, @cookies - @output_cookies = nil - @output_hidden = nil - - case @options[:tag_maker] - when "html3" - require_relative 'html' - extend Html3 - extend HtmlExtension - when "html4" - require_relative 'html' - extend Html4 - extend HtmlExtension - when "html4Tr" - require_relative 'html' - extend Html4Tr - extend HtmlExtension - when "html4Fr" - require_relative 'html' - extend Html4Tr - extend Html4Fr - extend HtmlExtension - when "html5" - require_relative 'html' - extend Html5 - extend HtmlExtension - end - end - -end # class CGI diff --git a/lib/cgi/escape.rb b/lib/cgi/escape.rb new file mode 100644 index 0000000000..555d24a5da --- /dev/null +++ b/lib/cgi/escape.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +# Since Ruby 4.0, \CGI is a small holder for various escaping methods, included from CGI::Escape +# +# require 'cgi/escape' +# +# CGI.escape("Ruby programming language") +# #=> "Ruby+programming+language" +# CGI.escapeURIComponent("Ruby programming language") +# #=> "Ruby%20programming%20language" +# +# See CGI::Escape module for methods list and their description. +class CGI + module Escape; end + include Escape + extend Escape + module EscapeExt; end # :nodoc: +end + +# Web-related escape/unescape functionality. +module CGI::Escape + @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) + + # URL-encode a string into application/x-www-form-urlencoded. + # Space characters (<tt>" "</tt>) are encoded with plus signs (<tt>"+"</tt>) + # url_encoded_string = CGI.escape("'Stop!' said Fred") + # # => "%27Stop%21%27+said+Fred" + def escape(string) + encoding = string.encoding + buffer = string.b + buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end + buffer.tr!(' ', '+') + buffer.force_encoding(encoding) + end + + # URL-decode an application/x-www-form-urlencoded string with encoding(optional). + # string = CGI.unescape("%27Stop%21%27+said+Fred") + # # => "'Stop!' said Fred" + def unescape(string, encoding = @@accept_charset) + str = string.tr('+', ' ') + str = str.b + str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| + [m.delete('%')].pack('H*') + end + str.force_encoding(encoding) + str.valid_encoding? ? str : str.force_encoding(string.encoding) + end + + # URL-encode a string following RFC 3986 + # Space characters (<tt>" "</tt>) are encoded with (<tt>"%20"</tt>) + # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred") + # # => "%27Stop%21%27%20said%20Fred" + def escapeURIComponent(string) + encoding = string.encoding + buffer = string.b + buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end + buffer.force_encoding(encoding) + end + alias escape_uri_component escapeURIComponent + + # URL-decode a string following RFC 3986 with encoding(optional). + # string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred") + # # => "'Stop!'+said Fred" + def unescapeURIComponent(string, encoding = @@accept_charset) + str = string.b + str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| + [m.delete('%')].pack('H*') + end + str.force_encoding(encoding) + str.valid_encoding? ? str : str.force_encoding(string.encoding) + end + + alias unescape_uri_component unescapeURIComponent + + # The set of special characters and their escaped values + TABLE_FOR_ESCAPE_HTML__ = { # :nodoc: + "'" => ''', + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', + } + + # \Escape special characters in HTML, namely <tt>'&\"<></tt> + # CGI.escapeHTML('Usage: foo "bar" <baz>') + # # => "Usage: foo "bar" <baz>" + def escapeHTML(string) + enc = string.encoding + unless enc.ascii_compatible? + if enc.dummy? + origenc = enc + enc = Encoding::Converter.asciicompat_encoding(enc) + string = enc ? string.encode(enc) : string.b + end + table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}] + string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table) + string.encode!(origenc) if origenc + string + else + string = string.b + string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) + string.force_encoding(enc) + end + end + + # Unescape a string that has been HTML-escaped + # CGI.unescapeHTML("Usage: foo "bar" <baz>") + # # => "Usage: foo \"bar\" <baz>" + def unescapeHTML(string) + enc = string.encoding + unless enc.ascii_compatible? + if enc.dummy? + origenc = enc + enc = Encoding::Converter.asciicompat_encoding(enc) + string = enc ? string.encode(enc) : string.b + end + string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do + case $1.encode(Encoding::US_ASCII) + when 'apos' then "'".encode(enc) + when 'amp' then '&'.encode(enc) + when 'quot' then '"'.encode(enc) + when 'gt' then '>'.encode(enc) + when 'lt' then '<'.encode(enc) + when /\A#0*(\d+)\z/ then $1.to_i.chr(enc) + when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc) + end + end + string.encode!(origenc) if origenc + return string + end + return string unless string.include? '&' + charlimit = case enc + when Encoding::UTF_8; 0x10ffff + when Encoding::ISO_8859_1; 256 + else 128 + end + string = string.b + string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do + match = $1.dup + case match + when 'apos' then "'" + when 'amp' then '&' + when 'quot' then '"' + when 'gt' then '>' + when 'lt' then '<' + when /\A#0*(\d+)\z/ + n = $1.to_i + if n < charlimit + n.chr(enc) + else + "&##{$1};" + end + when /\A#x([0-9a-f]+)\z/i + n = $1.hex + if n < charlimit + n.chr(enc) + else + "&#x#{$1};" + end + else + "&#{match};" + end + end + string.force_encoding enc + end + + alias escape_html escapeHTML + alias h escapeHTML + + alias unescape_html unescapeHTML + + # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there + unless RUBY_ENGINE == 'truffleruby' + begin + require 'cgi/escape.so' + rescue LoadError + end + end + + # \Escape only the tags of certain HTML elements in +string+. + # + # Takes an element or elements or array of elements. Each element + # is specified by the name of the element, without angle brackets. + # This matches both the start and the end tag of that element. + # The attribute list of the open tag will also be escaped (for + # instance, the double-quotes surrounding attribute values). + # + # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG") + # # "<BR><A HREF="url"></A>" + # + # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def escapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do + CGI.escapeHTML($&) + end + else + string + end + end + + # Undo escaping such as that done by CGI.escapeElement + # + # print CGI.unescapeElement( + # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG") + # # "<BR><A HREF="url"></A>" + # + # print CGI.unescapeElement( + # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def unescapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:>)?/im) do + unescapeHTML($&) + end + else + string + end + end + + alias escape_element escapeElement + + alias unescape_element unescapeElement + +end diff --git a/lib/cgi/html.rb b/lib/cgi/html.rb deleted file mode 100644 index 1543943320..0000000000 --- a/lib/cgi/html.rb +++ /dev/null @@ -1,1035 +0,0 @@ -# frozen_string_literal: true -class CGI - # Base module for HTML-generation mixins. - # - # Provides methods for code generation for tags following - # the various DTD element types. - module TagMaker # :nodoc: - - # Generate code for an element with required start and end tags. - # - # - - - def nn_element(element, attributes = {}) - s = nOE_element(element, attributes) - if block_given? - s << yield.to_s - end - s << "</#{element.upcase}>" - end - - def nn_element_def(attributes = {}, &block) - nn_element(__callee__, attributes, &block) - end - - # Generate code for an empty element. - # - # - O EMPTY - def nOE_element(element, attributes = {}) - attributes={attributes=>nil} if attributes.kind_of?(String) - s = "<#{element.upcase}".dup - attributes.each do|name, value| - next unless value - s << " " - s << CGI.escapeHTML(name.to_s) - if value != true - s << '="' - s << CGI.escapeHTML(value.to_s) - s << '"' - end - end - s << ">" - end - - def nOE_element_def(attributes = {}, &block) - nOE_element(__callee__, attributes, &block) - end - - - # Generate code for an element for which the end (and possibly the - # start) tag is optional. - # - # O O or - O - def nO_element(element, attributes = {}) - s = nOE_element(element, attributes) - if block_given? - s << yield.to_s - s << "</#{element.upcase}>" - end - s - end - - def nO_element_def(attributes = {}, &block) - nO_element(__callee__, attributes, &block) - end - - end # TagMaker - - - # Mixin module providing HTML generation methods. - # - # For example, - # cgi.a("http://www.example.com") { "Example" } - # # => "<A HREF=\"http://www.example.com\">Example</A>" - # - # Modules Html3, Html4, etc., contain more basic HTML-generation methods - # (+#title+, +#h1+, etc.). - # - # See class CGI for a detailed example. - # - module HtmlExtension - - - # Generate an Anchor element as a string. - # - # +href+ can either be a string, giving the URL - # for the HREF attribute, or it can be a hash of - # the element's attributes. - # - # The body of the element is the string returned by the no-argument - # block passed in. - # - # a("http://www.example.com") { "Example" } - # # => "<A HREF=\"http://www.example.com\">Example</A>" - # - # a("HREF" => "http://www.example.com", "TARGET" => "_top") { "Example" } - # # => "<A HREF=\"http://www.example.com\" TARGET=\"_top\">Example</A>" - # - def a(href = "") # :yield: - attributes = if href.kind_of?(String) - { "HREF" => href } - else - href - end - super(attributes) - end - - # Generate a Document Base URI element as a String. - # - # +href+ can either by a string, giving the base URL for the HREF - # attribute, or it can be a has of the element's attributes. - # - # The passed-in no-argument block is ignored. - # - # base("http://www.example.com/cgi") - # # => "<BASE HREF=\"http://www.example.com/cgi\">" - def base(href = "") # :yield: - attributes = if href.kind_of?(String) - { "HREF" => href } - else - href - end - super(attributes) - end - - # Generate a BlockQuote element as a string. - # - # +cite+ can either be a string, give the URI for the source of - # the quoted text, or a hash, giving all attributes of the element, - # or it can be omitted, in which case the element has no attributes. - # - # The body is provided by the passed-in no-argument block - # - # blockquote("http://www.example.com/quotes/foo.html") { "Foo!" } - # #=> "<BLOCKQUOTE CITE=\"http://www.example.com/quotes/foo.html\">Foo!</BLOCKQUOTE> - def blockquote(cite = {}) # :yield: - attributes = if cite.kind_of?(String) - { "CITE" => cite } - else - cite - end - super(attributes) - end - - - # Generate a Table Caption element as a string. - # - # +align+ can be a string, giving the alignment of the caption - # (one of top, bottom, left, or right). It can be a hash of - # all the attributes of the element. Or it can be omitted. - # - # The body of the element is provided by the passed-in no-argument block. - # - # caption("left") { "Capital Cities" } - # # => <CAPTION ALIGN=\"left\">Capital Cities</CAPTION> - def caption(align = {}) # :yield: - attributes = if align.kind_of?(String) - { "ALIGN" => align } - else - align - end - super(attributes) - end - - - # Generate a Checkbox Input element as a string. - # - # The attributes of the element can be specified as three arguments, - # +name+, +value+, and +checked+. +checked+ is a boolean value; - # if true, the CHECKED attribute will be included in the element. - # - # Alternatively, the attributes can be specified as a hash. - # - # checkbox("name") - # # = checkbox("NAME" => "name") - # - # checkbox("name", "value") - # # = checkbox("NAME" => "name", "VALUE" => "value") - # - # checkbox("name", "value", true) - # # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true) - def checkbox(name = "", value = nil, checked = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "checkbox", "NAME" => name, - "VALUE" => value, "CHECKED" => checked } - else - name["TYPE"] = "checkbox" - name - end - input(attributes) - end - - # Generate a sequence of checkbox elements, as a String. - # - # The checkboxes will all have the same +name+ attribute. - # Each checkbox is followed by a label. - # There will be one checkbox for each value. Each value - # can be specified as a String, which will be used both - # as the value of the VALUE attribute and as the label - # for that checkbox. A single-element array has the - # same effect. - # - # Each value can also be specified as a three-element array. - # The first element is the VALUE attribute; the second is the - # label; and the third is a boolean specifying whether this - # checkbox is CHECKED. - # - # Each value can also be specified as a two-element - # array, by omitting either the value element (defaults - # to the same as the label), or the boolean checked element - # (defaults to false). - # - # checkbox_group("name", "foo", "bar", "baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz - # - # checkbox_group("name", ["foo"], ["bar", true], "baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz - # - # checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo - # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz - # - # checkbox_group("NAME" => "name", - # "VALUES" => ["foo", "bar", "baz"]) - # - # checkbox_group("NAME" => "name", - # "VALUES" => [["foo"], ["bar", true], "baz"]) - # - # checkbox_group("NAME" => "name", - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - def checkbox_group(name = "", *values) - if name.kind_of?(Hash) - values = name["VALUES"] - name = name["NAME"] - end - values.collect{|value| - if value.kind_of?(String) - checkbox(name, value) + value - else - if value[-1] == true || value[-1] == false - checkbox(name, value[0], value[-1]) + - value[-2] - else - checkbox(name, value[0]) + - value[-1] - end - end - }.join - end - - - # Generate an File Upload Input element as a string. - # - # The attributes of the element can be specified as three arguments, - # +name+, +size+, and +maxlength+. +maxlength+ is the maximum length - # of the file's _name_, not of the file's _contents_. - # - # Alternatively, the attributes can be specified as a hash. - # - # See #multipart_form() for forms that include file uploads. - # - # file_field("name") - # # <INPUT TYPE="file" NAME="name" SIZE="20"> - # - # file_field("name", 40) - # # <INPUT TYPE="file" NAME="name" SIZE="40"> - # - # file_field("name", 40, 100) - # # <INPUT TYPE="file" NAME="name" SIZE="40" MAXLENGTH="100"> - # - # file_field("NAME" => "name", "SIZE" => 40) - # # <INPUT TYPE="file" NAME="name" SIZE="40"> - def file_field(name = "", size = 20, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "file", "NAME" => name, - "SIZE" => size.to_s } - else - name["TYPE"] = "file" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - - # Generate a Form element as a string. - # - # +method+ should be either "get" or "post", and defaults to the latter. - # +action+ defaults to the current CGI script name. +enctype+ - # defaults to "application/x-www-form-urlencoded". - # - # Alternatively, the attributes can be specified as a hash. - # - # See also #multipart_form() for forms that include file uploads. - # - # form{ "string" } - # # <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("get") { "string" } - # # <FORM METHOD="get" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("get", "url") { "string" } - # # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("METHOD" => "post", "ENCTYPE" => "enctype") { "string" } - # # <FORM METHOD="post" ENCTYPE="enctype">string</FORM> - def form(method = "post", action = script_name, enctype = "application/x-www-form-urlencoded") - attributes = if method.kind_of?(String) - { "METHOD" => method, "ACTION" => action, - "ENCTYPE" => enctype } - else - unless method.has_key?("METHOD") - method["METHOD"] = "post" - end - unless method.has_key?("ENCTYPE") - method["ENCTYPE"] = enctype - end - method - end - if block_given? - body = yield - else - body = "" - end - if @output_hidden - body << @output_hidden.collect{|k,v| - "<INPUT TYPE=\"HIDDEN\" NAME=\"#{k}\" VALUE=\"#{v}\">" - }.join - end - super(attributes){body} - end - - # Generate a Hidden Input element as a string. - # - # The attributes of the element can be specified as two arguments, - # +name+ and +value+. - # - # Alternatively, the attributes can be specified as a hash. - # - # hidden("name") - # # <INPUT TYPE="hidden" NAME="name"> - # - # hidden("name", "value") - # # <INPUT TYPE="hidden" NAME="name" VALUE="value"> - # - # hidden("NAME" => "name", "VALUE" => "reset", "ID" => "foo") - # # <INPUT TYPE="hidden" NAME="name" VALUE="value" ID="foo"> - def hidden(name = "", value = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "hidden", "NAME" => name, "VALUE" => value } - else - name["TYPE"] = "hidden" - name - end - input(attributes) - end - - # Generate a top-level HTML element as a string. - # - # The attributes of the element are specified as a hash. The - # pseudo-attribute "PRETTY" can be used to specify that the generated - # HTML string should be indented. "PRETTY" can also be specified as - # a string as the sole argument to this method. The pseudo-attribute - # "DOCTYPE", if given, is used as the leading DOCTYPE SGML tag; it - # should include the entire text of this tag, including angle brackets. - # - # The body of the html element is supplied as a block. - # - # html{ "string" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>string</HTML> - # - # html("LANG" => "ja") { "string" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML LANG="ja">string</HTML> - # - # html("DOCTYPE" => false) { "string" } - # # <HTML>string</HTML> - # - # html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">') { "string" } - # # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML>string</HTML> - # - # html("PRETTY" => " ") { "<BODY></BODY>" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # html("PRETTY" => "\t") { "<BODY></BODY>" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # html("PRETTY") { "<BODY></BODY>" } - # # = html("PRETTY" => " ") { "<BODY></BODY>" } - # - # html(if $VERBOSE then "PRETTY" end) { "HTML string" } - # - def html(attributes = {}) # :yield: - if nil == attributes - attributes = {} - elsif "PRETTY" == attributes - attributes = { "PRETTY" => true } - end - pretty = attributes.delete("PRETTY") - pretty = " " if true == pretty - buf = "".dup - - if attributes.has_key?("DOCTYPE") - if attributes["DOCTYPE"] - buf << attributes.delete("DOCTYPE") - else - attributes.delete("DOCTYPE") - end - else - buf << doctype - end - - buf << super(attributes) - - if pretty - CGI.pretty(buf, pretty) - else - buf - end - - end - - # Generate an Image Button Input element as a string. - # - # +src+ is the URL of the image to use for the button. +name+ - # is the input name. +alt+ is the alternative text for the image. - # - # Alternatively, the attributes can be specified as a hash. - # - # image_button("url") - # # <INPUT TYPE="image" SRC="url"> - # - # image_button("url", "name", "string") - # # <INPUT TYPE="image" SRC="url" NAME="name" ALT="string"> - # - # image_button("SRC" => "url", "ALT" => "string") - # # <INPUT TYPE="image" SRC="url" ALT="string"> - def image_button(src = "", name = nil, alt = nil) - attributes = if src.kind_of?(String) - { "TYPE" => "image", "SRC" => src, "NAME" => name, - "ALT" => alt } - else - src["TYPE"] = "image" - src["SRC"] ||= "" - src - end - input(attributes) - end - - - # Generate an Image element as a string. - # - # +src+ is the URL of the image. +alt+ is the alternative text for - # the image. +width+ is the width of the image, and +height+ is - # its height. - # - # Alternatively, the attributes can be specified as a hash. - # - # img("src", "alt", 100, 50) - # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50"> - # - # img("SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50) - # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50"> - def img(src = "", alt = "", width = nil, height = nil) - attributes = if src.kind_of?(String) - { "SRC" => src, "ALT" => alt } - else - src - end - attributes["WIDTH"] = width.to_s if width - attributes["HEIGHT"] = height.to_s if height - super(attributes) - end - - - # Generate a Form element with multipart encoding as a String. - # - # Multipart encoding is used for forms that include file uploads. - # - # +action+ is the action to perform. +enctype+ is the encoding - # type, which defaults to "multipart/form-data". - # - # Alternatively, the attributes can be specified as a hash. - # - # multipart_form{ "string" } - # # <FORM METHOD="post" ENCTYPE="multipart/form-data">string</FORM> - # - # multipart_form("url") { "string" } - # # <FORM METHOD="post" ACTION="url" ENCTYPE="multipart/form-data">string</FORM> - def multipart_form(action = nil, enctype = "multipart/form-data") - attributes = if action == nil - { "METHOD" => "post", "ENCTYPE" => enctype } - elsif action.kind_of?(String) - { "METHOD" => "post", "ACTION" => action, - "ENCTYPE" => enctype } - else - unless action.has_key?("METHOD") - action["METHOD"] = "post" - end - unless action.has_key?("ENCTYPE") - action["ENCTYPE"] = enctype - end - action - end - if block_given? - form(attributes){ yield } - else - form(attributes) - end - end - - - # Generate a Password Input element as a string. - # - # +name+ is the name of the input field. +value+ is its default - # value. +size+ is the size of the input field display. +maxlength+ - # is the maximum length of the inputted password. - # - # Alternatively, attributes can be specified as a hash. - # - # password_field("name") - # # <INPUT TYPE="password" NAME="name" SIZE="40"> - # - # password_field("name", "value") - # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="40"> - # - # password_field("password", "value", 80, 200) - # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200"> - # - # password_field("NAME" => "name", "VALUE" => "value") - # # <INPUT TYPE="password" NAME="name" VALUE="value"> - def password_field(name = "", value = nil, size = 40, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "password", "NAME" => name, - "VALUE" => value, "SIZE" => size.to_s } - else - name["TYPE"] = "password" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - # Generate a Select element as a string. - # - # +name+ is the name of the element. The +values+ are the options that - # can be selected from the Select menu. Each value can be a String or - # a one, two, or three-element Array. If a String or a one-element - # Array, this is both the value of that option and the text displayed for - # it. If a three-element Array, the elements are the option value, displayed - # text, and a boolean value specifying whether this option starts as selected. - # The two-element version omits either the option value (defaults to the same - # as the display text) or the boolean selected specifier (defaults to false). - # - # The attributes and options can also be specified as a hash. In this - # case, options are specified as an array of values as described above, - # with the hash key of "VALUES". - # - # popup_menu("name", "foo", "bar", "baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="foo">foo</OPTION> - # # <OPTION VALUE="bar">bar</OPTION> - # # <OPTION VALUE="baz">baz</OPTION> - # # </SELECT> - # - # popup_menu("name", ["foo"], ["bar", true], "baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="foo">foo</OPTION> - # # <OPTION VALUE="bar" SELECTED>bar</OPTION> - # # <OPTION VALUE="baz">baz</OPTION> - # # </SELECT> - # - # popup_menu("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="1">Foo</OPTION> - # # <OPTION SELECTED VALUE="2">Bar</OPTION> - # # <OPTION VALUE="Baz">Baz</OPTION> - # # </SELECT> - # - # popup_menu("NAME" => "name", "SIZE" => 2, "MULTIPLE" => true, - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - # # <SELECT NAME="name" MULTIPLE SIZE="2"> - # # <OPTION VALUE="1">Foo</OPTION> - # # <OPTION SELECTED VALUE="2">Bar</OPTION> - # # <OPTION VALUE="Baz">Baz</OPTION> - # # </SELECT> - def popup_menu(name = "", *values) - - if name.kind_of?(Hash) - values = name["VALUES"] - size = name["SIZE"].to_s if name["SIZE"] - multiple = name["MULTIPLE"] - name = name["NAME"] - else - size = nil - multiple = nil - end - - select({ "NAME" => name, "SIZE" => size, - "MULTIPLE" => multiple }){ - values.collect{|value| - if value.kind_of?(String) - option({ "VALUE" => value }){ value } - else - if value[value.size - 1] == true - option({ "VALUE" => value[0], "SELECTED" => true }){ - value[value.size - 2] - } - else - option({ "VALUE" => value[0] }){ - value[value.size - 1] - } - end - end - }.join - } - - end - - # Generates a radio-button Input element. - # - # +name+ is the name of the input field. +value+ is the value of - # the field if checked. +checked+ specifies whether the field - # starts off checked. - # - # Alternatively, the attributes can be specified as a hash. - # - # radio_button("name", "value") - # # <INPUT TYPE="radio" NAME="name" VALUE="value"> - # - # radio_button("name", "value", true) - # # <INPUT TYPE="radio" NAME="name" VALUE="value" CHECKED> - # - # radio_button("NAME" => "name", "VALUE" => "value", "ID" => "foo") - # # <INPUT TYPE="radio" NAME="name" VALUE="value" ID="foo"> - def radio_button(name = "", value = nil, checked = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "radio", "NAME" => name, - "VALUE" => value, "CHECKED" => checked } - else - name["TYPE"] = "radio" - name - end - input(attributes) - end - - # Generate a sequence of radio button Input elements, as a String. - # - # This works the same as #checkbox_group(). However, it is not valid - # to have more than one radiobutton in a group checked. - # - # radio_group("name", "foo", "bar", "baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="radio" NAME="name" VALUE="bar">bar - # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz - # - # radio_group("name", ["foo"], ["bar", true], "baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="bar">bar - # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz - # - # radio_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="1">Foo - # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="2">Bar - # # <INPUT TYPE="radio" NAME="name" VALUE="Baz">Baz - # - # radio_group("NAME" => "name", - # "VALUES" => ["foo", "bar", "baz"]) - # - # radio_group("NAME" => "name", - # "VALUES" => [["foo"], ["bar", true], "baz"]) - # - # radio_group("NAME" => "name", - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - def radio_group(name = "", *values) - if name.kind_of?(Hash) - values = name["VALUES"] - name = name["NAME"] - end - values.collect{|value| - if value.kind_of?(String) - radio_button(name, value) + value - else - if value[-1] == true || value[-1] == false - radio_button(name, value[0], value[-1]) + - value[-2] - else - radio_button(name, value[0]) + - value[-1] - end - end - }.join - end - - # Generate a reset button Input element, as a String. - # - # This resets the values on a form to their initial values. +value+ - # is the text displayed on the button. +name+ is the name of this button. - # - # Alternatively, the attributes can be specified as a hash. - # - # reset - # # <INPUT TYPE="reset"> - # - # reset("reset") - # # <INPUT TYPE="reset" VALUE="reset"> - # - # reset("VALUE" => "reset", "ID" => "foo") - # # <INPUT TYPE="reset" VALUE="reset" ID="foo"> - def reset(value = nil, name = nil) - attributes = if (not value) or value.kind_of?(String) - { "TYPE" => "reset", "VALUE" => value, "NAME" => name } - else - value["TYPE"] = "reset" - value - end - input(attributes) - end - - alias scrolling_list popup_menu - - # Generate a submit button Input element, as a String. - # - # +value+ is the text to display on the button. +name+ is the name - # of the input. - # - # Alternatively, the attributes can be specified as a hash. - # - # submit - # # <INPUT TYPE="submit"> - # - # submit("ok") - # # <INPUT TYPE="submit" VALUE="ok"> - # - # submit("ok", "button1") - # # <INPUT TYPE="submit" VALUE="ok" NAME="button1"> - # - # submit("VALUE" => "ok", "NAME" => "button1", "ID" => "foo") - # # <INPUT TYPE="submit" VALUE="ok" NAME="button1" ID="foo"> - def submit(value = nil, name = nil) - attributes = if (not value) or value.kind_of?(String) - { "TYPE" => "submit", "VALUE" => value, "NAME" => name } - else - value["TYPE"] = "submit" - value - end - input(attributes) - end - - # Generate a text field Input element, as a String. - # - # +name+ is the name of the input field. +value+ is its initial - # value. +size+ is the size of the input area. +maxlength+ - # is the maximum length of input accepted. - # - # Alternatively, the attributes can be specified as a hash. - # - # text_field("name") - # # <INPUT TYPE="text" NAME="name" SIZE="40"> - # - # text_field("name", "value") - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="40"> - # - # text_field("name", "value", 80) - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80"> - # - # text_field("name", "value", 80, 200) - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200"> - # - # text_field("NAME" => "name", "VALUE" => "value") - # # <INPUT TYPE="text" NAME="name" VALUE="value"> - def text_field(name = "", value = nil, size = 40, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "text", "NAME" => name, "VALUE" => value, - "SIZE" => size.to_s } - else - name["TYPE"] = "text" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - # Generate a TextArea element, as a String. - # - # +name+ is the name of the textarea. +cols+ is the number of - # columns and +rows+ is the number of rows in the display. - # - # Alternatively, the attributes can be specified as a hash. - # - # The body is provided by the passed-in no-argument block - # - # textarea("name") - # # = textarea("NAME" => "name", "COLS" => 70, "ROWS" => 10) - # - # textarea("name", 40, 5) - # # = textarea("NAME" => "name", "COLS" => 40, "ROWS" => 5) - def textarea(name = "", cols = 70, rows = 10) # :yield: - attributes = if name.kind_of?(String) - { "NAME" => name, "COLS" => cols.to_s, - "ROWS" => rows.to_s } - else - name - end - super(attributes) - end - - end # HtmlExtension - - - # Mixin module for HTML version 3 generation methods. - module Html3 # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">| - end - - instance_method(:nn_element_def).tap do |m| - # - - - for element in %w[ A TT I B U STRIKE BIG SMALL SUB SUP EM STRONG - DFN CODE SAMP KBD VAR CITE FONT ADDRESS DIV CENTER MAP - APPLET PRE XMP LISTING DL OL UL DIR MENU SELECT TABLE TITLE - STYLE SCRIPT H1 H2 H3 H4 H5 H6 TEXTAREA FORM BLOCKQUOTE - CAPTION ] - define_method(element.downcase, m) - end - end - - instance_method(:nOE_element_def).tap do |m| - # - O EMPTY - for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT - ISINDEX META ] - define_method(element.downcase, m) - end - end - - instance_method(:nO_element_def).tap do |m| - # O O or - O - for element in %w[ HTML HEAD BODY P PLAINTEXT DT DD LI OPTION TR - TH TD ] - define_method(element.downcase, m) - end - end - - end # Html3 - - - # Mixin module for HTML version 4 generation methods. - module Html4 # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">| - end - - # Initialize the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ TT I B BIG SMALL EM STRONG DFN CODE SAMP KBD - VAR CITE ABBR ACRONYM SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT - H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT OPTGROUP - FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT - TEXTAREA FORM A BLOCKQUOTE CAPTION ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ] - define_method(element.downcase, m) - end - end - - # O O or - O - instance_method(:nO_element_def).tap do |m| - for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY - COLGROUP TR TH TD HEAD ] - define_method(element.downcase, m) - end - end - - end # Html4 - - - # Mixin module for HTML version 4 transitional generation methods. - module Html4Tr # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">| - end - - # Initialise the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ TT I B U S STRIKE BIG SMALL EM STRONG DFN - CODE SAMP KBD VAR CITE ABBR ACRONYM FONT SUB SUP SPAN BDO - ADDRESS DIV CENTER MAP OBJECT APPLET H1 H2 H3 H4 H5 H6 PRE Q - INS DEL DL OL UL DIR MENU LABEL SELECT OPTGROUP FIELDSET - LEGEND BUTTON TABLE IFRAME NOFRAMES TITLE STYLE SCRIPT - NOSCRIPT TEXTAREA FORM A BLOCKQUOTE CAPTION ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT - COL ISINDEX META ] - define_method(element.downcase, m) - end - end - - # O O or - O - instance_method(:nO_element_def).tap do |m| - for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY - COLGROUP TR TH TD HEAD ] - define_method(element.downcase, m) - end - end - - end # Html4Tr - - - # Mixin module for generating HTML version 4 with framesets. - module Html4Fr # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">| - end - - # Initialise the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ FRAMESET ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ FRAME ] - define_method(element.downcase, m) - end - end - - end # Html4Fr - - - # Mixin module for HTML version 5 generation methods. - module Html5 # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML>| - end - - # Initialise the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ SECTION NAV ARTICLE ASIDE HGROUP HEADER - FOOTER FIGURE FIGCAPTION S TIME U MARK RUBY BDI IFRAME - VIDEO AUDIO CANVAS DATALIST OUTPUT PROGRESS METER DETAILS - SUMMARY MENU DIALOG I B SMALL EM STRONG DFN CODE SAMP KBD - VAR CITE ABBR SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT - H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT - FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT - TEXTAREA FORM A BLOCKQUOTE CAPTION ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META - COMMAND EMBED KEYGEN SOURCE TRACK WBR ] - define_method(element.downcase, m) - end - end - - # O O or - O - instance_method(:nO_element_def).tap do |m| - for element in %w[ HTML HEAD BODY P DT DD LI OPTION THEAD TFOOT TBODY - OPTGROUP COLGROUP RT RP TR TH TD ] - define_method(element.downcase, m) - end - end - - end # Html5 - - class HTML3 - include Html3 - include HtmlExtension - end - - class HTML4 - include Html4 - include HtmlExtension - end - - class HTML4Tr - include Html4Tr - include HtmlExtension - end - - class HTML4Fr - include Html4Tr - include Html4Fr - include HtmlExtension - end - - class HTML5 - include Html5 - include HtmlExtension - end - -end diff --git a/lib/cgi/session.rb b/lib/cgi/session.rb deleted file mode 100644 index aab60869bb..0000000000 --- a/lib/cgi/session.rb +++ /dev/null @@ -1,562 +0,0 @@ -# frozen_string_literal: true -# -# cgi/session.rb - session support for cgi scripts -# -# Copyright (C) 2001 Yukihiro "Matz" Matsumoto -# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. -# Copyright (C) 2000 Information-technology Promotion Agency, Japan -# -# Author: Yukihiro "Matz" Matsumoto -# -# Documentation: William Webber (william@williamwebber.com) - -require 'cgi' -require 'tmpdir' - -class CGI - - # == Overview - # - # This file provides the CGI::Session class, which provides session - # support for CGI scripts. A session is a sequence of HTTP requests - # and responses linked together and associated with a single client. - # Information associated with the session is stored - # on the server between requests. A session id is passed between client - # and server with every request and response, transparently - # to the user. This adds state information to the otherwise stateless - # HTTP request/response protocol. - # - # == Lifecycle - # - # A CGI::Session instance is created from a CGI object. By default, - # this CGI::Session instance will start a new session if none currently - # exists, or continue the current session for this client if one does - # exist. The +new_session+ option can be used to either always or - # never create a new session. See #new() for more details. - # - # #delete() deletes a session from session storage. It - # does not however remove the session id from the client. If the client - # makes another request with the same id, the effect will be to start - # a new session with the old session's id. - # - # == Setting and retrieving session data. - # - # The Session class associates data with a session as key-value pairs. - # This data can be set and retrieved by indexing the Session instance - # using '[]', much the same as hashes (although other hash methods - # are not supported). - # - # When session processing has been completed for a request, the - # session should be closed using the close() method. This will - # store the session's state to persistent storage. If you want - # to store the session's state to persistent storage without - # finishing session processing for this request, call the update() - # method. - # - # == Storing session state - # - # The caller can specify what form of storage to use for the session's - # data with the +database_manager+ option to CGI::Session::new. The - # following storage classes are provided as part of the standard library: - # - # CGI::Session::FileStore:: stores data as plain text in a flat file. Only - # works with String data. This is the default - # storage type. - # CGI::Session::MemoryStore:: stores data in an in-memory hash. The data - # only persists for as long as the current Ruby - # interpreter instance does. - # CGI::Session::PStore:: stores data in Marshalled format. Provided by - # cgi/session/pstore.rb. Supports data of any type, - # and provides file-locking and transaction support. - # - # Custom storage types can also be created by defining a class with - # the following methods: - # - # new(session, options) - # restore # returns hash of session data. - # update - # close - # delete - # - # Changing storage type mid-session does not work. Note in particular - # that by default the FileStore and PStore session data files have the - # same name. If your application switches from one to the other without - # making sure that filenames will be different - # and clients still have old sessions lying around in cookies, then - # things will break nastily! - # - # == Maintaining the session id. - # - # Most session state is maintained on the server. However, a session - # id must be passed backwards and forwards between client and server - # to maintain a reference to this session state. - # - # The simplest way to do this is via cookies. The CGI::Session class - # provides transparent support for session id communication via cookies - # if the client has cookies enabled. - # - # If the client has cookies disabled, the session id must be included - # as a parameter of all requests sent by the client to the server. The - # CGI::Session class in conjunction with the CGI class will transparently - # add the session id as a hidden input field to all forms generated - # using the CGI#form() HTML generation method. No built-in support is - # provided for other mechanisms, such as URL re-writing. The caller is - # responsible for extracting the session id from the session_id - # attribute and manually encoding it in URLs and adding it as a hidden - # input to HTML forms created by other mechanisms. Also, session expiry - # is not automatically handled. - # - # == Examples of use - # - # === Setting the user's name - # - # require 'cgi' - # require 'cgi/session' - # require 'cgi/session/pstore' # provides CGI::Session::PStore - # - # cgi = CGI.new("html4") - # - # session = CGI::Session.new(cgi, - # 'database_manager' => CGI::Session::PStore, # use PStore - # 'session_key' => '_rb_sess_id', # custom session key - # 'session_expires' => Time.now + 30 * 60, # 30 minute timeout - # 'prefix' => 'pstore_sid_') # PStore option - # if cgi.has_key?('user_name') and cgi['user_name'] != '' - # # coerce to String: cgi[] returns the - # # string-like CGI::QueryExtension::Value - # session['user_name'] = cgi['user_name'].to_s - # elsif !session['user_name'] - # session['user_name'] = "guest" - # end - # session.close - # - # === Creating a new session safely - # - # require 'cgi' - # require 'cgi/session' - # - # cgi = CGI.new("html4") - # - # # We make sure to delete an old session if one exists, - # # not just to free resources, but to prevent the session - # # from being maliciously hijacked later on. - # begin - # session = CGI::Session.new(cgi, 'new_session' => false) - # session.delete - # rescue ArgumentError # if no old session - # end - # session = CGI::Session.new(cgi, 'new_session' => true) - # session.close - # - class Session - - class NoSession < RuntimeError #:nodoc: - end - - # The id of this session. - attr_reader :session_id, :new_session - - def Session::callback(dbman) #:nodoc: - Proc.new{ - dbman[0].close unless dbman.empty? - } - end - - # Create a new session id. - # - # The session id is a secure random number by SecureRandom - # if possible, otherwise an SHA512 hash based upon the time, - # a random number, and a constant string. This routine is - # used internally for automatically generated session ids. - def create_new_id - require 'securerandom' - begin - # by OpenSSL, or system provided entropy pool - session_id = SecureRandom.hex(16) - rescue NotImplementedError - # never happens on modern systems - require 'digest' - d = Digest('SHA512').new - now = Time::now - d.update(now.to_s) - d.update(String(now.usec)) - d.update(String(rand(0))) - d.update(String($$)) - d.update('foobar') - session_id = d.hexdigest[0, 32] - end - session_id - end - private :create_new_id - - - # Create a new file to store the session data. - # - # This file will be created if it does not exist, or opened if it - # does. - # - # This path is generated under _tmpdir_ from _prefix_, the - # digested session id, and _suffix_. - # - # +option+ is a hash of options for the initializer. The - # following options are recognised: - # - # tmpdir:: the directory to use for storing the FileStore - # file. Defaults to Dir::tmpdir (generally "/tmp" - # on Unix systems). - # prefix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to "cgi_sid_". - # suffix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to the empty string. - def new_store_file(option={}) # :nodoc: - dir = option['tmpdir'] || Dir::tmpdir - prefix = option['prefix'] - suffix = option['suffix'] - require 'digest/md5' - md5 = Digest::MD5.hexdigest(session_id)[0,16] - path = dir+"/" - path << prefix if prefix - path << md5 - path << suffix if suffix - if File::exist? path - hash = nil - elsif new_session - hash = {} - else - raise NoSession, "uninitialized session" - end - return path, hash - end - - # Create a new CGI::Session object for +request+. - # - # +request+ is an instance of the +CGI+ class (see cgi.rb). - # +option+ is a hash of options for initialising this - # CGI::Session instance. The following options are - # recognised: - # - # session_key:: the parameter name used for the session id. - # Defaults to '_session_id'. - # session_id:: the session id to use. If not provided, then - # it is retrieved from the +session_key+ parameter - # of the request, or automatically generated for - # a new session. - # new_session:: if true, force creation of a new session. If not set, - # a new session is only created if none currently - # exists. If false, a new session is never created, - # and if none currently exists and the +session_id+ - # option is not set, an ArgumentError is raised. - # database_manager:: the name of the class providing storage facilities - # for session state persistence. Built-in support - # is provided for +FileStore+ (the default), - # +MemoryStore+, and +PStore+ (from - # cgi/session/pstore.rb). See the documentation for - # these classes for more details. - # - # The following options are also recognised, but only apply if the - # session id is stored in a cookie. - # - # session_expires:: the time the current session expires, as a - # +Time+ object. If not set, the session will terminate - # when the user's browser is closed. - # session_domain:: the hostname domain for which this session is valid. - # If not set, defaults to the hostname of the server. - # session_secure:: if +true+, this session will only work over HTTPS. - # session_path:: the path for which this session applies. Defaults - # to the directory of the CGI script. - # - # +option+ is also passed on to the session storage class initializer; see - # the documentation for each session storage class for the options - # they support. - # - # The retrieved or created session is automatically added to +request+ - # as a cookie, and also to its +output_hidden+ table, which is used - # to add hidden input elements to forms. - # - # *WARNING* the +output_hidden+ - # fields are surrounded by a <fieldset> tag in HTML 4 generation, which - # is _not_ invisible on many browsers; you may wish to disable the - # use of fieldsets with code similar to the following - # (see https://blade.ruby-lang.org/ruby-list/37805) - # - # cgi = CGI.new("html4") - # class << cgi - # undef_method :fieldset - # end - # - def initialize(request, option={}) - @new_session = false - session_key = option['session_key'] || '_session_id' - session_id = option['session_id'] - unless session_id - if option['new_session'] - session_id = create_new_id - @new_session = true - end - end - unless session_id - if request.key?(session_key) - session_id = request[session_key] - session_id = session_id.read if session_id.respond_to?(:read) - end - unless session_id - session_id, = request.cookies[session_key] - end - unless session_id - unless option.fetch('new_session', true) - raise ArgumentError, "session_key `%s' should be supplied"%session_key - end - session_id = create_new_id - @new_session = true - end - end - @session_id = session_id - dbman = option['database_manager'] || FileStore - begin - @dbman = dbman::new(self, option) - rescue NoSession - unless option.fetch('new_session', true) - raise ArgumentError, "invalid session_id `%s'"%session_id - end - session_id = @session_id = create_new_id unless session_id - @new_session=true - retry - end - request.instance_eval do - @output_hidden = {session_key => session_id} unless option['no_hidden'] - @output_cookies = [ - Cookie::new("name" => session_key, - "value" => session_id, - "expires" => option['session_expires'], - "domain" => option['session_domain'], - "secure" => option['session_secure'], - "path" => - if option['session_path'] - option['session_path'] - elsif ENV["SCRIPT_NAME"] - File::dirname(ENV["SCRIPT_NAME"]) - else - "" - end) - ] unless option['no_cookies'] - end - @dbprot = [@dbman] - ObjectSpace::define_finalizer(self, Session::callback(@dbprot)) - end - - # Retrieve the session data for key +key+. - def [](key) - @data ||= @dbman.restore - @data[key] - end - - # Set the session data for key +key+. - def []=(key, val) - @write_lock ||= true - @data ||= @dbman.restore - @data[key] = val - end - - # Store session data on the server. For some session storage types, - # this is a no-op. - def update - @dbman.update - end - - # Store session data on the server and close the session storage. - # For some session storage types, this is a no-op. - def close - @dbman.close - @dbprot.clear - end - - # Delete the session from storage. Also closes the storage. - # - # Note that the session's data is _not_ automatically deleted - # upon the session expiring. - def delete - @dbman.delete - @dbprot.clear - end - - # File-based session storage class. - # - # Implements session storage as a flat file of 'key=value' values. - # This storage type only works directly with String values; the - # user is responsible for converting other types to Strings when - # storing and from Strings when retrieving. - class FileStore - # Create a new FileStore instance. - # - # This constructor is used internally by CGI::Session. The - # user does not generally need to call it directly. - # - # +session+ is the session for which this instance is being - # created. The session id must only contain alphanumeric - # characters; automatically generated session ids observe - # this requirement. - # - # +option+ is a hash of options for the initializer. The - # following options are recognised: - # - # tmpdir:: the directory to use for storing the FileStore - # file. Defaults to Dir::tmpdir (generally "/tmp" - # on Unix systems). - # prefix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to "cgi_sid_". - # suffix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to the empty string. - # - # This session's FileStore file will be created if it does - # not exist, or opened if it does. - def initialize(session, option={}) - option = {'prefix' => 'cgi_sid_'}.update(option) - @path, @hash = session.new_store_file(option) - end - - # Restore session state from the session's FileStore file. - # - # Returns the session state as a hash. - def restore - unless @hash - @hash = {} - begin - lockf = File.open(@path+".lock", "r") - lockf.flock File::LOCK_SH - f = File.open(@path, 'r') - for line in f - line.chomp! - k, v = line.split('=',2) - @hash[CGI.unescape(k)] = Marshal.restore(CGI.unescape(v)) - end - ensure - f&.close - lockf&.close - end - end - @hash - end - - # Save session state to the session's FileStore file. - def update - return unless @hash - begin - lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600) - lockf.flock File::LOCK_EX - f = File.open(@path+".new", File::CREAT|File::TRUNC|File::WRONLY, 0600) - for k,v in @hash - f.printf "%s=%s\n", CGI.escape(k), CGI.escape(String(Marshal.dump(v))) - end - f.close - File.rename @path+".new", @path - ensure - f&.close - lockf&.close - end - end - - # Update and close the session's FileStore file. - def close - update - end - - # Close and delete the session's FileStore file. - def delete - File::unlink @path+".lock" rescue nil - File::unlink @path+".new" rescue nil - File::unlink @path rescue nil - end - end - - # In-memory session storage class. - # - # Implements session storage as a global in-memory hash. Session - # data will only persist for as long as the Ruby interpreter - # instance does. - class MemoryStore - GLOBAL_HASH_TABLE = {} #:nodoc: - - # Create a new MemoryStore instance. - # - # +session+ is the session this instance is associated with. - # +option+ is a list of initialisation options. None are - # currently recognized. - def initialize(session, option=nil) - @session_id = session.session_id - unless GLOBAL_HASH_TABLE.key?(@session_id) - unless session.new_session - raise CGI::Session::NoSession, "uninitialized session" - end - GLOBAL_HASH_TABLE[@session_id] = {} - end - end - - # Restore session state. - # - # Returns session data as a hash. - def restore - GLOBAL_HASH_TABLE[@session_id] - end - - # Update session state. - # - # A no-op. - def update - # don't need to update; hash is shared - end - - # Close session storage. - # - # A no-op. - def close - # don't need to close - end - - # Delete the session state. - def delete - GLOBAL_HASH_TABLE.delete(@session_id) - end - end - - # Dummy session storage class. - # - # Implements session storage place holder. No actual storage - # will be done. - class NullStore - # Create a new NullStore instance. - # - # +session+ is the session this instance is associated with. - # +option+ is a list of initialisation options. None are - # currently recognised. - def initialize(session, option=nil) - end - - # Restore (empty) session state. - def restore - {} - end - - # Update session state. - # - # A no-op. - def update - end - - # Close session storage. - # - # A no-op. - def close - end - - # Delete the session state. - # - # A no-op. - def delete - end - end - end -end diff --git a/lib/cgi/session/pstore.rb b/lib/cgi/session/pstore.rb deleted file mode 100644 index 6e3d10f075..0000000000 --- a/lib/cgi/session/pstore.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true -# -# cgi/session/pstore.rb - persistent storage of marshalled session data -# -# Documentation: William Webber (william@williamwebber.com) -# -# == Overview -# -# This file provides the CGI::Session::PStore class, which builds -# persistent of session data on top of the pstore library. See -# cgi/session.rb for more details on session storage managers. - -require_relative '../session' -begin - require 'pstore' -rescue LoadError -end - -class CGI - class Session - # PStore-based session storage class. - # - # This builds upon the top-level PStore class provided by the - # library file pstore.rb. Session data is marshalled and stored - # in a file. File locking and transaction services are provided. - class PStore - # Create a new CGI::Session::PStore instance - # - # This constructor is used internally by CGI::Session. The - # user does not generally need to call it directly. - # - # +session+ is the session for which this instance is being - # created. The session id must only contain alphanumeric - # characters; automatically generated session ids observe - # this requirement. - # - # +option+ is a hash of options for the initializer. The - # following options are recognised: - # - # tmpdir:: the directory to use for storing the PStore - # file. Defaults to Dir::tmpdir (generally "/tmp" - # on Unix systems). - # prefix:: the prefix to add to the session id when generating - # the filename for this session's PStore file. - # Defaults to the empty string. - # - # This session's PStore file will be created if it does - # not exist, or opened if it does. - def initialize(session, option={}) - option = {'suffix'=>''}.update(option) - path, @hash = session.new_store_file(option) - @p = ::PStore.new(path) - @p.transaction do |p| - File.chmod(0600, p.path) - end - end - - # Restore session state from the session's PStore file. - # - # Returns the session state as a hash. - def restore - unless @hash - @p.transaction do - @hash = @p['hash'] || {} - end - end - @hash - end - - # Save session state to the session's PStore file. - def update - @p.transaction do - @p['hash'] = @hash - end - end - - # Update and close the session's PStore file. - def close - update - end - - # Close and delete the session's PStore file. - def delete - path = @p.path - File::unlink path - end - - end if defined?(::PStore) - end -end -# :enddoc: diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb index 5f12eae130..50a2e91665 100644 --- a/lib/cgi/util.rb +++ b/lib/cgi/util.rb @@ -1,258 +1,7 @@ # frozen_string_literal: true -class CGI - module Util; end - include Util - extend Util -end -module CGI::Util - @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) - # URL-encode a string into application/x-www-form-urlencoded. - # Space characters (+" "+) are encoded with plus signs (+"+"+) - # url_encoded_string = CGI.escape("'Stop!' said Fred") - # # => "%27Stop%21%27+said+Fred" - def escape(string) - encoding = string.encoding - buffer = string.b - buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m| - '%' + m.unpack('H2' * m.bytesize).join('%').upcase - end - buffer.tr!(' ', '+') - buffer.force_encoding(encoding) - end - - # URL-decode an application/x-www-form-urlencoded string with encoding(optional). - # string = CGI.unescape("%27Stop%21%27+said+Fred") - # # => "'Stop!' said Fred" - def unescape(string, encoding = @@accept_charset) - str = string.tr('+', ' ') - str = str.b - str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| - [m.delete('%')].pack('H*') - end - str.force_encoding(encoding) - str.valid_encoding? ? str : str.force_encoding(string.encoding) - end - - # URL-encode a string following RFC 3986 - # Space characters (+" "+) are encoded with (+"%20"+) - # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred") - # # => "%27Stop%21%27%20said%20Fred" - def escapeURIComponent(string) - encoding = string.encoding - buffer = string.b - buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m| - '%' + m.unpack('H2' * m.bytesize).join('%').upcase - end - buffer.force_encoding(encoding) - end - alias escape_uri_component escapeURIComponent - - # URL-decode a string following RFC 3986 with encoding(optional). - # string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred") - # # => "'Stop!'+said Fred" - def unescapeURIComponent(string, encoding = @@accept_charset) - str = string.b - str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| - [m.delete('%')].pack('H*') - end - str.force_encoding(encoding) - str.valid_encoding? ? str : str.force_encoding(string.encoding) - end - - alias unescape_uri_component unescapeURIComponent - - # The set of special characters and their escaped values - TABLE_FOR_ESCAPE_HTML__ = { - "'" => ''', - '&' => '&', - '"' => '"', - '<' => '<', - '>' => '>', - } - - # Escape special characters in HTML, namely '&\"<> - # CGI.escapeHTML('Usage: foo "bar" <baz>') - # # => "Usage: foo "bar" <baz>" - def escapeHTML(string) - enc = string.encoding - unless enc.ascii_compatible? - if enc.dummy? - origenc = enc - enc = Encoding::Converter.asciicompat_encoding(enc) - string = enc ? string.encode(enc) : string.b - end - table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}] - string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table) - string.encode!(origenc) if origenc - string - else - string = string.b - string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) - string.force_encoding(enc) - end - end - - # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there - unless RUBY_ENGINE == 'truffleruby' - begin - require 'cgi/escape' - rescue LoadError - end - end - - # Unescape a string that has been HTML-escaped - # CGI.unescapeHTML("Usage: foo "bar" <baz>") - # # => "Usage: foo \"bar\" <baz>" - def unescapeHTML(string) - enc = string.encoding - unless enc.ascii_compatible? - if enc.dummy? - origenc = enc - enc = Encoding::Converter.asciicompat_encoding(enc) - string = enc ? string.encode(enc) : string.b - end - string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do - case $1.encode(Encoding::US_ASCII) - when 'apos' then "'".encode(enc) - when 'amp' then '&'.encode(enc) - when 'quot' then '"'.encode(enc) - when 'gt' then '>'.encode(enc) - when 'lt' then '<'.encode(enc) - when /\A#0*(\d+)\z/ then $1.to_i.chr(enc) - when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc) - end - end - string.encode!(origenc) if origenc - return string - end - return string unless string.include? '&' - charlimit = case enc - when Encoding::UTF_8; 0x10ffff - when Encoding::ISO_8859_1; 256 - else 128 - end - string = string.b - string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do - match = $1.dup - case match - when 'apos' then "'" - when 'amp' then '&' - when 'quot' then '"' - when 'gt' then '>' - when 'lt' then '<' - when /\A#0*(\d+)\z/ - n = $1.to_i - if n < charlimit - n.chr(enc) - else - "&##{$1};" - end - when /\A#x([0-9a-f]+)\z/i - n = $1.hex - if n < charlimit - n.chr(enc) - else - "&#x#{$1};" - end - else - "&#{match};" - end - end - string.force_encoding enc - end - - # Synonym for CGI.escapeHTML(str) - alias escape_html escapeHTML - - # Synonym for CGI.unescapeHTML(str) - alias unescape_html unescapeHTML - - # Escape only the tags of certain HTML elements in +string+. - # - # Takes an element or elements or array of elements. Each element - # is specified by the name of the element, without angle brackets. - # This matches both the start and the end tag of that element. - # The attribute list of the open tag will also be escaped (for - # instance, the double-quotes surrounding attribute values). - # - # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG") - # # "<BR><A HREF="url"></A>" - # - # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) - # # "<BR><A HREF="url"></A>" - def escapeElement(string, *elements) - elements = elements[0] if elements[0].kind_of?(Array) - unless elements.empty? - string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do - CGI.escapeHTML($&) - end - else - string - end - end - - # Undo escaping such as that done by CGI.escapeElement() - # - # print CGI.unescapeElement( - # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG") - # # "<BR><A HREF="url"></A>" - # - # print CGI.unescapeElement( - # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]) - # # "<BR><A HREF="url"></A>" - def unescapeElement(string, *elements) - elements = elements[0] if elements[0].kind_of?(Array) - unless elements.empty? - string.gsub(/<\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:>)?/im) do - unescapeHTML($&) - end - else - string - end - end - - # Synonym for CGI.escapeElement(str) - alias escape_element escapeElement - - # Synonym for CGI.unescapeElement(str) - alias unescape_element unescapeElement - - # Format a +Time+ object as a String using the format specified by RFC 1123. - # - # CGI.rfc1123_date(Time.now) - # # Sat, 01 Jan 2000 00:00:00 GMT - def rfc1123_date(time) - time.getgm.strftime("%a, %d %b %Y %T GMT") - end - - # Prettify (indent) an HTML string. - # - # +string+ is the HTML string to indent. +shift+ is the indentation - # unit to use; it defaults to two spaces. - # - # print CGI.pretty("<HTML><BODY></BODY></HTML>") - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # print CGI.pretty("<HTML><BODY></BODY></HTML>", "\t") - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - def pretty(string, shift = " ") - lines = string.gsub(/(?!\A)<.*?>/m, "\n\\0").gsub(/<.*?>(?!\n)/m, "\\0\n") - end_pos = 0 - while end_pos = lines.index(/^<\/(\w+)/, end_pos) - element = $1.dup - start_pos = lines.rindex(/^\s*<#{element}/i, end_pos) - lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__" - end - lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1') - end - - alias h escapeHTML -end +require "cgi/escape" +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE +CGI::Util is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you are using CGI.parse, please install and use the cgi gem instead. +WARNING diff --git a/lib/delegate.rb b/lib/delegate.rb index 824d02f28b..0cc3ddb1b0 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -39,7 +39,8 @@ # Be advised, RDoc will not detect delegated methods. # class Delegator < BasicObject - VERSION = "0.4.0" + # The version string + VERSION = "0.6.1" kernel = ::Kernel.dup kernel.class_eval do @@ -77,7 +78,7 @@ class Delegator < BasicObject end # - # Handles the magic of delegation through \_\_getobj\_\_. + # Handles the magic of delegation through +__getobj__+. # ruby2_keywords def method_missing(m, *args, &block) r = true @@ -94,7 +95,7 @@ class Delegator < BasicObject # # Checks for a method provided by this the delegate object by forwarding the - # call through \_\_getobj\_\_. + # call through +__getobj__+. # def respond_to_missing?(m, include_private) r = true @@ -107,7 +108,7 @@ class Delegator < BasicObject r end - KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) + KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) # :nodoc: private_constant :KERNEL_RESPOND_TO # Handle BasicObject instances @@ -126,7 +127,7 @@ class Delegator < BasicObject # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ methods. + # of this object's and +__getobj__+ methods. # def methods(all=true) __getobj__.methods(all) | super @@ -134,7 +135,7 @@ class Delegator < BasicObject # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ public methods. + # of this object's and +__getobj__+ public methods. # def public_methods(all=true) __getobj__.public_methods(all) | super @@ -142,7 +143,7 @@ class Delegator < BasicObject # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ protected methods. + # of this object's and +__getobj__+ protected methods. # def protected_methods(all=true) __getobj__.protected_methods(all) | super @@ -175,7 +176,7 @@ class Delegator < BasicObject end # - # Delegates ! to the \_\_getobj\_\_ + # Delegates ! to the +__getobj__+ # def ! !__getobj__ @@ -198,7 +199,7 @@ class Delegator < BasicObject end # - # Serialization support for the object returned by \_\_getobj\_\_. + # Serialization support for the object returned by +__getobj__+. # def marshal_dump ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var} @@ -232,7 +233,7 @@ class Delegator < BasicObject ## # :method: freeze - # Freeze both the object returned by \_\_getobj\_\_ and self. + # Freeze both the object returned by +__getobj__+ and self. # def freeze __getobj__.freeze @@ -398,6 +399,17 @@ def DelegateClass(superclass, &block) protected_instance_methods -= ignores public_instance_methods = superclass.public_instance_methods public_instance_methods -= ignores + + normal, special = public_instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) } + + source = normal.map do |method| + "def #{method}(...); __getobj__.#{method}(...); end" + end + + protected_instance_methods.each do |method| + source << "def #{method}(...); __getobj__.__send__(#{method.inspect}, ...); end" + end + klass.module_eval do def __getobj__ # :nodoc: unless defined?(@delegate_dc_obj) @@ -406,18 +418,21 @@ def DelegateClass(superclass, &block) end @delegate_dc_obj end + def __setobj__(obj) # :nodoc: __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj) @delegate_dc_obj = obj end - protected_instance_methods.each do |method| - define_method(method, Delegator.delegating_block(method)) - protected method - end - public_instance_methods.each do |method| + + class_eval(source.join(";"), __FILE__, __LINE__) + + special.each do |method| define_method(method, Delegator.delegating_block(method)) end + + protected(*protected_instance_methods) end + klass.define_singleton_method :public_instance_methods do |all=true| super(all) | superclass.public_instance_methods end diff --git a/lib/erb.rb b/lib/erb.rb index bdff2deceb..445d4795b0 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -12,340 +12,824 @@ # # You can redistribute it and/or modify it under the same terms as Ruby. -require 'cgi/util' +# A NOTE ABOUT TERMS: +# +# Formerly: The documentation in this file used the term _template_ to refer to an ERB object. +# +# Now: The documentation in this file uses the term _template_ +# to refer to the string input to ERB.new. +# +# The reason for the change: When documenting the ERB executable erb, +# we need a term that refers to its string input; +# _source_ is not a good idea, because ERB#src means something entirely different; +# the two different sorts of sources would bring confusion. +# +# Therefore we use the term _template_ to refer to: +# +# - The string input to ERB.new +# - The string input to executable erb. +# + require 'erb/version' require 'erb/compiler' require 'erb/def_method' require 'erb/util' +# :markup: markdown # -# = ERB -- Ruby Templating +# Class **ERB** (the name stands for **Embedded Ruby**) +# is an easy-to-use, but also very powerful, [template processor][template processor]. # -# == Introduction +# ## Usage # -# ERB provides an easy to use but powerful templating system for Ruby. Using -# ERB, actual Ruby code can be added to any plain text document for the -# purposes of generating document information details and/or flow control. +# Before you can use \ERB, you must first require it +# (examples on this page assume that this has been done): # -# A very simple example is this: +# ``` +# require 'erb' +# ``` # -# require 'erb' +# ## In Brief # -# x = 42 -# template = ERB.new <<-EOF -# The value of x is: <%= x %> -# EOF -# puts template.result(binding) +# Here's how \ERB works: # -# <em>Prints:</em> The value of x is: 42 +# - You can create a *template*: a plain-text string that includes specially formatted *tags*.. +# - You can create an \ERB object to store the template. +# - You can call instance method ERB#result to get the *result*. # -# More complex examples are given below. +# \ERB supports tags of three kinds: # +# - [Expression tags][expression tags]: +# each begins with `'<%='`, ends with `'%>'`; contains a Ruby expression; +# in the result, the value of the expression replaces the entire tag: # -# == Recognized Tags +# template = 'The magic word is <%= magic_word %>.' +# erb = ERB.new(template) +# magic_word = 'xyzzy' +# erb.result(binding) # => "The magic word is xyzzy." # -# ERB recognizes certain tags in the provided template and converts them based -# on the rules below: +# The above call to #result passes argument `binding`, +# which contains the binding of variable `magic_word` to its string value `'xyzzy'`. # -# <% Ruby code -- inline with output %> -# <%= Ruby expression -- replace with result %> -# <%# comment -- ignored -- useful in testing %> (`<% #` doesn't work. Don't use Ruby comments.) -# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new) -# %% replaced with % if first thing on a line and % processing is used -# <%% or %%> -- replace with <% or %> respectively +# The below call to #result need not pass a binding, +# because its expression `Date::DAYNAMES` is globally defined. # -# All other text is passed through ERB filtering unchanged. +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result # => "Today is Monday." # +# - [Execution tags][execution tags]: +# each begins with `'<%'`, ends with `'%>'`; contains Ruby code to be executed: # -# == Options +# template = '<% File.write("t.txt", "Some stuff.") %>' +# ERB.new(template).result +# File.read('t.txt') # => "Some stuff." # -# There are several settings you can change when you use ERB: -# * the nature of the tags that are recognized; -# * the binding used to resolve local variables in the template. +# - [Comment tags][comment tags]: +# each begins with `'<%#'`, ends with `'%>'`; contains comment text; +# in the result, the entire tag is omitted. # -# See the ERB.new and ERB#result methods for more detail. +# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(template).result # => "Some stuff; more stuff." # -# == Character encodings +# ## Some Simple Examples # -# ERB (or Ruby code generated by ERB) returns a string in the same -# character encoding as the input string. When the input string has -# a magic comment, however, it returns a string in the encoding specified -# by the magic comment. +# Here's a simple example of \ERB in action: # -# # -*- coding: utf-8 -*- -# require 'erb' +# ``` +# template = 'The time is <%= Time.now %>.' +# erb = ERB.new(template) +# erb.result +# # => "The time is 2025-09-09 10:49:26 -0500." +# ``` # -# template = ERB.new <<EOF -# <%#-*- coding: Big5 -*-%> -# \_\_ENCODING\_\_ is <%= \_\_ENCODING\_\_ %>. -# EOF -# puts template.result +# Details: # -# <em>Prints:</em> \_\_ENCODING\_\_ is Big5. +# 1. A plain-text string is assigned to variable `template`. +# Its embedded [expression tag][expression tags] `'<%= Time.now %>'` includes a Ruby expression, `Time.now`. +# 2. The string is put into a new \ERB object, and stored in variable `erb`. +# 4. Method call `erb.result` generates a string that contains the run-time value of `Time.now`, +# as computed at the time of the call. # +# The +# \ERB object may be re-used: # -# == Examples +# ``` +# erb.result +# # => "The time is 2025-09-09 10:49:33 -0500." +# ``` # -# === Plain Text +# Another example: # -# ERB is useful for any generic templating situation. Note that in this example, we use the -# convenient "% at start of line" tag, and we quote the template literally with -# <tt>%q{...}</tt> to avoid trouble with the backslash. +# ``` +# template = 'The magic word is <%= magic_word %>.' +# erb = ERB.new(template) +# magic_word = 'abracadabra' +# erb.result(binding) +# # => "The magic word is abracadabra." +# ``` # -# require "erb" +# Details: # -# # Create template. -# template = %q{ -# From: James Edward Gray II <james@grayproductions.net> -# To: <%= to %> -# Subject: Addressing Needs +# 1. As before, a plain-text string is assigned to variable `template`. +# Its embedded [expression tag][expression tags] `'<%= magic_word %>'` has a variable *name*, `magic_word`. +# 2. The string is put into a new \ERB object, and stored in variable `erb`; +# note that `magic_word` need not be defined before the \ERB object is created. +# 3. `magic_word = 'abracadabra'` assigns a value to variable `magic_word`. +# 4. Method call `erb.result(binding)` generates a string +# that contains the *value* of `magic_word`. # -# <%= to[/\w+/] %>: +# As before, the \ERB object may be re-used: # -# Just wanted to send a quick note assuring that your needs are being -# addressed. +# ``` +# magic_word = 'xyzzy' +# erb.result(binding) +# # => "The magic word is xyzzy." +# ``` # -# I want you to know that my team will keep working on the issues, -# especially: +# ## Bindings # -# <%# ignore numerous minor requests -- focus on priorities %> -# % priorities.each do |priority| -# * <%= priority %> -# % end +# A call to method #result, which produces the formatted result string, +# requires a [Binding object][binding object] as its argument. +# +# The binding object provides the bindings for expressions in [expression tags][expression tags]. +# +# There are three ways to provide the required binding: +# +# - [Default binding][default binding]. +# - [Local binding][local binding]. +# - [Augmented binding][augmented binding] +# +# ### Default Binding +# +# When you pass no `binding` argument to method #result, +# the method uses its default binding: the one returned by method #new_toplevel. +# This binding has the bindings defined by Ruby itself, +# which are those for Ruby's constants and variables. +# +# That binding is sufficient for an expression tag that refers only to Ruby's constants and variables; +# these expression tags refer only to Ruby's global constant `RUBY_COPYRIGHT` and global variable `$0`: # -# Thanks for your patience. +# ``` +# template = <<TEMPLATE +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# puts ERB.new(template).result +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# (The current process is `irb` because that's where we're doing these examples!) +# +# ### Local Binding +# +# The default binding is *not* sufficient for an expression +# that refers to a a constant or variable that is not defined there: +# +# ``` +# Foo = 1 # Defines local constant Foo. +# foo = 2 # Defines local variable foo. +# template = <<TEMPLATE +# The current value of constant Foo is <%= Foo %>. +# The current value of variable foo is <%= foo %>. +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# erb = ERB.new(template) +# ``` +# +# This call below raises `NameError` because although `Foo` and `foo` are defined locally, +# they are not defined in the default binding: +# +# ``` +# erb.result # Raises NameError. +# ``` +# +# To make the locally-defined constants and variables available, +# you can call #result with the local binding: +# +# ``` +# puts erb.result(binding) +# The current value of constant Foo is 1. +# The current value of variable foo is 2. +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# ### Augmented Binding +# +# Another way to make variable bindings (but not constant bindings) available +# is to use method #result_with_hash(hash); +# the passed hash has name/value pairs that are to be used to define and assign variables +# in a copy of the default binding: +# +# ``` +# template = <<TEMPLATE +# The current value of variable bar is <%= bar %>. +# The current value of variable baz is <%= baz %>. +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# erb = ERB.new(template) +# ``` +# +# Both of these calls raise `NameError`, because `bar` and `baz` +# are not defined in either the default binding or the local binding. +# +# ``` +# puts erb.result # Raises NameError. +# puts erb.result(binding) # Raises NameError. +# ``` +# +# This call passes a hash that causes `bar` and `baz` to be defined +# in a new binding (derived from #new_toplevel): +# +# ``` +# hash = {bar: 3, baz: 4} +# puts erb.result_with_hash(hash) +# The current value of variable bar is 3. +# The current value of variable baz is 4. +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# ## Tags +# +# The examples above use expression tags. +# These are the tags available in \ERB: +# +# - [Expression tag][expression tags]: the tag contains a Ruby expression; +# in the result, the entire tag is to be replaced with the run-time value of the expression. +# - [Execution tag][execution tags]: the tag contains Ruby code; +# in the result, the entire tag is to be replaced with the run-time value of the code. +# - [Comment tag][comment tags]: the tag contains comment code; +# in the result, the entire tag is to be omitted. +# +# ### Expression Tags +# +# You can embed a Ruby expression in a template using an *expression tag*. +# +# Its syntax is `<%= _expression_ %>`, +# where *expression* is any valid Ruby expression. +# +# When you call method #result, +# the method evaluates the expression and replaces the entire expression tag with the expression's value: +# +# ``` +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result +# # => "Today is Monday." +# ERB.new('Tomorrow will be <%= Date::DAYNAMES[Date.today.wday + 1] %>.').result +# # => "Tomorrow will be Tuesday." +# ERB.new('Yesterday was <%= Date::DAYNAMES[Date.today.wday - 1] %>.').result +# # => "Yesterday was Sunday." +# ``` +# +# Note that whitespace before and after the expression +# is allowed but not required, +# and that such whitespace is stripped from the result. +# +# ``` +# ERB.new('My appointment is on <%=Date::DAYNAMES[Date.today.wday + 2]%>.').result +# # => "My appointment is on Wednesday." +# ERB.new('My appointment is on <%= Date::DAYNAMES[Date.today.wday + 2] %>.').result +# # => "My appointment is on Wednesday." +# ``` +# +# ### Execution Tags +# +# You can embed Ruby executable code in template using an *execution tag*. +# +# Its syntax is `<% _code_ %>`, +# where *code* is any valid Ruby code. +# +# When you call method #result, +# the method executes the code and removes the entire execution tag +# (generating no text in the result): +# +# ``` +# ERB.new('foo <% Dir.chdir("C:/") %> bar').result # => "foo bar" +# ``` +# +# Whitespace before and after the embedded code is optional: +# +# ``` +# ERB.new('foo <%Dir.chdir("C:/")%> bar').result # => "foo bar" +# ``` +# +# You can interleave text with execution tags to form a control structure +# such as a conditional, a loop, or a `case` statements. +# +# Conditional: +# +# ``` +# template = <<TEMPLATE +# <% if verbosity %> +# An error has occurred. +# <% else %> +# Oops! +# <% end %> +# TEMPLATE +# erb = ERB.new(template) +# verbosity = true +# erb.result(binding) +# # => "\nAn error has occurred.\n\n" +# verbosity = false +# erb.result(binding) +# # => "\nOops!\n\n" +# ``` +# +# Note that the interleaved text may itself contain expression tags: +# +# Loop: +# +# ``` +# template = <<TEMPLATE +# <% Date::ABBR_DAYNAMES.each do |dayname| %> +# <%= dayname %> +# <% end %> +# TEMPLATE +# ERB.new(template).result +# # => "\nSun\n\nMon\n\nTue\n\nWed\n\nThu\n\nFri\n\nSat\n\n" +# ``` +# +# Other, non-control, lines of Ruby code may be interleaved with the text, +# and the Ruby code may itself contain regular Ruby comments: +# +# ``` +# template = <<TEMPLATE +# <% 3.times do %> +# <%= Time.now %> +# <% sleep(1) # Let's make the times different. %> +# <% end %> +# TEMPLATE +# ERB.new(template).result +# # => "\n2025-09-09 11:36:02 -0500\n\n\n2025-09-09 11:36:03 -0500\n\n\n2025-09-09 11:36:04 -0500\n\n\n" +# ``` +# +# The execution tag may also contain multiple lines of code: +# +# ``` +# template = <<TEMPLATE +# <% +# (0..2).each do |i| +# (0..2).each do |j| +# %> +# * <%=i%>,<%=j%> +# <% +# end +# end +# %> +# TEMPLATE +# ERB.new(template).result +# # => "\n* 0,0\n\n* 0,1\n\n* 0,2\n\n* 1,0\n\n* 1,1\n\n* 1,2\n\n* 2,0\n\n* 2,1\n\n* 2,2\n\n" +# ``` +# +# #### Shorthand Format for Execution Tags +# +# You can use keyword argument `trim_mode: '%'` to enable a shorthand format for execution tags; +# this example uses the shorthand format `% _code_` instead of `<% _code_ %>`: +# +# ``` +# template = <<TEMPLATE +# % priorities.each do |priority| +# * <%= priority %> +# % end +# TEMPLATE +# erb = ERB.new(template, trim_mode: '%') +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# puts erb.result(binding) +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk +# ``` +# +# Note that in the shorthand format, the character `'%'` must be the first character in the code line +# (no leading whitespace). +# +# #### Suppressing Unwanted Blank Lines +# +# With keyword argument `trim_mode` not given, +# all blank lines go into the result: +# +# ``` +# template = <<TEMPLATE +# <% if true %> +# <%= RUBY_VERSION %> +# <% end %> +# TEMPLATE +# ERB.new(template).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "\n" +# ``` +# +# You can give `trim_mode: '-'`, you can suppress each blank line +# whose source line ends with `-%>` (instead of `%>`): +# +# ``` +# template = <<TEMPLATE +# <% if true -%> +# <%= RUBY_VERSION %> +# <% end -%> +# TEMPLATE +# ERB.new(template, trim_mode: '-').result.lines.each {|line| puts line.inspect } +# "3.4.5\n" +# ``` +# +# It is an error to use the trailing `'-%>'` notation without `trim_mode: '-'`: +# +# ``` +# ERB.new(template).result.lines.each {|line| puts line.inspect } # Raises SyntaxError. +# ``` +# +# #### Suppressing Unwanted Newlines +# +# Consider this template: +# +# ``` +# template = <<TEMPLATE +# <% RUBY_VERSION %> +# <%= RUBY_VERSION %> +# foo <% RUBY_VERSION %> +# foo <%= RUBY_VERSION %> +# TEMPLATE +# ``` +# +# With keyword argument `trim_mode` not given, all newlines go into the result: +# +# ``` +# ERB.new(template).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "foo \n" +# "foo 3.4.5\n" +# ``` +# +# You can give `trim_mode: '>'` to suppress the trailing newline +# for each line that ends with `'%>'` (regardless of its beginning): +# +# ``` +# ERB.new(template, trim_mode: '>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo foo 3.4.5" +# ``` +# +# You can give `trim_mode: '<>'` to suppress the trailing newline +# for each line that both begins with `'<%'` and ends with `'%>'`: +# +# ``` +# ERB.new(template, trim_mode: '<>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo \n" +# "foo 3.4.5\n" +# ``` +# +# #### Combining Trim Modes +# +# You can combine certain trim modes: +# +# - `'%-'`: Enable shorthand and omit each blank line ending with `'-%>'`. +# - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. +# - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. +# +# ### Comment Tags +# +# You can embed a comment in a template using a *comment tag*; +# its syntax is `<%# _text_ %>`, +# where *text* is the text of the comment. +# +# When you call method #result, +# it removes the entire comment tag +# (generating no text in the result). +# +# Example: +# +# ``` +# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(template).result # => "Some stuff; more stuff." +# ``` +# +# A comment tag may appear anywhere in the template. +# +# Note that the beginning of the tag must be `'<%#'`, not `'<% #'`. +# +# In this example, the tag begins with `'<% #'`, and so is an execution tag, not a comment tag; +# the cited code consists entirely of a Ruby-style comment (which is of course ignored): +# +# ``` +# ERB.new('Some stuff;<% # Note to self: figure out what the stuff is. %> more stuff.').result +# # => "Some stuff;" +# ``` +# +# ## Encodings +# +# An \ERB object has an [encoding][encoding], +# which is by default the encoding of the template string; +# the result string will also have that encoding. # -# James Edward Gray II -# }.gsub(/^ /, '') +# ``` +# template = <<TEMPLATE +# <%# Comment. %> +# TEMPLATE +# erb = ERB.new(template) +# template.encoding # => #<Encoding:UTF-8> +# erb.encoding # => #<Encoding:UTF-8> +# erb.result.encoding # => #<Encoding:UTF-8> +# ``` # -# message = ERB.new(template, trim_mode: "%<>") +# You can specify a different encoding by adding a [magic comment][magic comments] +# at the top of the given template: # -# # Set up template data. -# to = "Community Spokesman <spokesman@ruby_community.org>" -# priorities = [ "Run Ruby Quiz", -# "Document Modules", -# "Answer Questions on Ruby Talk" ] +# ``` +# template = <<TEMPLATE +# <%#-*- coding: Big5 -*-%> +# <%# Comment. %> +# TEMPLATE +# erb = ERB.new(template) +# template.encoding # => #<Encoding:UTF-8> +# erb.encoding # => #<Encoding:Big5> +# erb.result.encoding # => #<Encoding:Big5> +# ``` # -# # Produce result. -# email = message.result -# puts email +# ## Error Reporting # -# <i>Generates:</i> +# Consider this template (containing an error): # -# From: James Edward Gray II <james@grayproductions.net> -# To: Community Spokesman <spokesman@ruby_community.org> -# Subject: Addressing Needs +# ``` +# template = '<%= nosuch %>' +# erb = ERB.new(template) +# ``` # -# Community: +# When \ERB reports an error, +# it includes a file name (if available) and a line number; +# the file name comes from method #filename, the line number from method #lineno. # -# Just wanted to send a quick note assuring that your needs are being addressed. +# Initially, those values are `nil` and `0`, respectively; +# these initial values are reported as `'(erb)'` and `1`, respectively: # -# I want you to know that my team will keep working on the issues, especially: +# ``` +# erb.filename # => nil +# erb.lineno # => 0 +# erb.result +# (erb):1:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# * Run Ruby Quiz -# * Document Modules -# * Answer Questions on Ruby Talk +# You can use methods #filename= and #lineno= to assign values +# that are more meaningful in your context: # -# Thanks for your patience. +# ``` +# erb.filename = 't.txt' +# erb.lineno = 555 +# erb.result +# t.txt:556:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# James Edward Gray II +# You can use method #location= to set both values: # -# === Ruby in HTML +# ``` +# erb.location = ['u.txt', 999] +# erb.result +# u.txt:1000:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# ERB is often used in <tt>.rhtml</tt> files (HTML with embedded Ruby). Notice the need in -# this example to provide a special binding when the template is run, so that the instance -# variables in the Product object can be resolved. +# ## Plain Text with Embedded Ruby # -# require "erb" +# Here's a plain-text template; +# it uses the literal notation `'%q{ ... }'` to define the template +# (see [%q literals][%q literals]); +# this avoids problems with backslashes. # -# # Build template data class. -# class Product -# def initialize( code, name, desc, cost ) -# @code = code -# @name = name -# @desc = desc -# @cost = cost +# ``` +# template = %q{ +# From: James Edward Gray II <james@grayproductions.net> +# To: <%= to %> +# Subject: Addressing Needs # -# @features = [ ] -# end +# <%= to[/\w+/] %>: # -# def add_feature( feature ) -# @features << feature -# end +# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# # Support templating of member data. -# def get_binding -# binding -# end +# I want you to know that my team will keep working on the issues, +# especially: # -# # ... -# end +# <%# ignore numerous minor requests -- focus on priorities %> +# % priorities.each do |priority| +# * <%= priority %> +# % end # -# # Create template. -# template = %{ -# <html> -# <head><title>Ruby Toys -- <%= @name %></title></head> -# <body> +# Thanks for your patience. # -# <h1><%= @name %> (<%= @code %>)</h1> -# <p><%= @desc %></p> +# James Edward Gray II +# } +# ``` # -# <ul> -# <% @features.each do |f| %> -# <li><b><%= f %></b></li> -# <% end %> -# </ul> +# The template will need these: # -# <p> -# <% if @cost < 10 %> -# <b>Only <%= @cost %>!!!</b> -# <% else %> -# Call for a price, today! -# <% end %> -# </p> +# ``` +# to = 'Community Spokesman <spokesman@ruby_community.org>' +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# ``` # -# </body> -# </html> -# }.gsub(/^ /, '') +# Finally, create the \ERB object and get the result # -# rhtml = ERB.new(template) +# ``` +# erb = ERB.new(template, trim_mode: '%<>') +# puts erb.result(binding) # -# # Set up template data. -# toy = Product.new( "TZ-1002", -# "Rubysapien", -# "Geek's Best Friend! Responds to Ruby commands...", -# 999.95 ) -# toy.add_feature("Listens for verbal commands in the Ruby language!") -# toy.add_feature("Ignores Perl, Java, and all C variants.") -# toy.add_feature("Karate-Chop Action!!!") -# toy.add_feature("Matz signature on left leg.") -# toy.add_feature("Gem studded eyes... Rubies, of course!") +# From: James Edward Gray II <james@grayproductions.net> +# To: Community Spokesman <spokesman@ruby_community.org> +# Subject: Addressing Needs # -# # Produce result. -# rhtml.run(toy.get_binding) +# Community: # -# <i>Generates (some blank lines removed):</i> +# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# <html> -# <head><title>Ruby Toys -- Rubysapien</title></head> -# <body> +# I want you to know that my team will keep working on the issues, +# especially: # -# <h1>Rubysapien (TZ-1002)</h1> -# <p>Geek's Best Friend! Responds to Ruby commands...</p> +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk # -# <ul> -# <li><b>Listens for verbal commands in the Ruby language!</b></li> -# <li><b>Ignores Perl, Java, and all C variants.</b></li> -# <li><b>Karate-Chop Action!!!</b></li> -# <li><b>Matz signature on left leg.</b></li> -# <li><b>Gem studded eyes... Rubies, of course!</b></li> -# </ul> +# Thanks for your patience. # -# <p> -# Call for a price, today! -# </p> +# James Edward Gray II +# ``` # -# </body> -# </html> +# ## HTML with Embedded Ruby # +# This example shows an HTML template. # -# == Notes +# First, here's a custom class, `Product`: # -# There are a variety of templating solutions available in various Ruby projects. -# For example, RDoc, distributed with Ruby, uses its own template engine, which -# can be reused elsewhere. +# ``` +# class Product +# def initialize(code, name, desc, cost) +# @code = code +# @name = name +# @desc = desc +# @cost = cost +# @features = [] +# end # -# Other popular engines could be found in the corresponding -# {Category}[https://www.ruby-toolbox.com/categories/template_engines] of -# The Ruby Toolbox. +# def add_feature(feature) +# @features << feature +# end +# +# # Support templating of member data. +# def get_binding +# binding +# end +# +# end +# ``` +# +# The template below will need these values: +# +# ``` +# toy = Product.new('TZ-1002', +# 'Rubysapien', +# "Geek's Best Friend! Responds to Ruby commands...", +# 999.95 +# ) +# toy.add_feature('Listens for verbal commands in the Ruby language!') +# toy.add_feature('Ignores Perl, Java, and all C variants.') +# toy.add_feature('Karate-Chop Action!!!') +# toy.add_feature('Matz signature on left leg.') +# toy.add_feature('Gem studded eyes... Rubies, of course!') +# ``` +# +# Here's the HTML: +# +# ``` +# template = <<TEMPLATE +# <html> +# <head><title>Ruby Toys -- <%= @name %></title></head> +# <body> +# <h1><%= @name %> (<%= @code %>)</h1> +# <p><%= @desc %></p> +# <ul> +# <% @features.each do |f| %> +# <li><b><%= f %></b></li> +# <% end %> +# </ul> +# <p> +# <% if @cost < 10 %> +# <b>Only <%= @cost %>!!!</b> +# <% else %> +# Call for a price, today! +# <% end %> +# </p> +# </body> +# </html> +# TEMPLATE +# ``` +# +# Finally, create the \ERB object and get the result (omitting some blank lines): +# +# ``` +# erb = ERB.new(template) +# puts erb.result(toy.get_binding) +# <html> +# <head><title>Ruby Toys -- Rubysapien</title></head> +# <body> +# <h1>Rubysapien (TZ-1002)</h1> +# <p>Geek's Best Friend! Responds to Ruby commands...</p> +# <ul> +# <li><b>Listens for verbal commands in the Ruby language!</b></li> +# <li><b>Ignores Perl, Java, and all C variants.</b></li> +# <li><b>Karate-Chop Action!!!</b></li> +# <li><b>Matz signature on left leg.</b></li> +# <li><b>Gem studded eyes... Rubies, of course!</b></li> +# </ul> +# <p> +# Call for a price, today! +# </p> +# </body> +# </html> +# ``` +# +# +# ## Other Template Processors +# +# Various Ruby projects have their own template processors. +# The Ruby Processing System [RDoc][rdoc], for example, has one that can be used elsewhere. +# +# Other popular template processors may found in the [Template Engines][template engines] page +# of the Ruby Toolbox. +# +# [%q literals]: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-25q-3A+Non-Interpolable+String+Literals +# [augmented binding]: rdoc-ref:ERB@Augmented+Binding +# [binding object]: https://docs.ruby-lang.org/en/master/Binding.html +# [comment tags]: rdoc-ref:ERB@Comment+Tags +# [default binding]: rdoc-ref:ERB@Default+Binding +# [encoding]: https://docs.ruby-lang.org/en/master/Encoding.html +# [execution tags]: rdoc-ref:ERB@Execution+Tags +# [expression tags]: rdoc-ref:ERB@Expression+Tags +# [kernel#binding]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-binding +# [local binding]: rdoc-ref:ERB@Local+Binding +# [magic comments]: https://docs.ruby-lang.org/en/master/syntax/comments_rdoc.html#label-Magic+Comments +# [rdoc]: https://ruby.github.io/rdoc +# [sprintf]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sprintf +# [template engines]: https://www.ruby-toolbox.com/categories/template_engines +# [template processor]: https://en.wikipedia.org/wiki/Template_processor # class ERB - Revision = '$Date:: $' # :nodoc: #' - deprecate_constant :Revision - - # Returns revision information for the erb.rb module. + # :markup: markdown + # + # :call-seq: + # self.version -> string + # + # Returns the string \ERB version. def self.version VERSION end + # :markup: markdown # - # Constructs a new ERB object with the template specified in _str_. + # :call-seq: + # ERB.new(template, trim_mode: nil, eoutvar: '_erbout') # - # An ERB object works by building a chunk of Ruby code that will output - # the completed template when run. + # Returns a new \ERB object containing the given string +template+. # - # If _trim_mode_ is passed a String containing one or more of the following - # modifiers, ERB will adjust its code generation as listed: + # For details about `template`, its embedded tags, and generated results, see ERB. # - # % enables Ruby code processing for lines beginning with % - # <> omit newline for lines starting with <% and ending in %> - # > omit newline for lines ending in %> - # - omit blank lines ending in -%> + # **Keyword Argument `trim_mode`** # - # _eoutvar_ can be used to set the name of the variable ERB will build up - # its output in. This is useful when you need to run multiple ERB - # templates through the same binding and/or when you want to control where - # output ends up. Pass the name of the variable to be used inside a String. + # You can use keyword argument `trim_mode: '%'` + # to enable the [shorthand format][shorthand format] for execution tags. # - # === Example + # This value allows [blank line control][blank line control]: # - # require "erb" + # - `'-'`: Omit each blank line ending with `'%>'`. # - # # build data class - # class Listings - # PRODUCT = { :name => "Chicken Fried Steak", - # :desc => "A well messaged pattie, breaded and fried.", - # :cost => 9.95 } + # Other values allow [newline control][newline control]: # - # attr_reader :product, :price + # - `'>'`: Omit newline for each line ending with `'%>'`. + # - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`. # - # def initialize( product = "", price = "" ) - # @product = product - # @price = price - # end + # You can also [combine trim modes][combine trim modes]. # - # def build - # b = binding - # # create and run templates, filling member data variables - # ERB.new(<<~'END_PRODUCT', trim_mode: "", eoutvar: "@product").result b - # <%= PRODUCT[:name] %> - # <%= PRODUCT[:desc] %> - # END_PRODUCT - # ERB.new(<<~'END_PRICE', trim_mode: "", eoutvar: "@price").result b - # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> - # <%= PRODUCT[:desc] %> - # END_PRICE - # end - # end + # **Keyword Argument `eoutvar`** # - # # setup template data - # listings = Listings.new - # listings.build + # The string value of keyword argument `eoutvar` specifies the name of the variable + # that method #result uses to construct its result string; + # see #src. # - # puts listings.product + "\n" + listings.price + # This is useful when you need to run multiple \ERB templates through the same binding + # and/or when you want to control where output ends up. # - # _Generates_ + # It's good practice to choose a variable name that begins with an underscore: `'_'`. # - # Chicken Fried Steak - # A well massaged pattie, breaded and fried. + # [blank line control]: rdoc-ref:ERB@Suppressing+Unwanted+Blank+Lines + # [combine trim modes]: rdoc-ref:ERB@Combining+Trim+Modes + # [newline control]: rdoc-ref:ERB@Suppressing+Unwanted+Newlines + # [shorthand format]: rdoc-ref:ERB@Shorthand+Format+for+Execution+Tags # - # Chicken Fried Steak -- 9.95 - # A well massaged pattie, breaded and fried. - # - def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') - # Complex initializer for $SAFE deprecation at [Feature #14256]. Use keyword arguments to pass trim_mode or eoutvar. - if safe_level != NOT_GIVEN - warn 'Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.', uplevel: 1 - end - if legacy_trim_mode != NOT_GIVEN - warn 'Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.', uplevel: 1 - trim_mode = legacy_trim_mode - end - if legacy_eoutvar != NOT_GIVEN - warn 'Passing eoutvar with the 4th argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) instead.', uplevel: 1 - eoutvar = legacy_eoutvar - end - + def initialize(str, trim_mode: nil, eoutvar: '_erbout') compiler = make_compiler(trim_mode) set_eoutvar(compiler, eoutvar) @src, @encoding, @frozen_string = *compiler.compile(str) @@ -353,54 +837,137 @@ class ERB @lineno = 0 @_init = self.class.singleton_class end - NOT_GIVEN = defined?(Ractor) ? Ractor.make_shareable(Object.new) : Object.new - private_constant :NOT_GIVEN - - ## - # Creates a new compiler for ERB. See ERB::Compiler.new for details + # :markup: markdown + # + # :call-seq: + # make_compiler -> erb_compiler + # + # Returns a new ERB::Compiler with the given `trim_mode`; + # for `trim_mode` values, see ERB.new: + # + # ``` + # ERB.new('').make_compiler(nil) + # # => #<ERB::Compiler:0x000001cff9467678 @insert_cmd="print", @percent=false, @post_cmd=[], @pre_cmd=[], @put_cmd="print", @trim_mode=nil> + # ``` + # def make_compiler(trim_mode) ERB::Compiler.new(trim_mode) end - # The Ruby code generated by ERB + # :markup: markdown + # + # Returns the Ruby code that, when executed, generates the result; + # the code is executed by method #result, + # and by its wrapper methods #result_with_hash and #run: + # + # ``` + # template = 'The time is <%= Time.now %>.' + # erb = ERB.new(template) + # erb.src + # # => "#coding:UTF-8\n_erbout = +''; _erbout.<< \"The time is \".freeze; _erbout.<<(( Time.now ).to_s); _erbout.<< \".\".freeze; _erbout" + # erb.result + # # => "The time is 2025-09-18 15:58:08 -0500." + # ``` + # + # In a more readable format: + # + # ``` + # # puts erb.src.split('; ') + # # #coding:UTF-8 + # # _erbout = +'' + # # _erbout.<< "The time is ".freeze + # # _erbout.<<(( Time.now ).to_s) + # # _erbout.<< ".".freeze + # # _erbout + # ``` + # + # Variable `_erbout` is used to store the intermediate results in the code; + # the name `_erbout` is the default in ERB.new, + # and can be changed via keyword argument `eoutvar`: + # + # ``` + # erb = ERB.new(template, eoutvar: '_foo') + # puts template.src.split('; ') + # #coding:UTF-8 + # _foo = +'' + # _foo.<< "The time is ".freeze + # _foo.<<(( Time.now ).to_s) + # _foo.<< ".".freeze + # _foo + # ``` + # attr_reader :src - # The encoding to eval + # :markup: markdown + # + # Returns the encoding of `self`; + # see [Encodings][encodings]: + # + # [encodings]: rdoc-ref:ERB@Encodings + # attr_reader :encoding - # The optional _filename_ argument passed to Kernel#eval when the ERB code - # is run + # :markup: markdown + # + # Sets or returns the file name to be used in reporting errors; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting attr_accessor :filename - # The optional _lineno_ argument passed to Kernel#eval when the ERB code - # is run + # :markup: markdown + # + # Sets or returns the line number to be used in reporting errors; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting attr_accessor :lineno + # :markup: markdown # - # Sets optional filename and line number that will be used in ERB code - # evaluation and error reporting. See also #filename= and #lineno= - # - # erb = ERB.new('<%= some_x %>') - # erb.render - # # undefined local variable or method `some_x' - # # from (erb):1 + # :call-seq: + # location = [filename, lineno] => [filename, lineno] + # location = filename -> filename # - # erb.location = ['file.erb', 3] - # # All subsequent error reporting would use new location - # erb.render - # # undefined local variable or method `some_x' - # # from file.erb:4 + # Sets the values of #filename and, if given, #lineno; + # see [Error Reporting][error reporting]. # + # [error reporting]: rdoc-ref:ERB@Error+Reporting def location=((filename, lineno)) @filename = filename @lineno = lineno if lineno end + # :markup: markdown # - # Can be used to set _eoutvar_ as described in ERB::new. It's probably - # easier to just use the constructor though, since calling this method - # requires the setup of an ERB _compiler_ object. + # :call-seq: + # set_eoutvar(compiler, eoutvar = '_erbout') -> [eoutvar] + # + # Sets the `eoutvar` value in the ERB::Compiler object `compiler`; + # returns a 1-element array containing the value of `eoutvar`: + # + # ``` + # template = ERB.new('') + # compiler = template.make_compiler(nil) + # pp compiler + # #<ERB::Compiler:0x000001cff8a9aa00 + # @insert_cmd="print", + # @percent=false, + # @post_cmd=[], + # @pre_cmd=[], + # @put_cmd="print", + # @trim_mode=nil> + # template.set_eoutvar(compiler, '_foo') # => ["_foo"] + # pp compiler + # #<ERB::Compiler:0x000001cff8a9aa00 + # @insert_cmd="_foo.<<", + # @percent=false, + # @post_cmd=["_foo"], + # @pre_cmd=["_foo = +''"], + # @put_cmd="_foo.<<", + # @trim_mode=nil> + # ``` # def set_eoutvar(compiler, eoutvar = '_erbout') compiler.put_cmd = "#{eoutvar}.<<" @@ -409,17 +976,34 @@ class ERB compiler.post_cmd = [eoutvar] end - # Generate results and print them. (see ERB#result) + # :markup: markdown + # + # :call-seq: + # run(binding = new_toplevel) -> nil + # + # Like #result, but prints the result string (instead of returning it); + # returns `nil`. def run(b=new_toplevel) print self.result(b) end + # :markup: markdown + # + # :call-seq: + # result(binding = new_toplevel) -> new_string + # + # Returns the string result formed by processing \ERB tags found in the stored template in `self`. + # + # With no argument given, uses the default binding; + # see [Default Binding][default binding]. + # + # With argument `binding` given, uses the local binding; + # see [Local Binding][local binding]. # - # Executes the generated ERB code to produce a completed template, returning - # the results of that code. + # See also #result_with_hash. # - # _b_ accepts a Binding object which is used to set the context of - # code evaluation. + # [default binding]: rdoc-ref:ERB@Default+Binding + # [local binding]: rdoc-ref:ERB@Local+Binding # def result(b=new_toplevel) unless @_init.equal?(self.class.singleton_class) @@ -428,8 +1012,18 @@ class ERB eval(@src, b, (@filename || '(erb)'), @lineno) end - # Render a template on a new toplevel binding with local variables specified - # by a Hash object. + # :markup: markdown + # + # :call-seq: + # result_with_hash(hash) -> new_string + # + # Returns the string result formed by processing \ERB tags found in the stored string in `self`; + # see [Augmented Binding][augmented binding]. + # + # See also #result. + # + # [augmented binding]: rdoc-ref:ERB@Augmented+Binding + # def result_with_hash(hash) b = new_toplevel(hash.keys) hash.each_pair do |key, value| @@ -438,10 +1032,22 @@ class ERB result(b) end - ## - # Returns a new binding each time *near* TOPLEVEL_BINDING for runs that do - # not specify a binding. - + # :markup: markdown + # + # :call-seq: + # new_toplevel(symbols) -> new_binding + # + # Returns a new binding based on `TOPLEVEL_BINDING`; + # used to create a default binding for a call to #result. + # + # See [Default Binding][default binding]. + # + # Argument `symbols` is an array of symbols; + # each symbol `symbol` is defined as a new variable to hide and + # prevent it from overwriting a variable of the same name already + # defined within the binding. + # + # [default binding]: rdoc-ref:ERB@Default+Binding def new_toplevel(vars = nil) b = TOPLEVEL_BINDING if vars @@ -454,13 +1060,31 @@ class ERB end private :new_toplevel - # Define _methodname_ as instance method of _mod_ from compiled Ruby source. + # :markup: markdown + # + # :call-seq: + # def_method(module, method_signature, filename = '(ERB)') -> method_name + # + # Creates and returns a new instance method in the given module `module`; + # returns the method name as a symbol. + # + # The method is created from the given `method_signature`, + # which consists of the method name and its argument names (if any). + # + # The `filename` sets the value of #filename; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting + # + # ``` + # template = '<%= arg1 %> <%= arg2 %>' + # erb = ERB.new(template) + # MyModule = Module.new + # erb.def_method(MyModule, 'render(arg1, arg2)') # => :render + # class MyClass; include MyModule; end + # MyClass.new.render('foo', 123) # => "foo 123" + # ``` # - # example: - # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.def_method(MyClass, 'render(arg1, arg2)', filename) - # print MyClass.new.render('foo', 123) def def_method(mod, methodname, fname='(ERB)') src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n" mod.module_eval do @@ -468,35 +1092,81 @@ class ERB end end - # Create unnamed module, define _methodname_ as instance method of it, and return it. - # - # example: - # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.filename = filename - # MyModule = erb.def_module('render(arg1, arg2)') - # class MyClass - # include MyModule - # end + # :markup: markdown + # + # :call-seq: + # def_module(method_name = 'erb') -> new_module + # + # Returns a new nameless module that has instance method `method_name`. + # + # ``` + # template = '<%= arg1 %> <%= arg2 %>' + # erb = ERB.new(template) + # MyModule = template.def_module('render(arg1, arg2)') + # class MyClass + # include MyModule + # end + # MyClass.new.render('foo', 123) + # # => "foo 123" + # ``` + # def def_module(methodname='erb') mod = Module.new def_method(mod, methodname, @filename || '(ERB)') mod end - # Define unnamed class which has _methodname_ as instance method, and return it. + # :markup: markdown # - # example: - # class MyClass_ - # def initialize(arg1, arg2) - # @arg1 = arg1; @arg2 = arg2 - # end + # :call-seq: + # def_class(super_class = Object, method_name = 'result') -> new_class + # + # Returns a new nameless class whose superclass is `super_class`, + # and which has instance method `method_name`. + # + # Create a template from HTML that has embedded expression tags that use `@arg1` and `@arg2`: + # + # ``` + # html = <<TEMPLATE + # <html> + # <body> + # <p><%= @arg1 %></p> + # <p><%= @arg2 %></p> + # </body> + # </html> + # TEMPLATE + # template = ERB.new(html) + # ``` + # + # Create a base class that has `@arg1` and `@arg2`: + # + # ``` + # class MyBaseClass + # def initialize(arg1, arg2) + # @arg1 = arg1 + # @arg2 = arg2 # end - # filename = 'example.rhtml' # @arg1 and @arg2 are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.filename = filename - # MyClass = erb.def_class(MyClass_, 'render()') - # print MyClass.new('foo', 123).render() + # end + # ``` + # + # Use method #def_class to create a subclass that has method `:render`: + # + # ``` + # MySubClass = template.def_class(MyBaseClass, :render) + # ``` + # + # Generate the result: + # + # ``` + # puts MySubClass.new('foo', 123).render + # <html> + # <body> + # <p>foo</p> + # <p>123</p> + # </body> + # </html> + # ``` + # def def_class(superklass=Object, methodname='result') cls = Class.new(superklass) def_method(cls, methodname, @filename || '(ERB)') diff --git a/lib/erb/compiler.rb b/lib/erb/compiler.rb index 08b5eb4ee1..6d70288b4f 100644 --- a/lib/erb/compiler.rb +++ b/lib/erb/compiler.rb @@ -225,7 +225,7 @@ class ERB::Compiler # :nodoc: end end - ERB_STAG = %w(<%= <%# <%) + ERB_STAG = %w(<%= <%# <%).freeze def is_erb_stag?(s) ERB_STAG.member?(s) end @@ -480,7 +480,6 @@ class ERB::Compiler # :nodoc: end }.new(caller(0)).c private_constant :WARNING_UPLEVEL - # :startdoc: def warn_invalid_trim_mode(mode, uplevel:) warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + WARNING_UPLEVEL diff --git a/lib/erb/def_method.rb b/lib/erb/def_method.rb index aee989a926..e503b37140 100644 --- a/lib/erb/def_method.rb +++ b/lib/erb/def_method.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -#-- + # ERB::DefMethod # # Utility module to define eRuby script as instance method. diff --git a/lib/erb.gemspec b/lib/erb/erb.gemspec index 94a8fd5c3e..3793e5d70f 100644 --- a/lib/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -2,23 +2,23 @@ begin require_relative 'lib/erb/version' rescue LoadError # for Ruby core repository - require_relative 'erb/version' + require_relative 'version' end Gem::Specification.new do |spec| spec.name = 'erb' - spec.version = ERB.const_get(:VERSION, false) + spec.version = ERB::VERSION spec.authors = ['Masatoshi SEKI', 'Takashi Kokubun'] spec.email = ['seki@ruby-lang.org', 'k0kubun@ruby-lang.org'] spec.summary = %q{An easy to use but powerful templating system for Ruby.} spec.description = %q{An easy to use but powerful templating system for Ruby.} spec.homepage = 'https://github.com/ruby/erb' - spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0') spec.licenses = ['Ruby', 'BSD-2-Clause'] spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage + spec.metadata['changelog_uri'] = "https://github.com/ruby/erb/blob/v#{spec.version}/NEWS.md" spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } @@ -27,12 +27,11 @@ Gem::Specification.new do |spec| spec.executables = ['erb'] spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.2.0' + if RUBY_ENGINE == 'jruby' spec.platform = 'java' else - spec.required_ruby_version = '>= 2.7.0' spec.extensions = ['ext/erb/escape/extconf.rb'] end - - spec.add_dependency 'cgi', '>= 0.3.3' end diff --git a/lib/erb/util.rb b/lib/erb/util.rb index 1d2a36275d..d7d69eb4f1 100644 --- a/lib/erb/util.rb +++ b/lib/erb/util.rb @@ -1,18 +1,25 @@ # frozen_string_literal: true -#-- -# ERB::Escape -# -# A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope -# Rails will not monkey-patch ERB::Escape#html_escape. + +# Load CGI.escapeHTML and CGI.escapeURIComponent. +# CRuby: +# cgi.gem v0.1.0+ (Ruby 2.7-3.4) and Ruby 4.0+ stdlib have 'cgi/escape' and CGI.escapeHTML. +# cgi.gem v0.3.3+ (Ruby 3.2-3.4) and Ruby 4.0+ stdlib have CGI.escapeURIComponent. +# JRuby: cgi.gem has a Java extension 'cgi/escape'. +# TruffleRuby: lib/truffle/cgi/escape.rb requires 'cgi/util'. +require 'cgi/escape' + +# Load or define ERB::Escape#html_escape. +# We don't build the C extension 'cgi/escape' for JRuby, TruffleRuby, and WASM. +# miniruby (used by CRuby build scripts) also fails to load erb/escape.so. begin - # We don't build the C extension for JRuby, TruffleRuby, and WASM - if $LOAD_PATH.resolve_feature_path('erb/escape') - require 'erb/escape' - end -rescue LoadError # resolve_feature_path raises LoadError on TruffleRuby 22.3.0 -end -unless defined?(ERB::Escape) + require 'erb/escape' +rescue LoadError + # ERB::Escape + # + # A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope + # Rails will not monkey-patch ERB::Escape#html_escape. module ERB::Escape + # :stopdoc: def html_escape(s) CGI.escapeHTML(s.to_s) end @@ -20,7 +27,6 @@ unless defined?(ERB::Escape) end end -#-- # ERB::Util # # A utility module for conversion routines, often handy in HTML generation. @@ -42,20 +48,28 @@ module ERB::Util alias h html_escape module_function :h - # - # A utility method for encoding the String _s_ as a URL. - # - # require "erb" - # include ERB::Util - # - # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") - # - # _Generates_ - # - # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide - # - def url_encode(s) - CGI.escapeURIComponent(s.to_s) + if CGI.respond_to?(:escapeURIComponent) + # + # A utility method for encoding the String _s_ as a URL. + # + # require "erb" + # include ERB::Util + # + # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") + # + # _Generates_ + # + # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide + # + def url_encode(s) + CGI.escapeURIComponent(s.to_s) + end + else # cgi.gem <= v0.3.2 + def url_encode(s) + s.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) do |m| + sprintf("%%%02X", m.unpack1("C")) + end + end end alias u url_encode module_function :u diff --git a/lib/erb/version.rb b/lib/erb/version.rb index b5fe39b330..e367a1b5c7 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB - VERSION = '4.0.4' - private_constant :VERSION + # The string \ERB version. + VERSION = '6.0.1' end diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb index 14e0ce5785..bc4a62c9d6 100644 --- a/lib/error_highlight/base.rb +++ b/lib/error_highlight/base.rb @@ -1,13 +1,13 @@ require_relative "version" module ErrorHighlight - # Identify the code fragment at that a given exception occurred. + # Identify the code fragment where a given exception occurred. # # Options: # # point_type: :name | :args - # :name (default) points the method/variable name that the exception occurred. - # :args points the arguments of the method call that the exception occurred. + # :name (default) points to the method/variable name where the exception occurred. + # :args points to the arguments of the method call where the exception occurred. # # backtrace_location: Thread::Backtrace::Location # It locates the code fragment of the given backtrace_location. @@ -113,7 +113,7 @@ module ErrorHighlight snippet = @node.script_lines[lineno - 1 .. last_lineno - 1].join("") snippet += "\n" unless snippet.end_with?("\n") - # It require some work to support Unicode (or multibyte) characters. + # It requires some work to support Unicode (or multibyte) characters. # Tentatively, we stop highlighting if the code snippet has non-ascii characters. # See https://github.com/ruby/error_highlight/issues/4 raise NonAscii unless snippet.ascii_only? @@ -122,56 +122,51 @@ module ErrorHighlight end end - OPT_GETCONSTANT_PATH = (RUBY_VERSION.split(".").map {|s| s.to_i } <=> [3, 2]) >= 0 - private_constant :OPT_GETCONSTANT_PATH - def spot return nil unless @node - if OPT_GETCONSTANT_PATH - # In Ruby 3.2 or later, a nested constant access (like `Foo::Bar::Baz`) - # is compiled to one instruction (opt_getconstant_path). - # @node points to the node of the whole `Foo::Bar::Baz` even if `Foo` - # or `Foo::Bar` causes NameError. - # So we try to spot the sub-node that causes the NameError by using - # `NameError#name`. - case @node.type - when :COLON2 - subnodes = [] - node = @node - while node.type == :COLON2 - node2, const = node.children - subnodes << node if const == @name - node = node2 - end - if node.type == :CONST || node.type == :COLON3 - if node.children.first == @name - subnodes << node - end - - # If we found only one sub-node whose name is equal to @name, use it - return nil if subnodes.size != 1 - @node = subnodes.first - else - # Do nothing; opt_getconstant_path is used only when the const base is - # NODE_CONST (`Foo`) or NODE_COLON3 (`::Foo`) - end - when :constant_path_node - subnodes = [] - node = @node - - begin - subnodes << node if node.name == @name - end while (node = node.parent).is_a?(Prism::ConstantPathNode) - - if node.is_a?(Prism::ConstantReadNode) && node.name == @name + # In Ruby 3.2 or later, a nested constant access (like `Foo::Bar::Baz`) + # is compiled to one instruction (opt_getconstant_path). + # @node points to the node of the whole `Foo::Bar::Baz` even if `Foo` + # or `Foo::Bar` causes NameError. + # So we try to spot the sub-node that causes the NameError by using + # `NameError#name`. + case @node.type + when :COLON2 + subnodes = [] + node = @node + while node.type == :COLON2 + node2, const = node.children + subnodes << node if const == @name + node = node2 + end + if node.type == :CONST || node.type == :COLON3 + if node.children.first == @name subnodes << node end # If we found only one sub-node whose name is equal to @name, use it return nil if subnodes.size != 1 @node = subnodes.first + else + # Do nothing; opt_getconstant_path is used only when the const base is + # NODE_CONST (`Foo`) or NODE_COLON3 (`::Foo`) end + when :constant_path_node + subnodes = [] + node = @node + + begin + subnodes << node if node.name == @name + end while (node = node.parent).is_a?(Prism::ConstantPathNode) + + if node.is_a?(Prism::ConstantReadNode) && node.name == @name + subnodes << node + end + + # If we found only one sub-node whose name is equal to @name, use it + return nil if subnodes.size != 1 + @node = subnodes.first end case @node.type @@ -239,6 +234,20 @@ module ErrorHighlight when :OP_CDECL spot_op_cdecl + when :DEFN + raise NotImplementedError if @point_type != :name + spot_defn + + when :DEFS + raise NotImplementedError if @point_type != :name + spot_defs + + when :LAMBDA + spot_lambda + + when :ITER + spot_iter + when :call_node case @point_type when :name @@ -280,6 +289,30 @@ module ErrorHighlight when :constant_path_operator_write_node prism_spot_constant_path_operator_write + when :def_node + case @point_type + when :name + prism_spot_def_for_name + when :args + raise NotImplementedError + end + + when :lambda_node + case @point_type + when :name + prism_spot_lambda_for_name + when :args + raise NotImplementedError + end + + when :block_node + case @point_type + when :name + prism_spot_block_for_name + when :args + raise NotImplementedError + end + end if @snippet && @beg_column && @end_column && @beg_column < @end_column @@ -471,7 +504,6 @@ module ErrorHighlight def spot_fcall_for_args _mid, nd_args = @node.children if nd_args && nd_args.first_lineno == nd_args.last_lineno - # binary operator fetch_line(nd_args.first_lineno) @beg_column = nd_args.first_column @end_column = nd_args.last_column @@ -621,6 +653,55 @@ module ErrorHighlight end end + # Example: + # def bar; end + # ^^^ + def spot_defn + mid, = @node.children + fetch_line(@node.first_lineno) + if @snippet.match(/\Gdef\s+(#{ Regexp.quote(mid) }\b)/, @node.first_column) + @beg_column = $~.begin(1) + @end_column = $~.end(1) + end + end + + # Example: + # def Foo.bar; end + # ^^^^ + def spot_defs + nd_recv, mid, = @node.children + fetch_line(nd_recv.last_lineno) + if @snippet.match(/\G\s*(\.\s*#{ Regexp.quote(mid) }\b)/, nd_recv.last_column) + @beg_column = $~.begin(1) + @end_column = $~.end(1) + end + end + + # Example: + # -> { ... } + # ^^ + def spot_lambda + fetch_line(@node.first_lineno) + if @snippet.match(/\G->/, @node.first_column) + @beg_column = $~.begin(0) + @end_column = $~.end(0) + end + end + + # Example: + # lambda { ... } + # ^ + # define_method :foo do + # ^^ + def spot_iter + _nd_fcall, nd_scope = @node.children + fetch_line(nd_scope.first_lineno) + if @snippet.match(/\G(?:do\b|\{)/, nd_scope.first_column) + @beg_column = $~.begin(0) + @end_column = $~.end(0) + end + end + def fetch_line(lineno) @beg_lineno = @end_lineno = lineno @snippet = @fetch[lineno] @@ -826,6 +907,31 @@ module ErrorHighlight prism_location(@node.binary_operator_loc.chop) end end + + # Example: + # def foo() + # ^^^ + def prism_spot_def_for_name + location = @node.name_loc + location = @node.operator_loc.join(location) if @node.operator_loc + prism_location(location) + end + + # Example: + # -> x, y { } + # ^^ + def prism_spot_lambda_for_name + prism_location(@node.operator_loc) + end + + # Example: + # lambda { } + # ^ + # define_method :foo do |x, y| + # ^ + def prism_spot_block_for_name + prism_location(@node.opening_loc) + end end private_constant :Spotter diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index b69093f74e..c3354f46cd 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -3,9 +3,38 @@ require_relative "formatter" module ErrorHighlight module CoreExt private def generate_snippet - spot = ErrorHighlight.spot(self) - return "" unless spot - return ErrorHighlight.formatter.message_for(spot) + if ArgumentError === self && message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/ + locs = self.backtrace_locations + return "" if locs.size < 2 + callee_loc, caller_loc = locs + callee_spot = ErrorHighlight.spot(self, backtrace_location: callee_loc, point_type: :name) + caller_spot = ErrorHighlight.spot(self, backtrace_location: caller_loc, point_type: :name) + if caller_spot && callee_spot && + caller_loc.path == callee_loc.path && + caller_loc.lineno == callee_loc.lineno && + caller_spot == callee_spot + callee_loc = callee_spot = nil + end + ret = +"\n" + [["caller", caller_loc, caller_spot], ["callee", callee_loc, callee_spot]].each do |header, loc, spot| + out = nil + if loc + out = " #{ header }: #{ loc.path }:#{ loc.lineno }" + if spot + _, _, snippet, highlight = ErrorHighlight.formatter.message_for(spot).lines + out += "\n | #{ snippet } #{ highlight }" + else + # do nothing + end + end + ret << "\n" + out if out + end + ret + else + spot = ErrorHighlight.spot(self) + return "" unless spot + return ErrorHighlight.formatter.message_for(spot) + end end if Exception.method_defined?(:detailed_message) diff --git a/lib/error_highlight/error_highlight.gemspec b/lib/error_highlight/error_highlight.gemspec index b2da18df83..edfc4b776f 100644 --- a/lib/error_highlight/error_highlight.gemspec +++ b/lib/error_highlight/error_highlight.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/ruby/error_highlight" spec.license = "MIT" - spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0.dev") + spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0") spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) } diff --git a/lib/error_highlight/formatter.rb b/lib/error_highlight/formatter.rb index 5180576405..d2fad9e75c 100644 --- a/lib/error_highlight/formatter.rb +++ b/lib/error_highlight/formatter.rb @@ -56,11 +56,11 @@ module ErrorHighlight end def self.terminal_width - # lazy load io/console, so it's not loaded when 'max_snippet_width' is set + # lazy load io/console to avoid loading it when 'max_snippet_width' is manually set require "io/console" $stderr.winsize[1] if $stderr.tty? rescue LoadError, NoMethodError, SystemCallError - # do not truncate when window size is not available + # skip truncation when terminal window size is unavailable end end diff --git a/lib/error_highlight/version.rb b/lib/error_highlight/version.rb index d7a29c7c1e..f0a5376b14 100644 --- a/lib/error_highlight/version.rb +++ b/lib/error_highlight/version.rb @@ -1,3 +1,3 @@ module ErrorHighlight - VERSION = "0.7.0" + VERSION = "0.7.1" end diff --git a/lib/fileutils.rb b/lib/fileutils.rb index b9d683797a..0706e007ca 100644 --- a/lib/fileutils.rb +++ b/lib/fileutils.rb @@ -181,7 +181,7 @@ end # module FileUtils # The version number. - VERSION = "1.7.3" + VERSION = "1.8.0" def self.private_module_function(name) #:nodoc: module_function name @@ -706,11 +706,12 @@ module FileUtils # 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) + return ln_sr(src, dest, force: force, target_directory: target_directory, noop: noop, verbose: verbose) end - fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose + fu_output_message "ln -s#{force ? 'f' : ''}#{ + target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose return if noop - fu_each_src_dest0(src, dest) do |s,d| + fu_each_src_dest0(src, dest, target_directory) do |s,d| remove_file d, true if force File.symlink s, d end @@ -730,42 +731,37 @@ module FileUtils # Like FileUtils.ln_s, but create links relative to +dest+. # 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)) + cmd = "ln -s#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" if verbose + fu_each_src_dest0(src, dest, target_directory) do |s,d| + if target_directory + parent = File.dirname(d) + destdirs = fu_split_path(parent) + real_ddirs = fu_split_path(File.realpath(parent)) else - destdirs = File.dirname(d = dest) + destdirs ||= fu_split_path(dest) + real_ddirs ||= fu_split_path(File.realdirpath(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) + srcdirs = fu_split_path(s) + i = fu_common_components(srcdirs, destdirs) + n = destdirs.size - i + n -= 1 unless target_directory + link1 = fu_clean_components(*Array.new([n, 0].max, '..'), *srcdirs[i..-1]) + begin + real_sdirs = fu_split_path(File.realdirpath(s)) rescue nil + rescue 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) + i = fu_common_components(real_sdirs, real_ddirs) + n = real_ddirs.size - i + n -= 1 unless target_directory + link2 = fu_clean_components(*Array.new([n, 0].max, '..'), *real_sdirs[i..-1]) + link1 = link2 if link1.size > link2.size end - fu_output_message "ln -s#{options} #{s} #{d}" if verbose + s = File.join(link1) + fu_output_message [cmd, s, d].flatten.join(' ') 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 @@ -800,13 +796,13 @@ module FileUtils # File.file?('dest1/dir1/t2.txt') # => true # File.file?('dest1/dir1/t3.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link. - # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default). + # - +remove_destination+ - removes +dest+ before creating links (+false+ by default). # # 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. + # and optional argument +remove_destination+ is not given. # # Related: FileUtils.ln (has different options). # @@ -1029,12 +1025,12 @@ module FileUtils # directories, and symbolic links; # other file types (FIFO streams, device files, etc.) are not supported. # - # Keyword arguments: + # Optional arguments: # - # - <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. + # - +dereference_root+ - if +src+ is a symbolic link, + # follows the link (+false+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1065,12 +1061,12 @@ module FileUtils # FileUtils.copy_file('src0.txt', 'dest0.txt') # File.file?('dest0.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <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. + # - +dereference+ - if +src+ is a symbolic link, + # follows the link (+true+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1491,7 +1487,8 @@ module FileUtils # 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 + raise Errno::ENOTDIR, path unless force or File.directory?(path) + remove_entry path, force end module_function :remove_dir @@ -2475,6 +2472,10 @@ module FileUtils def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc: if tmp = Array.try_convert(src) + unless target_directory or tmp.size <= 1 + tmp = tmp.map {|f| File.path(f)} # A workaround for RBS + raise ArgumentError, "extra target #{tmp}" + end tmp.each do |s| s = File.path(s) yield s, (target_directory ? File.join(dest, File.basename(s)) : dest) @@ -2509,7 +2510,11 @@ module FileUtils path = File.path(path) list = [] until (parent, base = File.split(path); parent == path or parent == ".") - list << base + if base != '..' and list.last == '..' and !(fu_have_symlink? && File.symlink?(path)) + list.pop + else + list << base + end path = parent end list << path @@ -2517,14 +2522,14 @@ module FileUtils end private_module_function :fu_split_path - def fu_relative_components_from(target, base) #:nodoc: + def fu_common_components(target, base) #:nodoc: i = 0 while target[i]&.== base[i] i += 1 end - Array.new(base.size-i, '..').concat(target[i..-1]) + i end - private_module_function :fu_relative_components_from + private_module_function :fu_common_components def fu_clean_components(*comp) #:nodoc: comp.shift while comp.first == "." @@ -2534,7 +2539,7 @@ module FileUtils while c = comp.shift if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path)) clean.pop - path.chomp!(%r((?<=\A|/)[^/]+/\z), "") + path.sub!(%r((?<=\A|/)[^/]+/\z), "") else clean << c path << c << "/" diff --git a/lib/find.rb b/lib/find.rb index 98a79cc76d..8223eea456 100644 --- a/lib/find.rb +++ b/lib/find.rb @@ -27,6 +27,7 @@ # module Find + # The version string VERSION = "0.2.0" # diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 71b4e6adad..175d6d9c6b 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -109,11 +109,11 @@ # +delegate.rb+. # module Forwardable - require 'forwardable/impl' - # Version of +forwardable.rb+ - VERSION = "1.3.3" + VERSION = "1.4.0" VERSION.freeze + + # Version for backward compatibility FORWARDABLE_VERSION = VERSION FORWARDABLE_VERSION.freeze @@ -190,9 +190,7 @@ module Forwardable # If it's not a class or module, it's an instance mod = Module === self ? self : singleton_class - ret = mod.module_eval(&gen) - mod.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7' - ret + mod.module_eval(&gen) end alias delegate instance_delegate @@ -206,36 +204,33 @@ module Forwardable if Module === obj ? obj.method_defined?(accessor) || obj.private_method_defined?(accessor) : obj.respond_to?(accessor, true) - accessor = "#{accessor}()" + accessor = "(#{accessor}())" end - method_call = ".__send__(:#{method}, *args, &block)" - if _valid_method?(method) + args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block' + method_call = ".__send__(:#{method}, #{args})" + if method.match?(/\A[_a-zA-Z]\w*[?!]?\z/) loc, = caller_locations(2,1) pre = "_ =" mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method " - method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}" - begin; - unless defined? _.#{method} - ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 - _#{method_call} - else - _.#{method}(*args, &block) - end - end; + method_call = <<~RUBY.chomp + if defined?(_.#{method}) + _.#{method}(#{args}) + else + ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 + _#{method_call} + end + RUBY end - _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1) - begin; + eval(<<~RUBY, nil, __FILE__, __LINE__ + 1) proc do - def #{ali}(*args, &block) - #{pre} - begin - #{accessor} - end#{method_call} + def #{ali}(#{args}) + #{pre}#{accessor} + #{method_call} end end - end; + RUBY end end @@ -310,9 +305,7 @@ module SingleForwardable def def_single_delegator(accessor, method, ali = method) gen = Forwardable._delegator_method(self, accessor, method, ali) - ret = instance_eval(&gen) - singleton_class.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7' - ret + instance_eval(&gen) end alias delegate single_delegate diff --git a/lib/forwardable/forwardable.gemspec b/lib/forwardable/forwardable.gemspec index 9ad59c5f8a..1b539bcfcb 100644 --- a/lib/forwardable/forwardable.gemspec +++ b/lib/forwardable/forwardable.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.licenses = ["Ruby", "BSD-2-Clause"] spec.required_ruby_version = '>= 2.4.0' - spec.files = ["forwardable.gemspec", "lib/forwardable.rb", "lib/forwardable/impl.rb"] + spec.files = ["forwardable.gemspec", "lib/forwardable.rb"] spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/forwardable/impl.rb b/lib/forwardable/impl.rb deleted file mode 100644 index 0322c136db..0000000000 --- a/lib/forwardable/impl.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Forwardable - # :stopdoc: - - def self._valid_method?(method) - catch {|tag| - eval("BEGIN{throw tag}; ().#{method}", binding, __FILE__, __LINE__) - } - rescue SyntaxError - false - else - true - end - - def self._compile_method(src, file, line) - eval(src, nil, file, line) - end -end diff --git a/lib/ipaddr.gemspec b/lib/ipaddr.gemspec index 5719f83fc4..cabc9161ba 100644 --- a/lib/ipaddr.gemspec +++ b/lib/ipaddr.gemspec @@ -29,7 +29,7 @@ Both IPv4 and IPv6 are supported. spec.homepage = "https://github.com/ruby/ipaddr" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.files = ["LICENSE.txt", "README.md", "ipaddr.gemspec", "lib/ipaddr.rb"] + spec.files = ["LICENSE.txt", "README.md", "lib/ipaddr.rb"] spec.require_paths = ["lib"] spec.required_ruby_version = ">= 2.4" diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index a45055496c..6b67d7eec6 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -40,7 +40,8 @@ require 'socket' # p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0> class IPAddr - VERSION = "1.2.7" + # The version string + VERSION = "1.2.8" # 32 bit mask for IPv4 IN4MASK = 0xffffffff @@ -151,6 +152,16 @@ class IPAddr return self.clone.set(addr_mask(~@addr)) end + # Returns a new ipaddr greater than the original address by offset + def +(offset) + self.clone.set(@addr + offset, @family) + end + + # Returns a new ipaddr less than the original address by offset + def -(offset) + self.clone.set(@addr - offset, @family) + end + # Returns true if two ipaddrs are equal. def ==(other) other = coerce_other(other) @@ -343,7 +354,7 @@ class IPAddr _ipv4_compat? end - def _ipv4_compat? + def _ipv4_compat? # :nodoc: if !ipv6? || (@addr >> 32) != 0 return false end @@ -357,7 +368,7 @@ class IPAddr # into an IPv4-mapped IPv6 address. def ipv4_mapped if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) @@ -369,9 +380,11 @@ class IPAddr def ipv4_compat warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end - return self.clone.set(@addr, Socket::AF_INET6) + clone = self.clone.set(@addr, Socket::AF_INET6) + clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) + clone end # Returns a new ipaddr built by converting the IPv6 address into a @@ -400,7 +413,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC3172. def ip6_arpa if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.arpa" end @@ -408,7 +421,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC1886. def ip6_int if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.int" end @@ -533,6 +546,7 @@ class IPAddr end protected + # :stopdoc: def begin_addr @addr & @mask_addr @@ -548,6 +562,7 @@ class IPAddr raise AddressFamilyError, "unsupported address family" end end + #:startdoc: # Set +@addr+, the internal stored ip address, to given +addr+. The # parameter +addr+ is validated using the first +family+ member, @@ -689,6 +704,7 @@ class IPAddr end end + # :stopdoc: def coerce_other(other) case other when IPAddr @@ -709,8 +725,8 @@ class IPAddr octets = addr.split('.') end octets.inject(0) { |i, s| - (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{@addr}" - (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}" + (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{addr}" + (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{addr}" i << 8 | n } end @@ -727,19 +743,19 @@ class IPAddr right = '' when RE_IPV6ADDRLIKE_COMPRESSED if $4 - left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}" + left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{left}" addr = in_addr($~[4,4]) left = $1 right = $3 + '0:0' else left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" left = $1 right = $2 addr = 0 end else - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" end l = left.split(':') r = right.split(':') @@ -800,7 +816,7 @@ unless Socket.const_defined? :AF_INET6 class << IPSocket private - def valid_v6?(addr) + def valid_v6?(addr) # :nodoc: case addr when IPAddr::RE_IPV6ADDRLIKE_FULL if $2 diff --git a/lib/mkmf.rb b/lib/mkmf.rb index 6f11073eb4..38a5a15fb5 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -40,7 +40,7 @@ class Array # :nodoc: end ## -# mkmf.rb is used by Ruby C extensions to generate a Makefile which will +# \Module \MakeMakefile is used by Ruby C extensions to generate a Makefile which will # correctly compile and link the C extension to Ruby and a third-party # library. module MakeMakefile @@ -419,7 +419,7 @@ MESSAGE # disable ASAN leak reporting - conftest programs almost always don't bother # to free their memory. - envs['ASAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('ASAN_OPTIONS') + envs['LSAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('LSAN_OPTIONS') return envs, expand[commands] end @@ -860,7 +860,7 @@ int main() {printf("%"PRI_CONFTEST_PREFIX"#{neg ? 'd' : 'u'}\\n", conftest_const v } unless strvars.empty? - prepare << "char " << strvars.map {|v| "#{v}[1024]"}.join(", ") << "; " + prepare << "char " << strvars.map {|v| %[#{v}[1024] = ""]}.join(", ") << "; " end when nil call = "" @@ -2594,7 +2594,7 @@ static: #{$extmk && !$static ? "all" : %[$(STATIC_LIB)#{$extout ? " install-rb" dest = "#{dir}/#{File.basename(f)}" mfile.print("do-install-rb#{sfx}: #{dest}\n") mfile.print("#{dest}: #{f} #{timestamp_file(dir, target_prefix)}\n") - mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $(@D)\n") + mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $@\n") if defined?($installed_list) and !$extout mfile.print("\t@echo #{dest}>>$(INSTALLED_LIST)\n") end diff --git a/lib/net/http.rb b/lib/net/http.rb index 797de6aaf5..98d6793aee 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -724,7 +724,7 @@ module Net #:nodoc: class HTTP < Protocol # :stopdoc: - VERSION = "0.6.0" + VERSION = "0.9.1" HTTPVersion = '1.1' begin require 'zlib' @@ -1179,6 +1179,7 @@ module Net #:nodoc: @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1321,6 +1322,9 @@ module Net #:nodoc: # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + + # Sets whether the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1527,9 +1531,9 @@ module Net #:nodoc: :verify_depth, :verify_mode, :verify_hostname, - ] # :nodoc: + ].freeze # :nodoc: - SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym } # :nodoc: + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file @@ -1632,6 +1636,21 @@ module Net #:nodoc: self end + # Finishes the \HTTP session: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1654,14 +1673,15 @@ module Net #:nodoc: end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = timeouted_connect(conn_addr, conn_port) + rescue => e + if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + e = Net::OpenTimeout.new(e) end - } + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? @@ -1754,23 +1774,30 @@ module Net #:nodoc: end private :connect - def on_connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) end - private :on_connect + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT - # Finishes the \HTTP session: - # - # http = Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish + def timeouted_connect(conn_addr, conn_port) + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + end + private :timeouted_connect + + def on_connect end + private :on_connect def do_finish @started = false @@ -1821,6 +1848,8 @@ module Net #:nodoc: } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1915,9 +1944,11 @@ module Net #:nodoc: alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) - require 'cgi/util' + require 'cgi/escape' + require 'cgi/util' unless defined?(CGI::EscapeExt) CGI.unescape(value) end @@ -1942,6 +1973,7 @@ module Net #:nodoc: path end end + # :startdoc: # # HTTP operations @@ -2396,7 +2428,9 @@ module Net #:nodoc: res end - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + # :stopdoc: + + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc: def transport_request(req) count = 0 @@ -2553,7 +2587,7 @@ module Net #:nodoc: alias_method :D, :debug end - # for backward compatibility until Ruby 3.5 + # for backward compatibility until Ruby 4.0 # https://bugs.ruby-lang.org/issues/20900 # https://github.com/bblimke/webmock/pull/1081 HTTPSession = HTTP diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index ceec8f7b0a..4342cfc0ef 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Net # Net::HTTP exception class. # You cannot use Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ module Net alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index 44e329a0c8..5b01ea4abd 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -19,16 +19,13 @@ class Net::HTTPGenericRequest if URI === uri_or_path then raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path - hostname = uri_or_path.hostname + hostname = uri_or_path.host raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup - host = @uri.hostname.dup - host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil - host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup @@ -51,7 +48,7 @@ class Net::HTTPGenericRequest initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= host if host + self['Host'] ||= @uri.authority if @uri @body = nil @body_stream = nil @body_data = nil @@ -102,6 +99,31 @@ class Net::HTTPGenericRequest "\#<#{self.class} #{@method}>" end + # Returns a string representation of the request with the details for pp: + # + # require 'pp' + # post = Net::HTTP::Post.new(uri) + # post.inspect # => "#<Net::HTTP::Post POST>" + # post.pretty_inspect + # # => #<Net::HTTP::Post + # POST + # path="/" + # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], + # "accept" => ["*/*"], + # "user-agent" => ["Ruby"], + # "host" => ["www.ruby-lang.org"]}> + # + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text @method + q.breakable + q.text "path="; q.pp @path + q.breakable + q.text "headers="; q.pp to_hash + } + end + ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. @@ -220,7 +242,7 @@ class Net::HTTPGenericRequest end if host = self['host'] - host.sub!(/:.*/m, '') + host = URI.parse("//#{host}").host # Remove a port component from the existing Host header elsif host = @uri.host else host = addr @@ -239,6 +261,8 @@ class Net::HTTPGenericRequest private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock @@ -260,7 +284,6 @@ class Net::HTTPGenericRequest def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body @@ -271,7 +294,6 @@ class Net::HTTPGenericRequest raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? @@ -373,12 +395,6 @@ class Net::HTTPGenericRequest buf.clear end - def supply_default_content_type - return if content_type() - warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE - set_content_type 'application/x-www-form-urlencoded' - end - ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. @@ -411,4 +427,3 @@ class Net::HTTPGenericRequest end end - diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index f6c36f1b5e..5dcdcc7d74 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ module Net::HTTPHeader end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ module Net::HTTPHeader ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,7 +494,7 @@ module Net::HTTPHeader alias canonical_each each_capitalized - def capitalize(name) + def capitalize(name) # :nodoc: name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ module Net::HTTPHeader @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ module Net::HTTPHeader false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/net/http/net-http.gemspec b/lib/net/http/net-http.gemspec index 70528d58cb..80e94c7bb6 100644 --- a/lib/net/http/net-http.gemspec +++ b/lib/net/http/net-http.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.summary = %q{HTTP client api for Ruby.} spec.description = %q{HTTP client api for Ruby.} spec.homepage = "https://github.com/ruby/net-http" - spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") + spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") spec.licenses = ["Ruby", "BSD-2-Clause"] spec.metadata["changelog_uri"] = spec.homepage + "/releases" @@ -30,11 +30,10 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.bindir = "exe" spec.require_paths = ["lib"] - spec.add_dependency "uri" + spec.add_dependency "uri", ">= 0.11.1" end diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index e58057adf1..939d413f91 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Net::HTTP#get: sends +GET+ request, returns response object. # class Net::HTTP::Get < Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ end # - Net::HTTP#head: sends +HEAD+ request, returns response object. # class Net::HTTP::Head < Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ end # - Net::HTTP#post: sends +POST+ request, returns response object. # class Net::HTTP::Post < Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -130,6 +133,7 @@ end # - Net::HTTP#put: sends +PUT+ request, returns response object. # class Net::HTTP::Put < Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -162,6 +166,7 @@ end # - Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Net::HTTP::Delete < Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -193,6 +198,7 @@ end # - Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Net::HTTP::Options < Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -224,6 +230,7 @@ end # - Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Net::HTTP::Trace < Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -258,6 +265,7 @@ end # - Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Net::HTTP::Patch < Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -285,6 +293,7 @@ end # - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Net::HTTP::Propfind < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -308,6 +317,7 @@ end # - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Net::HTTP::Proppatch < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -331,6 +341,7 @@ end # - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Net::HTTP::Mkcol < Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -354,6 +365,7 @@ end # - Net::HTTP#copy: sends +COPY+ request, returns response object. # class Net::HTTP::Copy < Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -377,6 +389,7 @@ end # - Net::HTTP#move: sends +MOVE+ request, returns response object. # class Net::HTTP::Move < Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -400,6 +413,7 @@ end # - Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Net::HTTP::Lock < Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -423,8 +437,8 @@ end # - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Net::HTTP::Unlock < Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index 40de963868..8804a99c9e 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -153,6 +153,7 @@ class Net::HTTPResponse end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ class Net::HTTPResponse # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 6f6fb8d055..941a6fed80 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -4,7 +4,9 @@ module Net + # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +21,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +37,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +53,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +68,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +83,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +101,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +119,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +136,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +155,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +173,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +191,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +209,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +229,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +247,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +266,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +285,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +303,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +323,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +341,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +359,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +378,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +396,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +414,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +432,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +449,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +467,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +484,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +501,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +518,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +535,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +553,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +570,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +587,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +605,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +622,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +639,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +657,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +675,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +693,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +711,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +728,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +746,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +765,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +782,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +800,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +821,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +838,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +854,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +871,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +891,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +908,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +925,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +943,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +961,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +982,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +1000,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1018,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1036,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1054,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1072,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1089,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1106,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1123,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1141,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,19 +1158,21 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Net::HTTPInformation, '2' => Net::HTTPSuccess, '3' => Net::HTTPRedirection, '4' => Net::HTTPClientError, '5' => Net::HTTPServerError - } + }.freeze CODE_TO_OBJ = { '100' => Net::HTTPContinue, '101' => Net::HTTPSwitchProtocol, @@ -1170,5 +1238,5 @@ class Net::HTTPResponse '508' => Net::HTTPLoopDetected, '510' => Net::HTTPNotExtended, '511' => Net::HTTPNetworkAuthenticationRequired, - } + }.freeze end diff --git a/lib/net/net-protocol.gemspec b/lib/net/net-protocol.gemspec index f9fd83f12b..2d911a966c 100644 --- a/lib/net/net-protocol.gemspec +++ b/lib/net/net-protocol.gemspec @@ -25,9 +25,8 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.require_paths = ["lib"] spec.add_dependency "timeout" diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 197ea09089..8c81298c0e 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -54,9 +54,20 @@ module Net # :nodoc: s.connect end end + + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) + end + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT end + # :stopdoc: class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end class ProtoFatalError < ProtocolError; end @@ -66,6 +77,7 @@ module Net # :nodoc: class ProtoCommandError < ProtocolError; end class ProtoRetriableError < ProtocolError; end ProtocRetryError = ProtoRetriableError + # :startdoc: ## # OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot @@ -78,6 +90,7 @@ module Net # :nodoc: # response cannot be read within the read_timeout. class ReadTimeout < Timeout::Error + # :stopdoc: def initialize(io = nil) @io = io end @@ -97,6 +110,7 @@ module Net # :nodoc: # response cannot be written within the write_timeout. Not raised on Windows. class WriteTimeout < Timeout::Error + # :stopdoc: def initialize(io = nil) @io = io end @@ -484,6 +498,7 @@ module Net # :nodoc: # The writer adapter class # class WriteAdapter + # :stopdoc: def initialize(writer) @writer = writer end diff --git a/lib/open-uri.rb b/lib/open-uri.rb index de710af261..5983c7368b 100644 --- a/lib/open-uri.rb +++ b/lib/open-uri.rb @@ -91,8 +91,10 @@ end module OpenURI + # The version string VERSION = "0.5.0" + # The default options Options = { :proxy => true, :proxy_http_basic_authentication => true, @@ -394,24 +396,28 @@ module OpenURI end end + # Raised on HTTP session failure class HTTPError < StandardError - def initialize(message, io) + def initialize(message, io) # :nodoc: super(message) @io = io end + # StringIO having the received data attr_reader :io end # Raised on redirection, # only occurs when +redirect+ option for HTTP is +false+. class HTTPRedirect < HTTPError - def initialize(message, io, uri) + def initialize(message, io, uri) # :nodoc: super(message, io) @uri = uri end + # URI to redirect attr_reader :uri end + # Raised on too many redirection, class TooManyRedirects < HTTPError end diff --git a/lib/open3/version.rb b/lib/open3/version.rb index bfcec44ccc..322dd71e2a 100644 --- a/lib/open3/version.rb +++ b/lib/open3/version.rb @@ -1,3 +1,4 @@ module Open3 + # The version string VERSION = "0.2.1" end diff --git a/lib/optparse.rb b/lib/optparse.rb index e1069b3505..97178e284b 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -7,6 +7,7 @@ # # See OptionParser for documentation. # +require 'set' unless defined?(Set) #-- # == Developer Documentation (not for RDoc output) @@ -425,7 +426,9 @@ # class OptionParser # The version string - OptionParser::Version = "0.7.0.dev.2" + VERSION = "0.8.1" + # An alias for compatibility + Version = VERSION # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -469,7 +472,6 @@ class OptionParser Completion.candidate(key, icase, pat, &method(:each)) end - public def complete(key, icase = false, pat = nil) candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} if candidates.size == 1 @@ -559,7 +561,7 @@ class OptionParser # Parses +arg+ and returns rest of +arg+ and matched portion to the # argument pattern. Yields when the pattern doesn't match substring. # - def parse_arg(arg) # :nodoc: + private def parse_arg(arg) # :nodoc: pattern or return nil, [arg] unless m = pattern.match(arg) yield(InvalidArgument, arg) @@ -577,14 +579,13 @@ class OptionParser yield(InvalidArgument, arg) # didn't match whole arg return arg[s.length..-1], m end - private :parse_arg # # Parses argument, converts and returns +arg+, +block+ and result of # conversion. Yields at semi-error condition instead of raising an # exception. # - def conv_arg(arg, val = []) # :nodoc: + private def conv_arg(arg, val = []) # :nodoc: v, = *val if conv val = conv.call(*val) @@ -596,7 +597,6 @@ class OptionParser end return arg, block, val end - private :conv_arg # # Produces the summary text. Each line of the summary is yielded to the @@ -880,14 +880,13 @@ class OptionParser # +lopts+:: Long style option list. # +nlopts+:: Negated long style options list. # - def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: + private def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: sopts.each {|o| @short[o] = sw} if sopts lopts.each {|o| @long[o] = sw} if lopts nlopts.each {|o| @long[o] = nsw} if nsw and nlopts used = @short.invert.update(@long.invert) @list.delete_if {|o| Switch === o and !used[o]} end - private :update # # Inserts +switch+ at the head of the list, and associates short, long @@ -1293,7 +1292,15 @@ XXX # to $0. # def program_name - @program_name || File.basename($0, '.*') + @program_name || strip_ext(File.basename($0)) + end + + private def strip_ext(name) # :nodoc: + exts = /#{ + require "rbconfig" + Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) + }\z/o + name.sub(exts, "") end # for experimental cascading :-) @@ -1448,14 +1455,13 @@ XXX # +prv+:: Previously specified argument. # +msg+:: Exception message. # - def notwice(obj, prv, msg) # :nodoc: + private def notwice(obj, prv, msg) # :nodoc: unless !prv or prv == obj raise(ArgumentError, "argument #{msg} given twice: #{obj}", ParseError.filter_backtrace(caller(2))) end obj end - private :notwice SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: @@ -1500,7 +1506,7 @@ XXX case o when Proc, Method block = notwice(o, block, 'block') - when Array, Hash + when Array, Hash, Set if Array === o o, v = o.partition {|v,| Completion.completable?(v)} values = notwice(v, values, 'values') unless v.empty? @@ -1722,7 +1728,7 @@ XXX parse_in_order(argv, setter, **keywords, &nonopt) end - def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: + private def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: opt, arg, val, rest = nil nonopt ||= proc {|a| throw :terminate, a} argv.unshift(arg) if arg = catch(:terminate) { @@ -1813,10 +1819,9 @@ XXX argv end - private :parse_in_order # Calls callback with _val_. - def callback!(cb, max_arity, *args) # :nodoc: + private def callback!(cb, max_arity, *args) # :nodoc: args.compact! if (size = args.size) < max_arity and cb.to_proc.lambda? @@ -1826,7 +1831,6 @@ XXX end cb.call(*args) end - private :callback! # # Parses command line arguments +argv+ in permutation mode and returns @@ -1846,7 +1850,7 @@ XXX # def permute!(argv = default_argv, **keywords) nonopts = [] - order!(argv, **keywords, &nonopts.method(:<<)) + order!(argv, **keywords) {|nonopt| nonopts << nonopt} argv[0, 0] = nonopts argv end @@ -1899,13 +1903,16 @@ XXX single_options, *long_options = *args result = {} + setter = (symbolize_names ? + ->(name, val) {result[name.to_sym] = val} + : ->(name, val) {result[name] = val}) single_options.scan(/(.)(:)?/) do |opt, val| if val - result[opt] = nil + setter[opt, nil] define("-#{opt} VAL") else - result[opt] = false + setter[opt, false] define("-#{opt}") end end if single_options @@ -1914,16 +1921,16 @@ XXX arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val - result[opt] = val.empty? ? nil : val + setter[opt, (val unless val.empty?)] define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else - result[opt] = false + setter[opt, false] define("--#{opt}", *[desc].compact) end end - parse_in_order(argv, result.method(:[]=), **keywords) - symbolize_names ? result.transform_keys(&:to_sym) : result + parse_in_order(argv, setter, **keywords) + result end # @@ -1937,24 +1944,22 @@ XXX # Traverses @stack, sending each element method +id+ with +args+ and # +block+. # - def visit(id, *args, &block) # :nodoc: + private def visit(id, *args, &block) # :nodoc: @stack.reverse_each do |el| el.__send__(id, *args, &block) end nil end - private :visit # # Searches +key+ in @stack for +id+ hash and returns or yields the result. # - def search(id, key) # :nodoc: + private def search(id, key) # :nodoc: block_given = block_given? visit(:search, id, key) do |k| return block_given ? yield(k) : k end end - private :search # # Completes shortened long style option switch and returns pair of @@ -1965,7 +1970,7 @@ XXX # +icase+:: Search case insensitive if true. # +pat+:: Optional pattern for completion. # - def complete(typ, opt, icase = false, *pat) # :nodoc: + private def complete(typ, opt, icase = false, *pat) # :nodoc: if pat.empty? search(typ, opt) {|sw| return [sw, opt]} # exact match or... end @@ -1973,9 +1978,8 @@ XXX visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption - raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) + raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end - private :complete # # Returns additional info. @@ -2038,19 +2042,27 @@ XXX def load(filename = nil, **keywords) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil + return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil basename << ".options" + if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty? + # https://specifications.freedesktop.org/basedir-spec/latest/#variables + # + # If $XDG_CONFIG_HOME is either not set or empty, a default + # equal to $HOME/.config should be used. + xdg = ['~/.config', true] + end return [ - # XDG - ENV['XDG_CONFIG_HOME'], - '~/.config', + xdg, + *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), # Haiku - '~/config/settings', - ].any? {|dir| + ['~/config/settings', true], + ].any? {|dir, expand| next if !dir or dir.empty? - load(File.expand_path(basename, dir), **keywords) rescue nil + filename = File.join(dir, basename) + filename = File.expand_path(filename) if expand + load(filename, **keywords) rescue nil } end begin @@ -2256,9 +2268,10 @@ XXX argv end + DIR = File.join(__dir__, '') def self.filter_backtrace(array) unless $DEBUG - array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + array.delete_if {|bt| bt.start_with?(DIR)} end array end @@ -2301,42 +2314,42 @@ XXX # Raises when ambiguously completable string is encountered. # class AmbiguousOption < ParseError - const_set(:Reason, 'ambiguous option') + Reason = 'ambiguous option' # :nodoc: end # # Raises when there is an argument for a switch which takes no argument. # class NeedlessArgument < ParseError - const_set(:Reason, 'needless argument') + Reason = 'needless argument' # :nodoc: end # # Raises when a switch with mandatory argument has no argument. # class MissingArgument < ParseError - const_set(:Reason, 'missing argument') + Reason = 'missing argument' # :nodoc: end # # Raises when switch is undefined. # class InvalidOption < ParseError - const_set(:Reason, 'invalid option') + Reason = 'invalid option' # :nodoc: end # # Raises when the given argument does not match required format. # class InvalidArgument < ParseError - const_set(:Reason, 'invalid argument') + Reason = 'invalid argument' # :nodoc: end # # Raises when the given argument word can't be completed uniquely. # class AmbiguousArgument < InvalidArgument - const_set(:Reason, 'ambiguous argument') + Reason = 'ambiguous argument' # :nodoc: end # @@ -2435,9 +2448,11 @@ XXX # and DecimalNumeric. See Acceptable argument classes (in source code). # module Acceptables - const_set(:DecimalInteger, OptionParser::DecimalInteger) - const_set(:OctalInteger, OptionParser::OctalInteger) - const_set(:DecimalNumeric, OptionParser::DecimalNumeric) + # :stopdoc: + DecimalInteger = OptionParser::DecimalInteger + OctalInteger = OptionParser::OctalInteger + DecimalNumeric = OptionParser::DecimalNumeric + # :startdoc: end end diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index 8589f1857c..885b0ec380 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -3,7 +3,7 @@ name = File.basename(__FILE__, ".gemspec") version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*OptionParser::Version\s*=\s*"(.*)"/ =~ line and break $1 + /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 end rescue nil end @@ -25,8 +25,9 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + - %w[README.md ChangeLog COPYING .document .rdoc_options] + dir, gemspec = File.split(__FILE__) + excludes = %W[#{gemspec} rakelib test/ Gemfile Rakefile .git* .editor*].map {|n| ":^"+n} + spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/pathname.rb b/lib/pathname.rb new file mode 100644 index 0000000000..b19e379cd4 --- /dev/null +++ b/lib/pathname.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +# +# = pathname.rb +# +# Object-Oriented Pathname Class +# +# Author:: Tanaka Akira <akr@m17n.org> +# Documentation:: Author and Gavin Sinclair +# +# For documentation, see class Pathname. +# + +class Pathname # * Find * + # + # Iterates over the directory tree in a depth first manner, yielding a + # Pathname for each file under "this" directory. + # + # Note that you need to require 'pathname' to use this method. + # + # Returns an Enumerator if no block is given. + # + # Since it is implemented by the standard library module Find, Find.prune can + # be used to control the traversal. + # + # If +self+ is +.+, yielded pathnames begin with a filename in the + # current directory, not +./+. + # + # See Find.find + # + def find(ignore_error: true) # :yield: pathname + return to_enum(__method__, ignore_error: ignore_error) unless block_given? + require 'find' + if @path == '.' + Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) } + else + Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) } + end + end +end + + +class Pathname # * FileUtils * + # Recursively deletes a directory, including all directories beneath it. + # + # Note that you need to require 'pathname' to use this method. + # + # See FileUtils.rm_rf + def rmtree(noop: nil, verbose: nil, secure: nil) + # The name "rmtree" is borrowed from File::Path of Perl. + # File::Path provides "mkpath" and "rmtree". + require 'fileutils' + FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure) + self + end +end + +class Pathname # * tmpdir * + # Creates a tmp directory and wraps the returned path in a Pathname object. + # + # Note that you need to require 'pathname' to use this method. + # + # See Dir.mktmpdir + def self.mktmpdir + require 'tmpdir' unless defined?(Dir.mktmpdir) + if block_given? + Dir.mktmpdir do |dir| + dir = self.new(dir) + yield dir + end + else + self.new(Dir.mktmpdir) + end + end +end @@ -64,7 +64,7 @@ require 'prettyprint' class PP < PrettyPrint # The version string - VERSION = "0.6.2" + VERSION = "0.6.3" # Returns the usable width for +out+. # As the width of +out+: @@ -145,21 +145,13 @@ class PP < PrettyPrint # Yields to a block # and preserves the previous set of objects being printed. def guard_inspect_key - if Thread.current[:__recursive_key__] == nil - Thread.current[:__recursive_key__] = {}.compare_by_identity - end - - if Thread.current[:__recursive_key__][:inspect] == nil - Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity - end - - save = Thread.current[:__recursive_key__][:inspect] - + recursive_state = Thread.current[:__recursive_key__] ||= {}.compare_by_identity + save = recursive_state[:inspect] ||= {}.compare_by_identity begin - Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity + recursive_state[:inspect] = {}.compare_by_identity yield ensure - Thread.current[:__recursive_key__][:inspect] = save + recursive_state[:inspect] = save end end @@ -167,9 +159,8 @@ class PP < PrettyPrint # to be pretty printed. Used to break cycles in chains of objects to be # pretty printed. def check_inspect_key(id) - Thread.current[:__recursive_key__] && - Thread.current[:__recursive_key__][:inspect] && - Thread.current[:__recursive_key__][:inspect].include?(id) + recursive_state = Thread.current[:__recursive_key__] or return false + recursive_state[:inspect]&.include?(id) end # Adds the object_id +id+ to the set of objects being pretty printed, so @@ -183,10 +174,10 @@ class PP < PrettyPrint Thread.current[:__recursive_key__][:inspect].delete id end - private def guard_inspect(object) + private def guard_inspect(object) # :nodoc: recursive_state = Thread.current[:__recursive_key__] - if recursive_state && recursive_state.key?(:inspect) + if recursive_state&.key?(:inspect) begin push_inspect_key(object) yield @@ -286,7 +277,7 @@ class PP < PrettyPrint kwsplat ? yield(*v, **kwsplat) : yield(*v) } end - EMPTY_KWHASH = if RUBY_VERSION >= "3.0" + EMPTY_KWHASH = if RUBY_VERSION >= "3.0" # :nodoc: {}.freeze end private_constant :EMPTY_KWHASH @@ -322,12 +313,10 @@ class PP < PrettyPrint # A pretty print for a pair of Hash def pp_hash_pair(k, v) if Symbol === k - sym_s = k.inspect - if sym_s[1].match?(/["$@!]/) || sym_s[-1].match?(/[%&*+\-\/<=>@\]^`|~]/) - text "#{k.to_s.inspect}:" - else - text "#{k}:" + if k.inspect.match?(%r[\A:["$@!]|[%&*+\-\/<=>@\]^`|~]\z]) + k = k.to_s.inspect end + text "#{k}:" else pp k text ' ' @@ -399,7 +388,8 @@ class PP < PrettyPrint # This method should return an array of names of instance variables as symbols or strings as: # +[:@a, :@b]+. def pretty_print_instance_variables - instance_variables.sort + ivars = respond_to?(:instance_variables_to_inspect, true) ? instance_variables_to_inspect || instance_variables : instance_variables + ivars.sort end # Is #inspect implementation using #pretty_print. @@ -442,22 +432,27 @@ class Hash # :nodoc: end end +if defined?(Set) + if set_pp = Set.instance_method(:initialize).source_location + set_pp = !set_pp.first.end_with?("/set.rb") # not defined in set.rb + else + set_pp = true # defined in C + end +end class Set # :nodoc: def pretty_print(pp) # :nodoc: - pp.group(1, '#<Set:', '>') { - pp.breakable - pp.group(1, '{', '}') { - pp.seplist(self) { |o| - pp.pp o - } + pp.group(1, "#{self.class.name}[", ']') { + pp.seplist(self) { |o| + pp.pp o } } end def pretty_print_cycle(pp) # :nodoc: - pp.text sprintf('#<Set: {%s}>', empty? ? '' : '...') + name = self.class.name + pp.text(empty? ? "#{name}[]" : "#{name}[...]") end -end +end if set_pp class << ENV # :nodoc: def pretty_print(q) # :nodoc: @@ -489,6 +484,13 @@ class Struct # :nodoc: end end +verbose, $VERBOSE = $VERBOSE, nil +begin + has_data_define = defined?(Data.define) +ensure + $VERBOSE = verbose +end + class Data # :nodoc: def pretty_print(q) # :nodoc: class_name = PP.mcall(self, Kernel, :class).name @@ -521,7 +523,7 @@ class Data # :nodoc: def pretty_print_cycle(q) # :nodoc: q.text sprintf("#<data %s:...>", PP.mcall(self, Kernel, :class).name) end -end if defined?(Data.define) +end if has_data_define class Range # :nodoc: def pretty_print(q) # :nodoc: diff --git a/lib/prettyprint.rb b/lib/prettyprint.rb index 6f50192f5d..a65407c130 100644 --- a/lib/prettyprint.rb +++ b/lib/prettyprint.rb @@ -33,6 +33,7 @@ # class PrettyPrint + # The version string VERSION = "0.2.0" # This is a convenience method which is same as follows: diff --git a/lib/prism.rb b/lib/prism.rb index eaab5cbfed..dab3420377 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown # The Prism Ruby parser. # @@ -19,7 +20,7 @@ module Prism autoload :DSL, "prism/dsl" autoload :InspectVisitor, "prism/inspect_visitor" autoload :LexCompat, "prism/lex_compat" - autoload :LexRipper, "prism/lex_compat" + autoload :LexRipper, "prism/lex_ripper" autoload :MutationCompiler, "prism/mutation_compiler" autoload :Pack, "prism/pack" autoload :Pattern, "prism/pattern" @@ -36,12 +37,31 @@ module Prism private_constant :LexCompat private_constant :LexRipper + # Raised when requested to parse as the currently running Ruby version but Prism has no support for it. + class CurrentVersionError < ArgumentError + # Initialize a new exception for the given ruby version string. + def initialize(version) + message = +"invalid version: Requested to parse as `version: 'current'`; " + segments = + if version.match?(/\A\d+\.\d+.\d+\z/) + version.split(".").map(&:to_i) + end + + if segments && ((segments[0] < 3) || (segments[0] == 3 && segments[1] < 3)) + message << " #{version} is below the minimum supported syntax." + else + message << " #{version} is unknown. Please update the `prism` gem." + end + + super(message) + end + end + # :call-seq: # Prism::lex_compat(source, **options) -> LexCompat::Result # # Returns a parse result whose value is an array of tokens that closely - # resembles the return value of Ripper::lex. The main difference is that the - # `:on_sp` token is not emitted. + # resembles the return value of Ripper::lex. # # For supported options, see Prism::parse. def self.lex_compat(source, **options) @@ -51,9 +71,8 @@ module Prism # :call-seq: # Prism::lex_ripper(source) -> Array # - # This lexes with the Ripper lex. It drops any space events but otherwise - # returns the same tokens. Raises SyntaxError if the syntax in source is - # invalid. + # This wraps the result of Ripper.lex. It produces almost exactly the + # same tokens. Raises SyntaxError if the syntax in source is invalid. def self.lex_ripper(source) LexRipper.new(source).result # steep:ignore end diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb index e3b15fc3b0..5d7d38d841 100644 --- a/lib/prism/desugar_compiler.rb +++ b/lib/prism/desugar_compiler.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class DesugarAndWriteNode # :nodoc: diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index a0da0b6195..d4c9d60c9a 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore # This file is responsible for mirroring the API provided by the C extension by @@ -85,6 +86,7 @@ module Prism end callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer + callback :pm_parse_stream_feof_t, [:pointer], :int enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY] enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE] @@ -100,7 +102,7 @@ module Prism "pm_string_query_local", "pm_string_query_constant", "pm_string_query_method_name", - [:pm_parse_stream_fgets_t] + [:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t] ) load_exported_functions_from( @@ -280,12 +282,14 @@ module Prism end } + eof_callback = -> (_) { stream.eof? } + # In the pm_serialize_parse_stream function it accepts a pointer to the # IO object as a void* and then passes it through to the callback as the # third argument, but it never touches it itself. As such, since we have # access to the IO object already through the closure of the lambda, we # can pass a null pointer here and not worry. - LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options)) + LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, eof_callback, dump_options(options)) Prism.load(source, buffer.read, options.fetch(:freeze, false)) end end @@ -419,17 +423,25 @@ module Prism # Return the value that should be dumped for the version option. def dump_options_version(version) - case version + current = version == "current" + + case current ? RUBY_VERSION : version when nil, "latest" - 0 + 0 # Handled in pm_parser_init when /\A3\.3(\.\d+)?\z/ 1 when /\A3\.4(\.\d+)?\z/ 2 - when /\A3\.5(\.\d+)?\z/ - 0 + when /\A3\.5(\.\d+)?\z/, /\A4\.0(\.\d+)?\z/ + 3 + when /\A4\.1(\.\d+)?\z/ + 4 else - raise ArgumentError, "invalid version: #{version}" + if current + raise CurrentVersionError, RUBY_VERSION + else + raise ArgumentError, "invalid version: #{version}" + end end end diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index a83c24cb41..597e63c73e 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# :markup: markdown require "delegate" -require "ripper" module Prism # This class is responsible for lexing the source using prism and then @@ -225,16 +225,8 @@ module Prism end end - # Ripper doesn't include the rest of the token in the event, so we need to - # trim it down to just the content on the first line when comparing. - class EndContentToken < Token - def ==(other) # :nodoc: - [self[0], self[1], self[2][0..self[2].index("\n")], self[3]] == other - end - end - # Tokens where state should be ignored - # used for :on_comment, :on_heredoc_end, :on_embexpr_end + # used for :on_sp, :on_comment, :on_heredoc_end, :on_embexpr_end class IgnoreStateToken < Token def ==(other) # :nodoc: self[0...-1] == other[0...-1] @@ -248,8 +240,8 @@ module Prism class IdentToken < Token def ==(other) # :nodoc: (self[0...-1] == other[0...-1]) && ( - (other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) || - (other[3] & Ripper::EXPR_ARG_ANY != 0) + (other[3] == Translation::Ripper::EXPR_LABEL | Translation::Ripper::EXPR_END) || + (other[3] & (Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_CMDARG) != 0) ) end end @@ -260,8 +252,8 @@ module Prism def ==(other) # :nodoc: return false unless self[0...-1] == other[0...-1] - if self[3] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED - other[3] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED != 0 + if self[3] == Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED + other[3] & Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED != 0 else self[3] == other[3] end @@ -279,8 +271,8 @@ module Prism class ParamToken < Token def ==(other) # :nodoc: (self[0...-1] == other[0...-1]) && ( - (other[3] == Ripper::EXPR_END) || - (other[3] == Ripper::EXPR_END | Ripper::EXPR_LABEL) + (other[3] == Translation::Ripper::EXPR_END) || + (other[3] == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL) ) end end @@ -614,10 +606,15 @@ module Prism private_constant :Heredoc - attr_reader :source, :options + # In previous versions of Ruby, Ripper wouldn't flush the bom before the + # first token, so we had to have a hack in place to account for that. + BOM_FLUSHED = RUBY_VERSION >= "3.3.0" + private_constant :BOM_FLUSHED + + attr_reader :options - def initialize(source, **options) - @source = source + def initialize(code, **options) + @code = code @options = options end @@ -627,16 +624,14 @@ module Prism state = :default heredoc_stack = [[]] #: Array[Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]] - result = Prism.lex(source, **options) + result = Prism.lex(@code, **options) + source = result.source result_value = result.value - previous_state = nil #: Ripper::Lexer::State? + previous_state = nil #: State? last_heredoc_end = nil #: Integer? + eof_token = nil - # In previous versions of Ruby, Ripper wouldn't flush the bom before the - # first token, so we had to have a hack in place to account for that. This - # checks for that behavior. - bom_flushed = Ripper.lex("\xEF\xBB\xBF# test")[0][0][1] == 0 - bom = source.byteslice(0..2) == "\xEF\xBB\xBF" + bom = source.slice(0, 3) == "\xEF\xBB\xBF" result_value.each_with_index do |(token, lex_state), index| lineno = token.location.start_line @@ -650,7 +645,7 @@ module Prism if bom && lineno == 1 column -= 3 - if index == 0 && column == 0 && !bom_flushed + if index == 0 && column == 0 && !BOM_FLUSHED flushed = case token.type when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE, @@ -674,12 +669,15 @@ module Prism event = RIPPER.fetch(token.type) value = token.value - lex_state = Ripper::Lexer::State.new(lex_state) + lex_state = Translation::Ripper::Lexer::State.cached(lex_state) token = case event when :on___end__ - EndContentToken.new([[lineno, column], event, value, lex_state]) + # Ripper doesn't include the rest of the token in the event, so we need to + # trim it down to just the content on the first line. + value = value[0..value.index("\n")] + Token.new([[lineno, column], event, value, lex_state]) when :on_comment IgnoreStateToken.new([[lineno, column], event, value, lex_state]) when :on_heredoc_end @@ -688,7 +686,7 @@ module Prism last_heredoc_end = token.location.end_offset IgnoreStateToken.new([[lineno, column], event, value, lex_state]) when :on_ident - if lex_state == Ripper::EXPR_END + if lex_state == Translation::Ripper::EXPR_END # If we have an identifier that follows a method name like: # # def foo bar @@ -698,7 +696,7 @@ module Prism # yet. We do this more accurately, so we need to allow comparing # against both END and END|LABEL. ParamToken.new([[lineno, column], event, value, lex_state]) - elsif lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL + elsif lex_state == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL # In the event that we're comparing identifiers, we're going to # allow a little divergence. Ripper doesn't account for local # variables introduced through named captures in regexes, and we @@ -738,13 +736,14 @@ module Prism counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0 end - Ripper::Lexer::State.new(result_value[current_index][1]) + Translation::Ripper::Lexer::State.cached(result_value[current_index][1]) else previous_state end Token.new([[lineno, column], event, value, lex_state]) when :on_eof + eof_token = token previous_token = result_value[index - 1][0] # If we're at the end of the file and the previous token was a @@ -767,7 +766,7 @@ module Prism end_offset += 3 end - tokens << Token.new([[lineno, 0], :on_nl, source.byteslice(start_offset...end_offset), lex_state]) + tokens << Token.new([[lineno, 0], :on_nl, source.slice(start_offset, end_offset - start_offset), lex_state]) end end @@ -861,67 +860,91 @@ module Prism # We sort by location to compare against Ripper's output tokens.sort_by!(&:location) - Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, Source.for(source)) - end - end - - private_constant :LexCompat - - # This is a class that wraps the Ripper lexer to produce almost exactly the - # same tokens. - class LexRipper # :nodoc: - attr_reader :source + # Add :on_sp tokens + tokens = add_on_sp_tokens(tokens, source, result.data_loc, bom, eof_token) - def initialize(source) - @source = source + Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, source) end - def result - previous = [] #: [[Integer, Integer], Symbol, String, untyped] | [] - results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]] - - lex(source).each do |token| - case token[1] - when :on_sp - # skip - when :on_tstring_content - if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@")) - previous[2] << token[2] - else - results << token - previous = token - end - when :on_words_sep - if previous[1] == :on_words_sep - previous[2] << token[2] + def add_on_sp_tokens(tokens, source, data_loc, bom, eof_token) + new_tokens = [] + + prev_token_state = Translation::Ripper::Lexer::State.cached(Translation::Ripper::EXPR_BEG) + prev_token_end = bom ? 3 : 0 + + tokens.each do |token| + line, column = token.location + start_offset = source.line_to_byte_offset(line) + column + # Ripper reports columns on line 1 without counting the BOM, so we adjust to get the real offset + start_offset += 3 if line == 1 && bom + + if start_offset > prev_token_end + sp_value = source.slice(prev_token_end, start_offset - prev_token_end) + sp_line = source.line(prev_token_end) + sp_column = source.column(prev_token_end) + # Ripper reports columns on line 1 without counting the BOM + sp_column -= 3 if sp_line == 1 && bom + continuation_index = sp_value.byteindex("\\") + + # ripper emits up to three :on_sp tokens when line continuations are used + if continuation_index + next_whitespace_index = continuation_index + 1 + next_whitespace_index += 1 if sp_value.byteslice(next_whitespace_index) == "\r" + next_whitespace_index += 1 + first_whitespace = sp_value[0...continuation_index] + continuation = sp_value[continuation_index...next_whitespace_index] + second_whitespace = sp_value[next_whitespace_index..] + + new_tokens << IgnoreStateToken.new([ + [sp_line, sp_column], + :on_sp, + first_whitespace, + prev_token_state + ]) unless first_whitespace.empty? + + new_tokens << IgnoreStateToken.new([ + [sp_line, sp_column + continuation_index], + :on_sp, + continuation, + prev_token_state + ]) + + new_tokens << IgnoreStateToken.new([ + [sp_line + 1, 0], + :on_sp, + second_whitespace, + prev_token_state + ]) unless second_whitespace.empty? else - results << token - previous = token + new_tokens << IgnoreStateToken.new([ + [sp_line, sp_column], + :on_sp, + sp_value, + prev_token_state + ]) end - else - results << token - previous = token end - end - results - end - - private - - if Ripper.method(:lex).parameters.assoc(:keyrest) - def lex(source) - Ripper.lex(source, raise_errors: true) + new_tokens << token + prev_token_state = token.state + prev_token_end = start_offset + token.value.bytesize end - else - def lex(source) - ripper = Ripper::Lexer.new(source) - ripper.lex.tap do |result| - raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? + + unless data_loc # no trailing :on_sp with __END__ as it is always preceded by :on_nl + end_offset = eof_token.location.end_offset + if prev_token_end < end_offset + new_tokens << IgnoreStateToken.new([ + [source.line(prev_token_end), source.column(prev_token_end)], + :on_sp, + source.slice(prev_token_end, end_offset - prev_token_end), + prev_token_state + ]) end end + + new_tokens end end - private_constant :LexRipper + private_constant :LexCompat end diff --git a/lib/prism/lex_ripper.rb b/lib/prism/lex_ripper.rb new file mode 100644 index 0000000000..2054cf55ac --- /dev/null +++ b/lib/prism/lex_ripper.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +# :markup: markdown + +require "ripper" + +module Prism + # This is a class that wraps the Ripper lexer to produce almost exactly the + # same tokens. + class LexRipper # :nodoc: + attr_reader :source + + def initialize(source) + @source = source + end + + def result + previous = [] #: [[Integer, Integer], Symbol, String, untyped] | [] + results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]] + + lex(source).each do |token| + case token[1] + when :on_tstring_content + if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@")) + previous[2] << token[2] + else + results << token + previous = token + end + when :on_words_sep + if previous[1] == :on_words_sep + previous[2] << token[2] + else + results << token + previous = token + end + else + results << token + previous = token + end + end + + results + end + + private + + if Ripper.method(:lex).parameters.assoc(:keyrest) + def lex(source) + Ripper.lex(source, raise_errors: true) + end + else + def lex(source) + ripper = Ripper::Lexer.new(source) + ripper.lex.tap do |result| + raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? + end + end + end + end + + private_constant :LexRipper +end diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index b007a051ea..469e54ca0c 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true +# :markup: markdown +#-- # Here we are reopening the prism module to provide methods on nodes that aren't # templated and are meant as convenience methods. +#++ module Prism class Node def deprecated(*replacements) # :nodoc: diff --git a/lib/prism/pack.rb b/lib/prism/pack.rb index c0de8ab8b7..166c04c3c0 100644 --- a/lib/prism/pack.rb +++ b/lib/prism/pack.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore +# module Prism # A parser for the pack template language. module Pack diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 9a3e7c5b79..12d19da562 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # This represents a source of Ruby code that has been parsed. It is used in @@ -75,6 +76,15 @@ module Prism source.byteslice(byte_offset, length) or raise end + # Converts the line number to a byte offset corresponding to the start of that line + def line_to_byte_offset(line) + l = line - @start_line + if l < 0 || l >= offsets.size + raise ArgumentError, "line #{line} is out of range" + end + offsets[l] + end + # Binary search through the offsets to find the line number for the given # byte offset. def line(byte_offset) @@ -154,21 +164,8 @@ module Prism # Binary search through the offsets to find the line number for the given # byte offset. def find_line(byte_offset) - left = 0 - right = offsets.length - 1 - - while left <= right - mid = left + (right - left) / 2 - return mid if (offset = offsets[mid]) == byte_offset - - if offset < byte_offset - left = mid + 1 - else - right = mid - 1 - end - end - - left - 1 + index = offsets.bsearch_index { |offset| offset > byte_offset } || offsets.length + index - 1 end end diff --git a/lib/prism/parse_result/comments.rb b/lib/prism/parse_result/comments.rb index 22c4148b2c..3e93316aff 100644 --- a/lib/prism/parse_result/comments.rb +++ b/lib/prism/parse_result/comments.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class ParseResult < Result diff --git a/lib/prism/parse_result/errors.rb b/lib/prism/parse_result/errors.rb index eb4f317248..26c376b3ce 100644 --- a/lib/prism/parse_result/errors.rb +++ b/lib/prism/parse_result/errors.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require "stringio" diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb index 37f64f8ae2..e7fd62cafe 100644 --- a/lib/prism/parse_result/newlines.rb +++ b/lib/prism/parse_result/newlines.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class ParseResult < Result diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb index 03fec26789..6ad2d9e5b9 100644 --- a/lib/prism/pattern.rb +++ b/lib/prism/pattern.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # A pattern is an object that wraps a Ruby pattern matching expression. The diff --git a/lib/prism/polyfill/scan_byte.rb b/lib/prism/polyfill/scan_byte.rb new file mode 100644 index 0000000000..9276e509fc --- /dev/null +++ b/lib/prism/polyfill/scan_byte.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "strscan" + +# Polyfill for StringScanner#scan_byte, which didn't exist until Ruby 3.4. +if !(StringScanner.method_defined?(:scan_byte)) + StringScanner.include( + Module.new { + def scan_byte # :nodoc: + get_byte&.b&.ord + end + } + ) +end diff --git a/lib/prism/polyfill/warn.rb b/lib/prism/polyfill/warn.rb index 560380d308..76a4264623 100644 --- a/lib/prism/polyfill/warn.rb +++ b/lib/prism/polyfill/warn.rb @@ -7,17 +7,14 @@ if (method = Kernel.instance_method(:warn)).respond_to?(:parameters) ? method.pa Kernel.prepend( Module.new { def warn(*msgs, uplevel: nil, category: nil) # :nodoc: - uplevel = - case uplevel - when nil - 1 - when Integer - uplevel + 1 - else - uplevel.to_int + 1 - end - - super(*msgs, uplevel: uplevel) + case uplevel + when nil + super(*msgs) + when Integer + super(*msgs, uplevel: uplevel + 1) + else + super(*msgs, uplevel: uplevel.to_int + 1) + end end } ) @@ -25,17 +22,14 @@ if (method = Kernel.instance_method(:warn)).respond_to?(:parameters) ? method.pa Object.prepend( Module.new { def warn(*msgs, uplevel: nil, category: nil) # :nodoc: - uplevel = - case uplevel - when nil - 1 - when Integer - uplevel + 1 - else - uplevel.to_int + 1 - end - - super(*msgs, uplevel: uplevel) + case uplevel + when nil + super(*msgs) + when Integer + super(*msgs, uplevel: uplevel + 1) + else + super(*msgs, uplevel: uplevel.to_int + 1) + end end } ) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 5cb5a98057..283c7b04aa 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.4.0" + spec.version = "1.8.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] @@ -77,6 +77,7 @@ Gem::Specification.new do |spec| "lib/prism/ffi.rb", "lib/prism/inspect_visitor.rb", "lib/prism/lex_compat.rb", + "lib/prism/lex_ripper.rb", "lib/prism/mutation_compiler.rb", "lib/prism/node_ext.rb", "lib/prism/node.rb", @@ -88,6 +89,7 @@ Gem::Specification.new do |spec| "lib/prism/pattern.rb", "lib/prism/polyfill/append_as_bytes.rb", "lib/prism/polyfill/byteindex.rb", + "lib/prism/polyfill/scan_byte.rb", "lib/prism/polyfill/unpack1.rb", "lib/prism/polyfill/warn.rb", "lib/prism/reflection.rb", @@ -97,13 +99,13 @@ Gem::Specification.new do |spec| "lib/prism/translation.rb", "lib/prism/translation/parser.rb", "lib/prism/translation/parser_current.rb", - "lib/prism/translation/parser33.rb", - "lib/prism/translation/parser34.rb", - "lib/prism/translation/parser35.rb", + "lib/prism/translation/parser_versions.rb", "lib/prism/translation/parser/builder.rb", "lib/prism/translation/parser/compiler.rb", "lib/prism/translation/parser/lexer.rb", "lib/prism/translation/ripper.rb", + "lib/prism/translation/ripper/filter.rb", + "lib/prism/translation/ripper/lexer.rb", "lib/prism/translation/ripper/sexp.rb", "lib/prism/translation/ripper/shim.rb", "lib/prism/translation/ruby_parser.rb", @@ -119,9 +121,7 @@ Gem::Specification.new do |spec| "rbi/prism/reflection.rbi", "rbi/prism/string_query.rbi", "rbi/prism/translation/parser.rbi", - "rbi/prism/translation/parser33.rbi", - "rbi/prism/translation/parser34.rbi", - "rbi/prism/translation/parser35.rbi", + "rbi/prism/translation/parser_versions.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/visitor.rbi", "sig/prism.rbs", diff --git a/lib/prism/relocation.rb b/lib/prism/relocation.rb index 163d2012c5..3e9210a785 100644 --- a/lib/prism/relocation.rb +++ b/lib/prism/relocation.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # Prism parses deterministically for the same input. This provides a nice diff --git a/lib/prism/string_query.rb b/lib/prism/string_query.rb index 9011051d2b..547f58d2fa 100644 --- a/lib/prism/string_query.rb +++ b/lib/prism/string_query.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # Query methods that allow categorizing strings based on their context for diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index 511c80febc..57b57135bc 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # This module is responsible for converting the prism syntax tree into other @@ -6,9 +7,11 @@ module Prism module Translation # steep:ignore autoload :Parser, "prism/translation/parser" autoload :ParserCurrent, "prism/translation/parser_current" - autoload :Parser33, "prism/translation/parser33" - autoload :Parser34, "prism/translation/parser34" - autoload :Parser35, "prism/translation/parser35" + autoload :Parser33, "prism/translation/parser_versions" + autoload :Parser34, "prism/translation/parser_versions" + autoload :Parser35, "prism/translation/parser_versions" + autoload :Parser40, "prism/translation/parser_versions" + autoload :Parser41, "prism/translation/parser_versions" autoload :Ripper, "prism/translation/ripper" autoload :RubyParser, "prism/translation/ruby_parser" end diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index d43ad7c1e4..fed4ac4cd1 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown begin required_version = ">= 3.3.7.2" @@ -18,6 +19,13 @@ module Prism # whitequark/parser gem's syntax tree. It inherits from the base parser for # the parser gem, and overrides the parse* methods to parse with prism and # then translate. + # + # Note that this version of the parser always parses using the latest + # version of Ruby syntax supported by Prism. If you want specific version + # support, use one of the version-specific subclasses, such as + # `Prism::Translation::Parser34`. If you want to parse using the same + # version of Ruby syntax as the currently running version of Ruby, use + # `Prism::Translation::ParserCurrent`. class Parser < ::Parser::Base Diagnostic = ::Parser::Diagnostic # :nodoc: private_constant :Diagnostic @@ -76,7 +84,7 @@ module Prism end def version # :nodoc: - 34 + 41 end # The default encoding for Ruby files is UTF-8. @@ -348,8 +356,10 @@ module Prism "3.3.1" when 34 "3.4.0" - when 35 - "3.5.0" + when 35, 40 + "4.0.0" + when 41 + "4.1.0" else "latest" end diff --git a/lib/prism/translation/parser/builder.rb b/lib/prism/translation/parser/builder.rb index d3b51f4275..6b620c25bc 100644 --- a/lib/prism/translation/parser/builder.rb +++ b/lib/prism/translation/parser/builder.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 0bd9d74f93..8805614603 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation @@ -216,7 +217,7 @@ module Prism rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil, token(rescue_clause.operator_loc), visit(rescue_clause.reference), - srange_find(find_start_offset, find_end_offset, ";"), + srange_semicolon(find_start_offset, find_end_offset), visit(rescue_clause.statements) ) end until (rescue_clause = rescue_clause.subsequent).nil? @@ -322,7 +323,7 @@ module Prism visit_all(arguments), token(node.closing_loc), ), - srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, "="), + token(node.equal_loc), visit(node.arguments.arguments.last) ), block @@ -339,7 +340,7 @@ module Prism if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil? builder.assign( builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)), - srange_find(message_loc.end_offset, node.arguments.location.start_offset, "="), + token(node.equal_loc), visit(node.arguments.arguments.last) ) else @@ -788,7 +789,7 @@ module Prism if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), token(node.end_keyword_loc) @@ -920,7 +921,7 @@ module Prism if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), case node.subsequent @@ -986,7 +987,7 @@ module Prism if (then_loc = node.then_loc) token(then_loc) else - srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, ";") + srange_semicolon(node.pattern.location.end_offset, node.statements&.location&.start_offset) end, visit(node.statements) ) @@ -1807,7 +1808,7 @@ module Prism if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset) end, visit(node.else_clause), token(node.else_clause&.else_keyword_loc), @@ -1838,7 +1839,7 @@ module Prism if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) end, visit(node.statements), token(node.closing_loc) @@ -1862,7 +1863,7 @@ module Prism if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, ";") + srange_semicolon(node.conditions.last.location.end_offset, node.statements&.location&.start_offset) end, visit(node.statements) ) @@ -1882,7 +1883,7 @@ module Prism if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) end, visit(node.statements), token(node.closing_loc) @@ -2011,16 +2012,16 @@ module Prism Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) end - # Constructs a new source range by finding the given character between - # the given start offset and end offset. If the needle is not found, it - # returns nil. Importantly it does not search past newlines or comments. + # Constructs a new source range by finding a semicolon between the given + # start offset and end offset. If the semicolon is not found, it returns + # nil. Importantly it does not search past newlines or comments. # # Note that end_offset is allowed to be nil, in which case this will # search until the end of the string. - def srange_find(start_offset, end_offset, character) - if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*#{character}/]) + def srange_semicolon(start_offset, end_offset) + if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*;/]) final_offset = start_offset + match.bytesize - [character, Range.new(source_buffer, offset_cache[final_offset - character.bytesize], offset_cache[final_offset])] + [";", Range.new(source_buffer, offset_cache[final_offset - 1], offset_cache[final_offset])] end end diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb index 8f2d065b73..75c48ef667 100644 --- a/lib/prism/translation/parser/lexer.rb +++ b/lib/prism/translation/parser/lexer.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +# :markup: markdown require "strscan" require_relative "../../polyfill/append_as_bytes" +require_relative "../../polyfill/scan_byte" module Prism module Translation @@ -201,7 +203,7 @@ module Prism # The following token types are listed as those classified as `tLPAREN`. LPAREN_CONVERSION_TOKEN_TYPES = Set.new([ :kBREAK, :tCARET, :kCASE, :tDIVIDE, :kFOR, :kIF, :kNEXT, :kRETURN, :kUNTIL, :kWHILE, :tAMPER, :tANDOP, :tBANG, :tCOMMA, :tDOT2, :tDOT3, - :tEQL, :tLPAREN, :tLPAREN2, :tLPAREN_ARG, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS + :tEQL, :tLPAREN, :tLPAREN2, :tLPAREN_ARG, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS, :tLCURLY ]) # Types of tokens that are allowed to continue a method call with comments in-between. @@ -275,20 +277,20 @@ module Prism when :tCOMMENT if token.type == :EMBDOC_BEGIN - while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1) + while !((next_token = lexed[index]&.first) && next_token.type == :EMBDOC_END) && (index < length - 1) value += next_token.value index += 1 end value += next_token.value - location = range(token.location.start_offset, lexed[index][0].location.end_offset) + location = range(token.location.start_offset, next_token.location.end_offset) index += 1 else is_at_eol = value.chomp!.nil? location = range(token.location.start_offset, token.location.end_offset + (is_at_eol ? 0 : -1)) - prev_token = lexed[index - 2][0] if index - 2 >= 0 - next_token = lexed[index][0] + prev_token, _ = lexed[index - 2] if index - 2 >= 0 + next_token, _ = lexed[index] is_inline_comment = prev_token&.location&.start_line == token.location.start_line if is_inline_comment && !is_at_eol && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type) @@ -307,7 +309,7 @@ module Prism end end when :tNL - next_token = next_token = lexed[index][0] + next_token, _ = lexed[index] # Newlines after comments are emitted out of order. if next_token&.type == :COMMENT comment_newline_location = location @@ -344,8 +346,8 @@ module Prism location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value)) value = nil when :tSTRING_BEG - next_token = lexed[index][0] - next_next_token = lexed[index + 1][0] + next_token, _ = lexed[index] + next_next_token, _ = lexed[index + 1] basic_quotes = value == '"' || value == "'" if basic_quotes && next_token&.type == :STRING_END @@ -413,7 +415,8 @@ module Prism while token.type == :STRING_CONTENT current_length += token.value.bytesize # Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line. - is_first_token_on_line = lexed[index - 1] && token.location.start_line != lexed[index - 2][0].location&.start_line + prev_token, _ = lexed[index - 2] if index - 2 >= 0 + is_first_token_on_line = prev_token && token.location.start_line != prev_token.location.start_line # The parser gem only removes indentation when the heredoc is not nested not_nested = heredoc_stack.size == 1 if is_percent_array @@ -423,11 +426,16 @@ module Prism end current_string << unescape_string(value, quote_stack.last) - if (backslash_count = token.value[/(\\{1,})\n/, 1]&.length).nil? || backslash_count.even? || !interpolation?(quote_stack.last) + relevant_backslash_count = if quote_stack.last.start_with?("%W", "%I") + 0 # the last backslash escapes the newline + else + token.value[/(\\{1,})\n/, 1]&.length || 0 + end + if relevant_backslash_count.even? || !interpolation?(quote_stack.last) tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]] break end - token = lexed[index][0] + token, _ = lexed[index] index += 1 end else @@ -482,7 +490,7 @@ module Prism end if percent_array?(quote_stack.pop) - prev_token = lexed[index - 2][0] if index - 2 >= 0 + prev_token, _ = lexed[index - 2] if index - 2 >= 0 empty = %i[PERCENT_LOWER_I PERCENT_LOWER_W PERCENT_UPPER_I PERCENT_UPPER_W].include?(prev_token&.type) ends_with_whitespace = prev_token&.type == :WORDS_SEP # parser always emits a space token after content in a percent array, even if no actual whitespace is present. @@ -491,7 +499,7 @@ module Prism end end when :tSYMBEG - if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END + if (next_token = lexed[index]&.first) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END next_location = token.location.join(next_token.location) type = :tSYMBOL value = next_token.value @@ -506,13 +514,13 @@ module Prism type = :tIDENTIFIER end when :tXSTRING_BEG - if (next_token = lexed[index][0]) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type) + if (next_token = lexed[index]&.first) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type) # self.`() type = :tBACK_REF2 end quote_stack.push(value) when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG - if (next_token = lexed[index][0]) && next_token.type == :WORDS_SEP + if (next_token = lexed[index]&.first) && next_token.type == :WORDS_SEP index += 1 end @@ -588,9 +596,9 @@ module Prism previous_line = -1 result = Float::MAX - while (lexed[next_token_index] && next_token = lexed[next_token_index][0]) + while (next_token = lexed[next_token_index]&.first) next_token_index += 1 - next_next_token = lexed[next_token_index] && lexed[next_token_index][0] + next_next_token, _ = lexed[next_token_index] first_token_on_line = next_token.location.start_column == 0 # String content inside nested heredocs and interpolation is ignored @@ -761,12 +769,12 @@ module Prism elsif (value = scanner.scan(/M-\\?(?=[[:print:]])/)) # \M-x where x is an ASCII printable character escape_read(result, scanner, control, true) - elsif (byte = scanner.get_byte) + elsif (byte = scanner.scan_byte) # Something else after an escape. - if control && byte == "?" + if control && byte == 0x3f # ASCII '?' result.append_as_bytes(escape_build(0x7f, false, meta)) else - result.append_as_bytes(escape_build(byte.ord, control, meta)) + result.append_as_bytes(escape_build(byte, control, meta)) end end end diff --git a/lib/prism/translation/parser33.rb b/lib/prism/translation/parser33.rb deleted file mode 100644 index b09266e06a..0000000000 --- a/lib/prism/translation/parser33.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Prism - module Translation - # This class is the entry-point for Ruby 3.3 of `Prism::Translation::Parser`. - class Parser33 < Parser - def version # :nodoc: - 33 - end - end - end -end diff --git a/lib/prism/translation/parser34.rb b/lib/prism/translation/parser34.rb deleted file mode 100644 index 0ead70ad3c..0000000000 --- a/lib/prism/translation/parser34.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Prism - module Translation - # This class is the entry-point for Ruby 3.4 of `Prism::Translation::Parser`. - class Parser34 < Parser - def version # :nodoc: - 34 - end - end - end -end diff --git a/lib/prism/translation/parser35.rb b/lib/prism/translation/parser35.rb deleted file mode 100644 index a6abc12589..0000000000 --- a/lib/prism/translation/parser35.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Prism - module Translation - # This class is the entry-point for Ruby 3.5 of `Prism::Translation::Parser`. - class Parser35 < Parser - def version # :nodoc: - 35 - end - end - end -end diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index b44769fde7..f13eff6bbe 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore +# module Prism module Translation case RUBY_VERSION @@ -8,11 +10,13 @@ module Prism ParserCurrent = Parser33 when /^3\.4\./ ParserCurrent = Parser34 - when /^3\.5\./ - ParserCurrent = Parser35 + when /^3\.5\./, /^4\.0\./ + ParserCurrent = Parser40 + when /^4\.1\./ + ParserCurrent = Parser41 else # Keep this in sync with released Ruby. - parser = Parser34 + parser = Parser40 major, minor, _patch = Gem::Version.new(RUBY_VERSION).segments warn "warning: `Prism::Translation::Current` is loading #{parser.name}, " \ "but you are running #{major}.#{minor}." diff --git a/lib/prism/translation/parser_versions.rb b/lib/prism/translation/parser_versions.rb new file mode 100644 index 0000000000..720c7d548c --- /dev/null +++ b/lib/prism/translation/parser_versions.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +# :markup: markdown + +module Prism + module Translation + # This class is the entry-point for Ruby 3.3 of `Prism::Translation::Parser`. + class Parser33 < Parser + def version # :nodoc: + 33 + end + end + + # This class is the entry-point for Ruby 3.4 of `Prism::Translation::Parser`. + class Parser34 < Parser + def version # :nodoc: + 34 + end + end + + # This class is the entry-point for Ruby 4.0 of `Prism::Translation::Parser`. + class Parser40 < Parser + def version # :nodoc: + 40 + end + end + + Parser35 = Parser40 # :nodoc: + + # This class is the entry-point for Ruby 4.1 of `Prism::Translation::Parser`. + class Parser41 < Parser + def version # :nodoc: + 41 + end + end + end +end diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 95f366ac91..735217d2e0 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true - -require "ripper" +# :markup: markdown module Prism module Translation @@ -70,7 +69,7 @@ module Prism # [[1, 13], :on_kw, "end", END ]] # def self.lex(src, filename = "-", lineno = 1, raise_errors: false) - result = Prism.lex_compat(src, filepath: filename, line: lineno) + result = Prism.lex_compat(src, filepath: filename, line: lineno, version: "current") if result.failure? && raise_errors raise SyntaxError, result.errors.first.message @@ -79,6 +78,19 @@ module Prism end end + # Tokenizes the Ruby program and returns an array of strings. + # The +filename+ and +lineno+ arguments are mostly ignored, since the + # return value is just the tokenized input. + # By default, this method does not handle syntax errors in +src+, + # use the +raise_errors+ keyword to raise a SyntaxError for an error in +src+. + # + # p Ripper.tokenize("def m(a) nil end") + # # => ["def", " ", "m", "(", "a", ")", " ", "nil", " ", "end"] + # + def self.tokenize(...) + lex(...).map(&:value) + end + # This contains a table of all of the parser events and their # corresponding arity. PARSER_EVENT_TABLE = { @@ -425,9 +437,35 @@ module Prism end end + autoload :Filter, "prism/translation/ripper/filter" + autoload :Lexer, "prism/translation/ripper/lexer" autoload :SexpBuilder, "prism/translation/ripper/sexp" autoload :SexpBuilderPP, "prism/translation/ripper/sexp" + # :stopdoc: + # This is not part of the public API but used by some gems. + + # Ripper-internal bitflags. + LEX_STATE_NAMES = %i[ + BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM + ].map.with_index.to_h { |name, i| [2 ** i, name] }.freeze + private_constant :LEX_STATE_NAMES + + LEX_STATE_NAMES.each do |value, key| + const_set("EXPR_#{key}", value) + end + EXPR_NONE = 0 + EXPR_VALUE = EXPR_BEG + EXPR_BEG_ANY = EXPR_BEG | EXPR_MID | EXPR_CLASS + EXPR_ARG_ANY = EXPR_ARG | EXPR_CMDARG + EXPR_END_ANY = EXPR_END | EXPR_ENDARG | EXPR_ENDFN + + def self.lex_state_name(state) + LEX_STATE_NAMES.filter_map { |flag, name| name if state & flag != 0 }.join("|") + end + + # :startdoc: + # The source that is being parsed. attr_reader :source @@ -794,7 +832,7 @@ module Prism # foo(bar) # ^^^ def visit_arguments_node(node) - arguments, _ = visit_call_node_arguments(node, nil, false) + arguments, _, _ = visit_call_node_arguments(node, nil, false) arguments end @@ -1004,16 +1042,16 @@ module Prism case node.name when :[] receiver = visit(node.receiver) - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) call = on_aref(receiver, arguments) - if block.nil? - call - else + if has_ripper_block bounds(node.location) on_method_add_block(call, block) + else + call end when :[]= receiver = visit(node.receiver) @@ -1072,9 +1110,9 @@ module Prism if node.variable_call? on_vcall(message) else - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) call = - if node.opening_loc.nil? && arguments&.any? + if node.opening_loc.nil? && get_arguments_and_block(node.arguments, node.block).first.any? bounds(node.location) on_command(message, arguments) elsif !node.opening_loc.nil? @@ -1085,11 +1123,11 @@ module Prism on_method_add_arg(on_fcall(message), on_args_new) end - if block.nil? - call - else + if has_ripper_block bounds(node.block.location) on_method_add_block(call, block) + else + call end end end @@ -1113,7 +1151,7 @@ module Prism bounds(node.location) on_assign(on_field(receiver, call_operator, message), value) else - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) call = if node.opening_loc.nil? bounds(node.location) @@ -1131,27 +1169,35 @@ module Prism on_method_add_arg(on_call(receiver, call_operator, message), arguments) end - if block.nil? - call - else + if has_ripper_block bounds(node.block.location) on_method_add_block(call, block) + else + call end end end end - # Visit the arguments and block of a call node and return the arguments - # and block as they should be used. - private def visit_call_node_arguments(arguments_node, block_node, trailing_comma) + # Extract the arguments and block Ripper-style, which means if the block + # is like `&b` then it's moved to arguments. + private def get_arguments_and_block(arguments_node, block_node) arguments = arguments_node&.arguments || [] block = block_node if block.is_a?(BlockArgumentNode) - arguments << block + arguments += [block] block = nil end + [arguments, block] + end + + # Visit the arguments and block of a call node and return the arguments + # and block as they should be used. + private def visit_call_node_arguments(arguments_node, block_node, trailing_comma) + arguments, block = get_arguments_and_block(arguments_node, block_node) + [ if arguments.length == 1 && arguments.first.is_a?(ForwardingArgumentsNode) visit(arguments.first) @@ -1165,7 +1211,8 @@ module Prism on_args_add_block(args, false) end end, - visit(block) + visit(block), + block != nil, ] end @@ -1602,10 +1649,10 @@ module Prism end bounds(node.location) - if receiver.nil? - on_def(name, parameters, bodystmt) - else + if receiver on_defs(receiver, operator, name, parameters, bodystmt) + else + on_def(name, parameters, bodystmt) end end @@ -2003,7 +2050,7 @@ module Prism # ^^^^^^^^^^^^^^^ def visit_index_operator_write_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) target = on_aref_field(receiver, arguments) @@ -2020,7 +2067,7 @@ module Prism # ^^^^^^^^^^^^^^^^ def visit_index_and_write_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) target = on_aref_field(receiver, arguments) @@ -2037,7 +2084,7 @@ module Prism # ^^^^^^^^^^^^^^^^ def visit_index_or_write_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) target = on_aref_field(receiver, arguments) @@ -2054,7 +2101,7 @@ module Prism # ^^^^^^^^ def visit_index_target_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) on_aref_field(receiver, arguments) @@ -3084,7 +3131,7 @@ module Prism # super(foo) # ^^^^^^^^^^ def visit_super_node(node) - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location)) if !node.lparen_loc.nil? bounds(node.lparen_loc) @@ -3094,11 +3141,11 @@ module Prism bounds(node.location) call = on_super(arguments) - if block.nil? - call - else + if has_ripper_block bounds(node.block.location) on_method_add_block(call, block) + else + call end end @@ -3294,7 +3341,7 @@ module Prism # Lazily initialize the parse result. def result - @result ||= Prism.parse(source, partial_script: true) + @result ||= Prism.parse(source, partial_script: true, version: "current") end ########################################################################## @@ -3408,12 +3455,12 @@ module Prism # :stopdoc: def _dispatch_0; end - def _dispatch_1(_); end - def _dispatch_2(_, _); end - def _dispatch_3(_, _, _); end - def _dispatch_4(_, _, _, _); end - def _dispatch_5(_, _, _, _, _); end - def _dispatch_7(_, _, _, _, _, _, _); end + def _dispatch_1(arg); arg end + def _dispatch_2(arg, _); arg end + def _dispatch_3(arg, _, _); arg end + def _dispatch_4(arg, _, _, _); arg end + def _dispatch_5(arg, _, _, _, _); arg end + def _dispatch_7(arg, _, _, _, _, _, _); arg end # :startdoc: # diff --git a/lib/prism/translation/ripper/filter.rb b/lib/prism/translation/ripper/filter.rb new file mode 100644 index 0000000000..19deef2d37 --- /dev/null +++ b/lib/prism/translation/ripper/filter.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Prism + module Translation + class Ripper + class Filter # :nodoc: + # :stopdoc: + def initialize(src, filename = '-', lineno = 1) + @__lexer = Lexer.new(src, filename, lineno) + @__line = nil + @__col = nil + @__state = nil + end + + def filename + @__lexer.filename + end + + def lineno + @__line + end + + def column + @__col + end + + def state + @__state + end + + def parse(init = nil) + data = init + @__lexer.lex.each do |pos, event, tok, state| + @__line, @__col = *pos + @__state = state + data = if respond_to?(event, true) + then __send__(event, tok, data) + else on_default(event, tok, data) + end + end + data + end + + private + + def on_default(event, token, data) + data + end + # :startdoc: + end + end + end +end diff --git a/lib/prism/translation/ripper/lexer.rb b/lib/prism/translation/ripper/lexer.rb new file mode 100644 index 0000000000..bed863af08 --- /dev/null +++ b/lib/prism/translation/ripper/lexer.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true +# :markup: markdown + +require_relative "../ripper" + +module Prism + module Translation + class Ripper + class Lexer < Ripper # :nodoc: + # :stopdoc: + class State + + attr_reader :to_int, :to_s + + def initialize(i) + @to_int = i + @to_s = Ripper.lex_state_name(i) + freeze + end + + def [](index) + case index + when 0, :to_int + @to_int + when 1, :to_s + @to_s + else + nil + end + end + + alias to_i to_int + alias inspect to_s + def pretty_print(q) q.text(to_s) end + def ==(i) super or to_int == i end + def &(i) self.class.new(to_int & i) end + def |(i) self.class.new(to_int | i) end + def allbits?(i) to_int.allbits?(i) end + def anybits?(i) to_int.anybits?(i) end + def nobits?(i) to_int.nobits?(i) end + + # Instances are frozen and there are only a handful of them so we cache them here. + STATES = Hash.new { |h,k| h[k] = State.new(k) } + + def self.cached(i) + STATES[i] + end + end + + class Elem + attr_accessor :pos, :event, :tok, :state, :message + + def initialize(pos, event, tok, state, message = nil) + @pos = pos + @event = event + @tok = tok + @state = State.cached(state) + @message = message + end + + def [](index) + case index + when 0, :pos + @pos + when 1, :event + @event + when 2, :tok + @tok + when 3, :state + @state + when 4, :message + @message + else + nil + end + end + + def inspect + "#<#{self.class}: #{event}@#{pos[0]}:#{pos[1]}:#{state}: #{tok.inspect}#{": " if message}#{message}>" + end + + alias to_s inspect + + def pretty_print(q) + q.group(2, "#<#{self.class}:", ">") { + q.breakable + q.text("#{event}@#{pos[0]}:#{pos[1]}") + q.breakable + state.pretty_print(q) + q.breakable + q.text("token: ") + tok.pretty_print(q) + if message + q.breakable + q.text("message: ") + q.text(message) + end + } + end + + def to_a + if @message + [@pos, @event, @tok, @state, @message] + else + [@pos, @event, @tok, @state] + end + end + end + + # Pretty much just the same as Prism.lex_compat. + def lex(raise_errors: false) + Ripper.lex(@source, filename, lineno, raise_errors: raise_errors) + end + + # Returns the lex_compat result wrapped in `Elem`. Errors are omitted. + # Since ripper is a streaming parser, tokens are expected to be emitted in the order + # that the parser encounters them. This is not implemented. + def parse(...) + lex(...).map do |position, event, token, state| + Elem.new(position, event, token, state.to_int) + end + end + + # Similar to parse but ripper sorts the elements by position in the source. Also + # includes errors. Since prism does error recovery, in cases of syntax errors + # the result may differ greatly compared to ripper. + def scan(...) + parse(...) + end + + # :startdoc: + end + end + end +end diff --git a/lib/prism/translation/ripper/sexp.rb b/lib/prism/translation/ripper/sexp.rb index dc26a639a3..8cfefc8472 100644 --- a/lib/prism/translation/ripper/sexp.rb +++ b/lib/prism/translation/ripper/sexp.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require_relative "../ripper" diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 8784e22d10..c026c4ad9c 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -1,12 +1,18 @@ # frozen_string_literal: true +# :markup: markdown begin - require "ruby_parser" + require "sexp" rescue LoadError - warn(%q{Error: Unable to load ruby_parser. Add `gem "ruby_parser"` to your Gemfile.}) + warn(%q{Error: Unable to load sexp. Add `gem "sexp_processor"` to your Gemfile.}) exit(1) end +class RubyParser # :nodoc: + class SyntaxError < RuntimeError # :nodoc: + end +end + module Prism module Translation # This module is the entry-point for converting a prism syntax tree into the @@ -15,7 +21,7 @@ module Prism # A prism visitor that builds Sexp objects. class Compiler < ::Prism::Compiler # This is the name of the file that we are compiling. We set it on every - # Sexp object that is generated, and also use it to compile __FILE__ + # Sexp object that is generated, and also use it to compile `__FILE__` # nodes. attr_reader :file @@ -34,26 +40,34 @@ module Prism @in_pattern = in_pattern end + # ``` # alias foo bar # ^^^^^^^^^^^^^ + # ``` def visit_alias_method_node(node) s(node, :alias, visit(node.new_name), visit(node.old_name)) end + # ``` # alias $foo $bar # ^^^^^^^^^^^^^^^ + # ``` def visit_alias_global_variable_node(node) s(node, :valias, node.new_name.name, node.old_name.name) end + # ``` # foo => bar | baz # ^^^^^^^^^ + # ``` def visit_alternation_pattern_node(node) s(node, :or, visit(node.left), visit(node.right)) end + # ``` # a and b # ^^^^^^^ + # ``` def visit_and_node(node) left = visit(node.left) @@ -70,8 +84,10 @@ module Prism end end + # ``` # [] # ^^ + # ``` def visit_array_node(node) if in_pattern s(node, :array_pat, nil).concat(visit_all(node.elements)) @@ -80,8 +96,10 @@ module Prism end end + # ``` # foo => [bar] # ^^^^^ + # ``` def visit_array_pattern_node(node) if node.constant.nil? && node.requireds.empty? && node.rest.nil? && node.posts.empty? s(node, :array_pat) @@ -103,23 +121,29 @@ module Prism end end + # ``` # foo(bar) # ^^^ + # ``` def visit_arguments_node(node) raise "Cannot visit arguments directly" end + # ``` # { a: 1 } # ^^^^ + # ``` def visit_assoc_node(node) [visit(node.key), visit(node.value)] end + # ``` # def foo(**); bar(**); end # ^^ # # { **foo } # ^^^^^ + # ``` def visit_assoc_splat_node(node) if node.value.nil? [s(node, :kwsplat)] @@ -128,14 +152,18 @@ module Prism end end + # ``` # $+ # ^^ + # ``` def visit_back_reference_read_node(node) - s(node, :back_ref, node.name.name.delete_prefix("$").to_sym) + s(node, :back_ref, node.name.to_s.delete_prefix("$").to_sym) end + # ``` # begin end # ^^^^^^^^^ + # ``` def visit_begin_node(node) result = node.statements.nil? ? s(node, :nil) : visit(node.statements) @@ -167,16 +195,20 @@ module Prism result end + # ``` # foo(&bar) # ^^^^ + # ``` def visit_block_argument_node(node) s(node, :block_pass).tap do |result| result << visit(node.expression) unless node.expression.nil? end end + # ``` # foo { |; bar| } # ^^^ + # ``` def visit_block_local_variable_node(node) node.name end @@ -186,8 +218,10 @@ module Prism s(node, :block_pass, visit(node.expression)) end + # ``` # def foo(&bar); end # ^^^^ + # ``` def visit_block_parameter_node(node) :"&#{node.name}" end @@ -228,11 +262,13 @@ module Prism result end + # ``` # break # ^^^^^ # # break foo # ^^^^^^^^^ + # ``` def visit_break_node(node) if node.arguments.nil? s(node, :break) @@ -243,6 +279,7 @@ module Prism end end + # ``` # foo # ^^^ # @@ -251,6 +288,7 @@ module Prism # # foo.bar() {} # ^^^^^^^^^^^^ + # ``` def visit_call_node(node) case node.name when :!~ @@ -289,8 +327,10 @@ module Prism visit_block(node, result, block) end + # ``` # foo.bar += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_operator_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, node.binary_operator) @@ -299,8 +339,10 @@ module Prism end end + # ``` # foo.bar &&= baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_and_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"&&") @@ -309,8 +351,10 @@ module Prism end end + # ``` # foo.bar ||= baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_or_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"||") @@ -332,32 +376,42 @@ module Prism node.safe_navigation? ? :"safe_#{type}" : type end + # ``` # foo.bar, = 1 # ^^^^^^^ + # ``` def visit_call_target_node(node) s(node, :attrasgn, visit(node.receiver), node.name) end + # ``` # foo => bar => baz # ^^^^^^^^^^ + # ``` def visit_capture_pattern_node(node) visit(node.target) << visit(node.value) end + # ``` # case foo; when bar; end # ^^^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_case_node(node) s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause) end + # ``` # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_case_match_node(node) s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause) end + # ``` # class Foo; end # ^^^^^^^^^^^^^^ + # ``` def visit_class_node(node) name = if node.constant_path.is_a?(ConstantReadNode) @@ -366,51 +420,67 @@ module Prism visit(node.constant_path) end - if node.body.nil? - s(node, :class, name, visit(node.superclass)) - elsif node.body.is_a?(StatementsNode) - compiler = copy_compiler(in_def: false) - s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) }) - else - s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false))) - end + result = + if node.body.nil? + s(node, :class, name, visit(node.superclass)) + elsif node.body.is_a?(StatementsNode) + compiler = copy_compiler(in_def: false) + s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) }) + else + s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false))) + end + + attach_comments(result, node) + result end + # ``` # @@foo # ^^^^^ + # ``` def visit_class_variable_read_node(node) s(node, :cvar, node.name) end + # ``` # @@foo = 1 # ^^^^^^^^^ # # @@foo, @@bar = 1 # ^^^^^ ^^^^^ + # ``` def visit_class_variable_write_node(node) s(node, class_variable_write_type, node.name, visit_write_value(node.value)) end + # ``` # @@foo += bar # ^^^^^^^^^^^^ + # ``` def visit_class_variable_operator_write_node(node) s(node, class_variable_write_type, node.name, s(node, :call, s(node, :cvar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # @@foo &&= bar # ^^^^^^^^^^^^^ + # ``` def visit_class_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value))) end + # ``` # @@foo ||= bar # ^^^^^^^^^^^^^ + # ``` def visit_class_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value))) end + # ``` # @@foo, = bar # ^^^^^ + # ``` def visit_class_variable_target_node(node) s(node, class_variable_write_type, node.name) end @@ -421,47 +491,61 @@ module Prism in_def ? :cvasgn : :cvdecl end + # ``` # Foo # ^^^ + # ``` def visit_constant_read_node(node) s(node, :const, node.name) end + # ``` # Foo = 1 # ^^^^^^^ # # Foo, Bar = 1 # ^^^ ^^^ + # ``` def visit_constant_write_node(node) s(node, :cdecl, node.name, visit_write_value(node.value)) end + # ``` # Foo += bar # ^^^^^^^^^^^ + # ``` def visit_constant_operator_write_node(node) s(node, :cdecl, node.name, s(node, :call, s(node, :const, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # Foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_constant_and_write_node(node) s(node, :op_asgn_and, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value))) end + # ``` # Foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_constant_or_write_node(node) s(node, :op_asgn_or, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value))) end + # ``` # Foo, = bar # ^^^ + # ``` def visit_constant_target_node(node) s(node, :cdecl, node.name) end + # ``` # Foo::Bar # ^^^^^^^^ + # ``` def visit_constant_path_node(node) if node.parent.nil? s(node, :colon3, node.name) @@ -470,35 +554,45 @@ module Prism end end + # ``` # Foo::Bar = 1 # ^^^^^^^^^^^^ # # Foo::Foo, Bar::Bar = 1 # ^^^^^^^^ ^^^^^^^^ + # ``` def visit_constant_path_write_node(node) s(node, :cdecl, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_operator_write_node(node) s(node, :op_asgn, visit(node.target), node.binary_operator, visit_write_value(node.value)) end + # ``` # Foo::Bar &&= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_and_write_node(node) s(node, :op_asgn_and, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar ||= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_or_write_node(node) s(node, :op_asgn_or, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar, = baz # ^^^^^^^^ + # ``` def visit_constant_path_target_node(node) inner = if node.parent.nil? @@ -510,11 +604,13 @@ module Prism s(node, :const, inner) end + # ``` # def foo; end # ^^^^^^^^^^^^ # # def self.foo; end # ^^^^^^^^^^^^^^^^^ + # ``` def visit_def_node(node) name = node.name_loc.slice.to_sym result = @@ -524,7 +620,9 @@ module Prism s(node, :defs, visit(node.receiver), name) end + attach_comments(result, node) result.line(node.name_loc.start_line) + if node.parameters.nil? result << s(node, :args).line(node.name_loc.start_line) else @@ -541,55 +639,71 @@ module Prism end end + # ``` # defined? a # ^^^^^^^^^^ # # defined?(a) # ^^^^^^^^^^^ + # ``` def visit_defined_node(node) s(node, :defined, visit(node.value)) end + # ``` # if foo then bar else baz end # ^^^^^^^^^^^^ + # ``` def visit_else_node(node) visit(node.statements) end + # ``` # "foo #{bar}" # ^^^^^^ + # ``` def visit_embedded_statements_node(node) result = s(node, :evstr) result << visit(node.statements) unless node.statements.nil? result end + # ``` # "foo #@bar" # ^^^^^ + # ``` def visit_embedded_variable_node(node) s(node, :evstr, visit(node.variable)) end + # ``` # begin; foo; ensure; bar; end # ^^^^^^^^^^^^ + # ``` def visit_ensure_node(node) node.statements.nil? ? s(node, :nil) : visit(node.statements) end + # ``` # false # ^^^^^ + # ``` def visit_false_node(node) s(node, :false) end + # ``` # foo => [*, bar, *] # ^^^^^^^^^^^ + # ``` def visit_find_pattern_node(node) s(node, :find_pat, visit_pattern_constant(node.constant), :"*#{node.left.expression&.name}", *visit_all(node.requireds), :"*#{node.right.expression&.name}") end + # ``` # if foo .. bar; end # ^^^^^^^^^^ + # ``` def visit_flip_flop_node(node) if node.left.is_a?(IntegerNode) && node.right.is_a?(IntegerNode) s(node, :lit, Range.new(node.left.value, node.right.value, node.exclude_end?)) @@ -598,86 +712,112 @@ module Prism end end + # ``` # 1.0 # ^^^ + # ``` def visit_float_node(node) s(node, :lit, node.value) end + # ``` # for foo in bar do end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_for_node(node) s(node, :for, visit(node.collection), visit(node.index), visit(node.statements)) end + # ``` # def foo(...); bar(...); end # ^^^ + # ``` def visit_forwarding_arguments_node(node) s(node, :forward_args) end + # ``` # def foo(...); end # ^^^ + # ``` def visit_forwarding_parameter_node(node) s(node, :forward_args) end + # ``` # super # ^^^^^ # # super {} # ^^^^^^^^ + # ``` def visit_forwarding_super_node(node) visit_block(node, s(node, :zsuper), node.block) end + # ``` # $foo # ^^^^ + # ``` def visit_global_variable_read_node(node) s(node, :gvar, node.name) end + # ``` # $foo = 1 # ^^^^^^^^ # # $foo, $bar = 1 # ^^^^ ^^^^ + # ``` def visit_global_variable_write_node(node) s(node, :gasgn, node.name, visit_write_value(node.value)) end + # ``` # $foo += bar # ^^^^^^^^^^^ + # ``` def visit_global_variable_operator_write_node(node) s(node, :gasgn, node.name, s(node, :call, s(node, :gvar, node.name), node.binary_operator, visit(node.value))) end + # ``` # $foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_global_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value))) end + # ``` # $foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_global_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value))) end + # ``` # $foo, = bar # ^^^^ + # ``` def visit_global_variable_target_node(node) s(node, :gasgn, node.name) end + # ``` # {} # ^^ + # ``` def visit_hash_node(node) s(node, :hash).concat(node.elements.flat_map { |element| visit(element) }) end + # ``` # foo => {} # ^^ + # ``` def visit_hash_pattern_node(node) result = s(node, :hash_pat, visit_pattern_constant(node.constant)).concat(node.elements.flat_map { |element| visit(element) }) @@ -691,6 +831,7 @@ module Prism result end + # ``` # if foo then bar end # ^^^^^^^^^^^^^^^^^^^ # @@ -699,6 +840,7 @@ module Prism # # foo ? bar : baz # ^^^^^^^^^^^^^^^ + # ``` def visit_if_node(node) s(node, :if, visit(node.predicate), visit(node.statements), visit(node.subsequent)) end @@ -708,18 +850,24 @@ module Prism s(node, :lit, node.value) end + # ``` # { foo: } # ^^^^ + # ``` def visit_implicit_node(node) end + # ``` # foo { |bar,| } # ^ + # ``` def visit_implicit_rest_node(node) end + # ``` # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_in_node(node) pattern = if node.pattern.is_a?(ConstantPathNode) @@ -731,8 +879,10 @@ module Prism s(node, :in, pattern).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # foo[bar] += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_index_operator_write_node(node) arglist = nil @@ -744,8 +894,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, node.binary_operator, visit_write_value(node.value)) end + # ``` # foo[bar] &&= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_index_and_write_node(node) arglist = nil @@ -757,8 +909,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, :"&&", visit_write_value(node.value)) end + # ``` # foo[bar] ||= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_index_or_write_node(node) arglist = nil @@ -770,8 +924,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, :"||", visit_write_value(node.value)) end + # ``` # foo[bar], = 1 # ^^^^^^^^ + # ``` def visit_index_target_node(node) arguments = visit_all(node.arguments&.arguments || []) arguments << visit(node.block) unless node.block.nil? @@ -779,53 +935,69 @@ module Prism s(node, :attrasgn, visit(node.receiver), :[]=).concat(arguments) end + # ``` # @foo # ^^^^ + # ``` def visit_instance_variable_read_node(node) s(node, :ivar, node.name) end + # ``` # @foo = 1 # ^^^^^^^^ # # @foo, @bar = 1 # ^^^^ ^^^^ + # ``` def visit_instance_variable_write_node(node) s(node, :iasgn, node.name, visit_write_value(node.value)) end + # ``` # @foo += bar # ^^^^^^^^^^^ + # ``` def visit_instance_variable_operator_write_node(node) s(node, :iasgn, node.name, s(node, :call, s(node, :ivar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # @foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_instance_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value))) end + # ``` # @foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_instance_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value))) end + # ``` # @foo, = bar # ^^^^ + # ``` def visit_instance_variable_target_node(node) s(node, :iasgn, node.name) end + # ``` # 1 # ^ + # ``` def visit_integer_node(node) s(node, :lit, node.value) end + # ``` # if /foo #{bar}/ then end # ^^^^^^^^^^^^ + # ``` def visit_interpolated_match_last_line_node(node) parts = visit_interpolated_parts(node.parts) regexp = @@ -841,8 +1013,10 @@ module Prism s(node, :match, regexp) end + # ``` # /foo #{bar}/ # ^^^^^^^^^^^^ + # ``` def visit_interpolated_regular_expression_node(node) parts = visit_interpolated_parts(node.parts) @@ -856,22 +1030,28 @@ module Prism end end + # ``` # "foo #{bar}" # ^^^^^^^^^^^^ + # ``` def visit_interpolated_string_node(node) parts = visit_interpolated_parts(node.parts) parts.length == 1 ? s(node, :str, parts.first) : s(node, :dstr).concat(parts) end + # ``` # :"foo #{bar}" # ^^^^^^^^^^^^^ + # ``` def visit_interpolated_symbol_node(node) parts = visit_interpolated_parts(node.parts) parts.length == 1 ? s(node, :lit, parts.first.to_sym) : s(node, :dsym).concat(parts) end + # ``` # `foo #{bar}` # ^^^^^^^^^^^^ + # ``` def visit_interpolated_x_string_node(node) source = node.heredoc? ? node.parts.first : node parts = visit_interpolated_parts(node.parts) @@ -951,23 +1131,29 @@ module Prism results end + # ``` # -> { it } # ^^ + # ``` def visit_it_local_variable_read_node(node) s(node, :call, nil, :it) end + # ``` # foo(bar: baz) # ^^^^^^^^ + # ``` def visit_keyword_hash_node(node) s(node, :hash).concat(node.elements.flat_map { |element| visit(element) }) end + # ``` # def foo(**bar); end # ^^^^^ # # def foo(**); end # ^^ + # ``` def visit_keyword_rest_parameter_node(node) :"**#{node.name}" end @@ -976,8 +1162,8 @@ module Prism def visit_lambda_node(node) parameters = case node.parameters - when nil, NumberedParametersNode - s(node, :args) + when nil, ItParametersNode, NumberedParametersNode + 0 else visit(node.parameters) end @@ -989,8 +1175,10 @@ module Prism end end + # ``` # foo # ^^^ + # ``` def visit_local_variable_read_node(node) if node.name.match?(/^_\d$/) s(node, :call, nil, node.name) @@ -999,59 +1187,77 @@ module Prism end end + # ``` # foo = 1 # ^^^^^^^ # # foo, bar = 1 # ^^^ ^^^ + # ``` def visit_local_variable_write_node(node) s(node, :lasgn, node.name, visit_write_value(node.value)) end + # ``` # foo += bar # ^^^^^^^^^^ + # ``` def visit_local_variable_operator_write_node(node) s(node, :lasgn, node.name, s(node, :call, s(node, :lvar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # foo &&= bar # ^^^^^^^^^^^ + # ``` def visit_local_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value))) end + # ``` # foo ||= bar # ^^^^^^^^^^^ + # ``` def visit_local_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value))) end + # ``` # foo, = bar # ^^^ + # ``` def visit_local_variable_target_node(node) s(node, :lasgn, node.name) end + # ``` # if /foo/ then end # ^^^^^ + # ``` def visit_match_last_line_node(node) s(node, :match, s(node, :lit, Regexp.new(node.unescaped, node.options))) end + # ``` # foo in bar # ^^^^^^^^^^ + # ``` def visit_match_predicate_node(node) s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil) end + # ``` # foo => bar # ^^^^^^^^^^ + # ``` def visit_match_required_node(node) s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil) end + # ``` # /(?<foo>foo)/ =~ bar # ^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_match_write_node(node) s(node, :match2, visit(node.call.receiver), visit(node.call.arguments.arguments.first)) end @@ -1063,8 +1269,10 @@ module Prism raise "Cannot visit missing node directly" end + # ``` # module Foo; end # ^^^^^^^^^^^^^^^ + # ``` def visit_module_node(node) name = if node.constant_path.is_a?(ConstantReadNode) @@ -1073,18 +1281,24 @@ module Prism visit(node.constant_path) end - if node.body.nil? - s(node, :module, name) - elsif node.body.is_a?(StatementsNode) - compiler = copy_compiler(in_def: false) - s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) }) - else - s(node, :module, name, node.body.accept(copy_compiler(in_def: false))) - end + result = + if node.body.nil? + s(node, :module, name) + elsif node.body.is_a?(StatementsNode) + compiler = copy_compiler(in_def: false) + s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) }) + else + s(node, :module, name, node.body.accept(copy_compiler(in_def: false))) + end + + attach_comments(result, node) + result end + # ``` # foo, bar = baz # ^^^^^^^^ + # ``` def visit_multi_target_node(node) targets = [*node.lefts] targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) @@ -1093,8 +1307,10 @@ module Prism s(node, :masgn, s(node, :array).concat(visit_all(targets))) end + # ``` # foo, bar = baz # ^^^^^^^^^^^^^^ + # ``` def visit_multi_write_node(node) targets = [*node.lefts] targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) @@ -1114,11 +1330,13 @@ module Prism s(node, :masgn, s(node, :array).concat(visit_all(targets)), value) end + # ``` # next # ^^^^ # # next foo # ^^^^^^^^ + # ``` def visit_next_node(node) if node.arguments.nil? s(node, :next) @@ -1130,44 +1348,58 @@ module Prism end end + # ``` # nil # ^^^ + # ``` def visit_nil_node(node) s(node, :nil) end + # ``` # def foo(**nil); end # ^^^^^ + # ``` def visit_no_keywords_parameter_node(node) in_pattern ? s(node, :kwrest, :"**nil") : :"**nil" end + # ``` # -> { _1 + _2 } # ^^^^^^^^^^^^^^ + # ``` def visit_numbered_parameters_node(node) raise "Cannot visit numbered parameters directly" end + # ``` # $1 # ^^ + # ``` def visit_numbered_reference_read_node(node) s(node, :nth_ref, node.number) end + # ``` # def foo(bar: baz); end # ^^^^^^^^ + # ``` def visit_optional_keyword_parameter_node(node) s(node, :kwarg, node.name, visit(node.value)) end + # ``` # def foo(bar = 1); end # ^^^^^^^ + # ``` def visit_optional_parameter_node(node) s(node, :lasgn, node.name, visit(node.value)) end + # ``` # a or b # ^^^^^^ + # ``` def visit_or_node(node) left = visit(node.left) @@ -1184,11 +1416,13 @@ module Prism end end + # ``` # def foo(bar, *baz); end # ^^^^^^^^^ + # ``` def visit_parameters_node(node) children = - node.compact_child_nodes.map do |element| + node.each_child_node.map do |element| if element.is_a?(MultiTargetNode) visit_destructured_parameter(element) else @@ -1199,8 +1433,10 @@ module Prism s(node, :args).concat(children) end + # ``` # def foo((bar, baz)); end # ^^^^^^^^^^ + # ``` private def visit_destructured_parameter(node) children = [*node.lefts, *node.rest, *node.rights].map do |child| @@ -1219,11 +1455,13 @@ module Prism s(node, :masgn).concat(children) end + # ``` # () # ^^ # # (1) # ^^^ + # ``` def visit_parentheses_node(node) if node.body.nil? s(node, :nil) @@ -1232,14 +1470,18 @@ module Prism end end + # ``` # foo => ^(bar) # ^^^^^^ + # ``` def visit_pinned_expression_node(node) node.expression.accept(copy_compiler(in_pattern: false)) end + # ``` # foo = 1 and bar => ^foo # ^^^^ + # ``` def visit_pinned_variable_node(node) if node.variable.is_a?(LocalVariableReadNode) && node.variable.name.match?(/^_\d$/) s(node, :lvar, node.variable.name) @@ -1263,8 +1505,10 @@ module Prism visit(node.statements) end + # ``` # 0..5 # ^^^^ + # ``` def visit_range_node(node) if !in_pattern && !node.left.nil? && !node.right.nil? && ([node.left.type, node.right.type] - %i[nil_node integer_node]).empty? left = node.left.value if node.left.is_a?(IntegerNode) @@ -1285,44 +1529,58 @@ module Prism end end + # ``` # 1r # ^^ + # ``` def visit_rational_node(node) s(node, :lit, node.value) end + # ``` # redo # ^^^^ + # ``` def visit_redo_node(node) s(node, :redo) end + # ``` # /foo/ # ^^^^^ + # ``` def visit_regular_expression_node(node) s(node, :lit, Regexp.new(node.unescaped, node.options)) end + # ``` # def foo(bar:); end # ^^^^ + # ``` def visit_required_keyword_parameter_node(node) s(node, :kwarg, node.name) end + # ``` # def foo(bar); end # ^^^ + # ``` def visit_required_parameter_node(node) node.name end + # ``` # foo rescue bar # ^^^^^^^^^^^^^^ + # ``` def visit_rescue_modifier_node(node) s(node, :rescue, visit(node.expression), s(node.rescue_expression, :resbody, s(node.rescue_expression, :array), visit(node.rescue_expression))) end + # ``` # begin; rescue; end # ^^^^^^^ + # ``` def visit_rescue_node(node) exceptions = if node.exceptions.length == 1 && node.exceptions.first.is_a?(SplatNode) @@ -1338,26 +1596,32 @@ module Prism s(node, :resbody, exceptions).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # def foo(*bar); end # ^^^^ # # def foo(*); end # ^ + # ``` def visit_rest_parameter_node(node) :"*#{node.name}" end + # ``` # retry # ^^^^^ + # ``` def visit_retry_node(node) s(node, :retry) end + # ``` # return # ^^^^^^ # # return 1 # ^^^^^^^^ + # ``` def visit_return_node(node) if node.arguments.nil? s(node, :return) @@ -1369,8 +1633,10 @@ module Prism end end + # ``` # self # ^^^^ + # ``` def visit_self_node(node) s(node, :self) end @@ -1380,33 +1646,42 @@ module Prism visit(node.write) end + # ``` # class << self; end # ^^^^^^^^^^^^^^^^^^ + # ``` def visit_singleton_class_node(node) s(node, :sclass, visit(node.expression)).tap do |sexp| sexp << node.body.accept(copy_compiler(in_def: false)) unless node.body.nil? end end + # ``` # __ENCODING__ # ^^^^^^^^^^^^ + # ``` def visit_source_encoding_node(node) # TODO s(node, :colon2, s(node, :const, :Encoding), :UTF_8) end + # ``` # __FILE__ # ^^^^^^^^ + # ``` def visit_source_file_node(node) s(node, :str, node.filepath) end + # ``` # __LINE__ # ^^^^^^^^ + # ``` def visit_source_line_node(node) s(node, :lit, node.location.start_line) end + # ``` # foo(*bar) # ^^^^ # @@ -1415,6 +1690,7 @@ module Prism # # def foo(*); bar(*); end # ^ + # ``` def visit_splat_node(node) if node.expression.nil? s(node, :splat) @@ -1434,8 +1710,10 @@ module Prism end end + # ``` # "foo" # ^^^^^ + # ``` def visit_string_node(node) unescaped = node.unescaped @@ -1447,8 +1725,10 @@ module Prism s(node, :str, unescaped) end + # ``` # super(foo) # ^^^^^^^^^^ + # ``` def visit_super_node(node) arguments = node.arguments&.arguments || [] block = node.block @@ -1461,60 +1741,76 @@ module Prism visit_block(node, s(node, :super).concat(visit_all(arguments)), block) end + # ``` # :foo # ^^^^ + # ``` def visit_symbol_node(node) node.value == "!@" ? s(node, :lit, :"!@") : s(node, :lit, node.unescaped.to_sym) end + # ``` # true # ^^^^ + # ``` def visit_true_node(node) s(node, :true) end + # ``` # undef foo # ^^^^^^^^^ + # ``` def visit_undef_node(node) names = node.names.map { |name| s(node, :undef, visit(name)) } names.length == 1 ? names.first : s(node, :block).concat(names) end + # ``` # unless foo; bar end # ^^^^^^^^^^^^^^^^^^^ # # bar unless foo # ^^^^^^^^^^^^^^ + # ``` def visit_unless_node(node) s(node, :if, visit(node.predicate), visit(node.else_clause), visit(node.statements)) end + # ``` # until foo; bar end # ^^^^^^^^^^^^^^^^^ # # bar until foo # ^^^^^^^^^^^^^ + # ``` def visit_until_node(node) s(node, :until, visit(node.predicate), visit(node.statements), !node.begin_modifier?) end + # ``` # case foo; when bar; end # ^^^^^^^^^^^^^ + # ``` def visit_when_node(node) s(node, :when, s(node, :array).concat(visit_all(node.conditions))).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # while foo; bar end # ^^^^^^^^^^^^^^^^^^ # # bar while foo # ^^^^^^^^^^^^^ + # ``` def visit_while_node(node) s(node, :while, visit(node.predicate), visit(node.statements), !node.begin_modifier?) end + # ``` # `foo` # ^^^^^ + # ``` def visit_x_string_node(node) result = s(node, :xstr, node.unescaped) @@ -1526,17 +1822,30 @@ module Prism result end + # ``` # yield # ^^^^^ # # yield 1 # ^^^^^^^ + # ``` def visit_yield_node(node) s(node, :yield).concat(visit_all(node.arguments&.arguments || [])) end private + # Attach prism comments to the given sexp. + def attach_comments(sexp, node) + return unless node.comments + return if node.comments.empty? + + extra = node.location.start_line - node.comments.last.location.start_line + comments = node.comments.map(&:slice) + comments.concat([nil] * [0, extra].max) + sexp.comments = comments.join("\n") + end + # Create a new compiler with the given options. def copy_compiler(in_def: self.in_def, in_pattern: self.in_pattern) Compiler.new(file, in_def: in_def, in_pattern: in_pattern) @@ -1615,6 +1924,14 @@ module Prism translate(Prism.parse_file(filepath, partial_script: true), filepath) end + # Parse the give file and translate it into the + # seattlerb/ruby_parser gem's Sexp format. This method is + # provided for API compatibility to RubyParser and takes an + # optional +timeout+ argument. + def process(ruby, file = "(string)", timeout = nil) + Timeout.timeout(timeout) { parse(ruby, file) } + end + class << self # Parse the given source and translate it into the seattlerb/ruby_parser # gem's Sexp format. @@ -1639,6 +1956,7 @@ module Prism raise ::RubyParser::SyntaxError, "#{filepath}:#{error.location.start_line} :: #{error.message}" end + result.attach_comments! result.value.accept(Compiler.new(filepath)) end end diff --git a/lib/resolv.gemspec b/lib/resolv.gemspec index bfa2f9ff31..66aed34e01 100644 --- a/lib/resolv.gemspec +++ b/lib/resolv.gemspec @@ -21,9 +21,8 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/resolv.rb b/lib/resolv.rb index ca72f41c5c..fa7d4e2e47 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -4,6 +4,7 @@ require 'socket' require 'timeout' require 'io/wait' require 'securerandom' +require 'rbconfig' # Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can # handle multiple DNS requests concurrently without blocking the entire Ruby @@ -33,7 +34,8 @@ require 'securerandom' class Resolv - VERSION = "0.6.0" + # The version string + VERSION = "0.7.0" ## # Looks up the first IP address for +name+. @@ -173,21 +175,19 @@ class Resolv class ResolvTimeout < Timeout::Error; end - WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ - private_constant :WINDOWS - ## # Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts - if WINDOWS + if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ begin require 'win32/resolv' unless defined?(Win32::Resolv) - DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL + hosts = Win32::Resolv.get_hosts_path || IO::NULL rescue LoadError end end - DefaultFileName ||= '/etc/hosts' + # The default file name for host names + DefaultFileName = hosts || '/etc/hosts' ## # Creates a new Resolv::Hosts, using +filename+ for its data source. @@ -525,6 +525,8 @@ class Resolv } end + # :stopdoc: + def fetch_resource(name, typeclass) lazy_initialize truncated = {} @@ -719,7 +721,8 @@ class Resolv begin reply, from = recv_reply(select_result[0]) rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD - Errno::ECONNRESET # Windows + Errno::ECONNRESET, # Windows + EOFError # No name server running on the server? # Don't wait anymore. raise ResolvTimeout @@ -928,8 +931,11 @@ class Resolv end def recv_reply(readable_socks) - len = readable_socks[0].read(2).unpack('n')[0] + len_data = readable_socks[0].read(2) + raise EOFError if len_data.nil? || len_data.bytesize != 2 + len = len_data.unpack('n')[0] reply = @socks[0].read(len) + raise EOFError if reply.nil? || reply.bytesize != len return reply, nil end @@ -1021,8 +1027,7 @@ class Resolv def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename Config.parse_resolv_conf(filename) - elsif WINDOWS - require 'win32/resolv' unless defined?(Win32::Resolv) + elsif defined?(Win32::Resolv) search, nameserver = Win32::Resolv.get_resolv_info config_hash = {} config_hash[:nameserver] = nameserver if nameserver @@ -1679,6 +1684,7 @@ class Resolv prev_index = @index save_index = nil d = [] + size = -1 while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) @@ -1699,7 +1705,10 @@ class Resolv end @index = idx else - d << self.get_label + l = self.get_label + d << l + size += 1 + l.string.bytesize + raise DecodeError.new("name label data exceed 255 octets") if size > 255 end end end @@ -2601,7 +2610,7 @@ class Resolv end ## - # Flags for this proprty: + # Flags for this property: # - Bit 0 : 0 = not critical, 1 = critical attr_reader :flags @@ -2922,15 +2931,21 @@ class Resolv class IPv4 - ## - # Regular expression IPv4 addresses must match. - Regex256 = /0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? - |[3-9][0-9]?/x + |[3-9][0-9]?/x # :nodoc: + + ## + # Regular expression IPv4 addresses must match. Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/ + ## + # Creates a new IPv4 address from +arg+ which may be: + # + # IPv4:: returns +arg+. + # String:: +arg+ must match the IPv4::Regex constant + def self.create(arg) case arg when IPv4 @@ -3239,13 +3254,15 @@ class Resolv end - module LOC + module LOC # :nodoc: ## # A Resolv::LOC::Size class Size + # Regular expression LOC size must match. + Regex = /^(\d+\.*\d*)[m]$/ ## @@ -3271,6 +3288,7 @@ class Resolv end end + # Internal use; use self.create. def initialize(scalar) @scalar = scalar end @@ -3308,6 +3326,8 @@ class Resolv class Coord + # Regular expression LOC Coord must match. + Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/ ## @@ -3337,6 +3357,7 @@ class Resolv end end + # Internal use; use self.create. def initialize(coordinates,orientation) unless coordinates.kind_of?(String) raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}") @@ -3399,6 +3420,8 @@ class Resolv class Alt + # Regular expression LOC Alt must match. + Regex = /^([+-]*\d+\.*\d*)[m]$/ ## @@ -3424,6 +3447,7 @@ class Resolv end end + # Internal use; use self.create. def initialize(altitude) @altitude = altitude end diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 1225cbe5cb..b52dd1b9d3 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,16 +9,15 @@ require "rbconfig" module Gem - VERSION = "3.7.0.dev" + VERSION = "4.1.0.dev" end -# Must be first since it unloads the prelude from 1.9.2 -require_relative "rubygems/compatibility" - require_relative "rubygems/defaults" require_relative "rubygems/deprecate" require_relative "rubygems/errors" require_relative "rubygems/target_rbconfig" +require_relative "rubygems/win_platform" +require_relative "rubygems/util/atomic_file_writer" ## # RubyGems is the Ruby standard for publishing and managing third party @@ -39,7 +38,7 @@ require_relative "rubygems/target_rbconfig" # Further RubyGems documentation can be found at: # # * {RubyGems Guides}[https://guides.rubygems.org] -# * {RubyGems API}[https://www.rubydoc.info/github/rubygems/rubygems] (also available from +# * {RubyGems API}[https://www.rubydoc.info/github/ruby/rubygems] (also available from # <tt>gem server</tt>) # # == RubyGems Plugins @@ -71,7 +70,7 @@ require_relative "rubygems/target_rbconfig" # == Bugs # # You can submit bugs to the -# {RubyGems bug tracker}[https://github.com/rubygems/rubygems/issues] +# {RubyGems bug tracker}[https://github.com/ruby/rubygems/issues] # on GitHub # # == Credits @@ -107,7 +106,7 @@ require_relative "rubygems/target_rbconfig" # # == License # -# See {LICENSE.txt}[https://github.com/rubygems/rubygems/blob/master/LICENSE.txt] for permissions. +# See {LICENSE.txt}[https://github.com/ruby/rubygems/blob/master/LICENSE.txt] for permissions. # # Thanks! # @@ -116,18 +115,6 @@ require_relative "rubygems/target_rbconfig" module Gem RUBYGEMS_DIR = __dir__ - ## - # An Array of Regexps that match windows Ruby platforms. - - WIN_PATTERNS = [ - /bccwin/i, - /cygwin/i, - /djgpp/i, - /mingw/i, - /mswin/i, - /wince/i, - ].freeze - GEM_DEP_FILES = %w[ gem.deps.rb gems.rb @@ -163,8 +150,6 @@ module Gem DEFAULT_SOURCE_DATE_EPOCH = 315_619_200 - @@win_platform = nil - @configuration = nil @gemdeps = nil @loaded_specs = {} @@ -227,7 +212,7 @@ module Gem finish_resolve rs end - def self.finish_resolve(request_set=Gem::RequestSet.new) + def self.finish_resolve(request_set = Gem::RequestSet.new) request_set.import Gem::Specification.unresolved_deps.values request_set.import Gem.loaded_specs.values.map {|s| Gem::Dependency.new(s.name, s.version) } @@ -249,6 +234,16 @@ module Gem find_spec_for_exe(name, exec_name, requirements).bin_file exec_name end + def self.find_and_activate_spec_for_exe(name, exec_name, requirements) + spec = find_spec_for_exe name, exec_name, requirements + Gem::LOADED_SPECS_MUTEX.synchronize do + spec.activate + finish_resolve + end + spec + end + private_class_method :find_and_activate_spec_for_exe + def self.find_spec_for_exe(name, exec_name, requirements) raise ArgumentError, "you must supply exec_name" unless exec_name @@ -274,6 +269,42 @@ module Gem private_class_method :find_spec_for_exe ## + # Find and load the full path to the executable for gem +name+. If the + # +exec_name+ is not given, an exception will be raised, otherwise the + # specified executable's path is returned. +requirements+ allows + # you to specify specific gem versions. + # + # A side effect of this method is that it will activate the gem that + # contains the executable. + # + # This method should *only* be used in bin stub files. + + def self.activate_and_load_bin_path(name, exec_name = nil, *requirements) + spec = find_and_activate_spec_for_exe name, exec_name, requirements + + if spec.name == "bundler" + # Old versions of Bundler need a workaround to support nested `bundle + # exec` invocations by overriding `Gem.activate_bin_path`. However, + # RubyGems now uses this new `Gem.activate_and_load_bin_path` helper in + # binstubs, which is of course not overridden in Bundler since it didn't + # exist at the time. So, include the override here to workaround that. + load ENV["BUNDLE_BIN_PATH"] if ENV["BUNDLE_BIN_PATH"] && spec.version <= Gem::Version.create("2.5.22") + + # Make sure there's no version of Bundler in `$LOAD_PATH` that's different + # from the version we just activated. If that was the case (it happens + # when testing Bundler from ruby/ruby), we would load Bundler extensions + # to RubyGems from the copy in `$LOAD_PATH` but then load the binstub from + # an installed copy, causing those copies to be mixed and yet more + # redefinition warnings. + # + require_path = $LOAD_PATH.resolve_feature_path("bundler").last.delete_suffix("/bundler.rb") + Gem.load_bundler_extensions(spec.version) if spec.full_require_paths.include?(require_path) + end + + load spec.bin_file(exec_name) + end + + ## # Find the full path to the executable for gem +name+. If the +exec_name+ # is not given, an exception will be raised, otherwise the # specified executable's path is returned. +requirements+ allows @@ -285,12 +316,7 @@ module Gem # This method should *only* be used in bin stub files. def self.activate_bin_path(name, exec_name = nil, *requirements) # :nodoc: - spec = find_spec_for_exe name, exec_name, requirements - Gem::LOADED_SPECS_MUTEX.synchronize do - spec.activate - finish_resolve - end - spec.bin_file exec_name + find_and_activate_spec_for_exe(name, exec_name, requirements).bin_file exec_name end ## @@ -303,7 +329,7 @@ module Gem ## # The path where gem executables are to be installed. - def self.bindir(install_dir=Gem.dir) + def self.bindir(install_dir = Gem.dir) return File.join install_dir, "bin" unless install_dir.to_s == Gem.default_dir.to_s Gem.default_bindir @@ -312,7 +338,7 @@ module Gem ## # The path were rubygems plugins are to be installed. - def self.plugindir(install_dir=Gem.dir) + def self.plugindir(install_dir = Gem.dir) File.join install_dir, "plugins" end @@ -496,7 +522,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Note that find_files will return all files even if they are from different # versions of the same gem. See also find_latest_files - def self.find_files(glob, check_load_path=true) + def self.find_files(glob, check_load_path = true) files = [] files = find_files_from_load_path glob if check_load_path @@ -533,7 +559,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Unlike find_files, find_latest_files will return only files from the # latest version of a gem. - def self.find_latest_files(glob, check_load_path=true) + def self.find_latest_files(glob, check_load_path = true) files = [] files = find_files_from_load_path glob if check_load_path @@ -637,6 +663,30 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end ## + # Load Bundler extensions to RubyGems, making sure to avoid redefinition + # warnings in platform constants + + def self.load_bundler_extensions(version) + return unless version <= Gem::Version.create("2.6.9") + + previous_platforms = {} + + platform_const_list = ["JAVA", "MSWIN", "MSWIN64", "MINGW", "X64_MINGW_LEGACY", "X64_MINGW", "UNIVERSAL_MINGW", "WINDOWS", "X64_LINUX", "X64_LINUX_MUSL"] + + platform_const_list.each do |platform| + previous_platforms[platform] = Gem::Platform.const_get(platform) + Gem::Platform.send(:remove_const, platform) + end + + require "bundler/rubygems_ext" + + platform_const_list.each do |platform| + Gem::Platform.send(:remove_const, platform) if Gem::Platform.const_defined?(platform) + Gem::Platform.const_set(platform, previous_platforms[platform]) + end + end + + ## # The file name and line number of the caller of the caller of this method. # # +depth+ is how many layers up the call stack it should go. @@ -784,14 +834,12 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end ## - # Safely write a file in binary mode on all platforms. + # Atomically write a file in binary mode on all platforms. def self.write_binary(path, data) - File.binwrite(path, data) - rescue Errno::ENOSPC - # If we ran out of space but the file exists, it's *guaranteed* to be corrupted. - File.delete(path) if File.exist?(path) - raise + Gem::AtomicFileWriter.open(path) do |file| + file.write(data) + end end ## @@ -1030,18 +1078,6 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end ## - # Is this a windows platform? - - def self.win_platform? - if @@win_platform.nil? - ruby_platform = RbConfig::CONFIG["host_os"] - @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil? - end - - @@win_platform - end - - ## # Is this a java platform? def self.java_platform? diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index a09e8ed0e1..0ed7fc60bb 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -34,15 +34,6 @@ class Gem::BasicSpecification internal_init end - def self.default_specifications_dir - Gem.default_specifications_dir - end - - class << self - extend Gem::Deprecate - rubygems_deprecate :default_specifications_dir, "Gem.default_specifications_dir" - end - ## # The path to the gem.build_complete file within the extension install # directory. @@ -199,6 +190,9 @@ class Gem::BasicSpecification File.expand_path(File.join(gems_dir, full_name, "data", name)) end + extend Gem::Deprecate + rubygems_deprecate :datadir, :none, "4.1" + ## # Full path of the target library file. # If the file is not in this gem, return nil. @@ -256,6 +250,13 @@ class Gem::BasicSpecification raise NotImplementedError end + def installable_on_platform?(target_platform) # :nodoc: + return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform) + return true if Gem::Platform.new(platform) === target_platform + + false + end + def raw_require_paths # :nodoc: raise NotImplementedError end diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index 4ebbad1c0c..c930c2e19c 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -2,7 +2,10 @@ module Gem::BundlerVersionFinder def self.bundler_version + return if bundle_config_version == "system" + v = ENV["BUNDLER_VERSION"] + v = nil if v&.empty? v ||= bundle_update_bundler_version return if v == true @@ -64,9 +67,12 @@ module Gem::BundlerVersionFinder return unless gemfile - lockfile = case gemfile - when "gems.rb" then "gems.locked" - else "#{gemfile}.lock" + lockfile = ENV["BUNDLE_LOCKFILE"] + lockfile = nil if lockfile&.empty? + + lockfile ||= case gemfile + when "gems.rb" then "gems.locked" + else "#{gemfile}.lock" end return unless File.file?(lockfile) @@ -74,4 +80,33 @@ module Gem::BundlerVersionFinder File.read(lockfile) end private_class_method :lockfile_contents + + def self.bundle_config_version + config_file = bundler_config_file + return unless config_file && File.file?(config_file) + + contents = File.read(config_file) + contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/ + + $1 + end + private_class_method :bundle_config_version + + def self.bundler_config_file + # see Bundler::Settings#global_config_file and local_config_file + # global + if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? + ENV["BUNDLE_CONFIG"] + elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty? + ENV["BUNDLE_USER_CONFIG"] + elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty? + ENV["BUNDLE_USER_HOME"] + "config" + elsif Gem.user_home && !Gem.user_home.empty? + Gem.user_home + ".bundle/config" + else + # local + "config" + end + end + private_class_method :bundler_config_file end diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb index 3a149ea38e..d38363f293 100644 --- a/lib/rubygems/command.rb +++ b/lib/rubygems/command.rb @@ -117,7 +117,7 @@ class Gem::Command # Unhandled arguments (gem names, files, etc.) are left in # <tt>options[:args]</tt>. - def initialize(command, summary=nil, defaults={}) + def initialize(command, summary = nil, defaults = {}) @command = command @summary = summary @program_name = "gem #{command}" diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index 15834ce4dd..76b2fba835 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -58,7 +58,6 @@ class Gem::CommandManager :owner, :pristine, :push, - :query, :rdoc, :rebuild, :search, @@ -118,7 +117,7 @@ class Gem::CommandManager ## # Register the Symbol +command+ as a gem command. - def register_command(command, obj=false) + def register_command(command, obj = false) @commands[command] = obj end @@ -148,7 +147,7 @@ class Gem::CommandManager ## # Run the command specified by +args+. - def run(args, build_args=nil) + def run(args, build_args = nil) process_args(args, build_args) rescue StandardError, Gem::Timeout::Error => ex if ex.respond_to?(:detailed_message) @@ -165,7 +164,7 @@ class Gem::CommandManager terminate_interaction(1) end - def process_args(args, build_args=nil) + def process_args(args, build_args = nil) if args.empty? say Gem::Command::HELP terminate_interaction 1 diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 2ec8324141..cfe1f8ec3c 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -25,13 +25,6 @@ class Gem::Commands::BuildCommand < Gem::Command add_option "-o", "--output FILE", "output gem with the given filename" do |value, options| options[:output] = value end - - add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, options| - options[:build_path] = value - end - deprecate_option "-C", - version: "4.0", - extra_msg: "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead" end def arguments # :nodoc: diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 72dcf1dd17..fe03841ddb 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -158,7 +158,7 @@ class Gem::Commands::CertCommand < Gem::Command cert = Gem::Security.create_cert_email( email, key, - (Gem::Security::ONE_DAY * expiration_length_days) + Gem::Security::ONE_DAY * expiration_length_days ) Gem::Security.write cert, "gem-public_cert.pem" diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index 2888b6c55a..70d32013ba 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -239,11 +239,7 @@ You can use `i` command instead of `install`. # Loads post-install hooks def load_hooks # :nodoc: - if options[:install_as_default] - require_relative "../install_default_message" - else - require_relative "../install_message" - end + require_relative "../install_message" require_relative "../rdoc" end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 97f1646ba0..942b75fba1 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -88,6 +88,10 @@ If you have made modifications to an installed gem, the pristine command will revert them. All extensions are rebuilt and all bin stubs for the gem are regenerated after checking for modifications. +Rebuilding extensions also refreshes C-extension gems against updated system +libraries (for example after OS or package upgrades) to avoid mismatches like +outdated library version warnings. + If the cached gem cannot be found it will be downloaded. If --no-extensions is provided pristine will not attempt to restore a gem @@ -137,11 +141,14 @@ extensions will be restored. specs.group_by(&:full_name_with_location).values.each do |grouped_specs| spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first - unless only_executables_or_plugins? + only_executables = options[:only_executables] + only_plugins = options[:only_plugins] + + unless only_executables || only_plugins # Default gemspecs include changes provided by ruby-core installer that # can't currently be pristined (inclusion of compiled extension targets in # the file list). So stick to resetting executables if it's a default gem. - options[:only_executables] = true if spec.default_gem? + only_executables = true if spec.default_gem? end if options.key? :skip @@ -151,14 +158,14 @@ extensions will be restored. end end - unless spec.extensions.empty? || options[:extensions] || only_executables_or_plugins? + unless spec.extensions.empty? || options[:extensions] || only_executables || only_plugins say "Skipped #{spec.full_name_with_location}, it needs to compile an extension" next end gem = spec.cache_file - unless File.exist?(gem) || only_executables_or_plugins? + unless File.exist?(gem) || only_executables || only_plugins require_relative "../remote_fetcher" say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..." @@ -194,10 +201,10 @@ extensions will be restored. bin_dir: bin_dir, } - if options[:only_executables] + if only_executables installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin - elsif options[:only_plugins] + elsif only_plugins installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_plugins else @@ -208,10 +215,4 @@ extensions will be restored. say "Restored #{spec.full_name_with_location}" end end - - private - - def only_executables_or_plugins? - options[:only_executables] || options[:only_plugins] - end end diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb deleted file mode 100644 index 3b527974a3..0000000000 --- a/lib/rubygems/commands/query_command.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -require_relative "../command" -require_relative "../query_utils" -require_relative "../deprecate" - -class Gem::Commands::QueryCommand < Gem::Command - extend Gem::Deprecate - rubygems_deprecate_command - - include Gem::QueryUtils - - alias_method :warning_without_suggested_alternatives, :deprecation_warning - def deprecation_warning - warning_without_suggested_alternatives - - message = "It is recommended that you use `gem search` or `gem list` instead.\n" - alert_warning message unless Gem::Deprecate.skip - end - - def initialize(name = "query", summary = "Query gem information in local or remote repositories") - super name, summary, - domain: :local, details: false, versions: true, - installed: nil, version: Gem::Requirement.default - - add_option("-n", "--name-matches REGEXP", - "Name of gem(s) to query on matches the", - "provided REGEXP") do |value, options| - options[:name] = /#{value}/i - end - - add_query_options - end - - def description # :nodoc: - <<-EOF -The query command is the basis for the list and search commands. - -You should really use the list and search commands instead. This command -is too hard to use. - EOF - end -end diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb index 77a474ef1d..23b9d7b3ba 100644 --- a/lib/rubygems/commands/rebuild_command.rb +++ b/lib/rubygems/commands/rebuild_command.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "date" require "digest" require "fileutils" require "tmpdir" diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 301ce25314..175599967c 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -7,8 +7,8 @@ require_relative "../command" # RubyGems checkout or tarball. class Gem::Commands::SetupCommand < Gem::Command - HISTORY_HEADER = %r{^#\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} - VERSION_MATCHER = %r{^#\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} + HISTORY_HEADER = %r{^##\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} + VERSION_MATCHER = %r{^##\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} ENV_PATHS = %w[/usr/bin/env /bin/env].freeze @@ -393,16 +393,20 @@ By default, this RubyGems will install gem as: Dir.chdir("bundler") do built_gem = Gem::Package.build(new_bundler_spec) begin - Gem::Installer.at( + installer = Gem::Installer.at( built_gem, env_shebang: options[:env_shebang], format_executable: options[:format_executable], force: options[:force], - install_as_default: true, bin_dir: bin_dir, install_dir: default_dir, wrappers: true - ).install + ) + # We need to install only executable and default spec files. + # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. + installer.extract_bin + installer.generate_bin + installer.write_default_spec ensure FileUtils.rm_f built_gem end diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 976f4a4ea2..7e5c2a2465 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -18,6 +18,14 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:add] = value end + add_option "--append SOURCE_URI", "Append source (can be used multiple times)" do |value, options| + options[:append] = value + end + + add_option "-p", "--prepend SOURCE_URI", "Prepend source (can be used multiple times)" do |value, options| + options[:prepend] = value + end + add_option "-l", "--list", "List sources" do |value, options| options[:list] = value end @@ -26,8 +34,7 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:remove] = value end - add_option "-c", "--clear-all", - "Remove all sources (clear the cache)" do |value, options| + add_option "-c", "--clear-all", "Remove all sources (clear the cache)" do |value, options| options[:clear_all] = value end @@ -68,6 +75,60 @@ class Gem::Commands::SourcesCommand < Gem::Command end end + def append_source(source_uri) # :nodoc: + check_rubygems_https source_uri + + source = Gem::Source.new source_uri + + check_typo_squatting(source) + + begin + source.load_specs :released + was_present = Gem.sources.include?(source) + Gem.sources.append source + Gem.configuration.write + + if was_present + say "#{source_uri} moved to end of sources" + else + say "#{source_uri} added to sources" + end + rescue Gem::URI::Error, ArgumentError + say "#{source_uri} is not a URI" + terminate_interaction 1 + rescue Gem::RemoteFetcher::FetchError => e + say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" + terminate_interaction 1 + end + end + + def prepend_source(source_uri) # :nodoc: + check_rubygems_https source_uri + + source = Gem::Source.new source_uri + + check_typo_squatting(source) + + begin + source.load_specs :released + was_present = Gem.sources.include?(source) + Gem.sources.prepend source + Gem.configuration.write + + if was_present + say "#{source_uri} moved to top of sources" + else + say "#{source_uri} added to sources" + end + rescue Gem::URI::Error, ArgumentError + say "#{source_uri} is not a URI" + terminate_interaction 1 + rescue Gem::RemoteFetcher::FetchError => e + say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" + terminate_interaction 1 + end + end + def check_typo_squatting(source) if source.typo_squatting?("rubygems.org") question = <<-QUESTION.chomp @@ -128,7 +189,7 @@ yourself to use your own gem server. Without any arguments the sources lists your currently configured sources: $ gem sources - *** CURRENT SOURCES *** + *** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** https://rubygems.org @@ -147,33 +208,49 @@ Since all of these sources point to the same set of gems you only need one of them in your list. https://rubygems.org is recommended as it brings the protections of an SSL connection to gem downloads. -To add a source use the --add argument: +To add a private gem source use the --prepend argument to insert it before +the default source. This is usually the best place for private gem sources: - $ gem sources --add https://rubygems.org - https://rubygems.org added to sources + $ gem sources --prepend https://my.private.source + https://my.private.source added to sources RubyGems will check to see if gems can be installed from the source given before it is added. +To add or move a source after all other sources, use --append: + + $ gem sources --append https://rubygems.org + https://rubygems.org moved to end of sources + To remove a source use the --remove argument: - $ gem sources --remove https://rubygems.org/ - https://rubygems.org/ removed from sources + $ gem sources --remove https://my.private.source/ + https://my.private.source/ removed from sources EOF end def list # :nodoc: - say "*** CURRENT SOURCES ***" + if configured_sources + header = "*** CURRENT SOURCES ***" + list = configured_sources + else + header = "*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***" + list = Gem.sources + end + + say header say - Gem.sources.each do |src| + list.each do |src| say src end end def list? # :nodoc: !(options[:add] || + options[:prepend] || + options[:append] || options[:clear_all] || options[:remove] || options[:update]) @@ -182,11 +259,13 @@ To remove a source use the --remove argument: def execute clear_all if options[:clear_all] - source_uri = options[:add] - add_source source_uri if source_uri + add_source options[:add] if options[:add] + + prepend_source options[:prepend] if options[:prepend] + + append_source options[:append] if options[:append] - source_uri = options[:remove] - remove_source source_uri if source_uri + remove_source options[:remove] if options[:remove] update if options[:update] @@ -194,13 +273,21 @@ To remove a source use the --remove argument: end def remove_source(source_uri) # :nodoc: - if Gem.sources.include? source_uri - Gem.sources.delete source_uri + source = Gem::Source.new source_uri + + if configured_sources&.include? source + Gem.sources.delete source Gem.configuration.write - say "#{source_uri} removed from sources" + if default_sources.include?(source) && configured_sources.one? + alert_warning "Removing a default source when it is the only source has no effect. Add a different source to #{config_file_name} if you want to stop using it as a source." + else + say "#{source_uri} removed from sources" + end + elsif configured_sources + say "source #{source_uri} cannot be removed because it's not present in #{config_file_name}" else - say "source #{source_uri} not present in cache" + say "source #{source_uri} cannot be removed because there are no configured sources in #{config_file_name}" end end @@ -224,4 +311,21 @@ To remove a source use the --remove argument: say "*** Unable to remove #{desc} source cache ***" end end + + private + + def default_sources + Gem::SourceList.from(Gem.default_sources) + end + + def configured_sources + return @configured_sources if defined?(@configured_sources) + + configuration_sources = Gem.configuration.sources + @configured_sources = Gem::SourceList.from(configuration_sources) if configuration_sources + end + + def config_file_name + Gem.configuration.config_file_name + end end diff --git a/lib/rubygems/compatibility.rb b/lib/rubygems/compatibility.rb deleted file mode 100644 index 0d9df56f8a..0000000000 --- a/lib/rubygems/compatibility.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -#-- -# This file contains all sorts of little compatibility hacks that we've -# had to introduce over the years. Quarantining them into one file helps -# us know when we can get rid of them. -# -# Ruby 1.9.x has introduced some things that are awkward, and we need to -# support them, so we define some constants to use later. -# -# TODO remove at RubyGems 4 -#++ - -module Gem - # :stopdoc: - - RubyGemsVersion = VERSION - deprecate_constant(:RubyGemsVersion) - - RbConfigPriorities = %w[ - MAJOR - MINOR - TEENY - EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name - ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir - rubylibdir - ].freeze - - if defined?(ConfigMap) - RbConfigPriorities.each do |key| - ConfigMap[key.to_sym] = RbConfig::CONFIG[key] - end - else - ## - # Configuration settings from ::RbConfig - ConfigMap = Hash.new do |cm, key| - cm[key] = RbConfig::CONFIG[key.to_s] - end - deprecate_constant(:ConfigMap) - end -end diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index a2bcb6dfbc..e58a83f6b7 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -345,7 +345,7 @@ if you believe they were disclosed to a third party. require "fileutils" FileUtils.mkdir_p(dirname) - permissions = 0o600 & (~File.umask) + permissions = 0o600 & ~File.umask File.open(credentials_path, "w", permissions) do |f| f.write self.class.dump_with_rubygems_yaml(config) end diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index 073966b696..3a9bdbdc9d 100644 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -64,8 +64,11 @@ module Kernel rp end - Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless - resolved_path + next if resolved_path + + Kernel.send(:gem, name, Gem::Requirement.default_prerelease) + + Gem.load_bundler_extensions(Gem.loaded_specs[name].version) if name == "bundler" next end diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index db07681a17..90f09fc191 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -13,7 +13,7 @@ module Gem # An Array of the default sources that come with RubyGems def self.default_sources - %w[https://rubygems.org/] + @default_sources ||= %w[https://rubygems.org/] end ## diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index d1ec9222af..1e91f493a6 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -217,7 +217,7 @@ class Gem::Dependency # NOTE: Unlike #matches_spec? this method does not return true when the # version is a prerelease version unless this is a prerelease dependency. - def match?(obj, version=nil, allow_prerelease=false) + def match?(obj, version = nil, allow_prerelease = false) if !version name = obj.name version = obj.version diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index b119dca1cf..6a6dfa5c20 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -7,14 +7,12 @@ require_relative "installer" require_relative "spec_fetcher" require_relative "user_interaction" require_relative "available_set" -require_relative "deprecate" ## # Installs a gem along with all its dependencies from local and remote gems. class Gem::DependencyInstaller include Gem::UserInteraction - extend Gem::Deprecate DEFAULT_OPTIONS = { # :nodoc: env_shebang: false, @@ -28,7 +26,6 @@ class Gem::DependencyInstaller wrappers: true, build_args: nil, build_docs_in_background: false, - install_as_default: false, }.freeze ## @@ -86,8 +83,8 @@ class Gem::DependencyInstaller @user_install = options[:user_install] @wrappers = options[:wrappers] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @build_docs_in_background = options[:build_docs_in_background] - @install_as_default = options[:install_as_default] @dir_mode = options[:dir_mode] @data_mode = options[:data_mode] @prog_mode = options[:prog_mode] @@ -121,78 +118,6 @@ class Gem::DependencyInstaller @domain == :both || @domain == :remote end - ## - # Returns a list of pairs of gemspecs and source_uris that match - # Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources) - # sources. Gems are sorted with newer gems preferred over older gems, and - # local gems preferred over remote gems. - - def find_gems_with_sources(dep, best_only=false) # :nodoc: - set = Gem::AvailableSet.new - - if consider_local? - sl = Gem::Source::Local.new - - if spec = sl.find_gem(dep.name) - if dep.matches_spec? spec - set.add spec, sl - end - end - end - - if consider_remote? - begin - # This is pulled from #spec_for_dependency to allow - # us to filter tuples before fetching specs. - tuples, errors = Gem::SpecFetcher.fetcher.search_for_dependency dep - - if best_only && !tuples.empty? - tuples.sort! do |a,b| - if b[0].version == a[0].version - if b[0].platform != Gem::Platform::RUBY - 1 - else - -1 - end - else - b[0].version <=> a[0].version - end - end - tuples = [tuples.first] - end - - specs = [] - tuples.each do |tup, source| - spec = source.fetch_spec(tup) - rescue Gem::RemoteFetcher::FetchError => e - errors << Gem::SourceFetchProblem.new(source, e) - else - specs << [spec, source] - end - - if @errors - @errors += errors - else - @errors = errors - end - - set << specs - rescue Gem::RemoteFetcher::FetchError => e - # FIX if there is a problem talking to the network, we either need to always tell - # the user (no really_verbose) or fail hard, not silently tell them that we just - # couldn't find their requested gem. - verbose do - "Error fetching remote data:\t\t#{e.message}\n" \ - "Falling back to local-only install" - end - @domain = :local - end - end - - set - end - rubygems_deprecate :find_gems_with_sources - def in_background(what) # :nodoc: fork_happened = false if @build_docs_in_background && Process.respond_to?(:fork) @@ -230,6 +155,7 @@ class Gem::DependencyInstaller options = { bin_dir: @bin_dir, build_args: @build_args, + build_jobs: @build_jobs, document: @document, env_shebang: @env_shebang, force: @force, @@ -240,7 +166,6 @@ class Gem::DependencyInstaller user_install: @user_install, wrappers: @wrappers, build_root: @build_root, - install_as_default: @install_as_default, dir_mode: @dir_mode, data_mode: @data_mode, prog_mode: @prog_mode, diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index ad5e59e8c1..d50cfe2d54 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -7,7 +7,6 @@ #++ require_relative "vendored_tsort" -require_relative "deprecate" ## # Gem::DependencyList is used for installing and uninstalling gems in the @@ -140,7 +139,7 @@ class Gem::DependencyList # If removing the gemspec creates breaks a currently ok dependency, then it # is NOT ok to remove the gemspec. - def ok_to_remove?(full_name, check_dev=true) + def ok_to_remove?(full_name, check_dev = true) gem_to_remove = find_name full_name # If the state is inconsistent, at least don't crash diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb index 7d24f9cbfc..eb503bb269 100644 --- a/lib/rubygems/deprecate.rb +++ b/lib/rubygems/deprecate.rb @@ -1,75 +1,75 @@ # frozen_string_literal: true -## -# Provides 3 methods for declaring when something is going away. -# -# +deprecate(name, repl, year, month)+: -# Indicate something may be removed on/after a certain date. -# -# +rubygems_deprecate(name, replacement=:none)+: -# Indicate something will be removed in the next major RubyGems version, -# and (optionally) a replacement for it. -# -# +rubygems_deprecate_command+: -# Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be -# removed in the next RubyGems version. -# -# Also provides +skip_during+ for temporarily turning off deprecation warnings. -# This is intended to be used in the test suite, so deprecation warnings -# don't cause test failures if you need to make sure stderr is otherwise empty. -# -# -# Example usage of +deprecate+ and +rubygems_deprecate+: -# -# class Legacy -# def self.some_class_method -# # ... -# end -# -# def some_instance_method -# # ... -# end -# -# def some_old_method -# # ... -# end -# -# extend Gem::Deprecate -# deprecate :some_instance_method, "X.z", 2011, 4 -# rubygems_deprecate :some_old_method, "Modern#some_new_method" -# -# class << self -# extend Gem::Deprecate -# deprecate :some_class_method, :none, 2011, 4 -# end -# end -# -# -# Example usage of +rubygems_deprecate_command+: -# -# class Gem::Commands::QueryCommand < Gem::Command -# extend Gem::Deprecate -# rubygems_deprecate_command -# -# # ... -# end -# -# -# Example usage of +skip_during+: -# -# class TestSomething < Gem::Testcase -# def test_some_thing_with_deprecations -# Gem::Deprecate.skip_during do -# actual_stdout, actual_stderr = capture_output do -# Gem.something_deprecated -# end -# assert_empty actual_stdout -# assert_equal(expected, actual_stderr) -# end -# end -# end - module Gem + ## + # Provides 3 methods for declaring when something is going away. + # + # <tt>deprecate(name, repl, year, month)</tt>: + # Indicate something may be removed on/after a certain date. + # + # <tt>rubygems_deprecate(name, replacement=:none)</tt>: + # Indicate something will be removed in the next major RubyGems version, + # and (optionally) a replacement for it. + # + # +rubygems_deprecate_command+: + # Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be + # removed in the next RubyGems version. + # + # Also provides +skip_during+ for temporarily turning off deprecation warnings. + # This is intended to be used in the test suite, so deprecation warnings + # don't cause test failures if you need to make sure stderr is otherwise empty. + # + # + # Example usage of +deprecate+ and +rubygems_deprecate+: + # + # class Legacy + # def self.some_class_method + # # ... + # end + # + # def some_instance_method + # # ... + # end + # + # def some_old_method + # # ... + # end + # + # extend Gem::Deprecate + # deprecate :some_instance_method, "X.z", 2011, 4 + # rubygems_deprecate :some_old_method, "Modern#some_new_method" + # + # class << self + # extend Gem::Deprecate + # deprecate :some_class_method, :none, 2011, 4 + # end + # end + # + # + # Example usage of +rubygems_deprecate_command+: + # + # class Gem::Commands::QueryCommand < Gem::Command + # extend Gem::Deprecate + # rubygems_deprecate_command + # + # # ... + # end + # + # + # Example usage of +skip_during+: + # + # class TestSomething < Gem::Testcase + # def test_some_thing_with_deprecations + # Gem::Deprecate.skip_during do + # actual_stdout, actual_stderr = capture_output do + # Gem.something_deprecated + # end + # assert_empty actual_stdout + # assert_equal(expected, actual_stderr) + # end + # end + # end + module Deprecate def self.skip # :nodoc: @skip ||= false @@ -126,17 +126,18 @@ module Gem # telling the user of +repl+ (unless +repl+ is :none) and the # Rubygems version that it is planned to go away. - def rubygems_deprecate(name, replacement=:none) + def rubygems_deprecate(name, replacement = :none, version = nil) class_eval do old = "_deprecated_#{name}" alias_method old, name define_method name do |*args, &block| klass = is_a? Module target = klass ? "#{self}." : "#{self.class}#" + version ||= Gem::Deprecate.next_rubygems_major_version msg = [ "NOTE: #{target}#{name} is deprecated", replacement == :none ? " with no replacement" : "; use #{replacement} instead", - ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}", + ". It will be removed in Rubygems #{version}", "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", ] warn "#{msg.join}." unless Gem::Deprecate.skip @@ -147,13 +148,14 @@ module Gem end # Deprecation method to deprecate Rubygems commands - def rubygems_deprecate_command(version = Gem::Deprecate.next_rubygems_major_version) + def rubygems_deprecate_command(version = nil) class_eval do define_method "deprecated?" do true end define_method "deprecation_warning" do + version ||= Gem::Deprecate.next_rubygems_major_version msg = [ "#{command} command is deprecated", ". It will be removed in Rubygems #{version}.\n", diff --git a/lib/rubygems/doctor.rb b/lib/rubygems/doctor.rb index 56b7c081eb..4f26260d83 100644 --- a/lib/rubygems/doctor.rb +++ b/lib/rubygems/doctor.rb @@ -113,7 +113,7 @@ class Gem::Doctor next if installed_specs.include? basename next if /^rubygems-\d/.match?(basename) next if sub_directory == "specifications" && basename == "default" - next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ (basename) + next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ basename type = File.directory?(child) ? "directory" : "file" diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb index 57fb3eb120..4bbc5217e0 100644 --- a/lib/rubygems/errors.rb +++ b/lib/rubygems/errors.rb @@ -26,7 +26,7 @@ module Gem # system. Instead of rescuing from this class, make sure to rescue from the # superclass Gem::LoadError to catch all types of load errors. class MissingSpecError < Gem::LoadError - def initialize(name, requirement, extra_message=nil) + def initialize(name, requirement, extra_message = nil) @name = name @requirement = requirement @extra_message = extra_message diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index 793324b875..40485bbadf 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require_relative "deprecate" require_relative "unknown_command_spell_checker" ## @@ -21,20 +20,11 @@ class Gem::UnknownCommandError < Gem::Exception end def self.attach_correctable - return if defined?(@attached) + return if method_defined?(:corrections) - if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable) - if DidYouMean.respond_to?(:correct_error) - DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker) - else - DidYouMean::SPELL_CHECKERS["Gem::UnknownCommandError"] = - Gem::UnknownCommandSpellChecker - - prepend DidYouMean::Correctable - end + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker) end - - @attached = true end end @@ -110,7 +100,7 @@ class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException # and +version+. Any +errors+ encountered when attempting to find the gem # are also stored. - def initialize(name, version, errors=nil) + def initialize(name, version, errors = nil) super "Could not find a valid gem '#{name}' (#{version}) locally or in a repository" @name = name @@ -261,7 +251,7 @@ class Gem::UnsatisfiableDependencyError < Gem::DependencyError # Creates a new UnsatisfiableDependencyError for the unsatisfiable # Gem::Resolver::DependencyRequest +dep+ - def initialize(dep, platform_mismatch=nil) + def initialize(dep, platform_mismatch = nil) if platform_mismatch && !platform_mismatch.empty? plats = platform_mismatch.map {|x| x.platform.to_s }.sort.uniq super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(", ")}" diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 05cd735bd9..62d36bcf48 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -11,6 +11,9 @@ require_relative "../user_interaction" class Gem::Ext::Builder include Gem::UserInteraction + class NoMakefileError < Gem::InstallError + end + attr_accessor :build_args # :nodoc: def self.class_name @@ -19,9 +22,10 @@ class Gem::Ext::Builder end def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"], - target_rbconfig: Gem.target_rbconfig) + target_rbconfig: Gem.target_rbconfig, n_jobs: nil) unless File.exist? File.join(make_dir, "Makefile") - raise Gem::InstallError, "Makefile not found" + # No makefile exists, nothing to do. + raise NoMakefileError, "No Makefile found in #{make_dir}" end # try to find make program from Ruby configure arguments first @@ -30,8 +34,18 @@ class Gem::Ext::Builder make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make" make_program = shellsplit(make_program_name) + is_nmake = /\bnmake/i.match?(make_program_name) # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform. - destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + destdir = !is_nmake || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + + # nmake doesn't support parallel build + unless is_nmake + have_make_arguments = make_program.size > 1 + + if !have_make_arguments && !ENV["MAKEFLAGS"] && n_jobs + make_program << "-j#{n_jobs}" + end + end env = [destdir] @@ -143,13 +157,12 @@ class Gem::Ext::Builder # have build arguments, saved, set +build_args+ which is an ARGV-style # array. - def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig) + def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig, build_jobs = nil) @spec = spec @build_args = build_args @gem_dir = spec.full_gem_path @target_rbconfig = target_rbconfig - - @ran_rake = false + @build_jobs = build_jobs end ## @@ -162,10 +175,9 @@ class Gem::Ext::Builder when /configure/ then Gem::Ext::ConfigureBuilder when /rakefile/i, /mkrf_conf/i then - @ran_rake = true Gem::Ext::RakeBuilder when /CMakeLists.txt/ then - Gem::Ext::CmakeBuilder + Gem::Ext::CmakeBuilder.new when /Cargo.toml/ then Gem::Ext::CargoBuilder.new else @@ -204,7 +216,7 @@ EOF FileUtils.mkdir_p dest_path results = builder.build(extension, dest_path, - results, @build_args, lib_dir, extension_dir, @target_rbconfig) + results, @build_args, lib_dir, extension_dir, @target_rbconfig, n_jobs: @build_jobs) verbose { results.join("\n") } @@ -235,8 +247,6 @@ EOF FileUtils.rm_f @spec.gem_build_complete_path @spec.extensions.each do |extension| - break if @ran_rake - build_extension extension, dest_path end diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 03024a640e..42dca3b102 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -15,7 +15,7 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder end def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "tempfile" require "fileutils" @@ -158,6 +158,10 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder # mkmf work properly. def linker_args cc_flag = self.class.shellsplit(makefile_config("CC")) + # Avoid to ccache like tool from Rust build + # see https://github.com/ruby/rubygems/pull/8521#issuecomment-2689854359 + # ex. CC="ccache gcc" or CC="sccache clang --any --args" + cc_flag.shift if cc_flag.size >= 2 && !cc_flag[1].start_with?("-") linker = cc_flag.shift link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] } diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb index 34564f668d..e660ed558b 100644 --- a/lib/rubygems/ext/cmake_builder.rb +++ b/lib/rubygems/ext/cmake_builder.rb @@ -1,21 +1,110 @@ # frozen_string_literal: true +# This builder creates extensions defined using CMake. Its is invoked if a Gem's spec file +# sets the `extension` property to a string that contains `CMakeLists.txt`. +# +# In general, CMake projects are built in two steps: +# +# * configure +# * build +# +# The builder follow this convention. First it runs a configuration step and then it runs a build step. +# +# CMake projects can be quite configurable - it is likely you will want to specify options when +# installing a gem. To pass options to CMake specify them after `--` in the gem install command. For example: +# +# gem install <gem_name> -- --preset <preset_name> +# +# Note that options are ONLY sent to the configure step - it is not currently possible to specify +# options for the build step. If this becomes and issue then the CMake builder can be updated to +# support build options. +# +# Useful options to know are: +# +# -G to specify a generator (-G Ninja is recommended) +# -D<CMAKE_VARIABLE> to set a CMake variable (for example -DCMAKE_BUILD_TYPE=Release) +# --preset <preset_name> to use a preset +# +# If the Gem author provides presets, via CMakePresets.json file, you will likely want to use one of them. +# If not, you may wish to specify a generator. Ninja is recommended because it can build projects in parallel +# and thus much faster than building them serially like Make does. + class Gem::Ext::CmakeBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + attr_accessor :runner, :profile + def initialize + @runner = self.class.method(:run) + @profile = :release + end + + def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring" end - unless File.exist?(File.join(cmake_dir, "Makefile")) - require_relative "../command" - cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args] + # Figure the build dir + build_dir = File.join(cmake_dir, "build") - run cmd, results, class_name, cmake_dir - end + # Check if the gem defined presets + check_presets(cmake_dir, args, results) + + # Configure + configure(cmake_dir, build_dir, dest_path, args, results) - make dest_path, results, cmake_dir, target_rbconfig: target_rbconfig + # Compile + compile(cmake_dir, build_dir, args, results) results end + + def configure(cmake_dir, build_dir, install_dir, args, results) + cmd = ["cmake", + cmake_dir, + "-B", + build_dir, + "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=#{install_dir}", # Windows + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=#{install_dir}", # Not Windows + *Gem::Command.build_args, + *args] + + runner.call(cmd, results, "cmake_configure", cmake_dir) + end + + def compile(cmake_dir, build_dir, args, results) + cmd = ["cmake", + "--build", + build_dir.to_s, + "--config", + @profile.to_s] + + runner.call(cmd, results, "cmake_compile", cmake_dir) + end + + private + + def check_presets(cmake_dir, args, results) + # Return if the user specified a preset + return unless args.grep(/--preset/i).empty? + + cmd = ["cmake", + "--list-presets"] + + presets = Array.new + begin + runner.call(cmd, presets, "cmake_presets", cmake_dir) + + # Remove the first two lines of the array which is the current_directory and the command + # that was run + presets = presets[2..].join + results << <<~EOS + The gem author provided a list of presets that can be used to build the gem. To use a preset specify it on the command line: + + gem install <gem_name> -- --preset <preset_name> + + #{presets} + EOS + rescue Gem::InstallError + # Do nothing, CMakePresets.json was not included in the Gem + end + end end diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb index d91b1ec5e8..230b214b3c 100644 --- a/lib/rubygems/ext/configure_builder.rb +++ b/lib/rubygems/ext/configure_builder.rb @@ -7,8 +7,8 @@ #++ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + def self.build(extension, dest_path, results, args = [], lib_dir = nil, configure_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring" end @@ -19,7 +19,7 @@ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder run cmd, results, class_name, configure_dir end - make dest_path, results, configure_dir, target_rbconfig: target_rbconfig + make dest_path, results, configure_dir, target_rbconfig: target_rbconfig, n_jobs: n_jobs results end diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index e652a221f8..822454355d 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -7,8 +7,8 @@ #++ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "fileutils" require "tempfile" @@ -41,7 +41,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder ENV["DESTDIR"] = nil - make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig + make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs full_tmp_dest = File.join(extension_dir, tmp_dest_relative) @@ -66,6 +66,10 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder end results + rescue Gem::Ext::Builder::NoMakefileError => error + results << error.message + results << "Skipping make for #{extension} as no Makefile was found." + # We are good, do not re-raise the error. ensure FileUtils.rm_rf tmp_dest if tmp_dest end diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb index 8edd8d1373..d702d7f339 100644 --- a/lib/rubygems/ext/rake_builder.rb +++ b/lib/rubygems/ext/rake_builder.rb @@ -7,8 +7,8 @@ #++ class Gem::Ext::RakeBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring" end diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb index 4cb924677f..e60cebd0cb 100644 --- a/lib/rubygems/gem_runner.rb +++ b/lib/rubygems/gem_runner.rb @@ -8,7 +8,6 @@ require_relative "../rubygems" require_relative "command_manager" -require_relative "deprecate" ## # Run an instance of the gem program. diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index d3176d4564..afe7957f43 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -263,7 +263,10 @@ module Gem::GemcutterUtilities port = server.addr[1].to_s url_with_port = "#{webauthn_url}?port=#{port}" - say "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option." + say "You have enabled multi-factor authentication. Please visit the following URL to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option." + say "" + say url_with_port + say "" threads = [WebauthnListener.listener_thread(host, server), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)] otp_thread = wait_for_otp_thread(*threads) @@ -316,7 +319,7 @@ module Gem::GemcutterUtilities end def get_scope_params(scope) - scope_params = { index_rubygems: true } + scope_params = { index_rubygems: true, push_rubygem: true } if scope scope_params = { scope => true } diff --git a/lib/rubygems/install_default_message.rb b/lib/rubygems/install_default_message.rb deleted file mode 100644 index 0640eaaf08..0000000000 --- a/lib/rubygems/install_default_message.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require_relative "../rubygems" -require_relative "user_interaction" - -## -# A post-install hook that displays "Successfully installed -# some_gem-1.0 as a default gem" - -Gem.post_install do |installer| - ui = Gem::DefaultUserInteraction.ui - ui.say "Successfully installed #{installer.spec.full_name} as a default gem" -end diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 0d0f0dc211..66cb5c049b 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -31,6 +31,15 @@ module Gem::InstallUpdateOptions options[:bin_dir] = File.expand_path(value) end + add_option(:"Install/Update", "-j", "--build-jobs VALUE", Integer, + "Specify the number of jobs to pass to `make` when installing", + "gems with native extensions.", + "Defaults to the number of processors.", + "This option is ignored on the mswin platform or", + "if the MAKEFLAGS environment variable is set.") do |value, options| + options[:build_jobs] = value + end + add_option(:"Install/Update", "--document [TYPES]", Array, "Generate documentation for installed gems", "List the documentation types you wish to", @@ -158,10 +167,9 @@ module Gem::InstallUpdateOptions options[:without_groups].concat v.map(&:intern) end - add_option(:"Install/Update", "--default", + add_option(:Deprecated, "--default", "Add the gem's full specification to", "specifications/default and extract only its bin") do |v,_o| - options[:install_as_default] = v end add_option(:"Install/Update", "--explain", diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 7f5d913ac4..914e413677 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -8,7 +8,6 @@ require_relative "installer_uninstaller_utils" require_relative "exceptions" -require_relative "deprecate" require_relative "package" require_relative "ext" require_relative "user_interaction" @@ -27,8 +26,6 @@ require_relative "user_interaction" # file. See Gem.pre_install and Gem.post_install for details. class Gem::Installer - extend Gem::Deprecate - ## # Paths where env(1) might live. Some systems are broken and have it in # /bin @@ -67,23 +64,6 @@ class Gem::Installer attr_reader :package class << self - # - # Changes in rubygems to lazily loading `rubygems/command` (in order to - # lazily load `optparse` as a side effect) affect bundler's custom installer - # which uses `Gem::Command` without requiring it (up until bundler 2.2.29). - # This hook is to compensate for that missing require. - # - # TODO: Remove when rubygems no longer supports running on bundler older - # than 2.2.29. - - def inherited(klass) - if klass.name == "Bundler::RubyGemsGemInstaller" - require "rubygems/command" - end - - super(klass) - end - ## # Overrides the executable format. # @@ -170,7 +150,7 @@ class Gem::Installer # process. If not set, then Gem::Command.build_args is used # :post_install_message:: Print gem post install message if true - def initialize(package, options={}) + def initialize(package, options = {}) require "fileutils" @options = options @@ -228,8 +208,7 @@ class Gem::Installer ruby_executable = true existing = io.read.slice(/ ^\s*( - gem \s | - load \s Gem\.bin_path\( | + Gem\.activate_and_load_bin_path\( | load \s Gem\.activate_bin_path\( ) (['"])(.*?)(\2), @@ -292,11 +271,7 @@ class Gem::Installer run_pre_install_hooks # Set loaded_from to ensure extension_dir is correct - if @options[:install_as_default] - spec.loaded_from = default_spec_file - else - spec.loaded_from = spec_file - end + spec.loaded_from = spec_file # Completely remove any previous gem files FileUtils.rm_rf gem_dir @@ -305,24 +280,17 @@ class Gem::Installer dir_mode = options[:dir_mode] FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 - if @options[:install_as_default] - extract_bin - write_default_spec - else - extract_files + extract_files - build_extensions - write_build_info_file - run_post_build_hooks - end + build_extensions + write_build_info_file + run_post_build_hooks generate_bin generate_plugins - unless @options[:install_as_default] - write_spec - write_cache_file - end + write_spec + write_cache_file File.chmod(dir_mode, gem_dir) if dir_mode @@ -411,15 +379,6 @@ class Gem::Installer end ## - # Unpacks the gem into the given directory. - - def unpack(directory) - @gem_dir = directory - extract_files - end - rubygems_deprecate :unpack - - ## # The location of the spec file that is installed. # @@ -427,12 +386,18 @@ class Gem::Installer File.join gem_home, "specifications", "#{spec.full_name}.gemspec" end + def default_spec_dir + dir = File.join(gem_home, "specifications", "default") + FileUtils.mkdir_p dir + dir + end + ## # The location of the default spec file for default gems. # def default_spec_file - File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec" + File.join default_spec_dir, "#{spec.full_name}.gemspec" end ## @@ -670,6 +635,7 @@ class Gem::Installer @build_root = options[:build_root] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @gem_home = @install_dir || user_install_dir || Gem.dir @@ -749,54 +715,53 @@ class Gem::Installer def app_script_text(bin_file_name) # NOTE: that the `load` lines cannot be indented, as old RG versions match # against the beginning of the line - <<-TEXT -#{shebang bin_file_name} -# -# This file was generated by RubyGems. -# -# The application '#{spec.name}' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' -#{gemdeps_load(spec.name)} -version = "#{Gem::Requirement.default_prerelease}" - -str = ARGV.first -if str - str = str.b[/\\A_(.*)_\\z/, 1] - if str and Gem::Version.correct?(str) - #{explicit_version_requirement(spec.name)} - ARGV.shift - end -end + <<~TEXT + #{shebang bin_file_name} + # + # This file was generated by RubyGems. + # + # The application '#{spec.name}' is installed as part of a gem, and + # this file is here to facilitate running it. + # + + require 'rubygems' + #{gemdeps_load(spec.name)} + version = "#{Gem::Requirement.default_prerelease}" + + str = ARGV.first + if str + str = str.b[/\\A_(.*)_\\z/, 1] + if str and Gem::Version.correct?(str) + #{explicit_version_requirement(spec.name)} + ARGV.shift + end + end -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) -else -gem #{spec.name.dump}, version -load Gem.bin_path(#{spec.name.dump}, #{bin_file_name.dump}, version) -end -TEXT + if Gem.respond_to?(:activate_and_load_bin_path) + Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version) + else + load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) + end + TEXT end def gemdeps_load(name) return "" if name == "bundler" - <<-TEXT + <<~TEXT -Gem.use_gemdeps -TEXT + Gem.use_gemdeps + TEXT end def explicit_version_requirement(name) code = "version = str" return code unless name == "bundler" - code += <<-TEXT + code += <<~TEXT - ENV['BUNDLER_VERSION'] = str -TEXT + ENV['BUNDLER_VERSION'] = str + TEXT end ## @@ -811,9 +776,9 @@ TEXT if File.exist?(File.join(bindir, ruby_exe)) # stub & ruby.exe within same folder. Portable - <<-TEXT -@ECHO OFF -@"%~dp0#{ruby_exe}" "%~dpn0" %* + <<~TEXT + @ECHO OFF + @"%~dp0#{ruby_exe}" "%~dpn0" %* TEXT elsif bindir.downcase.start_with? rb_topdir.downcase # stub within ruby folder, but not standard bin. Portable @@ -821,16 +786,16 @@ TEXT from = Pathname.new bindir to = Pathname.new "#{rb_topdir}/bin" rel = to.relative_path_from from - <<-TEXT -@ECHO OFF -@"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* + <<~TEXT + @ECHO OFF + @"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* TEXT else # outside ruby folder, maybe -user-install or bundler. Portable, but ruby # is dependent on PATH - <<-TEXT -@ECHO OFF -@#{ruby_exe} "%~dpn0" %* + <<~TEXT + @ECHO OFF + @#{ruby_exe} "%~dpn0" %* TEXT end end @@ -839,7 +804,7 @@ TEXT # configure scripts and rakefiles or mkrf_conf files. def build_extensions - builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig + builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs builder.build_extensions end @@ -908,11 +873,7 @@ TEXT ensure_loadable_spec - if options[:install_as_default] - Gem.ensure_default_gem_subdirectories gem_home - else - Gem.ensure_gem_subdirectories gem_home - end + Gem.ensure_gem_subdirectories gem_home return true if @force @@ -953,11 +914,8 @@ TEXT end def ensure_writable_dir(dir) # :nodoc: - begin - Dir.mkdir dir, *[options[:dir_mode] && 0o755].compact - rescue SystemCallError - raise unless File.directory? dir - end + require "fileutils" + FileUtils.mkdir_p dir, mode: options[:dir_mode] && 0o755 raise Gem::FilePermissionError.new(dir) unless File.writable? dir end @@ -984,6 +942,15 @@ TEXT end end + def build_jobs + @build_jobs ||= begin + require "etc" + Etc.nprocessors + 1 + rescue LoadError + 1 + end + end + def rb_config Gem.target_rbconfig end diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb index 3f4a6fcf3d..cbdf4d7ac5 100644 --- a/lib/rubygems/name_tuple.rb +++ b/lib/rubygems/name_tuple.rb @@ -6,7 +6,7 @@ # wrap the data returned from the indexes. class Gem::NameTuple - def initialize(name, version, platform=Gem::Platform::RUBY) + def initialize(name, version, platform = Gem::Platform::RUBY) @name = name @version = version @@ -81,6 +81,12 @@ class Gem::NameTuple [@name, @version, @platform] end + alias_method :deconstruct, :to_a + + def deconstruct_keys(keys) + { name: @name, version: @version, platform: @platform } + end + def inspect # :nodoc: "#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>" end diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index c855423ed7..6b21ff1b95 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -7,6 +7,7 @@ # rubocop:enable Style/AsciiComments +require_relative "win_platform" require_relative "security" require_relative "user_interaction" @@ -267,7 +268,7 @@ class Gem::Package tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| - copy_stream(src_io, dst_io) + copy_stream(src_io, dst_io, stat.size) end end end @@ -436,8 +437,6 @@ EOM symlinks << [full_name, link_target, destination, real_destination] end - FileUtils.rm_rf destination - mkdir = if entry.directory? destination @@ -451,8 +450,14 @@ EOM end if entry.file? - File.open(destination, "wb") {|out| copy_stream(entry, out) } - FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination + File.open(destination, "wb") do |out| + copy_stream(tar.io, out, entry.size) + # Flush needs to happen before chmod because there could be data + # in the IO buffer that needs to be written, and that could be + # written after the chmod (on close) which would mess up the perms + out.flush + out.chmod file_mode(entry.header.mode) & ~File.umask + end end verbose destination @@ -514,10 +519,12 @@ EOM destination end - def normalize_path(pathname) - if Gem.win_platform? + if Gem.win_platform? + def normalize_path(pathname) # :nodoc: pathname.downcase - else + end + else + def normalize_path(pathname) # :nodoc: pathname end end @@ -635,6 +642,8 @@ EOM raise Gem::Package::FormatError.new e.message, @gem end + private + ## # Verifies the +checksums+ against the +digests+. This check is not # cryptographically secure. Missing checksums are ignored. @@ -715,12 +724,12 @@ EOM end if RUBY_ENGINE == "truffleruby" - def copy_stream(src, dst) # :nodoc: - dst.write src.read + def copy_stream(src, dst, size) # :nodoc: + dst.write src.read(size) end else - def copy_stream(src, dst) # :nodoc: - IO.copy_stream(src, dst) + def copy_stream(src, dst, size) # :nodoc: + IO.copy_stream(src, dst, size) end end diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb index 0ebcbd789d..dd20d65080 100644 --- a/lib/rubygems/package/tar_header.rb +++ b/lib/rubygems/package/tar_header.rb @@ -56,7 +56,7 @@ class Gem::Package::TarHeader ## # Pack format for a tar header - PACK_FORMAT = "a100" + # name + PACK_FORMAT = ("a100" + # name "a8" + # mode "a8" + # uid "a8" + # gid @@ -71,12 +71,12 @@ class Gem::Package::TarHeader "a32" + # gname "a8" + # devmajor "a8" + # devminor - "a155" # prefix + "a155").freeze # prefix ## # Unpack format for a tar header - UNPACK_FORMAT = "A100" + # name + UNPACK_FORMAT = ("A100" + # name "A8" + # mode "A8" + # uid "A8" + # gid @@ -91,7 +91,7 @@ class Gem::Package::TarHeader "A32" + # gname "A8" + # devmajor "A8" + # devminor - "A155" # prefix + "A155").freeze # prefix attr_reader(*FIELDS) diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb index 25f9b2f945..b66a8a62bc 100644 --- a/lib/rubygems/package/tar_reader.rb +++ b/lib/rubygems/package/tar_reader.rb @@ -30,6 +30,8 @@ class Gem::Package::TarReader nil end + attr_reader :io # :nodoc: + ## # Creates a new tar file reader on +io+ which needs to respond to #pos, # #eof?, #read, #getc and #pos= diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb index b24bdb63e7..39fed9e2af 100644 --- a/lib/rubygems/package/tar_writer.rb +++ b/lib/rubygems/package/tar_writer.rb @@ -95,10 +95,11 @@ class Gem::Package::TarWriter end ## - # Adds file +name+ with permissions +mode+, and yields an IO for writing the - # file to + # Adds file +name+ with permissions +mode+ and mtime +mtime+ (sets + # Gem.source_date_epoch if not specified), and yields an IO for + # writing the file to - def add_file(name, mode) # :yields: io + def add_file(name, mode, mtime = nil) # :yields: io check_closed name, prefix = split_name name @@ -118,7 +119,7 @@ class Gem::Package::TarWriter header = Gem::Package::TarHeader.new name: name, mode: mode, size: size, prefix: prefix, - mtime: Gem.source_date_epoch + mtime: mtime || Gem.source_date_epoch @io.write header @io.pos = final_pos diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 450c214167..367b00e7e1 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "deprecate" - ## # Available list of platforms for targeting Gem installations. # @@ -21,15 +19,6 @@ class Gem::Platform end end - def self.match(platform) - match_platforms?(platform, Gem.platforms) - end - - class << self - extend Gem::Deprecate - rubygems_deprecate :match, "Gem::Platform.match_spec? or match_gem?" - end - def self.match_platforms?(platform, platforms) platform = Gem::Platform.new(platform) unless platform.is_a?(Gem::Platform) platforms.any? do |local_platform| @@ -88,56 +77,45 @@ class Gem::Platform when Array then @cpu, @os, @version = arch when String then - arch = arch.split "-" - - if arch.length > 2 && !arch.last.match?(/\d+(\.\d+)?$/) # reassemble x86-linux-{libc} - extra = arch.pop - arch.last << "-#{extra}" - end + cpu, os = arch.sub(/-+$/, "").split("-", 2) - cpu = arch.shift - - @cpu = case cpu - when /i\d86/ then "x86" - else cpu - end - - if arch.length == 2 && arch.last.match?(/^\d+(\.\d+)?$/) # for command-line - @os, @version = arch - return + @cpu = if cpu&.match?(/i\d86/) + "x86" + else + cpu end - os, = arch if os.nil? @cpu = nil os = cpu end # legacy jruby @os, @version = case os - when /aix(\d+)?/ then ["aix", $1] - when /cygwin/ then ["cygwin", nil] - when /darwin(\d+)?/ then ["darwin", $1] - when /^macruby$/ then ["macruby", nil] - when /freebsd(\d+)?/ then ["freebsd", $1] - when /^java$/, /^jruby$/ then ["java", nil] - when /^java([\d.]*)/ then ["java", $1] - when /^dalvik(\d+)?$/ then ["dalvik", $1] - when /^dotnet$/ then ["dotnet", nil] - when /^dotnet([\d.]*)/ then ["dotnet", $1] - when /linux-?(\w+)?/ then ["linux", $1] - when /mingw32/ then ["mingw32", nil] - when /mingw-?(\w+)?/ then ["mingw", $1] - when /(mswin\d+)(\_(\d+))?/ then + when /aix-?(\d+)?/ then ["aix", $1] + when /cygwin/ then ["cygwin", nil] + when /darwin-?(\d+)?/ then ["darwin", $1] + when "macruby" then ["macruby", nil] + when /^macruby-?(\d+(?:\.\d+)*)?/ then ["macruby", $1] + when /freebsd-?(\d+)?/ then ["freebsd", $1] + when "java", "jruby" then ["java", nil] + when /^java-?(\d+(?:\.\d+)*)?/ then ["java", $1] + when /^dalvik-?(\d+)?$/ then ["dalvik", $1] + when /^dotnet$/ then ["dotnet", nil] + when /^dotnet-?(\d+(?:\.\d+)*)?/ then ["dotnet", $1] + when /linux-?(\w+)?/ then ["linux", $1] + when /mingw32/ then ["mingw32", nil] + when /mingw-?(\w+)?/ then ["mingw", $1] + when /(mswin\d+)(?:[_-](\d+))?/ then os = $1 - version = $3 - @cpu = "x86" if @cpu.nil? && os =~ /32$/ + version = $2 + @cpu = "x86" if @cpu.nil? && os.end_with?("32") [os, version] - when /netbsdelf/ then ["netbsdelf", nil] - when /openbsd(\d+\.\d+)?/ then ["openbsd", $1] - when /solaris(\d+\.\d+)?/ then ["solaris", $1] - when /wasi/ then ["wasi", nil] + when /netbsdelf/ then ["netbsdelf", nil] + when /openbsd-?(\d+\.\d+)?/ then ["openbsd", $1] + when /solaris-?(\d+\.\d+)?/ then ["solaris", $1] + when /wasi/ then ["wasi", nil] # test - when /^(\w+_platform)(\d+)?/ then [$1, $2] + when /^(\w+_platform)-?(\d+)?/ then [$1, $2] else ["unknown", nil] end when Gem::Platform then @@ -154,7 +132,38 @@ class Gem::Platform end def to_s - to_a.compact.join "-" + to_a.compact.join(@cpu.nil? ? "" : "-") + end + + ## + # Deconstructs the platform into an array for pattern matching. + # Returns [cpu, os, version]. + # + # Gem::Platform.new("x86_64-linux").deconstruct #=> ["x86_64", "linux", nil] + # + # This enables array pattern matching: + # + # case Gem::Platform.new("arm64-darwin-21") + # in ["arm64", "darwin", version] + # # version => "21" + # end + alias_method :deconstruct, :to_a + + ## + # Deconstructs the platform into a hash for pattern matching. + # Returns a hash with keys +:cpu+, +:os+, and +:version+. + # + # Gem::Platform.new("x86_64-darwin-20").deconstruct_keys(nil) + # #=> { cpu: "x86_64", os: "darwin", version: "20" } + # + # This enables hash pattern matching: + # + # case Gem::Platform.new("x86_64-linux") + # in cpu: "x86_64", os: "linux" + # # Matches Linux on x86_64 + # end + def deconstruct_keys(keys) + { cpu: @cpu, os: @os, version: @version } end ## @@ -266,4 +275,118 @@ class Gem::Platform # This will be replaced with Gem::Platform::local. CURRENT = "current" + + JAVA = Gem::Platform.new("java") # :nodoc: + MSWIN = Gem::Platform.new("mswin32") # :nodoc: + MSWIN64 = Gem::Platform.new("mswin64") # :nodoc: + MINGW = Gem::Platform.new("x86-mingw32") # :nodoc: + X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc: + X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc: + UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc: + WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc: + X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc: + X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc: + + GENERICS = [JAVA, *WINDOWS].freeze # :nodoc: + private_constant :GENERICS + + GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc: + private_constant :GENERIC_CACHE + + class << self + ## + # Returns the generic platform for the given platform. + + def generic(platform) + return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY + + GENERIC_CACHE[platform] ||= begin + found = GENERICS.find do |match| + platform === match + end + found || Gem::Platform::RUBY + end + end + + ## + # Returns the platform specificity match for the given spec platform and user platform. + + def platform_specificity_match(spec_platform, user_platform) + return -1 if spec_platform == user_platform + return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY + + os_match(spec_platform, user_platform) + + cpu_match(spec_platform, user_platform) * 10 + + version_match(spec_platform, user_platform) * 100 + end + + ## + # Sorts and filters the best platform match for the given matching specs and platform. + + def sort_and_filter_best_platform_match(matching, platform) + return matching if matching.one? + + exact = matching.select {|spec| spec.platform == platform } + return exact if exact.any? + + sorted_matching = sort_best_platform_match(matching, platform) + exemplary_spec = sorted_matching.first + + sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) } + end + + ## + # Sorts the best platform match for the given matching specs and platform. + + def sort_best_platform_match(matching, platform) + matching.sort_by.with_index do |spec, i| + [ + platform_specificity_match(spec.platform, platform), + i, # for stable sort + ] + end + end + + private + + def same_specificity?(platform, spec, exemplary_spec) + platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) + end + + def same_deps?(spec, exemplary_spec) + spec.required_ruby_version == exemplary_spec.required_ruby_version && + spec.required_rubygems_version == exemplary_spec.required_rubygems_version && + spec.dependencies.sort == exemplary_spec.dependencies.sort + end + + def os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end + end + end end diff --git a/lib/rubygems/psych_tree.rb b/lib/rubygems/psych_tree.rb index 24857adb9d..8b4c425a33 100644 --- a/lib/rubygems/psych_tree.rb +++ b/lib/rubygems/psych_tree.rb @@ -22,7 +22,7 @@ module Gem def register(target, obj) end - # This is ported over from the yaml_tree in 1.9.3 + # This is ported over from the YAMLTree implementation in Ruby 1.9.3 def format_time(time) if time.utc? time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 4b5c74e0ea..805f7aaf82 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -72,7 +72,7 @@ class Gem::RemoteFetcher # +headers+: A set of additional HTTP headers to be sent to the server when # fetching the gem. - def initialize(proxy=nil, dns=nil, headers={}) + def initialize(proxy = nil, dns = nil, headers = {}) require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled require_relative "vendored_net_http" require_relative "vendor/uri/lib/uri" @@ -82,6 +82,7 @@ class Gem::RemoteFetcher @proxy = proxy @pools = {} @pool_lock = Thread::Mutex.new + @pool_size = 1 @cert_files = Gem::Request.get_cert_files @headers = headers @@ -245,11 +246,14 @@ class Gem::RemoteFetcher def fetch_path(uri, mtime = nil, head = false) uri = Gem::Uri.new uri - unless uri.scheme - raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" - end + method = { + "http" => "fetch_http", + "https" => "fetch_http", + "s3" => "fetch_s3", + "file" => "fetch_file", + }.fetch(uri.scheme) { raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" } - data = send "fetch_#{uri.scheme}", uri, mtime, head + data = send method, uri, mtime, head if data && !head && uri.to_s.end_with?(".gz") begin @@ -267,7 +271,7 @@ class Gem::RemoteFetcher def fetch_s3(uri, mtime = nil, head = false) begin - public_uri = s3_uri_signer(uri).sign + public_uri = s3_uri_signer(uri, head ? "HEAD" : "GET").sign rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e raise FetchError.new(e.message, "s3://#{uri.host}") end @@ -275,8 +279,8 @@ class Gem::RemoteFetcher end # we have our own signing code here to avoid a dependency on the aws-sdk gem - def s3_uri_signer(uri) - Gem::S3URISigner.new(uri) + def s3_uri_signer(uri, method) + Gem::S3URISigner.new(uri, method) end ## @@ -335,7 +339,7 @@ class Gem::RemoteFetcher def pools_for(proxy) @pool_lock.synchronize do - @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files + @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files, @pool_size end end end diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 6c1b04ab65..01e7e0629a 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -7,11 +7,12 @@ class Gem::Request::ConnectionPools # :nodoc: attr_accessor :client end - def initialize(proxy_uri, cert_files) + def initialize(proxy_uri, cert_files, pool_size = 1) @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} @pool_mutex = Thread::Mutex.new + @pool_size = pool_size end def pool_for(uri) @@ -20,9 +21,9 @@ class Gem::Request::ConnectionPools # :nodoc: @pool_mutex.synchronize do @pools[key] ||= if https? uri - Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri, @pool_size) else - Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri, @pool_size) end end end diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb index 52543de41f..468502ca6b 100644 --- a/lib/rubygems/request/http_pool.rb +++ b/lib/rubygems/request/http_pool.rb @@ -9,12 +9,14 @@ class Gem::Request::HTTPPool # :nodoc: attr_reader :cert_files, :proxy_uri - def initialize(http_args, cert_files, proxy_uri) + def initialize(http_args, cert_files, proxy_uri, pool_size) @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri - @queue = Thread::SizedQueue.new 1 - @queue << nil + @pool_size = pool_size + + @queue = Thread::SizedQueue.new @pool_size + setup_queue end def checkout @@ -31,7 +33,8 @@ class Gem::Request::HTTPPool # :nodoc: connection.finish end end - @queue.push(nil) + + setup_queue end private @@ -44,4 +47,8 @@ class Gem::Request::HTTPPool # :nodoc: connection.start connection end + + def setup_queue + @pool_size.times { @queue.push(nil) } + end end diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 875df7e019..5a855fdb10 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -181,13 +181,10 @@ class Gem::RequestSet # Install requested gems after they have been downloaded sorted_requests.each do |req| - if req.installed? + if req.installed? && @always_install.none? {|spec| spec == req.spec.spec } req.spec.spec.build_extensions - - if @always_install.none? {|spec| spec == req.spec.spec } - yield req, nil if block_given? - next - end + yield req, nil if block_given? + next end spec = diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb index c446b3ae51..da6dd329bc 100644 --- a/lib/rubygems/request_set/lockfile.rb +++ b/lib/rubygems/request_set/lockfile.rb @@ -38,7 +38,7 @@ class Gem::RequestSet::Lockfile end ## - # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ + # Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+ # location. def self.build(request_set, gem_deps_file, dependencies = nil) diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index 35d83abd2d..bc4fef893e 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -2,7 +2,6 @@ require_relative "dependency" require_relative "exceptions" -require_relative "util/list" ## # Given a set of Gem::Dependency objects as +needed+ and a way to query the @@ -144,7 +143,7 @@ class Gem::Resolver [spec, activation_request] end - def requests(s, act, reqs=[]) # :nodoc: + def requests(s, act, reqs = []) # :nodoc: return reqs if @ignore_dependencies s.fetch_development_dependencies if @development @@ -241,7 +240,7 @@ class Gem::Resolver sources.each do |source| groups[source]. - sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch + sort_by {|spec| [spec.version, -Gem::Platform.platform_specificity_match(spec.platform, Gem::Platform.local)] }. map {|spec| ActivationRequest.new spec, dependency }. each {|activation_request| activation_requests << activation_request } end diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb index e2307f6e02..e647a2c11b 100644 --- a/lib/rubygems/resolver/best_set.rb +++ b/lib/rubygems/resolver/best_set.rb @@ -21,7 +21,7 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet def pick_sets # :nodoc: @sources.each_source do |source| - @sets << source.dependency_resolver_set + @sets << source.dependency_resolver_set(@prerelease) end end diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb index 367a36b43d..77c3add4b3 100644 --- a/lib/rubygems/resolver/conflict.rb +++ b/lib/rubygems/resolver/conflict.rb @@ -21,7 +21,7 @@ class Gem::Resolver::Conflict # Creates a new resolver conflict when +dependency+ is in conflict with an # already +activated+ specification. - def initialize(dependency, activated, failed_dep=dependency) + def initialize(dependency, activated, failed_dep = dependency) @dependency = dependency @activated = activated @failed_dep = failed_dep diff --git a/lib/rubygems/resolver/source_set.rb b/lib/rubygems/resolver/source_set.rb index 296cf41078..074b473edc 100644 --- a/lib/rubygems/resolver/source_set.rb +++ b/lib/rubygems/resolver/source_set.rb @@ -42,6 +42,6 @@ class Gem::Resolver::SourceSet < Gem::Resolver::Set def get_set(name) link = @links[name] - @sets[link] ||= Gem::Source.new(link).dependency_resolver_set if link + @sets[link] ||= Gem::Source.new(link).dependency_resolver_set(@prerelease) if link end end diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index 7c95a9d4f5..148cba38c4 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true require_relative "openssl" +require_relative "user_interaction" ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems # More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html class Gem::S3URISigner + include Gem::UserInteraction + class ConfigurationError < Gem::Exception def initialize(message) super message @@ -27,9 +30,11 @@ class Gem::S3URISigner end attr_accessor :uri + attr_accessor :method - def initialize(uri) + def initialize(uri, method) @uri = uri + @method = method end ## @@ -38,7 +43,7 @@ class Gem::S3URISigner s3_config = fetch_s3_config current_time = Time.now.utc - date_time = current_time.strftime("%Y%m%dT%H%m%SZ") + date_time = current_time.strftime("%Y%m%dT%H%M%SZ") date = date_time[0,8] credential_info = "#{date}/#{s3_config.region}/s3/aws4_request" @@ -73,7 +78,7 @@ class Gem::S3URISigner def generate_canonical_request(canonical_host, query_params) [ - "GET", + method.upcase, uri.path, query_params, "host:#{canonical_host}", @@ -145,17 +150,40 @@ class Gem::S3URISigner require_relative "request/connection_pools" require "json" - iam_info = ec2_metadata_request(EC2_IAM_INFO) + # First try V2 fallback to V1 + res = nil + begin + res = ec2_metadata_credentials_imds_v2 + rescue InstanceProfileError + alert_warning "Unable to access ec2 credentials via IMDSv2, falling back to IMDSv1" + res = ec2_metadata_credentials_imds_v1 + end + res + end + + def ec2_metadata_credentials_imds_v2 + token = ec2_metadata_token + iam_info = ec2_metadata_request(EC2_IAM_INFO, token:) # Expected format: arn:aws:iam::<id>:instance-profile/<role_name> role_name = iam_info["InstanceProfileArn"].split("/").last - ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name) + ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token:) end - def ec2_metadata_request(url) - uri = Gem::URI(url) - @request_pool ||= create_request_pool(uri) - request = Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, @request_pool) - response = request.fetch + def ec2_metadata_credentials_imds_v1 + iam_info = ec2_metadata_request(EC2_IAM_INFO, token: nil) + # Expected format: arn:aws:iam::<id>:instance-profile/<role_name> + role_name = iam_info["InstanceProfileArn"].split("/").last + ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token: nil) + end + + def ec2_metadata_request(url, token:) + request = ec2_iam_request(Gem::URI(url), Gem::Net::HTTP::Get) + + response = request.fetch do |req| + if token + req.add_field "X-aws-ec2-metadata-token", token + end + end case response when Gem::Net::HTTPOK then @@ -165,6 +193,26 @@ class Gem::S3URISigner end end + def ec2_metadata_token + request = ec2_iam_request(Gem::URI(EC2_IAM_TOKEN), Gem::Net::HTTP::Put) + + response = request.fetch do |req| + req.add_field "X-aws-ec2-metadata-token-ttl-seconds", 60 + end + + case response + when Gem::Net::HTTPOK then + response.body + else + raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}") + end + end + + def ec2_iam_request(uri, verb) + @request_pool ||= create_request_pool(uri) + Gem::Request.new(uri, verb, nil, @request_pool) + end + def create_request_pool(uri) proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme)) certs = Gem::Request.get_cert_files @@ -172,6 +220,7 @@ class Gem::S3URISigner end BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze + EC2_IAM_TOKEN = "http://169.254.169.254/latest/api/token" EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info" EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" end diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 7b86ac5763..128958ab80 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -143,7 +143,7 @@ class Gem::Security::Policy end ## - # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and + # Ensures the root of +chain+ has a trusted certificate in Gem::Security.trust_dir and # the digests of the two certificates match according to +digester+ def check_trust(chain, digester, trust_dir) diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index 5732fb57fd..eeeeb52906 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -52,7 +52,7 @@ class Gem::Security::Signer re_signed_cert = Gem::Security.re_sign( expired_cert, private_key, - (Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days) + Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days ) Gem::Security.write(re_signed_cert, expired_cert_path) diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index bee5681dab..f203b7312b 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -67,28 +67,11 @@ class Gem::Source ## # Returns a Set that can fetch specifications from this source. - - def dependency_resolver_set # :nodoc: - return Gem::Resolver::IndexSet.new self if uri.scheme == "file" - - fetch_uri = if uri.host == "rubygems.org" - index_uri = uri.dup - index_uri.host = "index.rubygems.org" - index_uri - else - uri - end - - bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" - - begin - fetcher = Gem::RemoteFetcher.fetcher - response = fetcher.fetch_path bundler_api_uri, nil, true - rescue Gem::RemoteFetcher::FetchError - Gem::Resolver::IndexSet.new self - else - Gem::Resolver::APISet.new response.uri + "./info/" - end + # + # The set will optionally fetch prereleases if requested. + # + def dependency_resolver_set(prerelease = false) + new_dependency_resolver_set.tap {|set| set.prerelease = prerelease } end def hash # :nodoc: @@ -119,7 +102,7 @@ class Gem::Source end ## - # Fetches a specification for the given +name_tuple+. + # Fetches a specification for the given Gem::NameTuple. def fetch_spec(name_tuple) fetcher = Gem::RemoteFetcher.fetcher @@ -207,7 +190,7 @@ class Gem::Source # Downloads +spec+ and writes it to +dir+. See also # Gem::RemoteFetcher#download. - def download(spec, dir=Dir.pwd) + def download(spec, dir = Dir.pwd) fetcher = Gem::RemoteFetcher.fetcher fetcher.download spec, uri.to_s, dir end @@ -227,13 +210,36 @@ class Gem::Source end end - def typo_squatting?(host, distance_threshold=4) + def typo_squatting?(host, distance_threshold = 4) return if @uri.host.nil? levenshtein_distance(@uri.host, host).between? 1, distance_threshold end private + def new_dependency_resolver_set + return Gem::Resolver::IndexSet.new self if uri.scheme == "file" + + fetch_uri = if uri.host == "rubygems.org" + index_uri = uri.dup + index_uri.host = "index.rubygems.org" + index_uri + else + uri + end + + bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" + + begin + fetcher = Gem::RemoteFetcher.fetcher + response = fetcher.fetch_path bundler_api_uri, nil, true + rescue Gem::RemoteFetcher::FetchError + Gem::Resolver::IndexSet.new self + else + Gem::Resolver::APISet.new response.uri + "./info/" + end + end + def enforce_trailing_slash(uri) uri.merge(uri.path.gsub(%r{/+$}, "") + "/") end diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb index 33db64fbc1..19bf4595c4 100644 --- a/lib/rubygems/source_list.rb +++ b/lib/rubygems/source_list.rb @@ -60,6 +60,42 @@ class Gem::SourceList end ## + # Prepends +obj+ to the beginning of the source list which may be a Gem::Source, Gem::URI or URI + # Moves +obj+ to the beginning of the list if already present. + # String. + + def prepend(obj) + src = case obj + when Gem::Source + obj + else + Gem::Source.new(obj) + end + + @sources.delete(src) if @sources.include?(src) + @sources.unshift(src) + src + end + + ## + # Appends +obj+ to the end of the source list, moving it if already present. + # +obj+ may be a Gem::Source, Gem::URI or URI String. + # Moves +obj+ to the end of the list if already present. + + def append(obj) + src = case obj + when Gem::Source + obj + else + Gem::Source.new(obj) + end + + @sources.delete(src) if @sources.include?(src) + @sources << src + src + end + + ## # Replaces this SourceList with the sources in +other+ See #<< for # acceptable items in +other+. diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index 285f117b39..835dedf948 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -83,7 +83,7 @@ class Gem::SpecFetcher # # If +matching_platform+ is false, gems for all platforms are returned. - def search_for_dependency(dependency, matching_platform=true) + def search_for_dependency(dependency, matching_platform = true) found = {} rejected_specs = {} @@ -130,7 +130,7 @@ class Gem::SpecFetcher ## # Return all gem name tuples who's names match +obj+ - def detect(type=:complete) + def detect(type = :complete) tuples = [] list, _ = available_specs(type) @@ -150,7 +150,7 @@ class Gem::SpecFetcher # # If +matching_platform+ is false, gems for all platforms are returned. - def spec_for_dependency(dependency, matching_platform=true) + def spec_for_dependency(dependency, matching_platform = true) tuples, errors = search_for_dependency(dependency, matching_platform) specs = [] @@ -280,7 +280,7 @@ class Gem::SpecFetcher # Retrieves NameTuples from +source+ of the given +type+ (:prerelease, # etc.). If +gracefully_ignore+ is true, errors are ignored. - def tuples_for(source, type, gracefully_ignore=false) # :nodoc: + def tuples_for(source, type, gracefully_ignore = false) # :nodoc: @caches[type][source.uri] ||= source.load_specs(type).sort_by(&:name) rescue Gem::RemoteFetcher::FetchError diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 0b905a7ea7..3d1f2dad91 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -7,12 +7,10 @@ # See LICENSE.txt for permissions. #++ -require_relative "deprecate" require_relative "basic_specification" require_relative "stub_specification" require_relative "platform" require_relative "specification_record" -require_relative "util/list" require "rbconfig" @@ -38,8 +36,6 @@ require "rbconfig" # items you may add to a specification. class Gem::Specification < Gem::BasicSpecification - extend Gem::Deprecate - # REFACTOR: Consider breaking out this version stuff into a separate # module. There's enough special stuff around it that it may justify # a separate class. @@ -488,8 +484,6 @@ class Gem::Specification < Gem::BasicSpecification end @platform = @new_platform.to_s - - invalidate_memoized_attributes end ## @@ -738,14 +732,6 @@ class Gem::Specification < Gem::BasicSpecification attr_accessor :autorequire # :nodoc: ## - # Sets the default executable for this gem. - # - # Deprecated: You must now specify the executable name to Gem.bin_path. - - attr_writer :default_executable - rubygems_deprecate :default_executable= - - ## # Allows deinstallation of gems with legacy platforms. attr_writer :original_platform # :nodoc: @@ -1002,7 +988,7 @@ class Gem::Specification < Gem::BasicSpecification def self.find_in_unresolved_tree(path) unresolved_specs.each do |spec| spec.traverse do |_from_spec, _dep, to_spec, trail| - if to_spec.has_conflicts? || to_spec.conficts_when_loaded_with?(trail) + if to_spec.has_conflicts? || to_spec.conflicts_when_loaded_with?(trail) :next else return trail.reverse if to_spec.contains_requirable_file? path @@ -1321,7 +1307,7 @@ class Gem::Specification < Gem::BasicSpecification spec.instance_variable_set :@authors, array[12] spec.instance_variable_set :@description, array[13] spec.instance_variable_set :@homepage, array[14] - spec.instance_variable_set :@has_rdoc, array[15] + # offset due to has_rdoc removal spec.instance_variable_set :@licenses, array[17] spec.instance_variable_set :@metadata, array[18] spec.instance_variable_set :@loaded, false @@ -1620,14 +1606,14 @@ class Gem::Specification < Gem::BasicSpecification # spec's cached gem. def cache_dir - @cache_dir ||= File.join base_dir, "cache" + File.join base_dir, "cache" end ## # Returns the full path to the cached gem for this spec. def cache_file - @cache_file ||= File.join cache_dir, "#{full_name}.gem" + File.join cache_dir, "#{full_name}.gem" end ## @@ -1649,7 +1635,7 @@ class Gem::Specification < Gem::BasicSpecification ## # return true if there will be conflict when spec if loaded together with the list of specs. - def conficts_when_loaded_with?(list_of_specs) # :nodoc: + def conflicts_when_loaded_with?(list_of_specs) # :nodoc: result = list_of_specs.any? do |spec| spec.runtime_dependencies.any? {|dep| (dep.name == name) && !satisfies_requirement?(dep) } end @@ -1717,24 +1703,6 @@ class Gem::Specification < Gem::BasicSpecification end ## - # The default executable for this gem. - # - # Deprecated: The name of the gem is assumed to be the name of the - # executable now. See Gem.bin_path. - - def default_executable # :nodoc: - if defined?(@default_executable) && @default_executable - result = @default_executable - elsif @executables && @executables.size == 1 - result = Array(@executables).first - else - result = nil - end - result - end - rubygems_deprecate :default_executable - - ## # The default value for specification attribute +name+ def default_value(name) @@ -1757,7 +1725,7 @@ class Gem::Specification < Gem::BasicSpecification # # [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]] - def dependent_gems(check_dev=true) + def dependent_gems(check_dev = true) out = [] Gem::Specification.each do |spec| deps = check_dev ? spec.dependencies : spec.runtime_dependencies @@ -1903,10 +1871,6 @@ class Gem::Specification < Gem::BasicSpecification spec end - def full_name - @full_name ||= super - end - ## # Work around old bundler versions removing my methods # Can be removed once RubyGems can no longer install Bundler 2.5 @@ -1920,29 +1884,6 @@ class Gem::Specification < Gem::BasicSpecification end ## - # Deprecated and ignored, defaults to true. - # - # Formerly used to indicate this gem was RDoc-capable. - - def has_rdoc # :nodoc: - true - end - rubygems_deprecate :has_rdoc - - ## - # Deprecated and ignored. - # - # Formerly used to indicate this gem was RDoc-capable. - - def has_rdoc=(ignored) # :nodoc: - @has_rdoc = true - end - rubygems_deprecate :has_rdoc= - - alias_method :has_rdoc?, :has_rdoc # :nodoc: - rubygems_deprecate :has_rdoc? - - ## # True if this gem has files in test_files def has_unit_tests? # :nodoc: @@ -2044,17 +1985,6 @@ class Gem::Specification < Gem::BasicSpecification end end - ## - # Expire memoized instance variables that can incorrectly generate, replace - # or miss files due changes in certain attributes used to compute them. - - def invalidate_memoized_attributes - @full_name = nil - @cache_file = nil - end - - private :invalidate_memoized_attributes - def inspect # :nodoc: if $DEBUG super @@ -2093,8 +2023,6 @@ class Gem::Specification < Gem::BasicSpecification def internal_init # :nodoc: super @bin_dir = nil - @cache_dir = nil - @cache_file = nil @doc_dir = nil @ri_dir = nil @spec_dir = nil @@ -2447,8 +2375,6 @@ class Gem::Specification < Gem::BasicSpecification :required_rubygems_version, :specification_version, :version, - :has_rdoc, - :default_executable, :metadata, :signing_key, ] @@ -2586,29 +2512,11 @@ class Gem::Specification < Gem::BasicSpecification Gem::SpecificationPolicy.new(self).validate_for_resolution end - def validate_metadata - Gem::SpecificationPolicy.new(self).validate_metadata - end - rubygems_deprecate :validate_metadata - - def validate_dependencies - Gem::SpecificationPolicy.new(self).validate_dependencies - end - rubygems_deprecate :validate_dependencies - - def validate_permissions - Gem::SpecificationPolicy.new(self).validate_permissions - end - rubygems_deprecate :validate_permissions - ## # Set the version to +version+. def version=(version) - @version = Gem::Version.create(version) - return if @version.nil? - - invalidate_memoized_attributes + @version = version.nil? ? version : Gem::Version.create(version) end def stubbed? diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index d79ee7df92..e5008a24db 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -190,9 +190,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: ## # Checks that the gem does not depend on itself. - # Checks that dependencies use requirements as we recommend. Warnings are - # issued when dependencies are open-ended or overly strict for semantic - # versioning. def validate_dependencies # :nodoc: warning_messages = [] @@ -200,39 +197,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: if dep.name == @specification.name # warn on self reference warning_messages << "Self referencing dependency is unnecessary and strongly discouraged." end - - prerelease_dep = dep.requirements_list.any? do |req| - Gem::Requirement.new(req).prerelease? - end - - warning_messages << "prerelease dependency on #{dep} is not recommended" if - prerelease_dep && !@specification.version.prerelease? - - open_ended = dep.requirement.requirements.all? do |op, version| - !version.prerelease? && [">", ">="].include?(op) - end - - next unless open_ended - op, dep_version = dep.requirement.requirements.first - - segments = dep_version.segments - - base = segments.first 2 - - recommendation = if [">", ">="].include?(op) && segments == [0] - " use a bounded requirement, such as \"~> x.y\"" - else - bugfix = if op == ">" - ", \"> #{dep_version}\"" - elsif op == ">=" && base != segments - ", \">= #{dep_version}\"" - end - - " if #{dep.name} is semantically versioned, use:\n" \ - " add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join "."}\"#{bugfix}" - end - - warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n" end if warning_messages.any? warning_messages.each {|warning_message| warning warning_message } diff --git a/lib/rubygems/specification_record.rb b/lib/rubygems/specification_record.rb index 195a355496..d08410096f 100644 --- a/lib/rubygems/specification_record.rb +++ b/lib/rubygems/specification_record.rb @@ -73,7 +73,7 @@ module Gem end ## - # Adds +spec+ to the the record, keeping the collection properly sorted. + # Adds +spec+ to the record, keeping the collection properly sorted. def add_spec(spec) return if all.include? spec diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem index 8afb219058..8afb219058 100644 --- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem +++ b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem deleted file mode 100644 index f4ce4ca43d..0000000000 --- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- diff --git a/lib/rubygems/text.rb b/lib/rubygems/text.rb index da0795b771..88d4ce59b4 100644 --- a/lib/rubygems/text.rb +++ b/lib/rubygems/text.rb @@ -21,7 +21,7 @@ module Gem::Text # Wraps +text+ to +wrap+ characters and optionally indents by +indent+ # characters - def format_text(text, wrap, indent=0) + def format_text(text, wrap, indent = 0) result = [] work = clean_text(text) diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index 991bc6fb95..fe4c3a80cf 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -42,10 +42,25 @@ class Gem::Uninstaller attr_reader :spec ## - # Constructs an uninstaller that will uninstall +gem+ + # Constructs an uninstaller that will uninstall gem named +gem+. + # +options+ is a Hash with the following keys: + # + # :version:: Version requirement for the gem to uninstall. If not specified, + # uses Gem::Requirement.default. + # :install_dir:: The directory where the gem is installed. If not specified, + # uses Gem.dir. + # :executables:: Whether executables should be removed without confirmation or not. If nil, asks the user explicitly. + # :all:: If more than one version matches the requirement, whether to forcefully remove all matching versions or ask the user to select specific matching versions that should be removed. + # :ignore:: Ignore broken dependency checks when uninstalling. + # :bin_dir:: Directory containing executables to remove. If not specified, + # uses Gem.bindir. + # :format_executable:: In order to find executables to be removed, format executable names using Gem::Installer.exec_format. + # :abort_on_dependent:: Directly abort uninstallation if dependencies would be broken, rather than asking the user for confirmation. + # :check_dev:: When checking if uninstalling gem would leave broken dependencies around, also consider development dependencies. + # :force:: Set both :all and :ignore to true for forced uninstallation. + # :user_install:: Uninstall from user gem directory instead of system directory. def initialize(gem, options = {}) - # TODO: document the valid options @gem = gem @version = options[:version] || Gem::Requirement.default @install_dir = options[:install_dir] @@ -57,10 +72,6 @@ class Gem::Uninstaller @bin_dir = options[:bin_dir] @format_executable = options[:format_executable] @abort_on_dependent = options[:abort_on_dependent] - - # Indicate if development dependencies should be checked when - # uninstalling. (default: false) - # @check_dev = options[:check_dev] if options[:force] diff --git a/lib/rubygems/uri_formatter.rb b/lib/rubygems/uri_formatter.rb index f941eb1421..8856fdadd2 100644 --- a/lib/rubygems/uri_formatter.rb +++ b/lib/rubygems/uri_formatter.rb @@ -17,7 +17,8 @@ class Gem::UriFormatter # Creates a new URI formatter for +uri+. def initialize(uri) - require "cgi/util" + require "cgi/escape" + require "cgi/util" unless defined?(CGI::EscapeExt) @uri = uri end diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 0172c4ee89..9fe3e755c4 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -6,7 +6,6 @@ # See LICENSE.txt for permissions. #++ -require_relative "deprecate" require_relative "text" ## @@ -170,8 +169,6 @@ end # Gem::StreamUI implements a simple stream based user interface. class Gem::StreamUI - extend Gem::Deprecate - ## # The input stream @@ -193,7 +190,7 @@ class Gem::StreamUI # then special operations (like asking for passwords) will use the TTY # commands to disable character echo. - def initialize(in_stream, out_stream, err_stream=$stderr, usetty=true) + def initialize(in_stream, out_stream, err_stream = $stderr, usetty = true) @ins = in_stream @outs = out_stream @errs = err_stream @@ -246,7 +243,7 @@ class Gem::StreamUI # to a tty, raises an exception if default is nil, otherwise returns # default. - def ask_yes_no(question, default=nil) + def ask_yes_no(question, default = nil) unless tty? if default.nil? raise Gem::OperationNotSupportedError, @@ -325,14 +322,14 @@ class Gem::StreamUI ## # Display a statement. - def say(statement="") + def say(statement = "") @outs.puts statement end ## # Display an informational alert. Will ask +question+ if it is not nil. - def alert(statement, question=nil) + def alert(statement, question = nil) @outs.puts "INFO: #{statement}" ask(question) if question end @@ -340,7 +337,7 @@ class Gem::StreamUI ## # Display a warning on stderr. Will ask +question+ if it is not nil. - def alert_warning(statement, question=nil) + def alert_warning(statement, question = nil) @errs.puts "WARNING: #{statement}" ask(question) if question end @@ -349,7 +346,7 @@ class Gem::StreamUI # Display an error message in a location expected to get error messages. # Will ask +question+ if it is not nil. - def alert_error(statement, question=nil) + def alert_error(statement, question = nil) @errs.puts "ERROR: #{statement}" ask(question) if question end diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 51f9c2029f..ee4106c6ce 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "deprecate" - ## # This module contains various utility methods as module methods. @@ -57,26 +55,6 @@ module Gem::Util end ## - # Invokes system, but silences all output. - - def self.silent_system(*command) - opt = { out: IO::NULL, err: [:child, :out] } - if Hash === command.last - opt.update(command.last) - cmds = command[0...-1] - else - cmds = command.dup - end - system(*(cmds << opt)) - end - - class << self - extend Gem::Deprecate - - rubygems_deprecate :silent_system - end - - ## # Enumerates the parents of +directory+. def self.traverse_parents(directory, &block) diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb new file mode 100644 index 0000000000..32767c6a79 --- /dev/null +++ b/lib/rubygems/util/atomic_file_writer.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# Based on ActiveSupport's AtomicFile implementation +# Copyright (c) David Heinemeier Hansson +# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb +# Licensed under the MIT License + +module Gem + class AtomicFileWriter + ## + # Write to a file atomically. Useful for situations where you don't + # want other processes or threads to see half-written files. + + def self.open(file_name) + require "securerandom" unless defined?(SecureRandom) + + old_stat = begin + File.stat(file_name) + rescue SystemCallError + nil + end + + # Names can't be longer than 255B + tmp_suffix = ".tmp.#{SecureRandom.hex}" + dirname = File.dirname(file_name) + basename = File.basename(file_name) + tmp_path = File.join(dirname, ".#{basename.byteslice(0, 254 - tmp_suffix.bytesize)}#{tmp_suffix}") + + flags = File::RDWR | File::CREAT | File::EXCL | File::BINARY + flags |= File::SHARE_DELETE if defined?(File::SHARE_DELETE) + + File.open(tmp_path, flags) do |temp_file| + temp_file.binmode + if old_stat + # Set correct permissions on new file + begin + File.chown(old_stat.uid, old_stat.gid, temp_file.path) + # This operation will affect filesystem ACL's + File.chmod(old_stat.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end + + return_val = yield temp_file + rescue StandardError => error + begin + temp_file.close + rescue StandardError + nil + end + + begin + File.unlink(temp_file.path) + rescue StandardError + nil + end + + raise error + else + begin + File.rename(temp_file.path, file_name) + rescue StandardError + begin + File.unlink(temp_file.path) + rescue StandardError + end + + raise + end + + return_val + end + end + end +end diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb index 3d9df5ebcd..fbb7b55075 100644 --- a/lib/rubygems/util/licenses.rb +++ b/lib/rubygems/util/licenses.rb @@ -59,12 +59,15 @@ class Gem::Licenses Artistic-1.0-Perl Artistic-1.0-cl8 Artistic-2.0 + Artistic-dist + Aspell-RU BSD-1-Clause BSD-2-Clause BSD-2-Clause-Darwin BSD-2-Clause-Patent BSD-2-Clause-Views BSD-2-Clause-first-lines + BSD-2-Clause-pkgconf-disclaimer BSD-3-Clause BSD-3-Clause-Attribution BSD-3-Clause-Clear @@ -205,6 +208,7 @@ class Gem::Licenses Cornell-Lossless-JPEG Cronyx Crossword + CryptoSwift CrystalStacker Cube D-FSL-1.0 @@ -215,6 +219,7 @@ class Gem::Licenses DRL-1.0 DRL-1.1 DSDP + DocBook-DTD DocBook-Schema DocBook-Stylesheet DocBook-XML @@ -240,7 +245,10 @@ class Gem::Licenses FSFAP-no-warranty-disclaimer FSFUL FSFULLR + FSFULLRSD FSFULLRWD + FSL-1.1-ALv2 + FSL-1.1-MIT FTL Fair Ferguson-Twofish @@ -276,11 +284,13 @@ class Gem::Licenses GPL-2.0-or-later GPL-3.0-only GPL-3.0-or-later + Game-Programming-Gems Giftware Glide Glulxe Graphics-Gems Gutmann + HDF5 HIDAPI HP-1986 HP-1989 @@ -426,6 +436,7 @@ class Gem::Licenses NPL-1.1 NPOSL-3.0 NRL + NTIA-PD NTP NTP-0 Naumen @@ -528,11 +539,13 @@ class Gem::Licenses SMLNJ SMPPL SNIA + SOFA SPL-1.0 SSH-OpenSSH SSH-short SSLeay-standalone SSPL-1.0 + SUL-1.0 SWL Saxpath SchemeReport @@ -578,6 +591,8 @@ class Gem::Licenses Unicode-TOU UnixCrypt Unlicense + Unlicense-libtelnet + Unlicense-libwhirlpool VOSTROM VSL-1.0 Vim @@ -631,6 +646,8 @@ class Gem::Licenses gtkbook hdparm iMatix + jove + libpng-1.6.35 libpng-2.0 libselinux-1.0 libtiff @@ -638,10 +655,12 @@ class Gem::Licenses lsof magaz mailprio + man2html metamail mpi-permissive mpich2 mplus + ngrep pkgconf pnmstitch psfrag @@ -716,6 +735,7 @@ class Gem::Licenses CLISP-exception-2.0 Classpath-exception-2.0 DigiRule-FOSS-exception + Digia-Qt-LGPL-exception-1.1 FLTK-exception Fawkes-Runtime-exception Font-exception-2.0 @@ -772,6 +792,7 @@ class Gem::Licenses mif-exception mxml-exception openvpn-openssl-exception + polyparse-exception romic-exception stunnel-exception u-boot-exception-2.0 diff --git a/lib/rubygems/util/list.rb b/lib/rubygems/util/list.rb deleted file mode 100644 index 2899e8a2b9..0000000000 --- a/lib/rubygems/util/list.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gem - # The Gem::List class is currently unused and will be removed in the next major rubygems version - class List # :nodoc: - include Enumerable - attr_accessor :value, :tail - - def initialize(value = nil, tail = nil) - @value = value - @tail = tail - end - - def each - n = self - while n - yield n.value - n = n.tail - end - end - - def to_a - super.reverse - end - - def prepend(value) - List.new value, self - end - - def pretty_print(q) # :nodoc: - q.pp to_a - end - - def self.prepend(list, value) - return List.new(value) unless list - List.new value, list - end - end - deprecate_constant :List -end diff --git a/lib/rubygems/validator.rb b/lib/rubygems/validator.rb index 57e0747eb4..eb5b513570 100644 --- a/lib/rubygems/validator.rb +++ b/lib/rubygems/validator.rb @@ -59,7 +59,7 @@ class Gem::Validator #-- # TODO needs further cleanup - def alien(gems=[]) + def alien(gems = []) errors = Hash.new {|h,k| h[k] = {} } Gem::Specification.each do |spec| diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb index ad3e646cca..4800cd25f1 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http.rb @@ -46,7 +46,7 @@ module Gem::Net #:nodoc: # == Strategies # # - If you will make only a few GET requests, - # consider using {OpenURI}[rdoc-ref:OpenURI]. + # consider using {OpenURI}[https://docs.ruby-lang.org/en/master/OpenURI.html]. # - If you will make only a few requests of all kinds, # consider using the various singleton convenience methods in this class. # Each of the following methods automatically starts and finishes @@ -102,14 +102,14 @@ module Gem::Net #:nodoc: # # == URIs # - # On the internet, a URI + # On the internet, a Gem::URI # ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier]) # is a string that identifies a particular resource. # It consists of some or all of: scheme, hostname, path, query, and fragment; - # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. + # see {Gem::URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. # - # A Ruby {Gem::URI::Generic}[rdoc-ref:Gem::URI::Generic] object - # represents an internet URI. + # A Ruby {Gem::URI::Generic}[https://docs.ruby-lang.org/en/master/Gem::URI/Generic.html] object + # represents an internet Gem::URI. # It provides, among others, methods # +scheme+, +hostname+, +path+, +query+, and +fragment+. # @@ -144,7 +144,7 @@ module Gem::Net #:nodoc: # # === Queries # - # A host-specific query adds name/value pairs to the URI: + # A host-specific query adds name/value pairs to the Gem::URI: # # _uri = uri.dup # params = {userId: 1, completed: false} @@ -154,7 +154,7 @@ module Gem::Net #:nodoc: # # === Fragments # - # A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect + # A {Gem::URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect # in \Gem::Net::HTTP; # the same data is returned, regardless of whether a fragment is included. # @@ -327,9 +327,9 @@ module Gem::Net #:nodoc: # res = http.request(req) # end # - # Or if you simply want to make a GET request, you may pass in a URI + # Or if you simply want to make a GET request, you may pass in a Gem::URI # object that has an \HTTPS URL. \Gem::Net::HTTP automatically turns on TLS - # verification if the URI object has a 'https' :URI scheme: + # verification if the Gem::URI object has a 'https' Gem::URI scheme: # # uri # => #<Gem::URI::HTTPS https://jsonplaceholder.typicode.com/> # Gem::Net::HTTP.get(uri) @@ -374,7 +374,7 @@ module Gem::Net #:nodoc: # # When environment variable <tt>'http_proxy'</tt> # is set to a \Gem::URI string, - # the returned +http+ will have the server at that URI as its proxy; + # the returned +http+ will have the server at that Gem::URI as its proxy; # note that the \Gem::URI string must have a protocol # such as <tt>'http'</tt> or <tt>'https'</tt>: # @@ -460,7 +460,7 @@ module Gem::Net #:nodoc: # # First, what's elsewhere. Class Gem::Net::HTTP: # - # - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here]. + # - Inherits from {class Object}[https://docs.ruby-lang.org/en/master/Object.html#class-Object-label-What-27s+Here]. # # This is a categorized summary of methods and attributes. # @@ -475,8 +475,7 @@ module Gem::Net #:nodoc: # # - {::start}[rdoc-ref:Gem::Net::HTTP.start]: # Begins a new session in a new \Gem::Net::HTTP object. - # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?] - # (aliased as {#active?}[rdoc-ref:Gem::Net::HTTP#active?]): + # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?]: # Returns whether in a session. # - {#finish}[rdoc-ref:Gem::Net::HTTP#finish]: # Ends an active session. @@ -556,18 +555,15 @@ module Gem::Net #:nodoc: # Sends a PUT request and returns a response object. # - {#request}[rdoc-ref:Gem::Net::HTTP#request]: # Sends a request and returns a response object. - # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get] - # (aliased as {#get2}[rdoc-ref:Gem::Net::HTTP#get2]): + # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get]: # Sends a GET request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head] - # (aliased as {#head2}[rdoc-ref:Gem::Net::HTTP#head2]): + # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head]: # Sends a HEAD request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post] - # (aliased as {#post2}[rdoc-ref:Gem::Net::HTTP#post2]): + # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post]: # Sends a POST request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. @@ -605,8 +601,7 @@ module Gem::Net #:nodoc: # Returns whether +self+ is a proxy class. # - {#proxy?}[rdoc-ref:Gem::Net::HTTP#proxy?]: # Returns whether +self+ has a proxy. - # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address] - # (aliased as {#proxyaddr}[rdoc-ref:Gem::Net::HTTP#proxyaddr]): + # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]: # Returns the proxy address. # - {#proxy_from_env?}[rdoc-ref:Gem::Net::HTTP#proxy_from_env?]: # Returns whether the proxy is taken from an environment variable. @@ -718,8 +713,7 @@ module Gem::Net #:nodoc: # === \HTTP Version # # - {::version_1_2?}[rdoc-ref:Gem::Net::HTTP.version_1_2?] - # (aliased as {::is_version_1_2?}[rdoc-ref:Gem::Net::HTTP.is_version_1_2?] - # and {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]): + # (aliased as {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]): # Returns true; retained for compatibility. # # === Debugging @@ -730,7 +724,7 @@ module Gem::Net #:nodoc: class HTTP < Protocol # :stopdoc: - VERSION = "0.6.0" + VERSION = "0.9.1" HTTPVersion = '1.1' begin require 'zlib' @@ -796,7 +790,7 @@ module Gem::Net #:nodoc: # "completed": false # } # - # With URI object +uri+ and optional hash argument +headers+: + # With Gem::URI object +uri+ and optional hash argument +headers+: # # uri = Gem::URI('https://jsonplaceholder.typicode.com/todos/1') # headers = {'Content-type' => 'application/json; charset=UTF-8'} @@ -869,7 +863,7 @@ module Gem::Net #:nodoc: # Posts data to a host; returns a Gem::Net::HTTPResponse object. # - # Argument +url+ must be a URI; + # Argument +url+ must be a Gem::URI; # argument +data+ must be a hash: # # _uri = uri.dup @@ -1185,6 +1179,7 @@ module Gem::Net #:nodoc: @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1293,7 +1288,7 @@ module Gem::Net #:nodoc: # - The name of an encoding. # - An alias for an encoding name. # - # See {Encoding}[rdoc-ref:Encoding]. + # See {Encoding}[https://docs.ruby-lang.org/en/master/Encoding.html]. # # Examples: # @@ -1327,6 +1322,9 @@ module Gem::Net #:nodoc: # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + + # Sets whether the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1533,9 +1531,9 @@ module Gem::Net #:nodoc: :verify_depth, :verify_mode, :verify_hostname, - ] # :nodoc: + ].freeze # :nodoc: - SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym } # :nodoc: + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file @@ -1552,11 +1550,11 @@ module Gem::Net #:nodoc: attr_accessor :cert_store # Sets or returns the available SSL ciphers. - # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D]. + # See {OpenSSL::SSL::SSLContext#ciphers=}[OpenSSL::SSL::SSL::Context#ciphers=]. attr_accessor :ciphers # Sets or returns the extra X509 certificates to be added to the certificate chain. - # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate]. + # See {OpenSSL::SSL::SSLContext#add_certificate}[OpenSSL::SSL::SSL::Context#add_certificate]. attr_accessor :extra_chain_cert # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. @@ -1566,15 +1564,15 @@ module Gem::Net #:nodoc: attr_accessor :ssl_timeout # Sets or returns the SSL version. - # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D]. + # See {OpenSSL::SSL::SSLContext#ssl_version=}[OpenSSL::SSL::SSL::Context#ssl_version=]. attr_accessor :ssl_version # Sets or returns the minimum SSL version. - # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D]. + # See {OpenSSL::SSL::SSLContext#min_version=}[OpenSSL::SSL::SSL::Context#min_version=]. attr_accessor :min_version # Sets or returns the maximum SSL version. - # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D]. + # See {OpenSSL::SSL::SSLContext#max_version=}[OpenSSL::SSL::SSL::Context#max_version=]. attr_accessor :max_version # Sets or returns the callback for the server certification verification. @@ -1590,7 +1588,7 @@ module Gem::Net #:nodoc: # Sets or returns whether to verify that the server certificate is valid # for the hostname. - # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode]. + # See {OpenSSL::SSL::SSLContext#verify_hostname=}[OpenSSL::SSL::SSL::Context#verify_hostname=]. attr_accessor :verify_hostname # Returns the X509 certificate chain (an array of strings) @@ -1638,6 +1636,21 @@ module Gem::Net #:nodoc: self end + # Finishes the \HTTP session: + # + # http = Gem::Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1660,14 +1673,15 @@ module Gem::Net #:nodoc: end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = timeouted_connect(conn_addr, conn_port) + rescue => e + if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + e = Gem::Net::OpenTimeout.new(e) end - } + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? @@ -1760,23 +1774,30 @@ module Gem::Net #:nodoc: end private :connect - def on_connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) end - private :on_connect + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT - # Finishes the \HTTP session: - # - # http = Gem::Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish + def timeouted_connect(conn_addr, conn_port) + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + end + private :timeouted_connect + + def on_connect end + private :on_connect def do_finish @started = false @@ -1827,6 +1848,8 @@ module Gem::Net #:nodoc: } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1866,7 +1889,7 @@ module Gem::Net #:nodoc: @proxy_from_env end - # The proxy URI determined from the environment for this connection. + # The proxy Gem::URI determined from the environment for this connection. def proxy_uri # :nodoc: return if @proxy_uri == false @proxy_uri ||= Gem::URI::HTTP.new( @@ -1921,9 +1944,11 @@ module Gem::Net #:nodoc: alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) - require 'cgi/util' + require 'cgi/escape' + require 'cgi/util' unless defined?(CGI::EscapeExt) CGI.unescape(value) end @@ -1948,6 +1973,7 @@ module Gem::Net #:nodoc: path end end + # :startdoc: # # HTTP operations @@ -2402,7 +2428,9 @@ module Gem::Net #:nodoc: res end - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + # :stopdoc: + + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc: def transport_request(req) count = 0 @@ -2559,7 +2587,7 @@ module Gem::Net #:nodoc: alias_method :D, :debug end - # for backward compatibility until Ruby 3.5 + # for backward compatibility until Ruby 4.0 # https://bugs.ruby-lang.org/issues/20900 # https://github.com/bblimke/webmock/pull/1081 HTTPSession = HTTP diff --git a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb index c629c0113b..218df9a8bd 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Gem::Net # Gem::Net::HTTP exception class. # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ module Gem::Net alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb index 5cfe75a7cd..d6496d4ac1 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb @@ -19,16 +19,13 @@ class Gem::Net::HTTPGenericRequest if Gem::URI === uri_or_path then raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path - hostname = uri_or_path.hostname + hostname = uri_or_path.host raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup - host = @uri.hostname.dup - host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil - host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup @@ -51,7 +48,7 @@ class Gem::Net::HTTPGenericRequest initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= host if host + self['Host'] ||= @uri.authority if @uri @body = nil @body_stream = nil @body_data = nil @@ -102,6 +99,31 @@ class Gem::Net::HTTPGenericRequest "\#<#{self.class} #{@method}>" end + # Returns a string representation of the request with the details for pp: + # + # require 'pp' + # post = Gem::Net::HTTP::Post.new(uri) + # post.inspect # => "#<Gem::Net::HTTP::Post POST>" + # post.pretty_inspect + # # => #<Gem::Net::HTTP::Post + # POST + # path="/" + # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], + # "accept" => ["*/*"], + # "user-agent" => ["Ruby"], + # "host" => ["www.ruby-lang.org"]}> + # + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text @method + q.breakable + q.text "path="; q.pp @path + q.breakable + q.text "headers="; q.pp to_hash + } + end + ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. @@ -220,7 +242,7 @@ class Gem::Net::HTTPGenericRequest end if host = self['host'] - host.sub!(/:.*/m, '') + host = Gem::URI.parse("//#{host}").host # Remove a port component from the existing Host header elsif host = @uri.host else host = addr @@ -239,6 +261,8 @@ class Gem::Net::HTTPGenericRequest private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock @@ -260,7 +284,6 @@ class Gem::Net::HTTPGenericRequest def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body @@ -271,7 +294,6 @@ class Gem::Net::HTTPGenericRequest raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? @@ -373,12 +395,6 @@ class Gem::Net::HTTPGenericRequest buf.clear end - def supply_default_content_type - return if content_type() - warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE - set_content_type 'application/x-www-form-urlencoded' - end - ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. @@ -411,4 +427,3 @@ class Gem::Net::HTTPGenericRequest end end - diff --git a/lib/rubygems/vendor/net-http/lib/net/http/header.rb b/lib/rubygems/vendor/net-http/lib/net/http/header.rb index 5cb1da01ec..bc68cd2eef 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/header.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Gem::Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ module Gem::Net::HTTPHeader end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ module Gem::Net::HTTPHeader ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,7 +494,7 @@ module Gem::Net::HTTPHeader alias canonical_each each_capitalized - def capitalize(name) + def capitalize(name) # :nodoc: name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ module Gem::Net::HTTPHeader @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ module Gem::Net::HTTPHeader false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb index 45727e7f61..f990761042 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Gem::Net::HTTP#get: sends +GET+ request, returns response object. # class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ end # - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object. # class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ end # - Gem::Net::HTTP#post: sends +POST+ request, returns response object. # class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -130,6 +133,7 @@ end # - Gem::Net::HTTP#put: sends +PUT+ request, returns response object. # class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -162,6 +166,7 @@ end # - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -193,6 +198,7 @@ end # - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -224,6 +230,7 @@ end # - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -258,6 +265,7 @@ end # - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -285,6 +293,7 @@ end # - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -308,6 +317,7 @@ end # - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -331,6 +341,7 @@ end # - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -354,6 +365,7 @@ end # - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object. # class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -377,6 +389,7 @@ end # - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object. # class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -400,6 +413,7 @@ end # - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -423,8 +437,8 @@ end # - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/rubygems/vendor/net-http/lib/net/http/response.rb b/lib/rubygems/vendor/net-http/lib/net/http/response.rb index cbbd191d87..dc164f1504 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/response.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/response.rb @@ -153,6 +153,7 @@ class Gem::Net::HTTPResponse end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ class Gem::Net::HTTPResponse # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb index 0f26ae6c26..62ce1cba1b 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb @@ -4,7 +4,9 @@ module Gem::Net + # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +21,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +37,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +53,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +68,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +83,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +101,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +119,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +136,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +155,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +173,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +191,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +209,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +229,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +247,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +266,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +285,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +303,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +323,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +341,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +359,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +378,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +396,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +414,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +432,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +449,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +467,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +484,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +501,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +518,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +535,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +553,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +570,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +587,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +605,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +622,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +639,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +657,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +675,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +693,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +711,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +728,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +746,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +765,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +782,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +800,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +821,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +838,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +854,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +871,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +891,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +908,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +925,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +943,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +961,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +982,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +1000,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1018,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1036,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1054,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1072,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1089,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1106,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1123,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1141,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,19 +1158,21 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Gem::Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Gem::Net::HTTPInformation, '2' => Gem::Net::HTTPSuccess, '3' => Gem::Net::HTTPRedirection, '4' => Gem::Net::HTTPClientError, '5' => Gem::Net::HTTPServerError - } + }.freeze CODE_TO_OBJ = { '100' => Gem::Net::HTTPContinue, '101' => Gem::Net::HTTPSwitchProtocol, @@ -1170,5 +1238,5 @@ class Gem::Net::HTTPResponse '508' => Gem::Net::HTTPLoopDetected, '510' => Gem::Net::HTTPNotExtended, '511' => Gem::Net::HTTPNetworkAuthenticationRequired, - } + }.freeze end diff --git a/lib/rubygems/vendor/optparse/lib/optparse.rb b/lib/rubygems/vendor/optparse/lib/optparse.rb index 537d06f229..d39d9dd4e0 100644 --- a/lib/rubygems/vendor/optparse/lib/optparse.rb +++ b/lib/rubygems/vendor/optparse/lib/optparse.rb @@ -7,6 +7,7 @@ # # See Gem::OptionParser for documentation. # +require 'set' unless defined?(Set) #-- # == Developer Documentation (not for RDoc output) @@ -142,7 +143,7 @@ # Used: # # $ ruby optparse-test.rb -r -# optparse-test.rb:9:in `<main>': missing argument: -r (Gem::OptionParser::MissingArgument) +# optparse-test.rb:9:in '<main>': missing argument: -r (Gem::OptionParser::MissingArgument) # $ ruby optparse-test.rb -r my-library # You required my-library! # @@ -235,7 +236,7 @@ # $ ruby optparse-test.rb --user 2 # #<struct User id=2, name="Gandalf"> # $ ruby optparse-test.rb --user 3 -# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) +# optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError) # # === Store options to a Hash # @@ -425,7 +426,8 @@ # class Gem::OptionParser # The version string - Gem::OptionParser::Version = "0.6.0" + VERSION = "0.8.0" + Version = VERSION # for compatibility # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -461,6 +463,10 @@ class Gem::OptionParser candidates end + def self.completable?(key) + String.try_convert(key) or defined?(key.id2name) + end + def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end @@ -496,7 +502,6 @@ class Gem::OptionParser end end - # # Map from option/keyword string to object with completion. # @@ -504,7 +509,6 @@ class Gem::OptionParser include Completion end - # # Individual switch class. Not important to the user. # @@ -546,11 +550,11 @@ class Gem::OptionParser def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, - desc = ([] if short or long), block = nil, &_block) + desc = ([] if short or long), block = nil, values = nil, &_block) raise if Array === pattern block ||= _block - @pattern, @conv, @short, @long, @arg, @desc, @block = - pattern, conv, short, long, arg, desc, block + @pattern, @conv, @short, @long, @arg, @desc, @block, @values = + pattern, conv, short, long, arg, desc, block, values end # @@ -583,11 +587,15 @@ class Gem::OptionParser # exception. # def conv_arg(arg, val = []) # :nodoc: + v, = *val if conv val = conv.call(*val) else val = proc {|v| v}.call(*val) end + if @values + @values.include?(val) or raise InvalidArgument, v + end return arg, block, val end private :conv_arg @@ -668,7 +676,7 @@ class Gem::OptionParser (sopts+lopts).each do |opt| # "(-x -c -r)-l[left justify]" - if /^--\[no-\](.+)$/ =~ opt + if /\A--\[no-\](.+)$/ =~ opt o = $1 yield("--#{o}", desc.join("")) yield("--no-#{o}", desc.join("")) @@ -1032,7 +1040,6 @@ class Gem::OptionParser DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} - COMPSYS_HEADER = <<'XXX' # :nodoc: typeset -A opt_args @@ -1051,16 +1058,16 @@ XXX end def help_exit - if STDOUT.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) + if $stdout.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) less = ENV["LESS"] - args = [{"LESS" => "#{!less || less.empty? ? '-' : less}Fe"}, pager, "w"] + args = [{"LESS" => "#{less} -Fe"}, pager, "w"] print = proc do |f| f.puts help rescue Errno::EPIPE # pager terminated end if Process.respond_to?(:fork) and false - IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call(STDOUT)} + IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)} # unreachable end IO.popen(*args, &print) @@ -1102,7 +1109,7 @@ XXX # Officious['*-completion-zsh'] = proc do |parser| Switch::OptionalArgument.new do |arg| - parser.compsys(STDOUT, arg) + parser.compsys($stdout, arg) exit end end @@ -1288,7 +1295,15 @@ XXX # to $0. # def program_name - @program_name || File.basename($0, '.*') + @program_name || strip_ext(File.basename($0)) + end + + private def strip_ext(name) # :nodoc: + exts = /#{ + require "rbconfig" + Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) + }\z/o + name.sub(exts, "") end # for experimental cascading :-) @@ -1467,6 +1482,7 @@ XXX klass = nil q, a = nil has_arg = false + values = nil opts.each do |o| # argument class @@ -1480,7 +1496,7 @@ XXX end # directly specified pattern(any object possible to match) - if (!(String === o || Symbol === o)) and o.respond_to?(:match) + if !Completion.completable?(o) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc @@ -1494,7 +1510,12 @@ XXX case o when Proc, Method block = notwice(o, block, 'block') - when Array, Hash + when Array, Hash, Set + if Array === o + o, v = o.partition {|v,| Completion.completable?(v)} + values = notwice(v, values, 'values') unless v.empty? + next if o.empty? + end case pattern when CompletingHash when nil @@ -1504,11 +1525,13 @@ XXX raise ArgumentError, "argument pattern given twice" end o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} + when Range + values = notwice(o, values, 'values') when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') - when /^--no-([^\[\]=\s]*)(.+)?/ + when /\A--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style @@ -1519,7 +1542,7 @@ XXX (q = q.downcase).tr!('_', '-') long << "no-#{q}" nolong << q - when /^--\[no-\]([^\[\]=\s]*)(.+)?/ + when /\A--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a @@ -1532,7 +1555,7 @@ XXX not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << "no-#{o}" - when /^--([^\[\]=\s]*)(.+)?/ + when /\A--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1542,7 +1565,7 @@ XXX ldesc << "--#{q}" (o = q.downcase).tr!('_', '-') long << o - when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ + when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a @@ -1553,7 +1576,7 @@ XXX end sdesc << "-#{q}" short << Regexp.new(q) - when /^-(.)(.+)?/ + when /\A-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1562,7 +1585,7 @@ XXX end sdesc << "-#{q}" short << q - when /^=/ + when /\A=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else @@ -1571,12 +1594,18 @@ XXX end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern + if Range === values and klass + unless (!values.begin or klass === values.begin) and + (!values.end or klass === values.end) + raise ArgumentError, "range does not match class" + end + end if !(short.empty? and long.empty?) if has_arg and default_style == Switch::NoArgument default_style = Switch::RequiredArgument end s = (style || default_style).new(pattern || default_pattern, - conv, sdesc, ldesc, arg, desc, block) + conv, sdesc, ldesc, arg, desc, block, values) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) @@ -1585,7 +1614,7 @@ XXX else short << pattern s = (style || default_style).new(pattern, - conv, nil, nil, arg, desc, block) + conv, nil, nil, arg, desc, block, values) end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), @@ -1827,7 +1856,7 @@ XXX # def permute!(argv = default_argv, **keywords) nonopts = [] - order!(argv, **keywords, &nonopts.method(:<<)) + order!(argv, **keywords) {|nonopt| nonopts << nonopt} argv[0, 0] = nonopts argv end @@ -1880,13 +1909,16 @@ XXX single_options, *long_options = *args result = {} + setter = (symbolize_names ? + ->(name, val) {result[name.to_sym] = val} + : ->(name, val) {result[name] = val}) single_options.scan(/(.)(:)?/) do |opt, val| if val - result[opt] = nil + setter[opt, nil] define("-#{opt} VAL") else - result[opt] = false + setter[opt, false] define("-#{opt}") end end if single_options @@ -1895,16 +1927,16 @@ XXX arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val - result[opt] = val.empty? ? nil : val + setter[opt, (val unless val.empty?)] define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else - result[opt] = false + setter[opt, false] define("--#{opt}", *[desc].compact) end end - parse_in_order(argv, result.method(:[]=), **keywords) - symbolize_names ? result.transform_keys(&:to_sym) : result + parse_in_order(argv, setter, **keywords) + result end # @@ -1954,7 +1986,7 @@ XXX visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption - raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) + raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end private :complete @@ -2019,19 +2051,27 @@ XXX def load(filename = nil, **keywords) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil + return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil basename << ".options" + if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty? + # https://specifications.freedesktop.org/basedir-spec/latest/#variables + # + # If $XDG_CONFIG_HOME is either not set or empty, a default + # equal to $HOME/.config should be used. + xdg = ['~/.config', true] + end return [ - # XDG - ENV['XDG_CONFIG_HOME'], - '~/.config', + xdg, + *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), # Haiku - '~/config/settings', - ].any? {|dir| + ['~/config/settings', true], + ].any? {|dir, expand| next if !dir or dir.empty? - load(File.expand_path(basename, dir), **keywords) rescue nil + filename = File.join(dir, basename) + filename = File.expand_path(filename) if expand + load(filename, **keywords) rescue nil } end begin @@ -2237,9 +2277,10 @@ XXX argv end + DIR = File.join(__dir__, '') def self.filter_backtrace(array) unless $DEBUG - array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + array.delete_if {|bt| bt.start_with?(DIR)} end array end diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb index 4d95e5fc7f..168df21f3e 100644 --- a/lib/rubygems/vendor/resolv/lib/resolv.rb +++ b/lib/rubygems/vendor/resolv/lib/resolv.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'socket' -require_relative '../../timeout/lib/timeout' +require_relative '../../../vendored_timeout' require 'io/wait' require_relative '../../../vendored_securerandom' @@ -33,7 +33,7 @@ require_relative '../../../vendored_securerandom' class Gem::Resolv - VERSION = "0.6.0" + VERSION = "0.6.2" ## # Looks up the first IP address for +name+. @@ -173,13 +173,16 @@ class Gem::Resolv class ResolvTimeout < Gem::Timeout::Error; end + WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ + private_constant :WINDOWS + ## # Gem::Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts - if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and + if WINDOWS begin - require 'win32/resolv' + require 'win32/resolv' unless defined?(Win32::Resolv) DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL rescue LoadError end @@ -659,8 +662,20 @@ class Gem::Resolv } end - def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: - begin + case RUBY_PLATFORM + when *[ + # https://www.rfc-editor.org/rfc/rfc6056.txt + # Appendix A. Survey of the Algorithms in Use by Some Popular Implementations + /freebsd/, /linux/, /netbsd/, /openbsd/, /solaris/, + /darwin/, # the same as FreeBSD + ] then + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + udpsock.bind(bind_host, 0) + end + else + # Sequential port assignment + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + # Ephemeral port number range recommended by RFC 6056 port = random(1024..65535) udpsock.bind(bind_host, port) rescue Errno::EADDRINUSE, # POSIX @@ -983,13 +998,13 @@ class Gem::Resolv next unless keyword case keyword when 'nameserver' - nameserver.concat(args) + nameserver.concat(args.each(&:freeze)) when 'domain' next if args.empty? - search = [args[0]] + search = [args[0].freeze] when 'search' next if args.empty? - search = args + search = args.each(&:freeze) when 'options' args.each {|arg| case arg @@ -1000,22 +1015,22 @@ class Gem::Resolv end } } - return { :nameserver => nameserver, :search => search, :ndots => ndots } + return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze end def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename - config_hash = Config.parse_resolv_conf(filename) + Config.parse_resolv_conf(filename) + elsif WINDOWS + require 'win32/resolv' unless defined?(Win32::Resolv) + search, nameserver = Win32::Resolv.get_resolv_info + config_hash = {} + config_hash[:nameserver] = nameserver if nameserver + config_hash[:search] = [search].flatten if search + config_hash else - if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM - require 'win32/resolv' - search, nameserver = Win32::Resolv.get_resolv_info - config_hash = {} - config_hash[:nameserver] = nameserver if nameserver - config_hash[:search] = [search].flatten if search - end + {} end - config_hash || {} end def lazy_initialize @@ -1664,6 +1679,7 @@ class Gem::Resolv prev_index = @index save_index = nil d = [] + size = -1 while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) @@ -1684,7 +1700,10 @@ class Gem::Resolv end @index = idx else - d << self.get_label + l = self.get_label + d << l + size += 1 + l.string.bytesize + raise DecodeError.new("name label data exceed 255 octets") if size > 255 end end end @@ -2110,7 +2129,14 @@ class Gem::Resolv attr_reader :ttl - ClassHash = {} # :nodoc: + ClassHash = Module.new do + module_function + + def []=(type_class_value, klass) + type_value, class_value = type_class_value + Resource.const_set(:"Type#{type_value}_Class#{class_value}", klass) + end + end def encode_rdata(msg) # :nodoc: raise NotImplementedError.new @@ -2148,7 +2174,9 @@ class Gem::Resolv end def self.get_class(type_value, class_value) # :nodoc: - return ClassHash[[type_value, class_value]] || + cache = :"Type#{type_value}_Class#{class_value}" + + return (const_defined?(cache) && const_get(cache)) || Generic.create(type_value, class_value) end @@ -2577,7 +2605,7 @@ class Gem::Resolv end ## - # Flags for this proprty: + # Flags for this property: # - Bit 0 : 0 = not critical, 1 = critical attr_reader :flags diff --git a/lib/rubygems/vendor/timeout/lib/timeout.rb b/lib/rubygems/vendor/timeout/lib/timeout.rb index 455c504f47..376b8c0e2b 100644 --- a/lib/rubygems/vendor/timeout/lib/timeout.rb +++ b/lib/rubygems/vendor/timeout/lib/timeout.rb @@ -20,7 +20,7 @@ module Gem::Timeout # The version - VERSION = "0.4.3" + VERSION = "0.4.4" # Internal error raised to when a timeout is triggered. class ExitException < Exception @@ -123,6 +123,9 @@ module Gem::Timeout def self.ensure_timeout_thread_created unless @timeout_thread and @timeout_thread.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the @timeout_thread. + return if TIMEOUT_THREAD_MUTEX.owned? TIMEOUT_THREAD_MUTEX.synchronize do unless @timeout_thread and @timeout_thread.alive? @timeout_thread = create_timeout_thread diff --git a/lib/rubygems/vendor/uri/lib/uri/common.rb b/lib/rubygems/vendor/uri/lib/uri/common.rb index f0755f5fdc..e9bdfa6a07 100644 --- a/lib/rubygems/vendor/uri/lib/uri/common.rb +++ b/lib/rubygems/vendor/uri/lib/uri/common.rb @@ -30,6 +30,9 @@ module Gem::URI remove_const(:Parser) if defined?(::Gem::URI::Parser) const_set("Parser", parser.class) + remove_const(:PARSER) if defined?(::Gem::URI::PARSER) + const_set("PARSER", parser) + remove_const(:REGEXP) if defined?(::Gem::URI::REGEXP) remove_const(:PATTERN) if defined?(::Gem::URI::PATTERN) if Parser == RFC2396_Parser @@ -49,10 +52,10 @@ module Gem::URI warn "Gem::URI::REGEXP is obsolete. Use Gem::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE Gem::URI::RFC2396_REGEXP elsif value = RFC2396_PARSER.regexp[const] - warn "Gem::URI::#{const} is obsolete. Use RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE + warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE value elsif value = RFC2396_Parser.const_get(const) - warn "Gem::URI::#{const} is obsolete. Use RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE + warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE value else super @@ -92,6 +95,40 @@ module Gem::URI end module Schemes # :nodoc: + class << self + ReservedChars = ".+-" + EscapedChars = "\u01C0\u01C1\u01C2" + # Use Lo category chars as escaped chars for TruffleRuby, which + # does not allow Symbol categories as identifiers. + + def escape(name) + unless name and name.ascii_only? + return nil + end + name.upcase.tr(ReservedChars, EscapedChars) + end + + def unescape(name) + name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase + end + + def find(name) + const_get(name, false) if name and const_defined?(name, false) + end + + def register(name, klass) + unless scheme = escape(name) + raise ArgumentError, "invalid character as scheme - #{name}" + end + const_set(scheme, klass) + end + + def list + constants.map { |name| + [unescape(name.to_s), const_get(name)] + }.to_h + end + end end private_constant :Schemes @@ -104,7 +141,7 @@ module Gem::URI # 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) + Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: @@ -122,14 +159,14 @@ module Gem::URI # # Related: Gem::URI.register_scheme. def self.scheme_list - Schemes.constants.map { |name| - [name.to_s.upcase, Schemes.const_get(name)] - }.to_h + Schemes.list end + # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: @@ -148,12 +185,10 @@ module Gem::URI # # => #<Gem::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # def self.for(scheme, *arguments, default: Generic) - const_name = scheme.to_s.upcase + const_name = Schemes.escape(scheme) 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 ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) @@ -195,7 +230,7 @@ module Gem::URI # ["fragment", "top"]] # def self.split(uri) - DEFAULT_PARSER.split(uri) + PARSER.split(uri) end # Returns a new \Gem::URI object constructed from the given string +uri+: @@ -205,11 +240,11 @@ module Gem::URI # Gem::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => #<Gem::URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # - # It's recommended to first ::escape string +uri+ + # It's recommended to first Gem::URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid Gem::URI characters. # def self.parse(uri) - DEFAULT_PARSER.parse(uri) + PARSER.parse(uri) end # Merges the given Gem::URI strings +str+ @@ -265,7 +300,7 @@ module Gem::URI # def self.extract(str, schemes = nil, &block) # :nodoc: warn "Gem::URI.extract is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.extract(str, schemes, &block) + PARSER.extract(str, schemes, &block) end # @@ -302,7 +337,7 @@ module Gem::URI # def self.regexp(schemes = nil)# :nodoc: warn "Gem::URI.regexp is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.make_regexp(schemes) + PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: @@ -407,6 +442,8 @@ module Gem::URI _decode_uri_component(/%\h\h/, str, enc) end + # Returns a string derived from the given string +str+ with + # Gem::URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT @@ -421,6 +458,8 @@ module Gem::URI end private_class_method :_encode_uri_component + # Returns a string decoding characters matching +regexp+ from the + # given \URL-encoded string +str+. 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) @@ -859,6 +898,7 @@ module Gem # Returns a \Gem::URI object derived from the given +uri+, # which may be a \Gem::URI string or an existing \Gem::URI object: # + # require 'rubygems/vendor/uri/lib/uri' # # Returns a new Gem::URI. # uri = Gem::URI('http://github.com/ruby/ruby') # # => #<Gem::URI::HTTP http://github.com/ruby/ruby> @@ -866,6 +906,8 @@ module Gem # Gem::URI(uri) # # => #<Gem::URI::HTTP http://github.com/ruby/ruby> # + # You must require 'rubygems/vendor/uri/lib/uri' to use this method. + # def URI(uri) if uri.is_a?(Gem::URI::Generic) uri diff --git a/lib/rubygems/vendor/uri/lib/uri/file.rb b/lib/rubygems/vendor/uri/lib/uri/file.rb index 768755fc2d..391c499716 100644 --- a/lib/rubygems/vendor/uri/lib/uri/file.rb +++ b/lib/rubygems/vendor/uri/lib/uri/file.rb @@ -47,7 +47,7 @@ module Gem::URI # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # - # uri3 = Gem::URI::File.build({:path => Gem::URI::escape('/path/my file.txt')}) + # uri3 = Gem::URI::File.build({:path => Gem::URI::RFC2396_PARSER.escape('/path/my file.txt')}) # uri3.to_s # => "file:///path/my%20file.txt" # def self.build(args) diff --git a/lib/rubygems/vendor/uri/lib/uri/generic.rb b/lib/rubygems/vendor/uri/lib/uri/generic.rb index 2eabe2b4e3..d0bc77dfda 100644 --- a/lib/rubygems/vendor/uri/lib/uri/generic.rb +++ b/lib/rubygems/vendor/uri/lib/uri/generic.rb @@ -73,7 +73,7 @@ module Gem::URI # # At first, tries to create a new Gem::URI::Generic instance using # Gem::URI::Generic::build. But, if exception Gem::URI::InvalidComponentError is raised, - # then it does Gem::URI::Escape.escape all Gem::URI components and tries again. + # then it does Gem::URI::RFC2396_PARSER.escape all Gem::URI components and tries again. # def self.build2(args) begin @@ -126,9 +126,9 @@ module Gem::URI end end else - component = self.class.component rescue ::Gem::URI::Generic::COMPONENT + component = self.component rescue ::Gem::URI::Generic::COMPONENT raise ArgumentError, - "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil @@ -186,18 +186,18 @@ module Gem::URI if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -284,7 +284,7 @@ module Gem::URI # Returns the parser to be used. # - # Unless a Gem::URI::Parser is defined, DEFAULT_PARSER is used. + # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser @@ -315,7 +315,7 @@ module Gem::URI end # - # Checks the scheme +v+ component against the Gem::URI::Parser Regexp for :SCHEME. + # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v @@ -385,7 +385,7 @@ module Gem::URI # # Checks the user +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -409,7 +409,7 @@ module Gem::URI # # Checks the password +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -511,7 +511,7 @@ module Gem::URI user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ module Gem::URI # See also Gem::URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ module Gem::URI @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after Gem::URI decoding. def decoded_user Gem::URI.decode_uri_component(@user) if @user @@ -586,7 +592,7 @@ module Gem::URI # # Checks the host +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :HOST. + # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. @@ -615,6 +621,13 @@ module Gem::URI end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ module Gem::URI def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -675,7 +689,7 @@ module Gem::URI # # Checks the port +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :PORT. + # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. @@ -729,6 +743,7 @@ module Gem::URI def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end @@ -748,7 +763,7 @@ module Gem::URI # # Checks the path +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp + # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, @@ -853,7 +868,7 @@ module Gem::URI # # Checks the opaque +v+ component for RFC2396 compliance and - # against the Gem::URI::Parser Regexp for :OPAQUE. + # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. @@ -905,7 +920,7 @@ module Gem::URI end # - # Checks the fragment +v+ component against the Gem::URI::Parser Regexp for :FRAGMENT. + # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args @@ -1121,7 +1136,7 @@ module Gem::URI base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1134,9 +1149,7 @@ module Gem::URI # RFC2396, Section 5.2, 4) if authority - base.set_userinfo(rel.userinfo) - base.set_host(rel.host) - base.set_port(rel.port || base.default_port) + base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) @@ -1527,7 +1540,7 @@ module Gem::URI 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 + warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end diff --git a/lib/rubygems/vendor/uri/lib/uri/http.rb b/lib/rubygems/vendor/uri/lib/uri/http.rb index 658e9941dd..99c78358ac 100644 --- a/lib/rubygems/vendor/uri/lib/uri/http.rb +++ b/lib/rubygems/vendor/uri/lib/uri/http.rb @@ -61,6 +61,18 @@ module Gem::URI super(tmp) end + # Do not allow empty host names, as they are not allowed by RFC 3986. + def check_host(v) + ret = super + + if ret && v.empty? + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + ret + end + # # == Description # diff --git a/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb index 04a1d0c869..2bb4181649 100644 --- a/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb +++ b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb @@ -67,7 +67,7 @@ module Gem::URI # # == Synopsis # - # Gem::URI::Parser.new([opts]) + # Gem::URI::RFC2396_Parser.new([opts]) # # == Args # @@ -86,7 +86,7 @@ module Gem::URI # # == Examples # - # p = Gem::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # p = Gem::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> #<Gem::URI::HTTP http://example.jp/%uABCD> # Gem::URI.parse(u.to_s) #=> raises Gem::URI::InvalidURIError # @@ -108,12 +108,12 @@ module Gem::URI # The Hash of patterns. # - # See also Gem::URI::Parser.initialize_pattern. + # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # - # See also Gem::URI::Parser.initialize_regexp. + # See also #initialize_regexp. attr_reader :regexp # Returns a split Gem::URI against +regexp[:ABS_URI]+. @@ -202,8 +202,7 @@ module Gem::URI # # == Usage # - # p = Gem::URI::Parser.new - # p.parse("ldap://ldap.example.com/dc=example?user=john") + # Gem::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> #<Gem::URI::LDAP ldap://ldap.example.com/dc=example?user=john> # def parse(uri) @@ -244,7 +243,7 @@ module Gem::URI # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # - # See also Gem::URI::Parser.make_regexp. + # See also #make_regexp. # def extract(str, schemes = nil) if block_given? @@ -263,7 +262,7 @@ module Gem::URI unless schemes @regexp[:ABS_URI_REF] else - /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end @@ -524,6 +523,8 @@ module Gem::URI ret end + # Returns +uri+ as-is if it is Gem::URI, or convert it to Gem::URI if it is + # a String. def convert_to_uri(uri) if uri.is_a?(Gem::URI::Generic) uri diff --git a/lib/rubygems/vendor/uri/lib/uri/version.rb b/lib/rubygems/vendor/uri/lib/uri/version.rb index c2f617ce25..7ee577887b 100644 --- a/lib/rubygems/vendor/uri/lib/uri/version.rb +++ b/lib/rubygems/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Gem::URI # :stopdoc: - VERSION_CODE = '010003'.freeze - VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + VERSION = '1.1.1'.freeze + VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index 25c412be4b..90fe1b3c24 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -171,9 +171,7 @@ class Gem::Version # True if the +version+ string matches RubyGems' requirements. def self.correct?(version) - nil_versions_are_discouraged! if version.nil? - - ANCHORED_VERSION_PATTERN.match?(version.to_s) + version.nil? || ANCHORED_VERSION_PATTERN.match?(version.to_s) end ## @@ -182,15 +180,10 @@ class Gem::Version # # ver1 = Version.create('1.3.17') # -> (Version object) # ver2 = Version.create(ver1) # -> (ver1) - # ver3 = Version.create(nil) # -> nil def self.create(input) if self === input # check yourself before you wreck yourself input - elsif input.nil? - nil_versions_are_discouraged! - - nil else new input end @@ -206,14 +199,6 @@ class Gem::Version @@all[version] ||= super end - def self.nil_versions_are_discouraged! - unless Gem::Deprecate.skip - warn "nil versions are discouraged and will be deprecated in Rubygems 4" - end - end - - private_class_method :nil_versions_are_discouraged! - ## # Constructs a Version from the +version+ string. A version string is a # series of digits or ASCII letters separated by dots. @@ -224,7 +209,7 @@ class Gem::Version end # If version is an empty string convert it to 0 - version = 0 if version.is_a?(String) && /\A\s*\Z/.match?(version) + version = 0 if version.nil? || (version.is_a?(String) && /\A\s*\Z/.match?(version)) @version = version.to_s @@ -354,11 +339,14 @@ class Gem::Version ## # Compares this version with +other+ returning -1, 0, or 1 if the # other version is larger, the same, or smaller than this - # one. Attempts to compare to something that's not a - # <tt>Gem::Version</tt> or a valid version String return +nil+. + # one. +other+ must be an instance of Gem::Version, comparing with + # other types may raise an exception. def <=>(other) - return self <=> self.class.new(other) if (String === other) && self.class.correct?(other) + if String === other + return unless self.class.correct?(other) + return self <=> self.class.new(other) + end return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments diff --git a/lib/rubygems/win_platform.rb b/lib/rubygems/win_platform.rb new file mode 100644 index 0000000000..78e968fe49 --- /dev/null +++ b/lib/rubygems/win_platform.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "rbconfig" + +module Gem + ## + # An Array of Regexps that match windows Ruby platforms. + + WIN_PATTERNS = [ + /bccwin/i, + /cygwin/i, + /djgpp/i, + /mingw/i, + /mswin/i, + /wince/i, + ].freeze + + @@win_platform = nil + + ## + # Is this a windows platform? + + def self.win_platform? + if @@win_platform.nil? + ruby_platform = RbConfig::CONFIG["host_os"] + @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil? + end + + @@win_platform + end +end diff --git a/lib/set/sorted_set.rb b/lib/set/sorted_set.rb deleted file mode 100644 index bc07bc1fb0..0000000000 --- a/lib/set/sorted_set.rb +++ /dev/null @@ -1,6 +0,0 @@ -begin - require 'sorted_set' -rescue ::LoadError - raise "The `SortedSet` class has been extracted from the `set` library. " \ - "You must use the `sorted_set` gem or other alternatives." -end diff --git a/lib/set/subclass_compatible.rb b/lib/set/subclass_compatible.rb new file mode 100644 index 0000000000..f43c34f6a2 --- /dev/null +++ b/lib/set/subclass_compatible.rb @@ -0,0 +1,347 @@ +# frozen_string_literal: true + +# :markup: markdown +# +# set/subclass_compatible.rb - Provides compatibility for set subclasses +# +# Copyright (c) 2002-2024 Akinori MUSHA <knu@iDaemons.org> +# +# Documentation by Akinori MUSHA and Gavin Sinclair. +# +# All rights reserved. You can redistribute and/or modify it under the same +# terms as Ruby. + + +class Set + # This module is automatically included in subclasses of Set, to + # make them backwards compatible with the pure-Ruby set implementation + # used before Ruby 4. Users who want to use Set subclasses without + # this compatibility layer should subclass from Set::CoreSet. + # + # Note that Set subclasses that access `@hash` are not compatible even + # with this support. Such subclasses must be updated to support Ruby 4. + module SubclassCompatible + module ClassMethods + def [](*ary) + new(ary) + end + end + + # Creates a new set containing the elements of the given enumerable + # object. + # + # If a block is given, the elements of enum are preprocessed by the + # given block. + # + # Set.new([1, 2]) #=> #<Set: {1, 2}> + # Set.new([1, 2, 1]) #=> #<Set: {1, 2}> + # Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}> + # Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}> + # Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}> + def initialize(enum = nil, &block) # :yields: o + enum.nil? and return + + if block + do_with_enum(enum) { |o| add(block[o]) } + else + merge(enum) + end + end + + def do_with_enum(enum, &block) # :nodoc: + if enum.respond_to?(:each_entry) + enum.each_entry(&block) if block + elsif enum.respond_to?(:each) + enum.each(&block) if block + else + raise ArgumentError, "value must be enumerable" + end + end + private :do_with_enum + + def replace(enum) + if enum.instance_of?(self.class) + super + else + do_with_enum(enum) # make sure enum is enumerable before calling clear + clear + merge(enum) + end + end + + def to_set(&block) + return self if instance_of?(Set) && block.nil? + Set.new(self, &block) + end + + def flatten_merge(set, seen = {}) # :nodoc: + set.each { |e| + if e.is_a?(Set) + case seen[e_id = e.object_id] + when true + raise ArgumentError, "tried to flatten recursive Set" + when false + next + end + + seen[e_id] = true + flatten_merge(e, seen) + seen[e_id] = false + else + add(e) + end + } + + self + end + protected :flatten_merge + + def flatten + self.class.new.flatten_merge(self) + end + + def flatten! + replace(flatten()) if any?(Set) + end + + def superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size >= set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias >= superset? + + def proper_superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size > set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias > proper_superset? + + def subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size <= set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias <= subset? + + def proper_subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size < set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias < proper_subset? + + def <=>(set) + return unless set.is_a?(Set) + + case size <=> set.size + when -1 then -1 if proper_subset?(set) + when +1 then +1 if proper_superset?(set) + else 0 if self.==(set) + end + end + + def intersect?(set) + case set + when Set + if size < set.size + any?(set) + else + set.any?(self) + end + when Enumerable + set.any?(self) + else + raise ArgumentError, "value must be enumerable" + end + end + + def disjoint?(set) + !intersect?(set) + end + + def add?(o) + add(o) unless include?(o) + end + + def delete?(o) + delete(o) if include?(o) + end + + def delete_if(&block) + block_given? or return enum_for(__method__) { size } + select(&block).each { |o| delete(o) } + self + end + + def keep_if(&block) + block_given? or return enum_for(__method__) { size } + reject(&block).each { |o| delete(o) } + self + end + + def collect! + block_given? or return enum_for(__method__) { size } + set = self.class.new + each { |o| set << yield(o) } + replace(set) + end + alias map! collect! + + def reject!(&block) + block_given? or return enum_for(__method__) { size } + n = size + delete_if(&block) + self if size != n + end + + def select!(&block) + block_given? or return enum_for(__method__) { size } + n = size + keep_if(&block) + self if size != n + end + + alias filter! select! + + def merge(*enums, **nil) + enums.each do |enum| + if enum.instance_of?(self.class) + super(enum) + else + do_with_enum(enum) { |o| add(o) } + end + end + + self + end + + def subtract(enum) + do_with_enum(enum) { |o| delete(o) } + self + end + + def |(enum) + dup.merge(enum) + end + alias + | + alias union | + + def -(enum) + dup.subtract(enum) + end + alias difference - + + def &(enum) + n = self.class.new + if enum.is_a?(Set) + if enum.size > size + each { |o| n.add(o) if enum.include?(o) } + else + enum.each { |o| n.add(o) if include?(o) } + end + else + do_with_enum(enum) { |o| n.add(o) if include?(o) } + end + n + end + alias intersection & + + def ^(enum) + n = self.class.new(enum) + each { |o| n.add(o) unless n.delete?(o) } + n + end + + def ==(other) + if self.equal?(other) + true + elsif other.instance_of?(self.class) + super + elsif other.is_a?(Set) && self.size == other.size + other.all? { |o| include?(o) } + else + false + end + end + + def eql?(o) # :nodoc: + return false unless o.is_a?(Set) + super + end + + def classify + block_given? or return enum_for(__method__) { size } + + h = {} + + each { |i| + (h[yield(i)] ||= self.class.new).add(i) + } + + h + end + + def join(separator=nil) + to_a.join(separator) + end + + InspectKey = :__inspect_key__ # :nodoc: + + # Returns a string containing a human-readable representation of the + # set ("#<Set: {element1, element2, ...}>"). + def inspect + ids = (Thread.current[InspectKey] ||= []) + + if ids.include?(object_id) + return sprintf('#<%s: {...}>', self.class.name) + end + + ids << object_id + begin + return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2]) + ensure + ids.pop + end + end + + alias to_s inspect + + def pretty_print(pp) # :nodoc: + pp.group(1, sprintf('#<%s:', self.class.name), '>') { + pp.breakable + pp.group(1, '{', '}') { + pp.seplist(self) { |o| + pp.pp o + } + } + } + end + + def pretty_print_cycle(pp) # :nodoc: + pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') + end + end + private_constant :SubclassCompatible +end diff --git a/lib/shellwords.gemspec b/lib/shellwords.gemspec index 8d0c518ca5..b601508f94 100644 --- a/lib/shellwords.gemspec +++ b/lib/shellwords.gemspec @@ -25,7 +25,5 @@ Gem::Specification.new do |spec| spec.files = Dir.chdir(srcdir) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git|Rake)}) || f == gemspec_file} end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] end diff --git a/lib/singleton.rb b/lib/singleton.rb index b8e43a7794..74aec8903c 100644 --- a/lib/singleton.rb +++ b/lib/singleton.rb @@ -92,9 +92,10 @@ # p a.strip # => nil # module Singleton + # The version string VERSION = "0.3.0" - module SingletonInstanceMethods + module SingletonInstanceMethods # :nodoc: # Raises a TypeError to prevent cloning. def clone raise TypeError, "can't clone instance of singleton #{self.class}" @@ -143,11 +144,11 @@ module Singleton end end - def self.module_with_class_methods + def self.module_with_class_methods # :nodoc: SingletonClassMethods end - module SingletonClassProperties + module SingletonClassProperties # :nodoc: def self.included(c) # extending an object with Singleton is a bad idea @@ -196,10 +197,10 @@ module Singleton end if defined?(Ractor) - module RactorLocalSingleton + module RactorLocalSingleton # :nodoc: include Singleton::SingletonInstanceMethods - module RactorLocalSingletonClassMethods + module RactorLocalSingletonClassMethods # :nodoc: include Singleton::SingletonClassMethods def instance set_mutex(Thread::Mutex.new) if Ractor.current[mutex_key].nil? diff --git a/lib/syntax_suggest/api.rb b/lib/syntax_suggest/api.rb index 46c9c8adac..0f82d8362a 100644 --- a/lib/syntax_suggest/api.rb +++ b/lib/syntax_suggest/api.rb @@ -146,11 +146,7 @@ module SyntaxSuggest def self.valid_without?(without_lines:, code_lines:) lines = code_lines - Array(without_lines).flatten - if lines.empty? - true - else - valid?(lines) - end + lines.empty? || valid?(lines) end # SyntaxSuggest.invalid? [Private] diff --git a/lib/syntax_suggest/code_line.rb b/lib/syntax_suggest/code_line.rb index 58197e95d0..76ca892ac3 100644 --- a/lib/syntax_suggest/code_line.rb +++ b/lib/syntax_suggest/code_line.rb @@ -180,18 +180,17 @@ module SyntaxSuggest # EOM # expect(lines.first.trailing_slash?).to eq(true) # - if SyntaxSuggest.use_prism_parser? - def trailing_slash? - last = @lex.last - last&.type == :on_tstring_end - end - else - def trailing_slash? - last = @lex.last - return false unless last - return false unless last.type == :on_sp + def trailing_slash? + last = @lex.last + # Older versions of prism diverged slightly from Ripper in compatibility mode + case last&.type + when :on_sp last.token == TRAILING_SLASH + when :on_tstring_end + true + else + false end end diff --git a/lib/syntax_suggest/version.rb b/lib/syntax_suggest/version.rb index 1aa908f4e5..db50a1a89a 100644 --- a/lib/syntax_suggest/version.rb +++ b/lib/syntax_suggest/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxSuggest - VERSION = "2.0.2" + VERSION = "2.0.3" end diff --git a/lib/tempfile.rb b/lib/tempfile.rb index f3213c5684..cd512bb1c5 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -29,7 +29,7 @@ require 'tmpdir' # require 'tempfile' # # # Tempfile.create with a block -# # The filename are choosen automatically. +# # The filename are chosen automatically. # # (You can specify the prefix and suffix of the filename by an optional argument.) # Tempfile.create {|f| # f.puts "foo" @@ -550,8 +550,8 @@ end # # Implementation note: # -# The keyword argument +anonymous=true+ is implemented using FILE_SHARE_DELETE on Windows. -# O_TMPFILE is used on Linux. +# The keyword argument <tt>anonymous=true</tt> is implemented using +FILE_SHARE_DELETE+ on Windows. +# +O_TMPFILE+ is used on Linux. # # Related: Tempfile.new. # @@ -564,6 +564,8 @@ def Tempfile.create(basename="", tmpdir=nil, mode: 0, anonymous: false, **option end class << Tempfile +# :stopdoc: + private def create_with_filename(basename="", tmpdir=nil, mode: 0, **options) tmpfile = nil Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts| diff --git a/lib/time.gemspec b/lib/time.gemspec index 4b9f9e1218..73650ab12e 100644 --- a/lib/time.gemspec +++ b/lib/time.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = "https://github.com/ruby/time/releases" srcdir, gemspec = File.split(__FILE__) spec.files = Dir.chdir(srcdir) do diff --git a/lib/time.rb b/lib/time.rb index a0fc7dfbf5..e6aab3fa5d 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -27,7 +27,7 @@ require 'date' # # class Time - VERSION = "0.4.1" # :nodoc: + VERSION = "0.4.2" # :nodoc: class << Time @@ -80,7 +80,7 @@ class Time # # You must require 'time' to use this method. # - def zone_offset(zone, year=self.now.year) + def zone_offset(zone, year=nil) off = nil zone = zone.upcase if /\A([+-])(\d\d)(:?)(\d\d)(?:\3(\d\d))?\z/ =~ zone @@ -89,10 +89,13 @@ class Time off = zone.to_i * 3600 elsif ZoneOffset.include?(zone) off = ZoneOffset[zone] * 3600 - elsif ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false) - off = t.utc_offset - elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false) - off = t.utc_offset + else + year ||= self.now.year + if ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false) + off = t.utc_offset + elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false) + off = t.utc_offset + end end off end diff --git a/lib/timeout.rb b/lib/timeout.rb index 4fd1fa46da..e293e3be7c 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,9 +20,9 @@ module Timeout # The version - VERSION = "0.4.3" + VERSION = "0.6.0" - # Internal error raised to when a timeout is triggered. + # Internal exception raised to when a timeout is triggered. class ExitException < Exception def exception(*) # :nodoc: self @@ -44,12 +44,101 @@ module Timeout end # :stopdoc: - CONDVAR = ConditionVariable.new - QUEUE = Queue.new - QUEUE_MUTEX = Mutex.new - TIMEOUT_THREAD_MUTEX = Mutex.new - @timeout_thread = nil - private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX + + # We keep a private reference so that time mocking libraries won't break Timeout. + GET_TIME = Process.method(:clock_gettime) + if defined?(Ractor.make_shareable) + # Ractor.make_shareable(Method) only works on Ruby 4+ + Ractor.make_shareable(GET_TIME) rescue nil + end + private_constant :GET_TIME + + class State + def initialize + @condvar = ConditionVariable.new + @queue = Queue.new + @queue_mutex = Mutex.new + + @timeout_thread = nil + @timeout_thread_mutex = Mutex.new + end + + if defined?(Ractor.store_if_absent) && defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) + # Ractor support if + # 1. Ractor.store_if_absent is available + # 2. Method object can be shareable (4.0~) + def self.instance + Ractor.store_if_absent :timeout_gem_state do + State.new + end + end + else + GLOBAL_STATE = State.new + + def self.instance + GLOBAL_STATE + end + end + + def create_timeout_thread + # Threads unexpectedly inherit the interrupt mask: https://github.com/ruby/timeout/issues/41 + # So reset the interrupt mask to the default one for the timeout thread + Thread.handle_interrupt(Object => :immediate) do + watcher = Thread.new do + requests = [] + while true + until @queue.empty? and !requests.empty? # wait to have at least one request + req = @queue.pop + requests << req unless req.done? + end + closest_deadline = requests.min_by(&:deadline).deadline + + now = 0.0 + @queue_mutex.synchronize do + while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? + @condvar.wait(@queue_mutex, closest_deadline - now) + end + end + + requests.each do |req| + req.interrupt if req.expired?(now) + end + requests.reject!(&:done?) + end + end + + if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) + ThreadGroup::Default.add(watcher) + end + + watcher.name = "Timeout stdlib thread" + watcher.thread_variable_set(:"\0__detached_thread__", true) + watcher + end + end + + def ensure_timeout_thread_created + unless @timeout_thread&.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the Timeout thread. + return if @timeout_thread_mutex.owned? + + Sync.synchronize @timeout_thread_mutex do + unless @timeout_thread&.alive? + @timeout_thread = create_timeout_thread + end + end + end + end + + def add_request(request) + Sync.synchronize @queue_mutex do + @queue << request + @condvar.signal + end + end + end + private_constant :State class Request attr_reader :deadline @@ -64,6 +153,7 @@ module Timeout @done = false # protected by @mutex end + # Only called by the timeout thread, so does not need Sync.synchronize def done? @mutex.synchronize do @done @@ -74,6 +164,7 @@ module Timeout now >= @deadline end + # Only called by the timeout thread, so does not need Sync.synchronize def interrupt @mutex.synchronize do unless @done @@ -84,61 +175,36 @@ module Timeout end def finished - @mutex.synchronize do + Sync.synchronize @mutex do @done = true end end end private_constant :Request - def self.create_timeout_thread - watcher = Thread.new do - requests = [] - while true - until QUEUE.empty? and !requests.empty? # wait to have at least one request - req = QUEUE.pop - requests << req unless req.done? - end - closest_deadline = requests.min_by(&:deadline).deadline - - now = 0.0 - QUEUE_MUTEX.synchronize do - while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty? - CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now) - end - end - - requests.each do |req| - req.interrupt if req.expired?(now) - end - requests.reject!(&:done?) - end - end - ThreadGroup::Default.add(watcher) unless watcher.group.enclosed? - watcher.name = "Timeout stdlib thread" - watcher.thread_variable_set(:"\0__detached_thread__", true) - watcher - end - private_class_method :create_timeout_thread - - def self.ensure_timeout_thread_created - unless @timeout_thread and @timeout_thread.alive? - TIMEOUT_THREAD_MUTEX.synchronize do - unless @timeout_thread and @timeout_thread.alive? - @timeout_thread = create_timeout_thread - end + module Sync + # Calls mutex.synchronize(&block) but if that fails on CRuby due to being in a trap handler, + # run mutex.synchronize(&block) in a separate Thread instead. + def self.synchronize(mutex, &block) + begin + mutex.synchronize(&block) + rescue ThreadError => e + raise e unless e.message == "can't be called from trap context" + # Workaround CRuby issue https://bugs.ruby-lang.org/issues/19473 + # which raises on Mutex#synchronize in trap handler. + # It's expensive to create a Thread just for this, + # but better than failing. + Thread.new { + mutex.synchronize(&block) + }.join end end end - - # We keep a private reference so that time mocking libraries won't break - # Timeout. - GET_TIME = Process.method(:clock_gettime) - private_constant :GET_TIME + private_constant :Sync # :startdoc: - # Perform an operation in a block, raising an error if it takes longer than + # Perform an operation in a block, raising an exception if it takes longer than # +sec+ seconds to complete. # # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number @@ -146,25 +212,70 @@ module Timeout # value of 0 or +nil+ will execute the block without any timeout. # Any negative number will raise an ArgumentError. # +klass+:: Exception Class to raise if the block fails to terminate - # in +sec+ seconds. Omitting will use the default, Timeout::Error + # in +sec+ seconds. Omitting will use the default, Timeout::Error. # +message+:: Error message to raise with Exception Class. - # Omitting will use the default, "execution expired" + # Omitting will use the default, <tt>"execution expired"</tt>. # # Returns the result of the block *if* the block completed before - # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. + # +sec+ seconds, otherwise raises an exception, based on the value of +klass+. # - # The exception thrown to terminate the given block cannot be rescued inside - # the block unless +klass+ is given explicitly. However, the block can use - # ensure to prevent the handling of the exception. For that reason, this - # method cannot be relied on to enforce timeouts for untrusted blocks. + # The exception raised to terminate the given block is the given +klass+, or + # Timeout::ExitException if +klass+ is not given. The reason for that behavior + # is that Timeout::Error inherits from RuntimeError and might be caught unexpectedly by +rescue+. + # Timeout::ExitException inherits from Exception so it will only be rescued by <tt>rescue Exception</tt>. + # Note that the Timeout::ExitException is translated to a Timeout::Error once it reaches the Timeout.timeout call, + # so outside that call it will be a Timeout::Error. + # + # In general, be aware that the code block may rescue the exception, and in such a case not respect the timeout. + # Also, the block can use +ensure+ to prevent the handling of the exception. + # For those reasons, this method cannot be relied on to enforce timeouts for untrusted blocks. # # If a scheduler is defined, it will be used to handle the timeout by invoking - # Scheduler#timeout_after. + # Fiber::Scheduler#timeout_after. # # Note that this is both a method of module Timeout, so you can <tt>include # Timeout</tt> into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). - def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ + # + # ==== Ensuring the exception does not fire inside ensure blocks + # + # When using Timeout.timeout, it can be desirable to ensure the timeout exception does not fire inside an +ensure+ block. + # The simplest and best way to do so is to put the Timeout.timeout call inside the body of the +begin+/+ensure+/+end+: + # + # begin + # Timeout.timeout(sec) { some_long_operation } + # ensure + # cleanup # safe, cannot be interrupted by timeout + # end + # + # If that is not feasible, e.g. if there are +ensure+ blocks inside +some_long_operation+, + # they need to not be interrupted by timeout, and it's not possible to move these ensure blocks outside, + # one can use Thread.handle_interrupt to delay the timeout exception like so: + # + # Thread.handle_interrupt(Timeout::Error => :never) { + # Timeout.timeout(sec, Timeout::Error) do + # setup # timeout cannot happen here, no matter how long it takes + # Thread.handle_interrupt(Timeout::Error => :immediate) { + # some_long_operation # timeout can happen here + # } + # ensure + # cleanup # timeout cannot happen here, no matter how long it takes + # end + # } + # + # An important thing to note is the need to pass an exception +klass+ to Timeout.timeout, + # otherwise it does not work. Specifically, using <tt>Thread.handle_interrupt(Timeout::ExitException => ...)</tt> + # is unsupported and causes subtle errors like raising the wrong exception outside the block, do not use that. + # + # Note that Thread.handle_interrupt is somewhat dangerous because if setup or cleanup hangs + # then the current thread will hang too and the timeout will never fire. + # Also note the block might run for longer than +sec+ seconds: + # e.g. +some_long_operation+ executes for +sec+ seconds + whatever time cleanup takes. + # + # If you want the timeout to only happen on blocking operations, one can use +:on_blocking+ + # instead of +:immediate+. However, that means if the block uses no blocking operations after +sec+ seconds, + # the block will not be interrupted. + def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec @@ -174,13 +285,12 @@ module Timeout return scheduler.timeout_after(sec, klass || Error, message, &block) end - Timeout.ensure_timeout_thread_created + state = State.instance + state.ensure_timeout_thread_created + perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) - QUEUE_MUTEX.synchronize do - QUEUE << request - CONDVAR.signal - end + state.add_request(request) begin return yield(sec) ensure @@ -194,5 +304,8 @@ module Timeout Error.handle_timeout(message, &perform) end end - module_function :timeout + + private def timeout(*args, &block) + Timeout.timeout(*args, &block) + end end diff --git a/lib/tsort.gemspec b/lib/tsort.gemspec deleted file mode 100644 index 0e2f110a53..0000000000 --- a/lib/tsort.gemspec +++ /dev/null @@ -1,29 +0,0 @@ -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Tanaka Akira"] - spec.email = ["akr@fsij.org"] - - spec.summary = %q{Topological sorting using Tarjan's algorithm} - spec.description = %q{Topological sorting using Tarjan's algorithm} - spec.homepage = "https://github.com/ruby/tsort" - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] -end diff --git a/lib/tsort.rb b/lib/tsort.rb deleted file mode 100644 index dbaed45415..0000000000 --- a/lib/tsort.rb +++ /dev/null @@ -1,455 +0,0 @@ -# frozen_string_literal: true - -#-- -# tsort.rb - provides a module for topological sorting and strongly connected components. -#++ -# - -# -# TSort implements topological sorting using Tarjan's algorithm for -# strongly connected components. -# -# TSort is designed to be able to be used with any object which can be -# interpreted as a directed graph. -# -# 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 -# TSort uses Hash internally. -# -# == A Simple Example -# -# The following example demonstrates how to mix the 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 'tsort' -# -# class Hash -# include 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 '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 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 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 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, TSort::Cyclic is raised. - # - # class G - # include 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 TSort::Cyclic - # - def tsort - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - 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, 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 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 TSort.tsort(each_node, each_child) # raises 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, TSort::Cyclic is raised. - # - # class G - # include 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) - TSort.tsort_each(each_node, each_child, &block) - end - - # The iterator version of the 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) } - # 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 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) - 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 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 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 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) - TSort.each_strongly_connected_component(each_node, each_child, &block) - end - - # The iterator version of the 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) } - # 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) } - # 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 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 - 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. - # - # #TSort.each_strongly_connected_component_from is a class method and - # it doesn't need a class to represent a graph which includes TSort. - # - # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_child = lambda {|n, &b| graph[n].each(&b) } - # 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/unicode_normalize/normalize.rb b/lib/unicode_normalize/normalize.rb index e67fad187a..0447df8de7 100644 --- a/lib/unicode_normalize/normalize.rb +++ b/lib/unicode_normalize/normalize.rb @@ -82,16 +82,22 @@ module UnicodeNormalize # :nodoc: ## Canonical Ordering def self.canonical_ordering_one(string) - sorting = string.each_char.collect { |c| [c, CLASS_TABLE[c]] } - (sorting.length-2).downto(0) do |i| # almost, but not exactly bubble sort - (0..i).each do |j| - later_class = sorting[j+1].last - if 0<later_class and later_class<sorting[j].last - sorting[j], sorting[j+1] = sorting[j+1], sorting[j] - end + result = '' + unordered = [] + chars = string.chars + n = chars.size + chars.each_with_index do |char, i| + ccc = CLASS_TABLE[char] + if ccc == 0 + unordered.sort!.each { result << chars[it % n] } + unordered.clear + result << char + else + unordered << ccc * n + i end end - return sorting.collect(&:first).join('') + unordered.sort!.each { result << chars[it % n] } + result end ## Normalization Forms for Patterns (not whole Strings) @@ -105,17 +111,22 @@ module UnicodeNormalize # :nodoc: start = nfd_string[0] last_class = CLASS_TABLE[start]-1 accents = '' + result = '' nfd_string[1..-1].each_char do |accent| accent_class = CLASS_TABLE[accent] if last_class<accent_class and composite = COMPOSITION_TABLE[start+accent] start = composite + elsif accent_class == 0 + result << start << accents + start = accent + accents = '' + last_class = -1 else accents << accent last_class = accent_class end end - accents = nfc_one(accents) if accents.length>1 # TODO: change from recursion to loop - hangul_comp_one(start+accents) + hangul_comp_one(result+start+accents) end def self.normalize(string, form = :nfc) diff --git a/lib/unicode_normalize/tables.rb b/lib/unicode_normalize/tables.rb index b5b708defd..dd5d3499b8 100644 --- a/lib/unicode_normalize/tables.rb +++ b/lib/unicode_normalize/tables.rb @@ -1,8 +1,8 @@ # coding: us-ascii # frozen_string_literal: true -Encoding::UNICODE_VERSION == "16.0.0" or - raise "Unicode version mismatch: 16.0.0 expected but #{Encoding::UNICODE_VERSION}" +Encoding::UNICODE_VERSION == "17.0.0" or + raise "Unicode version mismatch: 17.0.0 expected but #{Encoding::UNICODE_VERSION}" # automatically generated by template/unicode_norm_gen.tmpl @@ -99,7 +99,8 @@ module UnicodeNormalize # :nodoc: "\u1A75-\u1A7C" \ "\u1A7F" \ "\u1AB0-\u1ABD" \ - "\u1ABF-\u1ACE" \ + "\u1ABF-\u1ADD" \ + "\u1AE0-\u1AEB" \ "\u1B34\u1B35" \ "\u1B44" \ "\u1B6B-\u1B73" \ @@ -154,6 +155,7 @@ module UnicodeNormalize # :nodoc: "\u{10D24}-\u{10D27}" \ "\u{10D69}-\u{10D6D}" \ "\u{10EAB}\u{10EAC}" \ + "\u{10EFA}\u{10EFB}" \ "\u{10EFD}-\u{10EFF}" \ "\u{10F46}-\u{10F50}" \ "\u{10F82}-\u{10F85}" \ @@ -230,6 +232,10 @@ module UnicodeNormalize # :nodoc: "\u{1E2EC}-\u{1E2EF}" \ "\u{1E4EC}-\u{1E4EF}" \ "\u{1E5EE}\u{1E5EF}" \ + "\u{1E6E3}" \ + "\u{1E6E6}" \ + "\u{1E6EE}\u{1E6EF}" \ + "\u{1E6F5}" \ "\u{1E8D0}-\u{1E8D6}" \ "\u{1E944}-\u{1E94A}" \ "]" @@ -1460,7 +1466,7 @@ module UnicodeNormalize # :nodoc: "\u3280-\u33FF" \ "\uA69C\uA69D" \ "\uA770" \ - "\uA7F2-\uA7F4" \ + "\uA7F1-\uA7F4" \ "\uA7F8\uA7F9" \ "\uAB5C-\uAB5F" \ "\uAB69" \ @@ -2019,6 +2025,33 @@ module UnicodeNormalize # :nodoc: "\u1ACC"=>230, "\u1ACD"=>230, "\u1ACE"=>230, + "\u1ACF"=>230, + "\u1AD0"=>230, + "\u1AD1"=>230, + "\u1AD2"=>230, + "\u1AD3"=>230, + "\u1AD4"=>230, + "\u1AD5"=>230, + "\u1AD6"=>230, + "\u1AD7"=>230, + "\u1AD8"=>230, + "\u1AD9"=>230, + "\u1ADA"=>230, + "\u1ADB"=>230, + "\u1ADC"=>230, + "\u1ADD"=>220, + "\u1AE0"=>230, + "\u1AE1"=>230, + "\u1AE2"=>230, + "\u1AE3"=>230, + "\u1AE4"=>230, + "\u1AE5"=>230, + "\u1AE6"=>220, + "\u1AE7"=>230, + "\u1AE8"=>230, + "\u1AE9"=>230, + "\u1AEA"=>230, + "\u1AEB"=>234, "\u1B34"=>7, "\u1B44"=>9, "\u1B6B"=>230, @@ -2293,6 +2326,8 @@ module UnicodeNormalize # :nodoc: "\u{10D6D}"=>230, "\u{10EAB}"=>230, "\u{10EAC}"=>230, + "\u{10EFA}"=>220, + "\u{10EFB}"=>220, "\u{10EFD}"=>220, "\u{10EFE}"=>220, "\u{10EFF}"=>220, @@ -2479,6 +2514,11 @@ module UnicodeNormalize # :nodoc: "\u{1E4EF}"=>230, "\u{1E5EE}"=>230, "\u{1E5EF}"=>220, + "\u{1E6E3}"=>230, + "\u{1E6E6}"=>230, + "\u{1E6EE}"=>230, + "\u{1E6EF}"=>230, + "\u{1E6F5}"=>230, "\u{1E8D0}"=>220, "\u{1E8D1}"=>220, "\u{1E8D2}"=>220, @@ -5922,6 +5962,7 @@ module UnicodeNormalize # :nodoc: "\uA69C"=>"\u044A", "\uA69D"=>"\u044C", "\uA770"=>"\uA76F", + "\uA7F1"=>"S", "\uA7F2"=>"C", "\uA7F3"=>"F", "\uA7F4"=>"Q", diff --git a/lib/uri/common.rb b/lib/uri/common.rb index 1115736297..0b3bb4f099 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -30,6 +30,9 @@ module URI remove_const(:Parser) if defined?(::URI::Parser) const_set("Parser", parser.class) + remove_const(:PARSER) if defined?(::URI::PARSER) + const_set("PARSER", parser) + remove_const(:REGEXP) if defined?(::URI::REGEXP) remove_const(:PATTERN) if defined?(::URI::PATTERN) if Parser == RFC2396_Parser @@ -92,6 +95,40 @@ module URI end module Schemes # :nodoc: + class << self + ReservedChars = ".+-" + EscapedChars = "\u01C0\u01C1\u01C2" + # Use Lo category chars as escaped chars for TruffleRuby, which + # does not allow Symbol categories as identifiers. + + def escape(name) + unless name and name.ascii_only? + return nil + end + name.upcase.tr(ReservedChars, EscapedChars) + end + + def unescape(name) + name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase + end + + def find(name) + const_get(name, false) if name and const_defined?(name, false) + end + + def register(name, klass) + unless scheme = escape(name) + raise ArgumentError, "invalid character as scheme - #{name}" + end + const_set(scheme, klass) + end + + def list + constants.map { |name| + [unescape(name.to_s), const_get(name)] + }.to_h + end + end end private_constant :Schemes @@ -104,7 +141,7 @@ module URI # 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) + Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: @@ -122,14 +159,14 @@ module URI # # Related: URI.register_scheme. def self.scheme_list - Schemes.constants.map { |name| - [name.to_s.upcase, Schemes.const_get(name)] - }.to_h + Schemes.list end + # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: @@ -148,12 +185,10 @@ module URI # # => #<URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # def self.for(scheme, *arguments, default: Generic) - const_name = scheme.to_s.upcase + const_name = Schemes.escape(scheme) 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 ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) @@ -195,7 +230,7 @@ module URI # ["fragment", "top"]] # def self.split(uri) - DEFAULT_PARSER.split(uri) + PARSER.split(uri) end # Returns a new \URI object constructed from the given string +uri+: @@ -205,11 +240,11 @@ module URI # URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => #<URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # - # It's recommended to first ::escape string +uri+ + # It's recommended to first URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid URI characters. # def self.parse(uri) - DEFAULT_PARSER.parse(uri) + PARSER.parse(uri) end # Merges the given URI strings +str+ @@ -265,7 +300,7 @@ module URI # def self.extract(str, schemes = nil, &block) # :nodoc: warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.extract(str, schemes, &block) + PARSER.extract(str, schemes, &block) end # @@ -302,7 +337,7 @@ module URI # def self.regexp(schemes = nil)# :nodoc: warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.make_regexp(schemes) + PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: @@ -407,6 +442,8 @@ module URI _decode_uri_component(/%\h\h/, str, enc) end + # Returns a string derived from the given string +str+ with + # URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT @@ -421,6 +458,8 @@ module URI end private_class_method :_encode_uri_component + # Returns a string decoding characters matching +regexp+ from the + # given \URL-encoded string +str+. 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) @@ -859,6 +898,7 @@ module Kernel # Returns a \URI object derived from the given +uri+, # which may be a \URI string or an existing \URI object: # + # require 'uri' # # Returns a new URI. # uri = URI('http://github.com/ruby/ruby') # # => #<URI::HTTP http://github.com/ruby/ruby> @@ -866,6 +906,8 @@ module Kernel # URI(uri) # # => #<URI::HTTP http://github.com/ruby/ruby> # + # You must require 'uri' to use this method. + # def URI(uri) if uri.is_a?(URI::Generic) uri diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index 07f329e3d1..6a0f638d76 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -126,9 +126,9 @@ module URI end end else - component = self.class.component rescue ::URI::Generic::COMPONENT + component = self.component rescue ::URI::Generic::COMPONENT raise ArgumentError, - "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil @@ -186,18 +186,18 @@ module URI if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -284,7 +284,7 @@ module URI # Returns the parser to be used. # - # Unless a URI::Parser is defined, DEFAULT_PARSER is used. + # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser @@ -315,7 +315,7 @@ module URI end # - # Checks the scheme +v+ component against the URI::Parser Regexp for :SCHEME. + # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v @@ -385,7 +385,7 @@ module URI # # Checks the user +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -409,7 +409,7 @@ module URI # # Checks the password +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -466,7 +466,7 @@ module URI # # uri = URI.parse("http://john:S3nsit1ve@my.example.com") # uri.user = "sam" - # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com" + # uri.to_s #=> "http://sam@my.example.com" # def user=(user) check_user(user) @@ -511,7 +511,7 @@ module URI user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ module URI # See also URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ module URI @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after URI decoding. def decoded_user URI.decode_uri_component(@user) if @user @@ -586,7 +592,7 @@ module URI # # Checks the host +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :HOST. + # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. @@ -615,6 +621,13 @@ module URI end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ module URI def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -675,7 +689,7 @@ module URI # # Checks the port +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :PORT. + # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. @@ -729,6 +743,7 @@ module URI def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end @@ -748,7 +763,7 @@ module URI # # Checks the path +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp + # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, @@ -853,7 +868,7 @@ module URI # # Checks the opaque +v+ component for RFC2396 compliance and - # against the URI::Parser Regexp for :OPAQUE. + # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. @@ -905,7 +920,7 @@ module URI end # - # Checks the fragment +v+ component against the URI::Parser Regexp for :FRAGMENT. + # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args @@ -1121,7 +1136,7 @@ module URI base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1134,9 +1149,7 @@ module URI # RFC2396, Section 5.2, 4) if authority - base.set_userinfo(rel.userinfo) - base.set_host(rel.host) - base.set_port(rel.port || base.default_port) + base.set_authority(*authority) base.set_path(rel.path) elsif base.path && rel.path base.set_path(merge_path(base.path, rel.path)) @@ -1527,7 +1540,7 @@ module URI 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 + warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end diff --git a/lib/uri/http.rb b/lib/uri/http.rb index 900b132c8c..3c41cd4e93 100644 --- a/lib/uri/http.rb +++ b/lib/uri/http.rb @@ -61,6 +61,18 @@ module URI super(tmp) end + # Do not allow empty host names, as they are not allowed by RFC 3986. + def check_host(v) + ret = super + + if ret && v.empty? + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + ret + end + # # == Description # diff --git a/lib/uri/rfc2396_parser.rb b/lib/uri/rfc2396_parser.rb index 75a2d2dbde..cefd126cc6 100644 --- a/lib/uri/rfc2396_parser.rb +++ b/lib/uri/rfc2396_parser.rb @@ -67,7 +67,7 @@ module URI # # == Synopsis # - # URI::Parser.new([opts]) + # URI::RFC2396_Parser.new([opts]) # # == Args # @@ -86,7 +86,7 @@ module URI # # == Examples # - # p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # p = URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> #<URI::HTTP http://example.jp/%uABCD> # URI.parse(u.to_s) #=> raises URI::InvalidURIError # @@ -108,12 +108,12 @@ module URI # The Hash of patterns. # - # See also URI::Parser.initialize_pattern. + # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # - # See also URI::Parser.initialize_regexp. + # See also #initialize_regexp. attr_reader :regexp # Returns a split URI against +regexp[:ABS_URI]+. @@ -202,8 +202,7 @@ module URI # # == Usage # - # p = URI::Parser.new - # p.parse("ldap://ldap.example.com/dc=example?user=john") + # URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> #<URI::LDAP ldap://ldap.example.com/dc=example?user=john> # def parse(uri) @@ -244,7 +243,7 @@ module URI # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # - # See also URI::Parser.make_regexp. + # See also #make_regexp. # def extract(str, schemes = nil) if block_given? @@ -263,7 +262,7 @@ module URI unless schemes @regexp[:ABS_URI_REF] else - /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end @@ -524,6 +523,8 @@ module URI ret end + # Returns +uri+ as-is if it is URI, or convert it to URI if it is + # a String. def convert_to_uri(uri) if uri.is_a?(URI::Generic) uri diff --git a/lib/uri/version.rb b/lib/uri/version.rb index b6a8ce1543..1f810602eb 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION_CODE = '010003'.freeze - VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + VERSION = '1.1.1'.freeze + VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end diff --git a/lib/weakref.rb b/lib/weakref.rb index f0a7e7b318..c7274f9664 100644 --- a/lib/weakref.rb +++ b/lib/weakref.rb @@ -17,7 +17,8 @@ require "delegate" # class WeakRef < Delegator - VERSION = "0.1.3" + # The version string + VERSION = "0.1.4" ## # RefError is raised when a referenced object has been recycled by the diff --git a/lib/yaml.rb b/lib/yaml.rb index 2cf11fc3df..c6f0f89fd2 100644 --- a/lib/yaml.rb +++ b/lib/yaml.rb @@ -66,5 +66,6 @@ YAML = Psych # :nodoc: # # Syck can also be found on github: https://github.com/ruby/syck module YAML + # The version of YAML wrapper LOADER_VERSION = "0.4.0" end |
