summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/.document26
-rw-r--r--lib/English.gemspec2
-rw-r--r--lib/benchmark/version.rb1
-rw-r--r--lib/bundler.rb9
-rw-r--r--lib/bundler/.document1
-rw-r--r--lib/bundler/cli.rb8
-rw-r--r--lib/bundler/cli/doctor.rb15
-rw-r--r--lib/bundler/cli/gem.rb1
-rw-r--r--lib/bundler/cli/info.rb7
-rw-r--r--lib/bundler/cli/install.rb5
-rw-r--r--lib/bundler/cli/issue.rb7
-rw-r--r--lib/bundler/cli/update.rb4
-rw-r--r--lib/bundler/compact_index_client/updater.rb5
-rw-r--r--lib/bundler/definition.rb188
-rw-r--r--lib/bundler/dependency.rb4
-rw-r--r--lib/bundler/digest.rb71
-rw-r--r--lib/bundler/dsl.rb48
-rw-r--r--lib/bundler/environment_preserver.rb5
-rw-r--r--lib/bundler/errors.rb20
-rw-r--r--lib/bundler/fetcher.rb3
-rw-r--r--lib/bundler/friendly_errors.rb35
-rw-r--r--lib/bundler/gem_helper.rb8
-rw-r--r--lib/bundler/injector.rb11
-rw-r--r--lib/bundler/installer.rb2
-rw-r--r--lib/bundler/installer/gem_installer.rb7
-rw-r--r--lib/bundler/lazy_specification.rb18
-rw-r--r--lib/bundler/lockfile_parser.rb22
-rw-r--r--lib/bundler/man/bundle-add.112
-rw-r--r--lib/bundler/man/bundle-add.1.ronn8
-rw-r--r--lib/bundler/man/bundle-binstubs.12
-rw-r--r--lib/bundler/man/bundle-cache.12
-rw-r--r--lib/bundler/man/bundle-check.12
-rw-r--r--lib/bundler/man/bundle-clean.12
-rw-r--r--lib/bundler/man/bundle-config.16
-rw-r--r--lib/bundler/man/bundle-config.1.ronn4
-rw-r--r--lib/bundler/man/bundle-doctor.12
-rw-r--r--lib/bundler/man/bundle-exec.12
-rw-r--r--lib/bundler/man/bundle-gem.12
-rw-r--r--lib/bundler/man/bundle-info.12
-rw-r--r--lib/bundler/man/bundle-init.12
-rw-r--r--lib/bundler/man/bundle-inject.12
-rw-r--r--lib/bundler/man/bundle-install.12
-rw-r--r--lib/bundler/man/bundle-list.12
-rw-r--r--lib/bundler/man/bundle-lock.12
-rw-r--r--lib/bundler/man/bundle-open.12
-rw-r--r--lib/bundler/man/bundle-outdated.12
-rw-r--r--lib/bundler/man/bundle-platform.12
-rw-r--r--lib/bundler/man/bundle-pristine.12
-rw-r--r--lib/bundler/man/bundle-remove.12
-rw-r--r--lib/bundler/man/bundle-show.12
-rw-r--r--lib/bundler/man/bundle-update.12
-rw-r--r--lib/bundler/man/bundle-viz.12
-rw-r--r--lib/bundler/man/bundle.12
-rw-r--r--lib/bundler/man/gemfile.528
-rw-r--r--lib/bundler/man/gemfile.5.ronn8
-rw-r--r--lib/bundler/plugin/api/source.rb1
-rw-r--r--lib/bundler/plugin/installer.rb2
-rw-r--r--lib/bundler/resolver.rb65
-rw-r--r--lib/bundler/ruby_version.rb2
-rw-r--r--lib/bundler/rubygems_gem_installer.rb26
-rw-r--r--lib/bundler/rubygems_integration.rb78
-rw-r--r--lib/bundler/runtime.rb2
-rw-r--r--lib/bundler/self_manager.rb94
-rw-r--r--lib/bundler/settings.rb1
-rw-r--r--lib/bundler/shared_helpers.rb7
-rw-r--r--lib/bundler/source.rb2
-rw-r--r--lib/bundler/source/git.rb4
-rw-r--r--lib/bundler/source/git/git_proxy.rb11
-rw-r--r--lib/bundler/source/metadata.rb2
-rw-r--r--lib/bundler/source/rubygems.rb124
-rw-r--r--lib/bundler/source/rubygems_aggregate.rb2
-rw-r--r--lib/bundler/source_list.rb36
-rw-r--r--lib/bundler/spec_set.rb2
-rw-r--r--lib/bundler/templates/Executable.bundler2
-rw-r--r--lib/bundler/templates/Gemfile2
-rw-r--r--lib/bundler/templates/gems.rb3
-rw-r--r--lib/bundler/templates/newgem/github/workflows/main.yml.tt4
-rw-r--r--lib/bundler/templates/newgem/newgem.gemspec.tt2
-rw-r--r--lib/bundler/templates/newgem/sig/newgem.rbs.tt8
-rw-r--r--lib/bundler/templates/newgem/standard.yml.tt3
-rw-r--r--lib/bundler/ui/shell.rb2
-rw-r--r--lib/bundler/vendor/.document1
-rw-r--r--lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb4
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions.rb8
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb12
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb8
-rw-r--r--lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb6
-rw-r--r--lib/bundler/vendor/thor/lib/thor/error.rb13
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/options.rb20
-rw-r--r--lib/bundler/vendor/thor/lib/thor/shell.rb2
-rw-r--r--lib/bundler/vendor/thor/lib/thor/shell/basic.rb26
-rw-r--r--lib/bundler/vendor/thor/lib/thor/util.rb2
-rw-r--r--lib/bundler/vendor/tsort/lib/tsort.rb453
-rw-r--r--lib/bundler/vendored_tsort.rb4
-rw-r--r--lib/bundler/version.rb2
-rw-r--r--lib/cgi.rb2
-rw-r--r--lib/cgi/cgi.gemspec4
-rw-r--r--lib/cgi/cookie.rb1
-rw-r--r--lib/cgi/session.rb58
-rw-r--r--lib/cgi/session/pstore.rb16
-rw-r--r--lib/csv.rb59
-rw-r--r--lib/csv/csv.gemspec2
-rw-r--r--lib/csv/fields_converter.rb8
-rw-r--r--lib/csv/input_record_separator.rb31
-rw-r--r--lib/csv/parser.rb58
-rw-r--r--lib/csv/version.rb2
-rw-r--r--lib/csv/writer.rb3
-rw-r--r--lib/delegate.rb2
-rw-r--r--lib/delegate/delegate.gemspec2
-rw-r--r--lib/did_you_mean.rb59
-rw-r--r--lib/did_you_mean/core_ext/name_error.rb2
-rw-r--r--lib/did_you_mean/formatter.rb44
-rw-r--r--lib/did_you_mean/formatters/plain_formatter.rb35
-rw-r--r--lib/did_you_mean/formatters/verbose_formatter.rb52
-rw-r--r--lib/did_you_mean/spell_checker.rb18
-rw-r--r--lib/did_you_mean/spell_checkers/method_name_checker.rb3
-rw-r--r--lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb3
-rw-r--r--lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb20
-rw-r--r--lib/did_you_mean/spell_checkers/require_path_checker.rb5
-rw-r--r--lib/did_you_mean/verbose.rb6
-rw-r--r--lib/did_you_mean/version.rb2
-rw-r--r--lib/drb/drb.rb11
-rw-r--r--lib/drb/extserv.rb4
-rw-r--r--lib/drb/extservm.rb6
-rw-r--r--lib/erb.rb2
-rw-r--r--lib/error_highlight/core_ext.rb5
-rw-r--r--lib/error_highlight/formatter.rb8
-rw-r--r--lib/error_highlight/version.rb2
-rw-r--r--lib/fileutils.rb2
-rw-r--r--lib/ipaddr.gemspec2
-rw-r--r--lib/irb.rb8
-rw-r--r--lib/irb/cmd/info.rb3
-rw-r--r--lib/irb/cmd/show_source.rb2
-rw-r--r--lib/irb/color.rb2
-rw-r--r--lib/irb/color_printer.rb2
-rw-r--r--lib/irb/extend-command.rb48
-rw-r--r--lib/irb/input-method.rb7
-rw-r--r--lib/irb/inspector.rb2
-rw-r--r--lib/irb/irb.gemspec2
-rw-r--r--lib/irb/ruby-lex.rb158
-rw-r--r--lib/irb/version.rb4
-rw-r--r--lib/logger/formatter.rb5
-rw-r--r--lib/logger/version.rb2
-rw-r--r--lib/mkmf.rb64
-rw-r--r--lib/mutex_m.rb1
-rw-r--r--lib/net/http.rb46
-rw-r--r--lib/net/http/backward.rb38
-rw-r--r--lib/net/http/generic_request.rb4
-rw-r--r--lib/net/http/net-http.gemspec1
-rw-r--r--lib/net/net-protocol.gemspec2
-rw-r--r--lib/net/protocol.rb2
-rw-r--r--lib/open-uri.gemspec3
-rw-r--r--lib/open3.rb6
-rw-r--r--lib/open3/open3.gemspec (renamed from lib/open3.gemspec)4
-rw-r--r--lib/open3/version.rb3
-rw-r--r--lib/optparse.rb6
-rw-r--r--lib/optparse/ac.rb2
-rw-r--r--lib/optparse/date.rb2
-rw-r--r--lib/optparse/kwargs.rb2
-rw-r--r--lib/optparse/optparse.gemspec1
-rw-r--r--lib/optparse/shellwords.rb2
-rw-r--r--lib/optparse/time.rb2
-rw-r--r--lib/optparse/uri.rb2
-rw-r--r--lib/ostruct.rb28
-rw-r--r--lib/pp.gemspec2
-rw-r--r--lib/pp.rb84
-rw-r--r--lib/prettyprint.gemspec2
-rw-r--r--lib/racc/grammar.rb6
-rw-r--r--lib/racc/grammarfileparser.rb4
-rw-r--r--lib/racc/info.rb2
-rw-r--r--lib/random/formatter.rb217
-rw-r--r--lib/rdoc/cross_reference.rb48
-rw-r--r--lib/rdoc/version.rb2
-rw-r--r--lib/readline.gemspec2
-rw-r--r--lib/readline.rb1
-rw-r--r--lib/reline.rb21
-rw-r--r--lib/reline/ansi.rb71
-rw-r--r--lib/reline/config.rb5
-rw-r--r--lib/reline/general_io.rb3
-rw-r--r--lib/reline/key_actor/emacs.rb2
-rw-r--r--lib/reline/key_stroke.rb1
-rw-r--r--lib/reline/line_editor.rb156
-rw-r--r--lib/reline/reline.gemspec8
-rw-r--r--lib/reline/terminfo.rb46
-rw-r--r--lib/reline/unicode.rb10
-rw-r--r--lib/reline/version.rb2
-rw-r--r--lib/reline/windows.rb138
-rw-r--r--lib/resolv.rb4
-rw-r--r--lib/rinda/rinda.gemspec2
-rw-r--r--lib/rubygems.rb80
-rw-r--r--lib/rubygems/bundler_version_finder.rb52
-rw-r--r--lib/rubygems/command.rb8
-rw-r--r--lib/rubygems/command_manager.rb4
-rw-r--r--lib/rubygems/commands/cert_command.rb29
-rw-r--r--lib/rubygems/commands/fetch_command.rb37
-rw-r--r--lib/rubygems/commands/pristine_command.rb10
-rw-r--r--lib/rubygems/commands/server_command.rb91
-rw-r--r--lib/rubygems/commands/setup_command.rb144
-rw-r--r--lib/rubygems/commands/uninstall_command.rb2
-rw-r--r--lib/rubygems/commands/update_command.rb9
-rw-r--r--lib/rubygems/dependency.rb14
-rw-r--r--lib/rubygems/dependency_list.rb4
-rw-r--r--lib/rubygems/errors.rb3
-rw-r--r--lib/rubygems/exceptions.rb11
-rw-r--r--lib/rubygems/ext/builder.rb11
-rw-r--r--lib/rubygems/ext/cmake_builder.rb2
-rw-r--r--lib/rubygems/install_update_options.rb4
-rw-r--r--lib/rubygems/installer.rb136
-rw-r--r--lib/rubygems/local_remote_options.rb6
-rw-r--r--lib/rubygems/optparse.rb3
-rw-r--r--lib/rubygems/optparse/.document1
-rw-r--r--lib/rubygems/optparse/lib/optionparser.rb2
-rw-r--r--lib/rubygems/optparse/lib/optparse.rb2230
-rw-r--r--lib/rubygems/optparse/lib/optparse/ac.rb54
-rw-r--r--lib/rubygems/optparse/lib/optparse/date.rb18
-rw-r--r--lib/rubygems/optparse/lib/optparse/kwargs.rb22
-rw-r--r--lib/rubygems/optparse/lib/optparse/shellwords.rb7
-rw-r--r--lib/rubygems/optparse/lib/optparse/time.rb11
-rw-r--r--lib/rubygems/optparse/lib/optparse/uri.rb7
-rw-r--r--lib/rubygems/optparse/lib/optparse/version.rb71
-rw-r--r--lib/rubygems/request_set.rb4
-rw-r--r--lib/rubygems/requirement.rb2
-rw-r--r--lib/rubygems/resolver/installer_set.rb18
-rw-r--r--lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb4
-rw-r--r--lib/rubygems/s3_uri_signer.rb3
-rw-r--r--lib/rubygems/security.rb77
-rw-r--r--lib/rubygems/security/policy.rb6
-rw-r--r--lib/rubygems/security/signer.rb7
-rw-r--r--lib/rubygems/security_option.rb6
-rw-r--r--lib/rubygems/server.rb882
-rw-r--r--lib/rubygems/source/git.rb4
-rw-r--r--lib/rubygems/spec_fetcher.rb2
-rw-r--r--lib/rubygems/specification.rb97
-rw-r--r--lib/rubygems/stub_specification.rb5
-rw-r--r--lib/rubygems/tsort.rb3
-rw-r--r--lib/rubygems/tsort/.document1
-rw-r--r--lib/rubygems/tsort/lib/tsort.rb454
-rw-r--r--lib/rubygems/util/licenses.rb2
-rw-r--r--lib/rubygems/version.rb6
-rw-r--r--lib/rubygems/version_option.rb4
-rw-r--r--lib/securerandom.gemspec4
-rw-r--r--lib/securerandom.rb233
-rw-r--r--lib/set/set.gemspec2
-rw-r--r--lib/tempfile.gemspec2
-rw-r--r--lib/tmpdir.rb4
-rw-r--r--lib/uri/http.rb39
-rw-r--r--lib/uri/version.rb2
247 files changed, 5864 insertions, 2627 deletions
diff --git a/lib/.document b/lib/.document
deleted file mode 100644
index 0d612d10774..00000000000
--- a/lib/.document
+++ /dev/null
@@ -1,26 +0,0 @@
-# Add files to this as they become documented
-
-*.rb
-
-# bundler # not in RDoc
-cgi
-csv
-drb
-forwardable
-irb
-matrix
-net
-optparse
-racc/*.rb
-racc/rdoc
-rdoc
-rexml
-rinda
-rss
-rubygems
-scanf.rb
-shell
-unicode_normalize
-uri
-webrick
-yaml
diff --git a/lib/English.gemspec b/lib/English.gemspec
index ad6eb7acede..274fb047b86 100644
--- a/lib/English.gemspec
+++ b/lib/English.gemspec
@@ -18,7 +18,5 @@ Gem::Specification.new do |spec|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z 2>/dev/null`.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/benchmark/version.rb b/lib/benchmark/version.rb
index 90ae2754e43..545575f4ab8 100644
--- a/lib/benchmark/version.rb
+++ b/lib/benchmark/version.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module Benchmark
VERSION = "0.2.0"
end
diff --git a/lib/bundler.rb b/lib/bundler.rb
index f2d874bdf3f..89865a82544 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -43,6 +43,7 @@ module Bundler
autoload :Dependency, File.expand_path("bundler/dependency", __dir__)
autoload :DepProxy, File.expand_path("bundler/dep_proxy", __dir__)
autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__)
+ autoload :Digest, File.expand_path("bundler/digest", __dir__)
autoload :Dsl, File.expand_path("bundler/dsl", __dir__)
autoload :EndpointSpecification, File.expand_path("bundler/endpoint_specification", __dir__)
autoload :Env, File.expand_path("bundler/env", __dir__)
@@ -65,6 +66,7 @@ module Bundler
autoload :RubyDsl, File.expand_path("bundler/ruby_dsl", __dir__)
autoload :RubyVersion, File.expand_path("bundler/ruby_version", __dir__)
autoload :Runtime, File.expand_path("bundler/runtime", __dir__)
+ autoload :SelfManager, File.expand_path("bundler/self_manager", __dir__)
autoload :Settings, File.expand_path("bundler/settings", __dir__)
autoload :SharedHelpers, File.expand_path("bundler/shared_helpers", __dir__)
autoload :Source, File.expand_path("bundler/source", __dir__)
@@ -642,6 +644,13 @@ EOF
Bundler.rubygems.clear_paths
end
+ def self_manager
+ @self_manager ||= begin
+ require_relative "bundler/self_manager"
+ Bundler::SelfManager.new
+ end
+ end
+
private
def eval_yaml_gemspec(path, contents)
diff --git a/lib/bundler/.document b/lib/bundler/.document
new file mode 100644
index 00000000000..238bbd8705b
--- /dev/null
+++ b/lib/bundler/.document
@@ -0,0 +1 @@
+# not in RDoc
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index d5de72e5d4c..f6e20e7c675 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -61,6 +61,8 @@ module Bundler
Bundler.reset_settings_and_root!
end
+ Bundler.self_manager.restart_with_locked_bundler_if_needed
+
Bundler.settings.set_command_option_if_given :retry, options[:retry]
current_cmd = args.last[:current_command].name
@@ -331,6 +333,7 @@ module Bundler
desc "info GEM [OPTIONS]", "Show information for the given gem"
method_option "path", :type => :boolean, :banner => "Print full path to gem"
+ method_option "version", :type => :boolean, :banner => "Print gem version"
def info(gem_name)
require_relative "cli/info"
Info.new(options, gem_name).run
@@ -366,8 +369,11 @@ module Bundler
method_option "version", :aliases => "-v", :type => :string
method_option "group", :aliases => "-g", :type => :string
method_option "source", :aliases => "-s", :type => :string
+ method_option "require", :aliases => "-r", :type => :string, :banner => "Adds require path to gem. Provide false, or a path as a string."
method_option "git", :type => :string
+ method_option "github", :type => :string
method_option "branch", :type => :string
+ method_option "ref", :type => :string
method_option "skip-install", :type => :boolean, :banner =>
"Adds gem to the Gemfile but does not install it"
method_option "optimistic", :type => :boolean, :banner => "Adds optimistic declaration of version to gem"
@@ -552,7 +558,7 @@ module Bundler
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 moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-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
diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb
index c28997bc7dd..74444ad0cef 100644
--- a/lib/bundler/cli/doctor.rb
+++ b/lib/bundler/cli/doctor.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require "rbconfig"
+require "shellwords"
+require "fiddle"
module Bundler
class CLI::Doctor
@@ -22,14 +24,14 @@ module Bundler
end
def dylibs_darwin(path)
- output = `/usr/bin/otool -L "#{path}"`.chomp
+ output = `/usr/bin/otool -L #{path.shellescape}`.chomp
dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq
# ignore @rpath and friends
dylibs.reject {|dylib| dylib.start_with? "@" }
end
def dylibs_ldd(path)
- output = `/usr/bin/ldd "#{path}"`.chomp
+ output = `/usr/bin/ldd #{path.shellescape}`.chomp
output.split("\n").map do |l|
match = l.match(LDD_REGEX)
next if match.nil?
@@ -70,7 +72,14 @@ module Bundler
definition.specs.each do |spec|
bundles_for_gem(spec).each do |bundle|
- bad_paths = dylibs(bundle).select {|f| !File.exist?(f) }
+ bad_paths = dylibs(bundle).select do |f|
+ begin
+ Fiddle.dlopen(f)
+ false
+ rescue Fiddle::DLError
+ true
+ end
+ end
if bad_paths.any?
broken_links[spec] ||= []
broken_links[spec].concat(bad_paths)
diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb
index e917ceb7d43..31e3af55806 100644
--- a/lib/bundler/cli/gem.rb
+++ b/lib/bundler/cli/gem.rb
@@ -76,6 +76,7 @@ module Bundler
"#{Bundler.preferred_gemfile_name}.tt" => Bundler.preferred_gemfile_name,
"lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb",
"lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb",
+ "sig/newgem.rbs.tt" => "sig/#{namespaced_path}.rbs",
"newgem.gemspec.tt" => "#{name}.gemspec",
"Rakefile.tt" => "Rakefile",
"README.md.tt" => "README.md",
diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb
index 3afed89cebf..76c8cf60c01 100644
--- a/lib/bundler/cli/info.rb
+++ b/lib/bundler/cli/info.rb
@@ -18,6 +18,7 @@ module Bundler
if spec
return print_gem_path(spec) if @options[:path]
+ return print_gem_version(spec) if @options[:version]
print_gem_info(spec)
end
end
@@ -39,6 +40,10 @@ module Bundler
raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(gem_name, Bundler.definition.dependencies)
end
+ def print_gem_version(spec)
+ Bundler.ui.info spec.version.to_s
+ end
+
def print_gem_path(spec)
name = spec.name
if name == "bundler"
@@ -70,7 +75,7 @@ module Bundler
gem_info << "\tPath: #{spec.full_gem_path}\n"
gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem?
- if spec.deleted_gem?
+ if name != "bundler" && spec.deleted_gem?
return Bundler.ui.warn "The gem #{name} has been deleted. Gemspec information is still available though:\n#{gem_info}"
end
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
index 4c1915fea6e..c3400c3959e 100644
--- a/lib/bundler/cli/install.rb
+++ b/lib/bundler/cli/install.rb
@@ -12,6 +12,8 @@ module Bundler
warn_if_root
+ Bundler.self_manager.install_locked_bundler_and_restart_with_it_if_needed
+
Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Bundler::FREEBSD
# Disable color in deployment mode
@@ -163,9 +165,6 @@ module Bundler
def normalize_settings
Bundler.settings.set_command_option :path, nil if options[:system]
- Bundler.settings.temporary(:path_relative_to_cwd => false) do
- Bundler.settings.set_command_option :path, "vendor/bundle" if Bundler.settings[:deployment] && Bundler.settings[:path].nil?
- end
Bundler.settings.set_command_option_if_given :path, options[:path]
Bundler.settings.temporary(:path_relative_to_cwd => false) do
Bundler.settings.set_command_option :path, "bundle" if options["standalone"] && Bundler.settings[:path].nil?
diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb
index f4cd5ac4df6..b891ecb1d2b 100644
--- a/lib/bundler/cli/issue.rb
+++ b/lib/bundler/cli/issue.rb
@@ -20,9 +20,10 @@ module Bundler
Hopefully the troubleshooting steps above resolved your problem! If things
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. Please
- view the Filing Issues guide for more information:
- https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md
+ 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,
+ and copy and pasting the information below.
EOS
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
index 1adcaef67cf..95a8886ea5c 100644
--- a/lib/bundler/cli/update.rb
+++ b/lib/bundler/cli/update.rb
@@ -66,7 +66,7 @@ module Bundler
if locked_gems = Bundler.definition.locked_gems
previous_locked_info = locked_gems.specs.reduce({}) do |h, s|
- h[s.name] = { :spec => s, :version => s.version, :source => s.source.to_s }
+ h[s.name] = { :spec => s, :version => s.version, :source => s.source.identifier }
h
end
end
@@ -95,7 +95,7 @@ module Bundler
end
locked_source = locked_info[:source]
- new_source = new_spec.source.to_s
+ new_source = new_spec.source.identifier
next if locked_source != new_source
new_version = new_spec.version
diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb
index 06486f98cb7..d9b9cec0d45 100644
--- a/lib/bundler/compact_index_client/updater.rb
+++ b/lib/bundler/compact_index_client/updater.rb
@@ -76,11 +76,6 @@ module Bundler
update(local_path, remote_path, :retrying)
end
- rescue Errno::EACCES
- raise Bundler::PermissionError,
- "Bundler does not have write access to create a temp directory " \
- "within #{Dir.tmpdir}. Bundler must have write access to your " \
- "systems temp directory to function properly. "
rescue Zlib::GzipFile::Error
raise Bundler::HTTPError
end
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index da180ce255d..f985e6a3742 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -87,10 +87,11 @@ module Bundler
@platforms = @locked_platforms.dup
@locked_bundler_version = @locked_gems.bundler_version
@locked_ruby_version = @locked_gems.ruby_version
+ @originally_locked_specs = SpecSet.new(@locked_gems.specs)
if unlock != true
@locked_deps = @locked_gems.dependencies
- @locked_specs = SpecSet.new(@locked_gems.specs)
+ @locked_specs = @originally_locked_specs
@locked_sources = @locked_gems.sources
else
@unlock = {}
@@ -143,7 +144,7 @@ module Bundler
@dependency_changes = converge_dependencies
@local_changes = converge_locals
- @locked_specs_incomplete_for_platform = !@locked_specs.for(expand_dependencies(requested_dependencies & locked_dependencies), true, true)
+ @locked_specs_incomplete_for_platform = !@locked_specs.for(requested_dependencies & expand_dependencies(locked_dependencies), true, true)
@requires = compute_requires
end
@@ -163,10 +164,6 @@ module Bundler
end
end
- def multisource_allowed?
- @multisource_allowed
- end
-
def resolve_only_locally!
@remote = false
sources.local_only!
@@ -239,16 +236,17 @@ module Bundler
end
def specs_for(groups)
- groups = requested_groups if groups.empty?
+ return specs if groups.empty?
deps = dependencies_for(groups)
- materialize(expand_dependencies(deps))
+ materialize(deps)
end
def dependencies_for(groups)
groups.map!(&:to_sym)
- current_dependencies.reject do |d|
+ deps = current_dependencies.reject do |d|
(d.groups & groups).empty?
end
+ expand_dependencies(deps)
end
# Resolve all the dependencies specified in Gemfile. It ensures that
@@ -295,10 +293,7 @@ module Bundler
locked_major = @locked_bundler_version.segments.first
current_major = Gem::Version.create(Bundler::VERSION).segments.first
- if updating_major = locked_major < current_major
- Bundler.ui.warn "Warning: the lockfile is being updated to Bundler #{current_major}, " \
- "after which you will be unable to return to Bundler #{@locked_bundler_version.segments.first}."
- end
+ updating_major = locked_major < current_major
end
preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler))
@@ -374,44 +369,31 @@ module Bundler
added.concat new_platforms.map {|p| "* platform: #{p}" }
deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }
- gemfile_sources = sources.lock_sources
-
- new_sources = gemfile_sources - @locked_sources
- deleted_sources = @locked_sources - gemfile_sources
-
new_deps = @dependencies - locked_dependencies
deleted_deps = locked_dependencies - @dependencies
- # Check if it is possible that the source is only changed thing
- if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?)
- new_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
- deleted_sources.reject! {|source| (source.path? && source.path.exist?) || equivalent_rubygems_remotes?(source) }
- end
+ added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
+ deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any?
- if @locked_sources != gemfile_sources
- if new_sources.any?
- added.concat new_sources.map {|source| "* source: #{source}" }
- end
+ both_sources = Hash.new {|h, k| h[k] = [] }
+ @dependencies.each {|d| both_sources[d.name][0] = d }
- if deleted_sources.any?
- deleted.concat deleted_sources.map {|source| "* source: #{source}" }
- end
- end
+ locked_dependencies.each do |d|
+ next if !Bundler.feature_flag.bundler_3_mode? && @locked_specs[d.name].empty?
- added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
- if deleted_deps.any?
- deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" }
+ both_sources[d.name][1] = d
end
- both_sources = Hash.new {|h, k| h[k] = [] }
- @dependencies.each {|d| both_sources[d.name][0] = d }
- @locked_deps.each {|name, d| both_sources[name][1] = d.source }
+ both_sources.each do |name, (dep, lock_dep)|
+ next if dep.nil? || lock_dep.nil?
- both_sources.each do |name, (dep, lock_source)|
- next if lock_source.nil? || (dep && lock_source.can_lock?(dep))
- gemfile_source_name = (dep && dep.source) || "no specified source"
- lockfile_source_name = lock_source
- changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`"
+ gemfile_source = dep.source || sources.default_source
+ lock_source = lock_dep.source || sources.default_source
+ next if lock_source.include?(gemfile_source)
+
+ gemfile_source_name = dep.source ? gemfile_source.identifier : "no specified source"
+ lockfile_source_name = lock_dep.source ? lock_source.identifier : "no specified source"
+ changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`"
end
reason = change_reason
@@ -655,25 +637,14 @@ module Bundler
end
def converge_dependencies
- frozen = Bundler.frozen_bundle?
- (@dependencies + locked_dependencies).each do |dep|
- locked_source = @locked_deps[dep.name]
- # This is to make sure that if bundler is installing in deployment mode and
- # after locked_source and sources don't match, we still use locked_source.
- if frozen && !locked_source.nil? &&
- locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist?
- dep.source = locked_source.source
- elsif dep.source
+ changes = false
+
+ @dependencies.each do |dep|
+ if dep.source
dep.source = sources.get(dep.source)
end
- end
- changes = false
- # We want to know if all match, but don't want to check all entries
- # This means we need to return false if any dependency doesn't match
- # the lock or doesn't exist in the lock.
- @dependencies.each do |dependency|
- unless locked_dep = @locked_deps[dependency.name]
+ unless locked_dep = @locked_deps[dep.name]
changes = true
next
end
@@ -684,11 +655,11 @@ module Bundler
# directive, the lockfile dependencies and resolved dependencies end up
# with a mismatch on #type. Work around that by setting the type on the
# dep from the lockfile.
- locked_dep.instance_variable_set(:@type, dependency.type)
+ locked_dep.instance_variable_set(:@type, dep.type)
# We already know the name matches from the hash lookup
# so we only need to check the requirement now
- changes ||= dependency.requirement != locked_dep.requirement
+ changes ||= dep.requirement != locked_dep.requirement
end
changes
@@ -698,39 +669,39 @@ module Bundler
# commonly happen if the Gemfile has changed since the lockfile was last
# generated
def converge_locked_specs
- deps = []
-
- # Build a list of dependencies that are the same in the Gemfile
- # and Gemfile.lock. If the Gemfile modified a dependency, but
- # the gem in the Gemfile.lock still satisfies it, this is fine
- # too.
- @dependencies.each do |dep|
- locked_dep = @locked_deps[dep.name]
+ resolve = converge_specs(@locked_specs)
- # If the locked_dep doesn't match the dependency we're looking for then we ignore the locked_dep
- locked_dep = nil unless locked_dep == dep
+ diff = nil
- if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep)
- deps << dep
- elsif dep.source.is_a?(Source::Path) && dep.current_platform? && (!locked_dep || dep.source != locked_dep.source)
- @locked_specs.each do |s|
- @unlock[:gems] << s.name if s.source == dep.source
- end
+ # Now, we unlock any sources that do not have anymore gems pinned to it
+ sources.all_sources.each do |source|
+ next unless source.respond_to?(:unlock!)
- dep.source.unlock! if dep.source.respond_to?(:unlock!)
- dep.source.specs.each {|s| @unlock[:gems] << s.name }
+ unless resolve.any? {|s| s.source == source }
+ diff ||= @locked_specs.to_a - resolve.to_a
+ source.unlock! if diff.any? {|s| s.source == source }
end
end
+ resolve
+ end
+
+ def converge_specs(specs)
+ deps = []
converged = []
- @locked_specs.each do |s|
+
+ @dependencies.each do |dep|
+ if specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
+ deps << dep
+ end
+ end
+
+ specs.each do |s|
# Replace the locked dependency's source with the equivalent source from the Gemfile
dep = @dependencies.find {|d| s.satisfies?(d) }
- s.source = (dep && dep.source) || sources.get(s.source) unless multisource_allowed?
- # Don't add a spec to the list if its source is expired. For example,
- # if you change a Git gem to RubyGems.
- next if s.source.nil?
+ s.source = (dep && dep.source) || sources.get(s.source) || sources.default_source unless Bundler.frozen_bundle?
+
next if @unlock[:sources].include?(s.source.name)
# If the spec is from a path source and it doesn't exist anymore
@@ -743,7 +714,7 @@ module Bundler
rescue PathError, GitError
# if we won't need the source (according to the lockfile),
# don't error if the path/git source isn't available
- next if @locked_specs.
+ next if specs.
for(requested_dependencies, false, true).
none? {|locked_spec| locked_spec.source == s.source }
@@ -759,35 +730,15 @@ module Bundler
s.dependencies.replace(new_spec.dependencies)
end
- converged << s
- end
-
- resolve = SpecSet.new(converged)
- resolve = SpecSet.new(resolve.for(expand_dependencies(deps, true), false, false).reject{|s| @unlock[:gems].include?(s.name) })
- diff = nil
-
- # Now, we unlock any sources that do not have anymore gems pinned to it
- sources.all_sources.each do |source|
- next unless source.respond_to?(:unlock!)
-
- unless resolve.any? {|s| s.source == source }
- diff ||= @locked_specs.to_a - resolve.to_a
- source.unlock! if diff.any? {|s| s.source == source }
+ if dep.nil? && requested_dependencies.find {|d| s.name == d.name }
+ @unlock[:gems] << s.name
+ else
+ converged << s
end
end
- resolve
- end
-
- def in_locked_deps?(dep, locked_dep)
- # Because the lockfile can't link a dep to a specific remote, we need to
- # treat sources as equivalent anytime the locked dep has all the remotes
- # that the Gemfile dep does.
- locked_dep && locked_dep.source && dep.source && locked_dep.source.include?(dep.source)
- end
-
- def satisfies_locked_spec?(dep)
- @locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
+ resolve = SpecSet.new(converged)
+ SpecSet.new(resolve.for(expand_dependencies(deps, true), false, false).reject{|s| @unlock[:gems].include?(s.name) })
end
def metadata_dependencies
@@ -880,22 +831,11 @@ module Bundler
def additional_base_requirements_for_resolve
return [] unless @locked_gems && unlocking? && !sources.expired_sources?(@locked_gems.sources)
- dependencies_by_name = dependencies.inject({}) {|memo, dep| memo.update(dep.name => dep) }
- @locked_gems.specs.reduce({}) do |requirements, locked_spec|
+ converge_specs(@originally_locked_specs).map do |locked_spec|
name = locked_spec.name
- dependency = dependencies_by_name[name]
- next requirements if @locked_gems.dependencies[name] != dependency
- next requirements if dependency && dependency.source.is_a?(Source::Path)
dep = Gem::Dependency.new(name, ">= #{locked_spec.version}")
- requirements[name] = DepProxy.get_proxy(dep, locked_spec.platform)
- requirements
- end.values
- end
-
- def equivalent_rubygems_remotes?(source)
- return false unless source.is_a?(Source::Rubygems)
-
- Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
+ DepProxy.get_proxy(dep, locked_spec.platform)
+ end
end
def source_map
diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb
index 27206b1bac9..94e85053dd5 100644
--- a/lib/bundler/dependency.rb
+++ b/lib/bundler/dependency.rb
@@ -7,7 +7,7 @@ require_relative "rubygems_ext"
module Bundler
class Dependency < Gem::Dependency
attr_reader :autorequire
- attr_reader :groups, :platforms, :gemfile, :git, :branch
+ attr_reader :groups, :platforms, :gemfile, :git, :github, :branch, :ref
PLATFORM_MAP = {
:ruby => Gem::Platform::RUBY,
@@ -82,7 +82,9 @@ module Bundler
@groups = Array(options["group"] || :default).map(&:to_sym)
@source = options["source"]
@git = options["git"]
+ @github = options["github"]
@branch = options["branch"]
+ @ref = options["ref"]
@platforms = Array(options["platforms"])
@env = options["env"]
@should_include = options.fetch("should_include", true)
diff --git a/lib/bundler/digest.rb b/lib/bundler/digest.rb
new file mode 100644
index 00000000000..759f6094165
--- /dev/null
+++ b/lib/bundler/digest.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+# This code was extracted from https://github.com/Solistra/ruby-digest which is under public domain
+module Bundler
+ module Digest
+ # The initial constant values for the 32-bit constant words A, B, C, D, and
+ # E, respectively.
+ SHA1_WORDS = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0].freeze
+
+ # The 8-bit field used for bitwise `AND` masking. Defaults to `0xFFFFFFFF`.
+ SHA1_MASK = 0xFFFFFFFF
+
+ class << self
+ def sha1(string)
+ unless string.is_a?(String)
+ raise TypeError, "can't convert #{string.class.inspect} into String"
+ end
+
+ buffer = string.b
+
+ words = SHA1_WORDS.dup
+ generate_split_buffer(buffer) do |chunk|
+ w = []
+ chunk.each_slice(4) do |a, b, c, d|
+ w << (((a << 8 | b) << 8 | c) << 8 | d)
+ 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)
+ end
+ 0.upto(79) do |i|
+ case i
+ when 0..19
+ f = ((b & c) | (~b & d))
+ k = 0x5A827999
+ when 20..39
+ f = (b ^ c ^ d)
+ k = 0x6ED9EBA1
+ when 40..59
+ f = ((b & c) | (b & d) | (c & d))
+ k = 0x8F1BBCDC
+ when 60..79
+ f = (b ^ c ^ d)
+ k = 0xCA62C1D6
+ end
+ t = SHA1_MASK & (SHA1_MASK & rotate(a, 5) + f + e + k + w[i])
+ a, b, c, d, e = t, a, SHA1_MASK & rotate(b, 30), c, d # rubocop:disable Style/ParallelAssignment
+ end
+ mutated = [a, b, c, d, e]
+ words.map!.with_index {|word, index| SHA1_MASK & (word + mutated[index]) }
+ end
+
+ words.pack("N*").unpack("H*").first
+ end
+
+ private
+
+ def generate_split_buffer(string, &block)
+ size = string.bytesize * 8
+ buffer = string.bytes << 128
+ buffer << 0 while buffer.size % 64 != 56
+ buffer.concat([size].pack("Q>").bytes)
+ buffer.each_slice(64, &block)
+ end
+
+ def rotate(value, spaces)
+ value << spaces | value >> (32 - spaces)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index ed7b3e2d6b7..f7922b1fba0 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -18,6 +18,8 @@ module Bundler
VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules
platform platforms type source install_if gemfile].freeze
+ GITHUB_PULL_REQUEST_URL = %r{\Ahttps://github\.com/([A-Za-z0-9_\-\.]+/[A-Za-z0-9_\-\.]+)/pull/(\d+)\z}.freeze
+
attr_reader :gemspecs
attr_accessor :dependencies
@@ -275,26 +277,24 @@ module Bundler
def add_git_sources
git_source(:github) do |repo_name|
- warn_deprecated_git_source(:github, <<-'RUBY'.strip, 'Change any "reponame" :github sources to "username/reponame".')
-"https://github.com/#{repo_name}.git"
- RUBY
- repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
- "https://github.com/#{repo_name}.git"
+ if repo_name =~ GITHUB_PULL_REQUEST_URL
+ {
+ "git" => "https://github.com/#{$1}.git",
+ "branch" => "refs/pull/#{$2}/head",
+ "ref" => nil,
+ "tag" => nil,
+ }
+ else
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
+ "https://github.com/#{repo_name}.git"
+ end
end
git_source(:gist) do |repo_name|
- warn_deprecated_git_source(:gist, '"https://gist.github.com/#{repo_name}.git"')
-
"https://gist.github.com/#{repo_name}.git"
end
git_source(:bitbucket) do |repo_name|
- warn_deprecated_git_source(:bitbucket, <<-'RUBY'.strip)
-user_name, repo_name = repo_name.split("/")
-repo_name ||= user_name
-"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
- RUBY
-
user_name, repo_name = repo_name.split("/")
repo_name ||= user_name
"https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
@@ -365,7 +365,11 @@ repo_name ||= user_name
git_name = (git_names & opts.keys).last
if @git_sources[git_name]
- opts["git"] = @git_sources[git_name].call(opts[git_name])
+ git_opts = @git_sources[git_name].call(opts[git_name])
+ git_opts = { "git" => git_opts } if git_opts.is_a?(String)
+ opts.merge!(git_opts) do |key, _gemfile_value, _git_source_value|
+ raise GemfileError, %(The :#{key} option can't be used with `#{git_name}: #{opts[git_name].inspect}`)
+ end
end
%w[git path].each do |type|
@@ -475,22 +479,6 @@ repo_name ||= user_name
end
end
- def warn_deprecated_git_source(name, replacement, additional_message = nil)
- additional_message &&= " #{additional_message}"
- replacement = if replacement.count("\n").zero?
- "{|repo_name| #{replacement} }"
- else
- "do |repo_name|\n#{replacement.to_s.gsub(/^/, " ")}\n end"
- end
-
- Bundler::SharedHelpers.major_deprecation 3, <<-EOS
-The :#{name} git source is deprecated, and will be removed in the future.#{additional_message} Add this code to the top of your Gemfile to ensure it continues to work:
-
- git_source(:#{name}) #{replacement}
-
- EOS
- end
-
class DSLError < GemfileError
# @return [String] the description that should be presented to the user.
#
diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb
index a77f7e0816b..0f08e049d8d 100644
--- a/lib/bundler/environment_preserver.rb
+++ b/lib/bundler/environment_preserver.rb
@@ -38,7 +38,10 @@ module Bundler
# Replaces `ENV` with the bundler environment variables backed up
def replace_with_backup
- ENV.replace(backup) unless Gem.win_platform?
+ unless Gem.win_platform?
+ ENV.replace(backup)
+ return
+ end
# Fallback logic for Windows below to workaround
# https://bugs.ruby-lang.org/issues/16798. Can be dropped once all
diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb
index 565aaeeb9d9..9ad7460e585 100644
--- a/lib/bundler/errors.rb
+++ b/lib/bundler/errors.rb
@@ -75,10 +75,26 @@ module Bundler
end
end
+ def permission_type
+ case @permission_type
+ when :create
+ "executable permissions for all parent directories and write permissions for `#{parent_folder}`"
+ when :delete
+ permissions = "executable permissions for all parent directories and write permissions for `#{parent_folder}`"
+ permissions += ", and the same thing for all subdirectories inside #{@path}" if File.directory?(@path)
+ permissions
+ else
+ "#{@permission_type} permissions for that path"
+ end
+ end
+
+ def parent_folder
+ File.dirname(@path)
+ end
+
def message
"There was an error while trying to #{action} `#{@path}`. " \
- "It is likely that you need to grant #{@permission_type} permissions " \
- "for that path."
+ "It is likely that you need to grant #{permission_type}."
end
status_code(23)
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
index 6dd6d089eb0..a453157e686 100644
--- a/lib/bundler/fetcher.rb
+++ b/lib/bundler/fetcher.rb
@@ -122,7 +122,6 @@ module Bundler
# return the specs in the bundler format as an index
def specs(gem_names, source)
- old = Bundler.rubygems.sources
index = Bundler::Index.new
if Bundler::Fetcher.disable_endpoint
@@ -153,8 +152,6 @@ module Bundler
rescue CertificateFailureError
Bundler.ui.info "" if gem_names && use_api # newline after dots
raise
- ensure
- Bundler.rubygems.sources = old
end
def use_api
diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb
index db43e0f6549..cc615db60ca 100644
--- a/lib/bundler/friendly_errors.rb
+++ b/lib/bundler/friendly_errors.rb
@@ -63,34 +63,6 @@ module Bundler
def request_issue_report_for(e)
Bundler.ui.error <<-EOS.gsub(/^ {8}/, ""), nil, nil
--- ERROR REPORT TEMPLATE -------------------------------------------------------
- # Error Report
-
- ## Questions
-
- Please fill out answers to these questions, it'll help us figure out
- why things are going wrong.
-
- - **What did you do?**
-
- I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}`
-
- - **What did you expect to happen?**
-
- I expected Bundler to...
-
- - **What happened instead?**
-
- Instead, what happened was...
-
- - **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?**
-
- I tried...
-
- - **Have you read our issues document, https://github.com/rubygems/rubygems/blob/master/bundler/doc/contributing/ISSUES.md?**
-
- ...
-
- ## Backtrace
```
#{e.class}: #{e.message}
@@ -109,8 +81,7 @@ module Bundler
First, try this link to see if there are any existing issue reports for this error:
#{issues_url(e)}
- If there aren't any reports for this error yet, please copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at:
- https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md
+ If there aren't any reports for this error yet, please fill in the new issue form located at #{new_issue_url}, and copy and paste the report template above in there.
EOS
end
@@ -121,6 +92,10 @@ module Bundler
"https://github.com/rubygems/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"
+ end
end
def self.with_friendly_errors
diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb
index 9af85015254..0bbd2d9b75b 100644
--- a/lib/bundler/gem_helper.rb
+++ b/lib/bundler/gem_helper.rb
@@ -107,9 +107,9 @@ module Bundler
SharedHelpers.filesystem_access(File.join(base, "checksums")) {|p| FileUtils.mkdir_p(p) }
file_name = "#{File.basename(built_gem_path)}.sha512"
require "digest/sha2"
- checksum = Digest::SHA512.new.hexdigest(built_gem_path.to_s)
+ checksum = ::Digest::SHA512.file(built_gem_path).hexdigest
target = File.join(base, "checksums", file_name)
- File.write(target, checksum)
+ File.write(target, checksum + "\n")
Bundler.ui.confirm "#{name} #{version} checksum written to checksums/#{file_name}."
end
@@ -129,8 +129,8 @@ module Bundler
def git_push(remote = nil)
remote ||= default_remote
- sh(%W[git push #{remote} refs/heads/#{current_branch}])
- sh(%W[git push #{remote} refs/tags/#{version_tag}])
+ sh("git push #{remote} refs/heads/#{current_branch}".shellsplit)
+ sh("git push #{remote} refs/tags/#{version_tag}".shellsplit)
Bundler.ui.confirm "Pushed git commits and release tag."
end
diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb
index 613bda4f844..42f837a919c 100644
--- a/lib/bundler/injector.rb
+++ b/lib/bundler/injector.rb
@@ -112,9 +112,12 @@ module Bundler
source = ", :source => \"#{d.source}\"" unless d.source.nil?
git = ", :git => \"#{d.git}\"" unless d.git.nil?
+ github = ", :github => \"#{d.github}\"" unless d.github.nil?
branch = ", :branch => \"#{d.branch}\"" unless d.branch.nil?
+ ref = ", :ref => \"#{d.ref}\"" unless d.ref.nil?
+ require_path = ", :require => #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil?
- %(gem #{name}#{requirement}#{group}#{source}#{git}#{branch})
+ %(gem #{name}#{requirement}#{group}#{source}#{git}#{github}#{branch}#{ref}#{require_path})
end.join("\n")
end
@@ -269,5 +272,11 @@ module Bundler
def show_warning(message)
Bundler.ui.info Bundler.ui.add_color(message, :yellow)
end
+
+ def convert_autorequire(autorequire)
+ autorequire = autorequire.first
+ return autorequire if autorequire == "false"
+ autorequire.inspect
+ end
end
end
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb
index 49ff177c0ff..61bf1e06d4b 100644
--- a/lib/bundler/installer.rb
+++ b/lib/bundler/installer.rb
@@ -239,7 +239,7 @@ module Bundler
def ensure_specs_are_compatible!
system_ruby = Bundler::RubyVersion.system
- rubygems_version = Gem::Version.create(Gem::VERSION)
+ rubygems_version = Bundler.rubygems.version
@definition.specs.each do |spec|
if required_ruby_version = spec.required_ruby_version
unless required_ruby_version.satisfied_by?(system_ruby.gem_version)
diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb
index 1df86ccfbc8..13a1356f56d 100644
--- a/lib/bundler/installer/gem_installer.rb
+++ b/lib/bundler/installer/gem_installer.rb
@@ -13,7 +13,7 @@ module Bundler
end
def install_from_spec
- post_install_message = spec_settings ? install_with_settings : install
+ post_install_message = install
Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
generate_executable_stubs
return true, post_install_message
@@ -54,11 +54,6 @@ module Bundler
spec.source.install(spec, :force => force, :ensure_builtin_gems_cached => standalone, :build_args => Array(spec_settings))
end
- def install_with_settings
- # Build arguments are global, so this is mutexed
- Bundler.rubygems.install_with_build_args([spec_settings]) { install }
- end
-
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
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index 6760edba42c..4eb228f3145 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -38,8 +38,24 @@ module Bundler
identifier.hash
end
+ ##
+ # Does this locked specification satisfy +dependency+?
+ #
+ # NOTE: Rubygems default requirement is ">= 0", which doesn't match
+ # prereleases of 0 versions, like "0.0.0.dev" or "0.0.0.SNAPSHOT". However,
+ # bundler users expect those to work. We need to make sure that Gemfile
+ # dependencies without explicit requirements (which use ">= 0" under the
+ # hood by default) are still valid for locked specs using this kind of
+ # versions. The method implements an ad-hoc fix for that. A better solution
+ # might be to change default rubygems requirement of dependencies to be ">=
+ # 0.A" but that's a major refactoring likely to break things. Hopefully we
+ # can attempt it in the future.
+ #
+
def satisfies?(dependency)
- @name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version))
+ effective_requirement = dependency.requirement == Gem::Requirement.default ? Gem::Requirement.new(">= 0.A") : dependency.requirement
+
+ @name == dependency.name && effective_requirement.satisfied_by?(Gem::Version.new(@version))
end
def to_lock
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
index 8d0e44d0aea..e074cbfc33a 100644
--- a/lib/bundler/lockfile_parser.rb
+++ b/lib/bundler/lockfile_parser.rb
@@ -46,6 +46,16 @@ module Bundler
attributes
end
+ def self.bundled_with
+ lockfile = Bundler.default_lockfile
+ return unless lockfile.file?
+
+ lockfile_contents = Bundler.read_file(lockfile)
+ return unless lockfile_contents.include?(BUNDLED)
+
+ lockfile_contents.split(BUNDLED).last.strip
+ end
+
def initialize(lockfile)
@platforms = []
@sources = []
@@ -77,24 +87,12 @@ module Bundler
end
end
@specs = @specs.values.sort_by(&:identifier)
- warn_for_outdated_bundler_version
rescue ArgumentError => e
Bundler.ui.debug(e)
raise LockfileError, "Your lockfile is unreadable. Run `rm #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` " \
"and then `bundle install` to generate a new lockfile."
end
- def warn_for_outdated_bundler_version
- return unless bundler_version
- prerelease_text = bundler_version.prerelease? ? " --pre" : ""
- current_version = Gem::Version.create(Bundler::VERSION)
- return unless current_version < bundler_version
- Bundler.ui.warn "Warning: the running version of Bundler (#{current_version}) is older " \
- "than the version that created the lockfile (#{bundler_version}). We suggest you to " \
- "upgrade to the version that created the lockfile by running `gem install " \
- "bundler:#{bundler_version}#{prerelease_text}`.\n"
- end
-
private
TYPES = {
diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1
index 0cac84bbe82..6c462ba839b 100644
--- a/lib/bundler/man/bundle-add.1
+++ b/lib/bundler/man/bundle-add.1
@@ -1,13 +1,13 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-ADD" "1" "August 2021" "" ""
+.TH "BUNDLE\-ADD" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
.
.SH "SYNOPSIS"
-\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-git=GIT] [\-\-branch=BRANCH] [\-\-skip\-install] [\-\-strict] [\-\-optimistic]
+\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-git=GIT] [\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-skip\-install] [\-\-strict] [\-\-optimistic]
.
.SH "DESCRIPTION"
Adds the named gem to the Gemfile and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\.
@@ -49,10 +49,18 @@ Specify the source for the added gem\.
Specify the git source for the added gem\.
.
.TP
+\fB\-\-github\fR
+Specify the github source for the added gem\.
+.
+.TP
\fB\-\-branch\fR
Specify the git branch for the added gem\.
.
.TP
+\fB\-\-ref\fR
+Specify the git ref for the added gem\.
+.
+.TP
\fB\-\-skip\-install\fR
Adds the gem to the Gemfile but does not install it\.
.
diff --git a/lib/bundler/man/bundle-add.1.ronn b/lib/bundler/man/bundle-add.1.ronn
index 26cbe556471..6547297c862 100644
--- a/lib/bundler/man/bundle-add.1.ronn
+++ b/lib/bundler/man/bundle-add.1.ronn
@@ -3,7 +3,7 @@ bundle-add(1) -- Add gem to the Gemfile and run bundle install
## SYNOPSIS
-`bundle add` <GEM_NAME> [--group=GROUP] [--version=VERSION] [--source=SOURCE] [--git=GIT] [--branch=BRANCH] [--skip-install] [--strict] [--optimistic]
+`bundle add` <GEM_NAME> [--group=GROUP] [--version=VERSION] [--source=SOURCE] [--git=GIT] [--github=GITHUB] [--branch=BRANCH] [--ref=REF] [--skip-install] [--strict] [--optimistic]
## DESCRIPTION
Adds the named gem to the Gemfile and run `bundle install`. `bundle install` can be avoided by using the flag `--skip-install`.
@@ -33,9 +33,15 @@ bundle add rails --group "development, test"
* `--git`:
Specify the git source for the added gem.
+* `--github`:
+ Specify the github source for the added gem.
+
* `--branch`:
Specify the git branch for the added gem.
+* `--ref`:
+ Specify the git ref for the added gem.
+
* `--skip-install`:
Adds the gem to the Gemfile but does not install it.
diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1
index 017e302fec7..6d1b1d4247d 100644
--- a/lib/bundler/man/bundle-binstubs.1
+++ b/lib/bundler/man/bundle-binstubs.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-BINSTUBS" "1" "August 2021" "" ""
+.TH "BUNDLE\-BINSTUBS" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1
index cfb4b847201..acbdae0df2f 100644
--- a/lib/bundler/man/bundle-cache.1
+++ b/lib/bundler/man/bundle-cache.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-CACHE" "1" "August 2021" "" ""
+.TH "BUNDLE\-CACHE" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1
index bc8f4772f65..e555c9b3996 100644
--- a/lib/bundler/man/bundle-check.1
+++ b/lib/bundler/man/bundle-check.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-CHECK" "1" "August 2021" "" ""
+.TH "BUNDLE\-CHECK" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1
index 814d28e4541..d4032475240 100644
--- a/lib/bundler/man/bundle-clean.1
+++ b/lib/bundler/man/bundle-clean.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-CLEAN" "1" "August 2021" "" ""
+.TH "BUNDLE\-CLEAN" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory
diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1
index 06f2a7bb73a..5c07e227fdd 100644
--- a/lib/bundler/man/bundle-config.1
+++ b/lib/bundler/man/bundle-config.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-CONFIG" "1" "August 2021" "" ""
+.TH "BUNDLE\-CONFIG" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-config\fR \- Set bundler configuration options
@@ -462,13 +462,13 @@ export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x\-oauth\-basic
.IP "" 0
.
.P
-Note that any configured credentials will be redacted by informative commands such as \fBbundle config list\fR or \fBbundle config get\fR, unless you use the \fB\-\-parseable\fR flag\. This is to avoid unintentially leaking credentials when copy\-pasting bundler output\.
+Note that any configured credentials will be redacted by informative commands such as \fBbundle config list\fR or \fBbundle config get\fR, unless you use the \fB\-\-parseable\fR flag\. This is to avoid unintentionally leaking credentials when copy\-pasting bundler output\.
.
.P
Also note that to guarantee a sane mapping between valid environment variable names and valid host names, bundler makes the following transformations:
.
.IP "\(bu" 4
-Any \fB\-\fR characters in a host name are mapped to a triple dash (\fB___\fR) in the corresponding enviroment variable\.
+Any \fB\-\fR characters in a host name are mapped to a triple dash (\fB___\fR) in the corresponding environment variable\.
.
.IP "\(bu" 4
Any \fB\.\fR characters in a host name are mapped to a double dash (\fB__\fR) in the corresponding environment variable\.
diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn
index 96374d9f2ba..a21aa027ad2 100644
--- a/lib/bundler/man/bundle-config.1.ronn
+++ b/lib/bundler/man/bundle-config.1.ronn
@@ -367,14 +367,14 @@ where you can use personal OAuth tokens:
Note that any configured credentials will be redacted by informative commands
such as `bundle config list` or `bundle config get`, unless you use the
-`--parseable` flag. This is to avoid unintentially leaking credentials when
+`--parseable` flag. This is to avoid unintentionally leaking credentials when
copy-pasting bundler output.
Also note that to guarantee a sane mapping between valid environment variable
names and valid host names, bundler makes the following transformations:
* Any `-` characters in a host name are mapped to a triple dash (`___`) in the
- corresponding enviroment variable.
+ corresponding environment variable.
* Any `.` characters in a host name are mapped to a double dash (`__`) in the
corresponding environment variable.
diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1
index 5509d315c0e..87a7fe5f2f6 100644
--- a/lib/bundler/man/bundle-doctor.1
+++ b/lib/bundler/man/bundle-doctor.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-DOCTOR" "1" "August 2021" "" ""
+.TH "BUNDLE\-DOCTOR" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-doctor\fR \- Checks the bundle for common problems
diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1
index 627ba57ec45..69adfa7c92c 100644
--- a/lib/bundler/man/bundle-exec.1
+++ b/lib/bundler/man/bundle-exec.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-EXEC" "1" "August 2021" "" ""
+.TH "BUNDLE\-EXEC" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-exec\fR \- Execute a command in the context of the bundle
diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1
index 0190cefa3b4..fae5c34e7e9 100644
--- a/lib/bundler/man/bundle-gem.1
+++ b/lib/bundler/man/bundle-gem.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-GEM" "1" "August 2021" "" ""
+.TH "BUNDLE\-GEM" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem
diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1
index 5a1ff7757d9..9e1400ec56c 100644
--- a/lib/bundler/man/bundle-info.1
+++ b/lib/bundler/man/bundle-info.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-INFO" "1" "August 2021" "" ""
+.TH "BUNDLE\-INFO" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-info\fR \- Show information for the given gem in your bundle
diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1
index 7c729479575..612d16031c6 100644
--- a/lib/bundler/man/bundle-init.1
+++ b/lib/bundler/man/bundle-init.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-INIT" "1" "August 2021" "" ""
+.TH "BUNDLE\-INIT" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-init\fR \- Generates a Gemfile into the current working directory
diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1
index c281f212ba7..ded4d6d64bf 100644
--- a/lib/bundler/man/bundle-inject.1
+++ b/lib/bundler/man/bundle-inject.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-INJECT" "1" "August 2021" "" ""
+.TH "BUNDLE\-INJECT" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile
diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1
index 8b86954996a..6824dea3b0b 100644
--- a/lib/bundler/man/bundle-install.1
+++ b/lib/bundler/man/bundle-install.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-INSTALL" "1" "August 2021" "" ""
+.TH "BUNDLE\-INSTALL" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1
index 48f817ed736..a697173af9c 100644
--- a/lib/bundler/man/bundle-list.1
+++ b/lib/bundler/man/bundle-list.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-LIST" "1" "August 2021" "" ""
+.TH "BUNDLE\-LIST" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-list\fR \- List all the gems in the bundle
diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1
index 623e76406bc..ef515b03373 100644
--- a/lib/bundler/man/bundle-lock.1
+++ b/lib/bundler/man/bundle-lock.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-LOCK" "1" "August 2021" "" ""
+.TH "BUNDLE\-LOCK" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-lock\fR \- Creates / Updates a lockfile without installing
diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1
index d1c5314ed41..dd28566bdb0 100644
--- a/lib/bundler/man/bundle-open.1
+++ b/lib/bundler/man/bundle-open.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-OPEN" "1" "August 2021" "" ""
+.TH "BUNDLE\-OPEN" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-open\fR \- Opens the source directory for a gem in your bundle
diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1
index d327de4ba63..b9d50a1c710 100644
--- a/lib/bundler/man/bundle-outdated.1
+++ b/lib/bundler/man/bundle-outdated.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-OUTDATED" "1" "August 2021" "" ""
+.TH "BUNDLE\-OUTDATED" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-outdated\fR \- List installed gems with newer versions available
diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1
index 872afb6d58b..b1c859f64b2 100644
--- a/lib/bundler/man/bundle-platform.1
+++ b/lib/bundler/man/bundle-platform.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-PLATFORM" "1" "August 2021" "" ""
+.TH "BUNDLE\-PLATFORM" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-platform\fR \- Displays platform compatibility information
diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1
index 8361597f7de..6e4a0286660 100644
--- a/lib/bundler/man/bundle-pristine.1
+++ b/lib/bundler/man/bundle-pristine.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-PRISTINE" "1" "August 2021" "" ""
+.TH "BUNDLE\-PRISTINE" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-pristine\fR \- Restores installed gems to their pristine condition
diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1
index 548c208403b..0b4edd14149 100644
--- a/lib/bundler/man/bundle-remove.1
+++ b/lib/bundler/man/bundle-remove.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-REMOVE" "1" "August 2021" "" ""
+.TH "BUNDLE\-REMOVE" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-remove\fR \- Removes gems from the Gemfile
diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1
index 8669a8add0a..375699ddf08 100644
--- a/lib/bundler/man/bundle-show.1
+++ b/lib/bundler/man/bundle-show.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-SHOW" "1" "August 2021" "" ""
+.TH "BUNDLE\-SHOW" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1
index 9df9c05ea70..f5a79fe24ea 100644
--- a/lib/bundler/man/bundle-update.1
+++ b/lib/bundler/man/bundle-update.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-UPDATE" "1" "August 2021" "" ""
+.TH "BUNDLE\-UPDATE" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-update\fR \- Update your gems to the latest available versions
diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1
index 6603fad30d9..f792aa63460 100644
--- a/lib/bundler/man/bundle-viz.1
+++ b/lib/bundler/man/bundle-viz.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE\-VIZ" "1" "August 2021" "" ""
+.TH "BUNDLE\-VIZ" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1
index 2fe30724e6d..b1458bf57b9 100644
--- a/lib/bundler/man/bundle.1
+++ b/lib/bundler/man/bundle.1
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "BUNDLE" "1" "August 2021" "" ""
+.TH "BUNDLE" "1" "December 2021" "" ""
.
.SH "NAME"
\fBbundle\fR \- Ruby Dependency Management
diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5
index 5ef91541b06..1ac003a7800 100644
--- a/lib/bundler/man/gemfile.5
+++ b/lib/bundler/man/gemfile.5
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
-.TH "GEMFILE" "5" "August 2021" "" ""
+.TH "GEMFILE" "5" "December 2021" "" ""
.
.SH "NAME"
\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs
@@ -506,6 +506,32 @@ gem "rails", :git => "git://github\.com/rails/rails\.git"
.P
Since the \fBgithub\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
.
+.P
+You can also directly pass a pull request URL:
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :github => "https://github\.com/rails/rails/pull/43753"
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Which is equivalent to:
+.
+.IP "" 4
+.
+.nf
+
+gem "rails", :github => "rails/rails", branch: "refs/pull/43753/head"
+.
+.fi
+.
+.IP "" 0
+.
.SS "GIST"
If the git repository you want to use is hosted as a GitHub Gist and is public, you can use the :gist shorthand to specify the gist identifier (without the trailing "\.git")\.
.
diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn
index ea7cf40e63c..0feaf582463 100644
--- a/lib/bundler/man/gemfile.5.ronn
+++ b/lib/bundler/man/gemfile.5.ronn
@@ -372,6 +372,14 @@ Are both equivalent to
Since the `github` method is a specialization of `git_source`, it accepts a `:branch` named argument.
+You can also directly pass a pull request URL:
+
+ gem "rails", :github => "https://github.com/rails/rails/pull/43753"
+
+Which is equivalent to:
+
+ gem "rails", :github => "rails/rails", branch: "refs/pull/43753/head"
+
### GIST
If the git repository you want to use is hosted as a GitHub Gist and is public, you can use
diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb
index f6f4ac4f0a2..32b1d0ee381 100644
--- a/lib/bundler/plugin/api/source.rb
+++ b/lib/bundler/plugin/api/source.rb
@@ -283,6 +283,7 @@ module Bundler
def to_s
"plugin source for #{@type} with uri #{@uri}"
end
+ alias_method :identifier, :to_s
# Note: Do not override if you don't know what you are doing.
def include?(other)
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
index d7411fff450..81ecafa470b 100644
--- a/lib/bundler/plugin/installer.rb
+++ b/lib/bundler/plugin/installer.rb
@@ -21,7 +21,7 @@ module Bundler
elsif options[:local_git]
install_local_git(names, version, options)
else
- sources = options[:source] || Bundler.rubygems.sources
+ sources = options[:source] || Gem.sources
install_rubygems(names, version, sources)
end
end
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index d26e2feb109..22d61fba367 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -30,10 +30,8 @@ module Bundler
@resolver = Molinillo::Resolver.new(self, self)
@search_for = {}
@base_dg = Molinillo::DependencyGraph.new
- aggregate_global_source = @source_requirements[:default].is_a?(Source::RubygemsAggregate)
@base.each do |ls|
dep = Dependency.new(ls.name, ls.version)
- ls.source = source_for(ls.name) unless aggregate_global_source
@base_dg.add_vertex(ls.name, DepProxy.get_proxy(dep, ls.platform), true)
end
additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) }
@@ -136,6 +134,7 @@ module Bundler
end
nested.reduce([]) do |groups, (version, specs)|
next groups if locked_requirement && !locked_requirement.satisfied_by?(version)
+ next groups unless specs.any? {|spec| spec.match_platform(platform) }
specs_by_platform = Hash.new do |current_specs, current_platform|
current_specs[current_platform] = select_best_platform_match(specs, current_platform)
@@ -147,7 +146,7 @@ module Bundler
next groups if @resolving_only_for_ruby
spec_group = SpecGroup.create_for(specs_by_platform, @platforms, platform)
- groups << spec_group if spec_group
+ groups << spec_group
groups
end
@@ -264,30 +263,37 @@ module Bundler
"If you are updating multiple gems in your Gemfile at once,\n" \
"try passing them all to `bundle update`"
else
- source = source_for(name)
- specs = source.specs.search(name)
- versions_with_platforms = specs.map {|s| [s.version, s.platform] }
- cache_message = begin
- " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
- rescue GemfileNotFound
- nil
- end
- message = String.new("Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in #{source.to_err}#{cache_message}.\n")
- message << "The source contains the following versions of '#{name}': #{formatted_versions_with_platforms(versions_with_platforms)}" if versions_with_platforms.any?
+ message = gem_not_found_message(name, requirement, source_for(name))
end
raise GemNotFound, message
end
end
- def formatted_versions_with_platforms(versions_with_platforms)
- version_platform_strs = versions_with_platforms.map do |vwp|
- version = vwp.first
- platform = vwp.last
- version_platform_str = String.new(version.to_s)
- version_platform_str << " #{platform}" unless platform.nil? || platform == Gem::Platform::RUBY
- version_platform_str
+ def gem_not_found_message(name, requirement, source, extra_message = "")
+ specs = source.specs.search(name)
+ matching_part = name
+ requirement_label = SharedHelpers.pretty_dependency(requirement)
+ cache_message = begin
+ " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
+ rescue GemfileNotFound
+ nil
+ end
+ specs_matching_requirement = specs.select {| spec| requirement.matches_spec?(spec) }
+
+ if specs_matching_requirement.any?
+ specs = specs_matching_requirement
+ matching_part = requirement_label
+ requirement_label = "#{requirement_label} #{requirement.__platform}"
end
- version_platform_strs.join(", ")
+
+ message = String.new("Could not find gem '#{requirement_label}'#{extra_message} in #{source}#{cache_message}.\n")
+
+ if specs.any?
+ message << "\nThe source contains the following gems matching '#{matching_part}':\n"
+ message << specs.map {|s| " * #{s.full_name}" }.join("\n")
+ end
+
+ message
end
def version_conflict_message(e)
@@ -359,19 +365,16 @@ module Bundler
metadata_requirement = name.end_with?("\0")
- o << "Could not find gem '" unless metadata_requirement
- o << SharedHelpers.pretty_dependency(conflict.requirement)
- o << "'" unless metadata_requirement
- if conflict.requirement_trees.first.size > 1
- o << ", which is required by "
- o << "gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}',"
+ extra_message = if conflict.requirement_trees.first.size > 1
+ ", which is required by gem '#{SharedHelpers.pretty_dependency(conflict.requirement_trees.first[-2])}',"
+ else
+ ""
end
- o << " "
- o << if metadata_requirement
- "is not available in #{relevant_source}"
+ if metadata_requirement
+ o << "#{SharedHelpers.pretty_dependency(conflict.requirement)}#{extra_message} is not available in #{relevant_source}"
else
- "in #{relevant_source.to_err}.\n"
+ o << gem_not_found_message(name, conflict.requirement, relevant_source, extra_message)
end
end
end,
diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb
index 491f8c55a4e..d3b7963920c 100644
--- a/lib/bundler/ruby_version.rb
+++ b/lib/bundler/ruby_version.rb
@@ -103,7 +103,7 @@ module Bundler
def self.system
ruby_engine = RUBY_ENGINE.dup
- ruby_version = ENV.fetch("BUNDLER_SPEC_RUBY_VERSION") { RUBY_VERSION }.dup
+ ruby_version = RUBY_VERSION.dup
ruby_engine_version = RUBY_ENGINE_VERSION.dup
patchlevel = RUBY_PATCHLEVEL.to_s
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
index 88905823180..452583617b3 100644
--- a/lib/bundler/rubygems_gem_installer.rb
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -16,10 +16,12 @@ module Bundler
spec.loaded_from = spec_file
# Completely remove any previous gem files
- FileUtils.rm_rf gem_dir
- FileUtils.rm_rf spec.extension_dir
+ strict_rm_rf gem_dir
+ strict_rm_rf spec.extension_dir
- FileUtils.mkdir_p gem_dir, :mode => 0o755
+ SharedHelpers.filesystem_access(gem_dir, :create) do
+ FileUtils.mkdir_p gem_dir, :mode => 0o755
+ end
extract_files
@@ -31,7 +33,10 @@ module Bundler
generate_plugins
write_spec
- write_cache_file
+
+ SharedHelpers.filesystem_access("#{gem_home}/cache", :write) do
+ write_cache_file
+ end
say spec.post_install_message unless spec.post_install_message.nil?
@@ -62,7 +67,7 @@ module Bundler
def build_extensions
extension_cache_path = options[:bundler_extension_cache_path]
unless extension_cache_path && extension_dir = spec.extension_dir
- require "shellwords" # compensate missing require in rubygems before version 3.2.25
+ require "shellwords" unless Bundler.rubygems.provides?(">= 3.2.25")
return super
end
@@ -87,6 +92,17 @@ module Bundler
private
+ def strict_rm_rf(dir)
+ # FileUtils.rm_rf should probably rise in case of permission issues like
+ # `rm -rf` does. However, it fails to delete the folder silently due to
+ # https://github.com/ruby/fileutils/issues/57. It should probably be fixed
+ # inside `fileutils` but for now I`m checking whether the folder was
+ # removed after it completes, and raising otherwise.
+ FileUtils.rm_rf dir
+
+ raise PermissionError.new(dir, :delete) if File.directory?(dir)
+ end
+
def validate_bundler_checksum(checksum)
return true if Bundler.settings[:disable_checksum_validation]
return true unless checksum
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index 8589c14704d..785f7fa360a 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -12,25 +12,21 @@ module Bundler
EXT_LOCK = Monitor.new
end
- def self.version
- @version ||= Gem::Version.new(Gem::VERSION)
- end
-
- def self.provides?(req_str)
- Gem::Requirement.new(req_str).satisfied_by?(version)
- end
-
def initialize
@replaced_methods = {}
backport_ext_builder_monitor
end
def version
- self.class.version
+ @version ||= Gem.rubygems_version
end
def provides?(req_str)
- self.class.provides?(req_str)
+ Gem::Requirement.new(req_str).satisfied_by?(version)
+ end
+
+ def supports_bundler_trampolining?
+ provides?(">= 3.3.0.a")
end
def build_args
@@ -142,19 +138,6 @@ module Bundler
end
end
- def sources=(val)
- # Gem.configuration creates a new Gem::ConfigFile, which by default will read ~/.gemrc
- # If that file exists, its settings (including sources) will overwrite the values we
- # are about to set here. In order to avoid that, we force memoizing the config file now.
- configuration
-
- Gem.sources = val
- end
-
- def sources
- Gem.sources
- end
-
def gem_dir
Gem.dir
end
@@ -232,18 +215,6 @@ module Bundler
EXT_LOCK
end
- def with_build_args(args)
- ext_lock.synchronize do
- old_args = build_args
- begin
- self.build_args = args
- yield
- ensure
- self.build_args = old_args
- end
- end
- end
-
def spec_from_gem(path, policy = nil)
require "rubygems/security"
require_relative "psyched_yaml"
@@ -500,14 +471,15 @@ module Bundler
end
def fetch_specs(remote, name)
+ require "rubygems/remote_fetcher"
path = remote.uri.to_s + "#{name}.#{Gem.marshal_version}.gz"
fetcher = gem_remote_fetcher
fetcher.headers = { "X-Gemfile-Source" => remote.original_uri.to_s } if remote.original_uri
string = fetcher.fetch_path(path)
Bundler.load_marshal(string)
- rescue Gem::RemoteFetcher::FetchError => e
+ rescue Gem::RemoteFetcher::FetchError
# it's okay for prerelease to fail
- raise e unless name == "prerelease_specs"
+ raise unless name == "prerelease_specs"
end
def fetch_all_remote_specs(remote)
@@ -517,12 +489,32 @@ module Bundler
specs.concat(pres)
end
- def download_gem(spec, uri, path)
+ def download_gem(spec, uri, cache_dir)
+ require "rubygems/remote_fetcher"
uri = Bundler.settings.mirror_for(uri)
fetcher = gem_remote_fetcher
fetcher.headers = { "X-Gemfile-Source" => spec.remote.original_uri.to_s } if spec.remote.original_uri
Bundler::Retry.new("download gem from #{uri}").attempts do
- fetcher.download(spec, uri, path)
+ gem_file_name = spec.file_name
+ local_gem_path = File.join cache_dir, gem_file_name
+ return if File.exist? local_gem_path
+
+ begin
+ remote_gem_path = uri + "gems/#{gem_file_name}"
+ remote_gem_path = remote_gem_path.to_s if provides?("< 3.2.0.rc.1")
+
+ SharedHelpers.filesystem_access(local_gem_path) do
+ fetcher.cache_update_path remote_gem_path, local_gem_path
+ end
+ rescue Gem::RemoteFetcher::FetchError
+ raise if spec.original_platform == spec.platform
+
+ original_gem_file_name = "#{spec.original_name}.gem"
+ raise if gem_file_name == original_gem_file_name
+
+ gem_file_name = original_gem_file_name
+ retry
+ end
end
rescue Gem::RemoteFetcher::FetchError => e
raise Bundler::HTTPError, "Could not download gem from #{uri} due to underlying error <#{e.message}>"
@@ -550,10 +542,6 @@ module Bundler
Gem::REPOSITORY_SUBDIRECTORIES
end
- def install_with_build_args(args)
- yield
- end
-
def path_separator
Gem.path_separator
end
@@ -583,6 +571,10 @@ module Bundler
end
end
+ def find_bundler(version)
+ find_name("bundler").find {|s| s.version.to_s == version }
+ end
+
def find_name(name)
Gem::Specification.stubs_for(name).map(&:to_spec)
end
diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb
index 31e71388a68..c7276b0e251 100644
--- a/lib/bundler/runtime.rb
+++ b/lib/bundler/runtime.rb
@@ -265,7 +265,7 @@ module Bundler
return if manuals.empty?
Bundler::SharedHelpers.set_env "MANPATH", manuals.concat(
- ENV["MANPATH"].to_s.split(File::PATH_SEPARATOR)
+ ENV["MANPATH"] ? ENV["MANPATH"].to_s.split(File::PATH_SEPARATOR) : [""]
).uniq.join(File::PATH_SEPARATOR)
end
diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb
new file mode 100644
index 00000000000..024b2bfbf2d
--- /dev/null
+++ b/lib/bundler/self_manager.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+module Bundler
+ #
+ # This class handles installing and switching to the version of bundler needed
+ # by an application.
+ #
+ class SelfManager
+ def restart_with_locked_bundler_if_needed
+ return unless needs_switching? && installed?
+
+ restart_with_locked_bundler
+ end
+
+ def install_locked_bundler_and_restart_with_it_if_needed
+ return unless needs_switching?
+
+ install_and_restart_with_locked_bundler
+ end
+
+ private
+
+ def install_and_restart_with_locked_bundler
+ bundler_dep = Gem::Dependency.new("bundler", lockfile_version)
+ spec = fetch_spec_for(bundler_dep)
+ return if spec.nil?
+
+ Bundler.ui.info \
+ "Bundler #{current_version} is running, but your lockfile was generated with #{lockfile_version}. " \
+ "Installing Bundler #{lockfile_version} and restarting using that version."
+
+ spec.source.install(spec)
+ rescue StandardError => e
+ Bundler.ui.trace e
+ Bundler.ui.warn "There was an error installing the locked bundler version (#{lockfile_version}), rerun with the `--verbose` flag for more details. Going on using bundler #{current_version}."
+ else
+ restart_with_locked_bundler
+ end
+
+ def fetch_spec_for(bundler_dep)
+ source = Bundler::Source::Rubygems.new("remotes" => "https://rubygems.org")
+ source.remote!
+ source.add_dependency_names("bundler")
+ spec = source.specs.search(bundler_dep).first
+ if spec.nil?
+ Bundler.ui.warn "Your lockfile is locked to a version of bundler (#{lockfile_version}) that doesn't exist at https://rubygems.org/. Going on using #{current_version}"
+ end
+ spec
+ end
+
+ def restart_with_locked_bundler
+ configured_gem_home = ENV["GEM_HOME"]
+ configured_gem_path = ENV["GEM_PATH"]
+
+ cmd = [$PROGRAM_NAME, *ARGV]
+ cmd.unshift(Gem.ruby) unless File.executable?($PROGRAM_NAME)
+
+ Bundler.with_original_env do
+ Kernel.exec(
+ { "GEM_HOME" => configured_gem_home, "GEM_PATH" => configured_gem_path, "BUNDLER_VERSION" => lockfile_version },
+ *cmd
+ )
+ end
+ end
+
+ def needs_switching?
+ ENV["BUNDLER_VERSION"].nil? &&
+ Bundler.rubygems.supports_bundler_trampolining? &&
+ SharedHelpers.in_bundle? &&
+ lockfile_version &&
+ !lockfile_version.end_with?(".dev") &&
+ lockfile_version != current_version &&
+ !updating?
+ end
+
+ def updating?
+ "update".start_with?(ARGV.first || " ") && ARGV[1..-1].any? {|a| a.start_with?("--bundler") }
+ end
+
+ def installed?
+ Bundler.configure
+
+ Bundler.rubygems.find_bundler(lockfile_version)
+ end
+
+ def current_version
+ @current_version ||= Bundler::VERSION
+ end
+
+ def lockfile_version
+ @lockfile_version ||= Bundler::LockfileParser.bundled_with
+ end
+ end
+end
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index 1ced590b6f3..72728fb20f8 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -219,6 +219,7 @@ module Bundler
def path
configs.each do |_level, settings|
path = value_for("path", settings)
+ path = "vendor/bundle" if value_for("deployment", settings) && path.nil?
path_system = value_for("path.system", settings)
disabled_shared_gems = value_for("disable_shared_gems", settings)
next if path.nil? && path_system.nil? && disabled_shared_gems.nil?
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index 405ade95ddf..e48010232a8 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -239,7 +239,7 @@ module Bundler
current = File.expand_path(SharedHelpers.pwd).tap{|x| x.untaint if RUBY_VERSION < "2.7" }
until !File.directory?(current) || current == previous
- if ENV["BUNDLE_SPEC_RUN"]
+ if ENV["BUNDLER_SPEC_RUN"]
# avoid stepping above the tmp directory when testing
gemspec = if ENV["GEM_COMMAND"]
# for Ruby Core
@@ -313,12 +313,11 @@ module Bundler
end
def clean_load_path
- bundler_lib = bundler_ruby_lib
-
loaded_gem_paths = Bundler.rubygems.loaded_gem_paths
$LOAD_PATH.reject! do |p|
- next if resolve_path(p).start_with?(bundler_lib)
+ resolved_path = resolve_path(p)
+ next if $LOADED_FEATURES.any? {|lf| lf.start_with?(resolved_path) }
loaded_gem_paths.delete(p)
end
$LOAD_PATH.uniq!
diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb
index 434112ac8a9..2a2b332cff7 100644
--- a/lib/bundler/source.rb
+++ b/lib/bundler/source.rb
@@ -67,7 +67,7 @@ module Bundler
"#<#{self.class}:0x#{object_id} #{self}>"
end
- def to_err
+ def identifier
to_s
end
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index 679fb225745..a41a2f23e96 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -307,7 +307,9 @@ module Bundler
# If there is no URI scheme, assume it is an ssh/git URI
input = uri
end
- SharedHelpers.digest(:SHA1).hexdigest(input)
+ # We use SHA1 here for historical reason and to preserve backward compatibility.
+ # But a transition to a simpler mangling algorithm would be welcome.
+ Bundler::Digest.sha1(input)
end
def cached_revision
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index 7555561edd5..745a7fe118f 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -56,7 +56,6 @@ module Bundler
@ref = ref
@revision = revision
@git = git
- raise GitNotInstalledError.new if allow? && !Bundler.git_present?
end
def revision
@@ -96,12 +95,12 @@ module Bundler
SharedHelpers.filesystem_access(path.dirname) do |p|
FileUtils.mkdir_p(p)
end
- git_retry "clone", configured_uri, path.to_s, "--bare", "--no-hardlinks", "--quiet"
+ git_retry "clone", "--bare", "--no-hardlinks", "--quiet", "--", configured_uri, path.to_s
return unless extra_ref
end
with_path do
- git_retry(*["fetch", "--force", "--quiet", "--tags", configured_uri, "refs/heads/*:refs/heads/*", extra_ref].compact, :dir => path)
+ git_retry(*["fetch", "--force", "--quiet", "--tags", "--", configured_uri, "refs/heads/*:refs/heads/*", extra_ref].compact, :dir => path)
end
end
@@ -208,7 +207,11 @@ module Bundler
end
def allow?
- @git ? @git.allow_git_ops? : true
+ allowed = @git ? @git.allow_git_ops? : true
+
+ raise GitNotInstalledError.new if allowed && !Bundler.git_present?
+
+ allowed
end
def with_path(&blk)
diff --git a/lib/bundler/source/metadata.rb b/lib/bundler/source/metadata.rb
index 50b65ce0ea4..8bdbaa55279 100644
--- a/lib/bundler/source/metadata.rb
+++ b/lib/bundler/source/metadata.rb
@@ -25,7 +25,7 @@ module Bundler
s.loaded_from = File.expand_path("..", __FILE__)
end
- if local_spec = Bundler.rubygems.find_name("bundler").find {|s| s.version.to_s == VERSION }
+ if local_spec = Bundler.rubygems.find_bundler(VERSION)
idx << local_spec
end
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index 28c1ee3c968..8bc3aa17e98 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -98,26 +98,30 @@ module Bundler
out << " specs:\n"
end
- def to_err
+ def to_s
if remotes.empty?
"locally installed gems"
- elsif @allow_remote
+ elsif @allow_remote && @allow_cached && @allow_local
+ "rubygems repository #{remote_names}, cached gems or installed locally"
+ elsif @allow_remote && @allow_local
"rubygems repository #{remote_names} or installed locally"
- elsif @allow_cached
- "cached gems from rubygems repository #{remote_names} or installed locally"
+ elsif @allow_remote
+ "rubygems repository #{remote_names}"
+ elsif @allow_cached && @allow_local
+ "cached gems or installed locally"
else
"locally installed gems"
end
end
- def to_s
+ def identifier
if remotes.empty?
"locally installed gems"
else
- "rubygems repository #{remote_names} or installed locally"
+ "rubygems repository #{remote_names}"
end
end
- alias_method :name, :to_s
+ alias_method :name, :identifier
def specs
@specs ||= begin
@@ -162,7 +166,7 @@ module Bundler
begin
s = Bundler.rubygems.spec_from_gem(path, Bundler.settings["trust-policy"])
spec.__swap__(s)
- rescue StandardError
+ rescue Gem::Package::FormatError
Bundler.rm_rf(path)
raise
end
@@ -174,6 +178,7 @@ module Bundler
Bundler.ui.confirm message
path = cached_gem(spec)
+ raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path
if requires_sudo?
install_path = Bundler.tmp(spec.full_name)
bin_path = install_path.join("bin")
@@ -233,12 +238,8 @@ module Bundler
end
def cache(spec, custom_path = nil)
- if spec.default_gem?
- cached_path = cached_built_in_gem(spec)
- else
- cached_path = cached_gem(spec)
- end
- raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
+ cached_path = cached_gem(spec)
+ raise GemNotFound, "Missing gem file '#{spec.file_name}'." unless cached_path
return if File.dirname(cached_path) == Bundler.app_cache.to_s
Bundler.ui.info " * #{File.basename(cached_path)}"
FileUtils.cp(cached_path, Bundler.app_cache(custom_path))
@@ -265,10 +266,6 @@ module Bundler
@remotes.unshift(uri) unless @remotes.include?(uri)
end
- def equivalent_remotes?(other_remotes)
- other_remotes.map(&method(:remove_auth)) == @remotes.map(&method(:remove_auth))
- end
-
def spec_names
if @allow_remote && dependency_api_available?
remote_specs.spec_names
@@ -337,7 +334,11 @@ module Bundler
end
def credless_remotes
- remotes.map(&method(:suppress_configured_credentials))
+ if Bundler.settings[:allow_deployment_source_credential_changes]
+ remotes.map(&method(:remove_auth))
+ else
+ remotes.map(&method(:suppress_configured_credentials))
+ end
end
def remotes_for_spec(spec)
@@ -352,14 +353,17 @@ module Bundler
end
def cached_gem(spec)
- cached_gem = cached_path(spec)
- unless cached_gem
- raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation"
+ if spec.default_gem?
+ cached_built_in_gem(spec)
+ else
+ cached_path(spec)
end
- cached_gem
end
def cached_path(spec)
+ global_cache_path = download_cache_path(spec)
+ @caches << global_cache_path if global_cache_path
+
possibilities = @caches.map {|p| "#{p}/#{spec.file_name}" }
possibilities.find {|p| File.exist?(p) }
end
@@ -459,19 +463,26 @@ module Bundler
spec.fetch_platform
- download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir
- gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem"
+ cache_path = download_cache_path(spec) || default_cache_path_for(rubygems_dir)
+ gem_path = "#{cache_path}/#{spec.file_name}"
+
+ if requires_sudo?
+ download_path = Bundler.tmp(spec.full_name)
+ download_cache_path = default_cache_path_for(download_path)
+ else
+ download_cache_path = cache_path
+ end
- SharedHelpers.filesystem_access("#{download_path}/cache") do |p|
+ SharedHelpers.filesystem_access(download_cache_path) do |p|
FileUtils.mkdir_p(p)
end
- download_gem(spec, download_path)
+ download_gem(spec, download_cache_path)
if requires_sudo?
- SharedHelpers.filesystem_access("#{rubygems_dir}/cache") do |p|
+ SharedHelpers.filesystem_access(cache_path) do |p|
Bundler.mkdir_p(p)
end
- Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}"
+ Bundler.sudo "mv #{download_cache_path}/#{spec.file_name} #{gem_path}"
end
gem_path
@@ -491,6 +502,10 @@ module Bundler
Bundler.rubygems.gem_dir
end
+ def default_cache_path_for(dir)
+ "#{dir}/cache"
+ end
+
def cache_path
Bundler.app_cache
end
@@ -503,52 +518,13 @@ module Bundler
# @param [Specification] spec
# the spec we want to download or retrieve from the cache.
#
- # @param [String] download_path
+ # @param [String] download_cache_path
# the local directory the .gem will end up in.
#
- def download_gem(spec, download_path)
- local_path = File.join(download_path, "cache/#{spec.full_name}.gem")
-
- if (cache_path = download_cache_path(spec)) && cache_path.file?
- SharedHelpers.filesystem_access(local_path) do
- FileUtils.cp(cache_path, local_path)
- end
- else
- uri = spec.remote.uri
- Bundler.ui.confirm("Fetching #{version_message(spec)}")
- rubygems_local_path = Bundler.rubygems.download_gem(spec, uri, download_path)
-
- # older rubygems return varying file:// variants depending on version
- rubygems_local_path = rubygems_local_path.gsub(/\Afile:/, "") unless Bundler.rubygems.provides?(">= 3.2.0.rc.2")
- rubygems_local_path = rubygems_local_path.gsub(%r{\A//}, "") if Bundler.rubygems.provides?("< 3.1.0")
-
- if rubygems_local_path != local_path
- SharedHelpers.filesystem_access(local_path) do
- FileUtils.mv(rubygems_local_path, local_path)
- end
- end
- cache_globally(spec, local_path)
- end
- end
-
- # Checks if the requested spec exists in the global cache. If it does
- # not, we create the relevant global cache subdirectory if it does not
- # exist and copy the spec from the local cache to the global cache.
- #
- # @param [Specification] spec
- # the spec we want to copy to the global cache.
- #
- # @param [String] local_cache_path
- # the local directory from which we want to copy the .gem.
- #
- def cache_globally(spec, local_cache_path)
- return unless cache_path = download_cache_path(spec)
- return if cache_path.exist?
-
- SharedHelpers.filesystem_access(cache_path.dirname, &:mkpath)
- SharedHelpers.filesystem_access(cache_path) do
- FileUtils.cp(local_cache_path, cache_path)
- end
+ def download_gem(spec, download_cache_path)
+ uri = spec.remote.uri
+ Bundler.ui.confirm("Fetching #{version_message(spec)}")
+ Bundler.rubygems.download_gem(spec, uri, download_cache_path)
end
# Returns the global cache path of the calling Rubygems::Source object.
@@ -567,7 +543,7 @@ module Bundler
return unless remote = spec.remote
return unless cache_slug = remote.cache_slug
- Bundler.user_cache.join("gems", cache_slug, spec.file_name)
+ Bundler.user_cache.join("gems", cache_slug)
end
def extension_cache_slug(spec)
diff --git a/lib/bundler/source/rubygems_aggregate.rb b/lib/bundler/source/rubygems_aggregate.rb
index 09cf4002eab..99ef81ad54b 100644
--- a/lib/bundler/source/rubygems_aggregate.rb
+++ b/lib/bundler/source/rubygems_aggregate.rb
@@ -16,7 +16,7 @@ module Bundler
@index
end
- def to_err
+ def identifier
to_s
end
diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb
index d6310b78c06..a4773397c7d 100644
--- a/lib/bundler/source_list.rb
+++ b/lib/bundler/source_list.rb
@@ -98,7 +98,7 @@ module Bundler
end
def get(source)
- source_list_for(source).find {|s| equal_source?(source, s) || equivalent_source?(source, s) }
+ source_list_for(source).find {|s| equivalent_source?(source, s) }
end
def lock_sources
@@ -106,14 +106,14 @@ module Bundler
end
def lock_other_sources
- (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
+ (path_sources + git_sources + plugin_sources).sort_by(&:identifier)
end
def lock_rubygems_sources
if merged_gem_lockfile_sections?
[combine_rubygems_sources]
else
- rubygems_sources.sort_by(&:to_s)
+ rubygems_sources.sort_by(&:identifier)
end
end
@@ -173,7 +173,7 @@ module Bundler
end
def different_sources?(lock_sources, replacement_sources)
- !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources)
+ !equivalent_sources?(lock_sources, replacement_sources)
end
def rubygems_aggregate_class
@@ -210,34 +210,12 @@ module Bundler
end
end
- def equal_sources?(lock_sources, replacement_sources)
- lock_sources.sort_by(&:to_s) == replacement_sources.sort_by(&:to_s)
- end
-
- def equal_source?(source, other_source)
- return source.include?(other_source) if source.is_a?(Source::Rubygems) && other_source.is_a?(Source::Rubygems)
-
- source == other_source
- end
-
- def equivalent_source?(source, other_source)
- return false unless Bundler.settings[:allow_deployment_source_credential_changes] && source.is_a?(Source::Rubygems)
-
- equivalent_rubygems_sources?([source], [other_source])
- end
-
def equivalent_sources?(lock_sources, replacement_sources)
- return false unless Bundler.settings[:allow_deployment_source_credential_changes]
-
- lock_rubygems_sources, lock_other_sources = lock_sources.partition {|s| s.is_a?(Source::Rubygems) }
- replacement_rubygems_sources, replacement_other_sources = replacement_sources.partition {|s| s.is_a?(Source::Rubygems) }
-
- equivalent_rubygems_sources?(lock_rubygems_sources, replacement_rubygems_sources) && equal_sources?(lock_other_sources, replacement_other_sources)
+ lock_sources.sort_by(&:identifier) == replacement_sources.sort_by(&:identifier)
end
- def equivalent_rubygems_sources?(lock_sources, replacement_sources)
- actual_remotes = replacement_sources.map(&:remotes).flatten.uniq
- lock_sources.all? {|s| s.equivalent_remotes?(actual_remotes) }
+ def equivalent_source?(source, other_source)
+ source == other_source
end
end
end
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
index 26d41cb9b8d..a19d18388ab 100644
--- a/lib/bundler/spec_set.rb
+++ b/lib/bundler/spec_set.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "tsort"
+require_relative "vendored_tsort"
module Bundler
class SpecSet
diff --git a/lib/bundler/templates/Executable.bundler b/lib/bundler/templates/Executable.bundler
index 8009412ea29..6bb5c510907 100644
--- a/lib/bundler/templates/Executable.bundler
+++ b/lib/bundler/templates/Executable.bundler
@@ -73,7 +73,7 @@ m = Module.new do
requirement = bundler_gem_version.approximate_recommendation
- return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
+ return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")
requirement += ".a" if bundler_gem_version.prerelease?
diff --git a/lib/bundler/templates/Gemfile b/lib/bundler/templates/Gemfile
index d41f2719b44..d2403f18b2d 100644
--- a/lib/bundler/templates/Gemfile
+++ b/lib/bundler/templates/Gemfile
@@ -2,6 +2,4 @@
source "https://rubygems.org"
-git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
-
# gem "rails"
diff --git a/lib/bundler/templates/gems.rb b/lib/bundler/templates/gems.rb
index 56a62a7a822..d2403f18b2d 100644
--- a/lib/bundler/templates/gems.rb
+++ b/lib/bundler/templates/gems.rb
@@ -1,8 +1,5 @@
# frozen_string_literal: true
-# A sample gems.rb
source "https://rubygems.org"
-git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
-
# gem "rails"
diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt
index 952cd649a24..6570d177afe 100644
--- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt
+++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt
@@ -10,11 +10,11 @@ on:
jobs:
build:
runs-on: ubuntu-latest
-
+ name: Ruby ${{ matrix.ruby }}
strategy:
matrix:
ruby:
- - <%= RUBY_VERSION %>
+ - '<%= RUBY_VERSION %>'
steps:
- uses: actions/checkout@v2
diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt
index e07ec5867d7..546a28b78ae 100644
--- a/lib/bundler/templates/newgem/newgem.gemspec.tt
+++ b/lib/bundler/templates/newgem/newgem.gemspec.tt
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject do |f|
- (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
end
end
spec.bindir = "exe"
diff --git a/lib/bundler/templates/newgem/sig/newgem.rbs.tt b/lib/bundler/templates/newgem/sig/newgem.rbs.tt
new file mode 100644
index 00000000000..eb7b380bbb9
--- /dev/null
+++ b/lib/bundler/templates/newgem/sig/newgem.rbs.tt
@@ -0,0 +1,8 @@
+<%- config[:constant_array].each_with_index do |c, i| -%>
+<%= " " * i %>module <%= c %>
+<%- end -%>
+<%= " " * config[:constant_array].size %>VERSION: String
+<%= " " * config[:constant_array].size %># See the writing guide of rbs: https://github.com/ruby/rbs#guides
+<%- (config[:constant_array].size-1).downto(0) do |i| -%>
+<%= " " * i %>end
+<%- end -%>
diff --git a/lib/bundler/templates/newgem/standard.yml.tt b/lib/bundler/templates/newgem/standard.yml.tt
index e720f410313..934b0b2c37f 100644
--- a/lib/bundler/templates/newgem/standard.yml.tt
+++ b/lib/bundler/templates/newgem/standard.yml.tt
@@ -1,4 +1,3 @@
# For available configuration options, see:
# https://github.com/testdouble/standard
-
-default_ignores: false
+ruby_version: <%= ::Gem::Version.new(config[:required_ruby_version]).segments[0..1].join(".") %>
diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb
index 17777af4acb..384752a340a 100644
--- a/lib/bundler/ui/shell.rb
+++ b/lib/bundler/ui/shell.rb
@@ -81,7 +81,7 @@ module Bundler
def trace(e, newline = nil, force = false)
return unless debug? || force
msg = "#{e.class}: #{e.message}\n#{e.backtrace.join("\n ")}"
- tell_me(msg, nil, newline)
+ tell_err(msg, nil, newline)
end
def silence(&blk)
diff --git a/lib/bundler/vendor/.document b/lib/bundler/vendor/.document
new file mode 100644
index 00000000000..0c43bbd6b38
--- /dev/null
+++ b/lib/bundler/vendor/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
index 936399ed2f5..10a25d2f08a 100644
--- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
+++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'tsort'
+require_relative '../../../../vendored_tsort'
require_relative 'dependency_graph/log'
require_relative 'dependency_graph/vertex'
@@ -17,7 +17,7 @@ module Bundler::Molinillo
vertices.values.each { |v| yield v }
end
- include TSort
+ include Bundler::TSort
# @!visibility private
alias tsort_each_node each
diff --git a/lib/bundler/vendor/thor/lib/thor/actions.rb b/lib/bundler/vendor/thor/lib/thor/actions.rb
index de9323b2dbe..de9b3b4c86f 100644
--- a/lib/bundler/vendor/thor/lib/thor/actions.rb
+++ b/lib/bundler/vendor/thor/lib/thor/actions.rb
@@ -161,6 +161,8 @@ class Bundler::Thor
# to the block you provide. The path is set back to the previous path when
# the method exits.
#
+ # Returns the value yielded by the block.
+ #
# ==== Parameters
# dir<String>:: the directory to move to.
# config<Hash>:: give :verbose => true to log and use padding.
@@ -179,16 +181,18 @@ class Bundler::Thor
FileUtils.mkdir_p(destination_root)
end
+ result = nil
if pretend
# In pretend mode, just yield down to the block
- block.arity == 1 ? yield(destination_root) : yield
+ result = block.arity == 1 ? yield(destination_root) : yield
else
require "fileutils"
- FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
+ FileUtils.cd(destination_root) { result = block.arity == 1 ? yield(destination_root) : yield }
end
@destination_stack.pop
shell.padding -= 1 if verbose
+ result
end
# Goes to the root and execute the given block.
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 90a8d2e8472..bf2a737c849 100644
--- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb
+++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb
@@ -210,9 +210,9 @@ class Bundler::Thor
#
# ==== Examples
#
- # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n"
+ # inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " filter_parameter :password\n"
#
- # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
+ # inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
# " filter_parameter :password\n"
# end
#
@@ -233,9 +233,9 @@ class Bundler::Thor
#
# ==== Examples
#
- # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
+ # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper", " def help; 'help'; end\n"
#
- # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
+ # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper" do
# " def help; 'help'; end\n"
# end
#
@@ -252,7 +252,7 @@ class Bundler::Thor
# 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.
+ # :force => true, to force the replacement regardles of runner behavior.
#
# ==== Example
#
@@ -331,7 +331,7 @@ class Bundler::Thor
path = File.expand_path(path, destination_root)
say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
- if !options[:pretend] && File.exist?(path)
+ if !options[:pretend] && (File.exist?(path) || File.symlink?(path))
require "fileutils"
::FileUtils.rm_rf(path)
end
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb
index 09ce0864f01..f52ced2bcd9 100644
--- a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb
+++ b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb
@@ -106,12 +106,14 @@ class Bundler::Thor
# Adds the content to the file.
#
def replace!(regexp, string, force)
- return if pretend?
content = File.read(destination)
- if force || !content.include?(replacement)
+ before, after = content.split(regexp, 2)
+ snippet = (behavior == :after ? after : before).to_s
+
+ if force || !snippet.include?(replacement)
success = content.gsub!(regexp, string)
- File.open(destination, "wb") { |file| file.write(content) }
+ File.open(destination, "wb") { |file| file.write(content) } unless pretend?
success
end
end
diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb
index c167aa33b86..3c4483e5dda 100644
--- a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb
+++ b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb
@@ -28,6 +28,12 @@ class Bundler::Thor
super(convert_key(key))
end
+ def except(*keys)
+ dup.tap do |hash|
+ keys.each { |key| hash.delete(convert_key(key)) }
+ end
+ end
+
def fetch(key, *args)
super(convert_key(key), *args)
end
diff --git a/lib/bundler/vendor/thor/lib/thor/error.rb b/lib/bundler/vendor/thor/lib/thor/error.rb
index 7d57129b836..03f2ce85bb9 100644
--- a/lib/bundler/vendor/thor/lib/thor/error.rb
+++ b/lib/bundler/vendor/thor/lib/thor/error.rb
@@ -102,9 +102,14 @@ class Bundler::Thor
end
if Correctable
- DidYouMean::SPELL_CHECKERS.merge!(
- 'Bundler::Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
- 'Bundler::Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
- )
+ if DidYouMean.respond_to?(:correct_error)
+ DidYouMean.correct_error(Bundler::Thor::UndefinedCommandError, UndefinedCommandError::SpellChecker)
+ DidYouMean.correct_error(Bundler::Thor::UnknownArgumentError, UnknownArgumentError::SpellChecker)
+ else
+ DidYouMean::SPELL_CHECKERS.merge!(
+ 'Bundler::Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
+ 'Bundler::Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
+ )
+ end
end
end
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
index 3a8927d09c5..5bd97aba6f8 100644
--- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb
+++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
@@ -45,6 +45,7 @@ class Bundler::Thor
@switches = {}
@extra = []
@stopped_parsing_after_extra_index = nil
+ @is_treated_as_value = false
options.each do |option|
@switches[option.switch_name] = option
@@ -74,8 +75,19 @@ class Bundler::Thor
end
end
+ def shift
+ @is_treated_as_value = false
+ super
+ end
+
+ def unshift(arg, is_value: false)
+ @is_treated_as_value = is_value
+ super(arg)
+ end
+
def parse(args) # rubocop:disable MethodLength
@pile = args.dup
+ @is_treated_as_value = false
@parsing_options = true
while peek
@@ -88,7 +100,10 @@ class Bundler::Thor
when SHORT_SQ_RE
unshift($1.split("").map { |f| "-#{f}" })
next
- when EQ_RE, SHORT_NUM
+ when EQ_RE
+ unshift($2, is_value: true)
+ switch = $1
+ when SHORT_NUM
unshift($2)
switch = $1
when LONG_RE, SHORT_RE
@@ -148,6 +163,7 @@ class Bundler::Thor
# Two booleans are returned. The first is true if the current value
# starts with a hyphen; the second is true if it is a registered switch.
def current_is_switch?
+ return [false, false] if @is_treated_as_value
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
[true, switch?($1)]
@@ -159,6 +175,7 @@ class Bundler::Thor
end
def current_is_switch_formatted?
+ return false if @is_treated_as_value
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
true
@@ -168,6 +185,7 @@ class Bundler::Thor
end
def current_is_value?
+ return true if @is_treated_as_value
peek && (!parsing_options? || super)
end
diff --git a/lib/bundler/vendor/thor/lib/thor/shell.rb b/lib/bundler/vendor/thor/lib/thor/shell.rb
index e36fa472d69..a4137d1bde2 100644
--- a/lib/bundler/vendor/thor/lib/thor/shell.rb
+++ b/lib/bundler/vendor/thor/lib/thor/shell.rb
@@ -21,7 +21,7 @@ class Bundler::Thor
end
module Shell
- SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
+ SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_error, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
attr_writer :shell
autoload :Basic, File.expand_path("shell/basic", __dir__)
diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
index 2dddd4a53ab..8eff00bf3db 100644
--- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
+++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
@@ -103,6 +103,23 @@ class Bundler::Thor
stdout.flush
end
+ # Say (print) an error to the user. If the sentence ends with a whitespace
+ # or tab character, a new line is not appended (print + flush). Otherwise
+ # are passed straight to puts (behavior got from Highline).
+ #
+ # ==== Example
+ # say_error("error: something went wrong")
+ #
+ def say_error(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
+ return if quiet?
+
+ buffer = prepare_message(message, *color)
+ buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
+
+ stderr.print(buffer)
+ stderr.flush
+ end
+
# Say a status with the given color and appends the message. Since this
# method is used frequently by actions, it allows nil or false to be given
# in log_status, avoiding the message from being shown. If a Symbol is
@@ -111,13 +128,14 @@ class Bundler::Thor
def say_status(status, message, log_status = true)
return if quiet? || log_status == false
spaces = " " * (padding + 1)
- color = log_status.is_a?(Symbol) ? log_status : :green
-
status = status.to_s.rjust(12)
+ margin = " " * status.length + spaces
+
+ color = log_status.is_a?(Symbol) ? log_status : :green
status = set_color status, color, true if color
- buffer = "#{status}#{spaces}#{message}"
- buffer = "#{buffer}\n" unless buffer.end_with?("\n")
+ message = message.to_s.chomp.gsub(/(?<!\A)^/, margin)
+ buffer = "#{status}#{spaces}#{message}\n"
stdout.print(buffer)
stdout.flush
diff --git a/lib/bundler/vendor/thor/lib/thor/util.rb b/lib/bundler/vendor/thor/lib/thor/util.rb
index ddf4d21b90b..d2572a42491 100644
--- a/lib/bundler/vendor/thor/lib/thor/util.rb
+++ b/lib/bundler/vendor/thor/lib/thor/util.rb
@@ -211,7 +211,7 @@ class Bundler::Thor
#
def globs_for(path)
path = escape_globs(path)
- ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
+ ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/**/*.thor"]
end
# Return the path to the ruby interpreter taking into account multiple
diff --git a/lib/bundler/vendor/tsort/lib/tsort.rb b/lib/bundler/vendor/tsort/lib/tsort.rb
new file mode 100644
index 00000000000..8454583295e
--- /dev/null
+++ b/lib/bundler/vendor/tsort/lib/tsort.rb
@@ -0,0 +1,453 @@
+# 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 Bundler
+ module TSort
+ 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 TSort.tsort(each_node, each_child)
+ TSort.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 TSort.tsort_each(each_node, each_child) # :yields: node
+ return to_enum(__method__, each_node, each_child) unless block_given?
+
+ TSort.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 TSort.strongly_connected_components(each_node, each_child)
+ TSort.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 TSort.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
+ TSort.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 TSort.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 =
+ TSort.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
+end
diff --git a/lib/bundler/vendored_tsort.rb b/lib/bundler/vendored_tsort.rb
new file mode 100644
index 00000000000..38aed0b5def
--- /dev/null
+++ b/lib/bundler/vendored_tsort.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+module Bundler; end
+require_relative "vendor/tsort/lib/tsort"
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
index 0c0d9d360df..1acb00fd3aa 100644
--- a/lib/bundler/version.rb
+++ b/lib/bundler/version.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
module Bundler
- VERSION = "2.3.0.dev".freeze
+ VERSION = "2.4.0.dev".freeze
def self.bundler_major_version
@bundler_major_version ||= VERSION.split(".").first.to_i
diff --git a/lib/cgi.rb b/lib/cgi.rb
index b6ffbae3318..1a03d064f1b 100644
--- a/lib/cgi.rb
+++ b/lib/cgi.rb
@@ -288,7 +288,7 @@
#
class CGI
- VERSION = "0.3.0"
+ VERSION = "0.3.1"
end
require 'cgi/core'
diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec
index 5d23ef0f614..3ba62b93f6d 100644
--- a/lib/cgi/cgi.gemspec
+++ b/lib/cgi/cgi.gemspec
@@ -23,9 +23,9 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
+ `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) }
end
- spec.bindir = "exe"
+ spec.extensions = ["ext/cgi/escape/extconf.rb"]
spec.executables = []
spec.require_paths = ["lib"]
end
diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb
index ae9ab58edef..6b0d89ca3ba 100644
--- a/lib/cgi/cookie.rb
+++ b/lib/cgi/cookie.rb
@@ -159,7 +159,6 @@ class CGI
raw_cookie.split(/;\s?/).each do |pairs|
name, values = pairs.split('=',2)
next unless name and values
- name = CGI.unescape(name)
values ||= ""
values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) }
if cookies.has_key?(name)
diff --git a/lib/cgi/session.rb b/lib/cgi/session.rb
index 29e7b3ece3b..70c7ebca426 100644
--- a/lib/cgi/session.rb
+++ b/lib/cgi/session.rb
@@ -189,6 +189,47 @@ class CGI
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).
@@ -373,21 +414,8 @@ class CGI
# This session's FileStore file will be created if it does
# not exist, or opened if it does.
def initialize(session, option={})
- dir = option['tmpdir'] || Dir::tmpdir
- prefix = option['prefix'] || 'cgi_sid_'
- suffix = option['suffix'] || ''
- id = session.session_id
- require 'digest/md5'
- md5 = Digest::MD5.hexdigest(id)[0,16]
- @path = dir+"/"+prefix+md5+suffix
- if File::exist? @path
- @hash = nil
- else
- unless session.new_session
- raise CGI::Session::NoSession, "uninitialized session"
- end
- @hash = {}
- end
+ option = {'prefix' => 'cgi_sid_'}.update(option)
+ @path, @hash = session.new_store_file(option)
end
# Restore session state from the session's FileStore file.
diff --git a/lib/cgi/session/pstore.rb b/lib/cgi/session/pstore.rb
index cc3006400fa..45d0d8ae2cb 100644
--- a/lib/cgi/session/pstore.rb
+++ b/lib/cgi/session/pstore.rb
@@ -44,20 +44,8 @@ class CGI
# This session's PStore file will be created if it does
# not exist, or opened if it does.
def initialize(session, option={})
- dir = option['tmpdir'] || Dir::tmpdir
- prefix = option['prefix'] || ''
- id = session.session_id
- require 'digest/md5'
- md5 = Digest::MD5.hexdigest(id)[0,16]
- path = dir+"/"+prefix+md5
- if File::exist?(path)
- @hash = nil
- else
- unless session.new_session
- raise CGI::Session::NoSession, "uninitialized session"
- end
- @hash = {}
- end
+ option = {'suffix'=>''}.update(option)
+ path, @hash = session.new_store_file(option)
@p = ::PStore.new(path)
@p.transaction do |p|
File.chmod(0600, p.path)
diff --git a/lib/csv.rb b/lib/csv.rb
index 868f8e5b0e9..06a490f34c6 100644
--- a/lib/csv.rb
+++ b/lib/csv.rb
@@ -90,11 +90,11 @@
# with any questions.
require "forwardable"
-require "English"
require "date"
require "stringio"
require_relative "csv/fields_converter"
+require_relative "csv/input_record_separator"
require_relative "csv/match_p"
require_relative "csv/parser"
require_relative "csv/row"
@@ -330,6 +330,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# liberal_parsing: false,
# nil_value: nil,
# empty_value: "",
+# strip: false,
# # For generating.
# write_headers: nil,
# quote_empty: true,
@@ -337,7 +338,6 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# write_converters: nil,
# write_nil_value: nil,
# write_empty_value: "",
-# strip: false,
# }
#
# ==== Options for Parsing
@@ -355,8 +355,9 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# - +header_converters+: Specifies the header converters to be used.
# - +skip_blanks+: Specifies whether blanks lines are to be ignored.
# - +skip_lines+: Specifies how comments lines are to be recognized.
-# - +strip+: Specifies whether leading and trailing whitespace are
-# to be stripped from fields..
+# - +strip+: Specifies whether leading and trailing whitespace are to be
+# stripped from fields. This must be compatible with +col_sep+; if it is not,
+# then an +ArgumentError+ exception will be raised.
# - +liberal_parsing+: Specifies whether \CSV should attempt to parse
# non-compliant data.
# - +nil_value+: Specifies the object that is to be substituted for each null (no-text) field.
@@ -502,7 +503,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
# [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
# [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
-# Each CSV::Info object shows:
+# Each CSV::FieldInfo object shows:
# - The 0-based field index.
# - The 1-based line index.
# - The field header, if any.
@@ -536,6 +537,14 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
#
# There is no such storage structure for write headers.
#
+# In order for the parsing methods to access stored converters in non-main-Ractors, the
+# storage structure must be made shareable first.
+# Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
+# <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
+# of Ractors that use the converters stored in these structures. (Since making the storage
+# structures shareable involves freezing them, any custom converters that are to be used
+# must be added first.)
+#
# ===== Converter Lists
#
# A _converter_ _list_ is an \Array that may include any assortment of:
@@ -908,6 +917,7 @@ class CSV
gsub(/\s+/, "_").to_sym
}
}
+
# Default values for method options.
DEFAULT_OPTIONS = {
# For both parsing and generating.
@@ -926,6 +936,7 @@ class CSV
liberal_parsing: false,
nil_value: nil,
empty_value: "",
+ strip: false,
# For generating.
write_headers: nil,
quote_empty: true,
@@ -933,7 +944,6 @@ class CSV
write_converters: nil,
write_nil_value: nil,
write_empty_value: "",
- strip: false,
}.freeze
class << self
@@ -946,6 +956,8 @@ class CSV
# Creates or retrieves cached \CSV objects.
# For arguments and options, see CSV.new.
#
+ # This API is not Ractor-safe.
+ #
# ---
#
# With no block given, returns a \CSV object.
@@ -1051,7 +1063,7 @@ class CSV
# out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
def filter(input=nil, output=nil, **options)
# parse options for input, output, or both
- in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR}
+ in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
options.each do |key, value|
case key.to_s
when /\Ain(?:put)?_(.+)\Z/
@@ -1292,8 +1304,8 @@ class CSV
# Argument +ary+ must be an \Array.
#
# Special options:
- # * Option <tt>:row_sep</tt> defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>
- # (<tt>$/</tt>).:
+ # * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
+ # and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
# $INPUT_RECORD_SEPARATOR # => "\n"
# * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
# Encoding for the output. This method will try to guess your Encoding from
@@ -1315,7 +1327,7 @@ class CSV
# CSV.generate_line(:foo)
#
def generate_line(row, **options)
- options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
+ options = {row_sep: InputRecordSeparator.value}.merge(options)
str = +""
if options[:encoding]
str.force_encoding(options[:encoding])
@@ -1749,11 +1761,11 @@ class CSV
encoding: nil,
nil_value: nil,
empty_value: "",
+ strip: false,
quote_empty: true,
write_converters: nil,
write_nil_value: nil,
- write_empty_value: "",
- strip: false)
+ write_empty_value: "")
raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
if data.is_a?(String)
@@ -1873,6 +1885,10 @@ class CSV
# csv.converters # => [:integer]
# csv.convert(proc {|x| x.to_s })
# csv.converters
+ #
+ # Notes that you need to call
+ # +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
+ # this method.
def converters
parser_fields_converter.map do |converter|
name = Converters.rassoc(converter)
@@ -1935,6 +1951,10 @@ class CSV
# Returns an \Array containing header converters; used for parsing;
# see {Header Converters}[#class-CSV-label-Header+Converters]:
# CSV.new('').header_converters # => []
+ #
+ # Notes that you need to call
+ # +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
+ # to use this method.
def header_converters
header_fields_converter.map do |converter|
name = HeaderConverters.rassoc(converter)
@@ -2575,7 +2595,7 @@ class CSV
def build_parser_fields_converter
specific_options = {
- builtin_converters: Converters,
+ builtin_converters_name: :Converters,
}
options = @base_fields_converter_options.merge(specific_options)
build_fields_converter(@initial_converters, options)
@@ -2587,7 +2607,7 @@ class CSV
def build_header_fields_converter
specific_options = {
- builtin_converters: HeaderConverters,
+ builtin_converters_name: :HeaderConverters,
accept_nil: true,
}
options = @base_fields_converter_options.merge(specific_options)
@@ -2650,8 +2670,15 @@ end
# c.read.any? { |a| a.include?("zombies") }
# } #=> false
#
-def CSV(*args, &block)
- CSV.instance(*args, &block)
+# CSV options may also be given.
+#
+# io = StringIO.new
+# CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
+#
+# This API is not Ractor-safe.
+#
+def CSV(*args, **options, &block)
+ CSV.instance(*args, **options, &block)
end
require_relative "csv/version"
diff --git a/lib/csv/csv.gemspec b/lib/csv/csv.gemspec
index d0284728f78..11c5b0f2a6e 100644
--- a/lib/csv/csv.gemspec
+++ b/lib/csv/csv.gemspec
@@ -60,5 +60,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "bundler"
spec.add_development_dependency "rake"
spec.add_development_dependency "benchmark_driver"
- spec.add_development_dependency "simplecov"
+ spec.add_development_dependency "test-unit", ">= 3.4.8"
end
diff --git a/lib/csv/fields_converter.rb b/lib/csv/fields_converter.rb
index 178ffb37bc8..b206118d99b 100644
--- a/lib/csv/fields_converter.rb
+++ b/lib/csv/fields_converter.rb
@@ -16,7 +16,7 @@ class CSV
@empty_value = options[:empty_value]
@empty_value_is_empty_string = (@empty_value == "")
@accept_nil = options[:accept_nil]
- @builtin_converters = options[:builtin_converters]
+ @builtin_converters_name = options[:builtin_converters_name]
@need_static_convert = need_static_convert?
end
@@ -24,7 +24,7 @@ class CSV
if name.nil? # custom converter
@converters << converter
else # named converter
- combo = @builtin_converters[name]
+ combo = builtin_converters[name]
case combo
when Array # combo converter
combo.each do |sub_name|
@@ -80,5 +80,9 @@ class CSV
@need_static_convert or
(not @converters.empty?)
end
+
+ def builtin_converters
+ @builtin_converters ||= ::CSV.const_get(@builtin_converters_name)
+ end
end
end
diff --git a/lib/csv/input_record_separator.rb b/lib/csv/input_record_separator.rb
new file mode 100644
index 00000000000..bbf13479f78
--- /dev/null
+++ b/lib/csv/input_record_separator.rb
@@ -0,0 +1,31 @@
+require "English"
+require "stringio"
+
+class CSV
+ module InputRecordSeparator
+ class << self
+ is_input_record_separator_deprecated = false
+ verbose, $VERBOSE = $VERBOSE, true
+ stderr, $stderr = $stderr, StringIO.new
+ input_record_separator = $INPUT_RECORD_SEPARATOR
+ begin
+ $INPUT_RECORD_SEPARATOR = "\r\n"
+ is_input_record_separator_deprecated = (not $stderr.string.empty?)
+ ensure
+ $INPUT_RECORD_SEPARATOR = input_record_separator
+ $stderr = stderr
+ $VERBOSE = verbose
+ end
+
+ if is_input_record_separator_deprecated
+ def value
+ "\n"
+ end
+ else
+ def value
+ $INPUT_RECORD_SEPARATOR
+ end
+ end
+ end
+ end
+end
diff --git a/lib/csv/parser.rb b/lib/csv/parser.rb
index 2fb3b0a46ed..7e943acf214 100644
--- a/lib/csv/parser.rb
+++ b/lib/csv/parser.rb
@@ -3,6 +3,7 @@
require "strscan"
require_relative "delete_suffix"
+require_relative "input_record_separator"
require_relative "match_p"
require_relative "row"
require_relative "table"
@@ -84,9 +85,10 @@ class CSV
# If there is no more data (eos? = true), it returns "".
#
class InputsScanner
- def initialize(inputs, encoding, chunk_size: 8192)
+ def initialize(inputs, encoding, row_separator, chunk_size: 8192)
@inputs = inputs.dup
@encoding = encoding
+ @row_separator = row_separator
@chunk_size = chunk_size
@last_scanner = @inputs.empty?
@keeps = []
@@ -232,7 +234,7 @@ class CSV
@last_scanner = @inputs.empty?
true
else
- chunk = input.gets(nil, @chunk_size)
+ chunk = input.gets(@row_separator, @chunk_size)
if chunk
raise InvalidEncoding unless chunk.valid_encoding?
@scanner = StringScanner.new(chunk)
@@ -360,6 +362,7 @@ class CSV
prepare_skip_lines
prepare_strip
prepare_separators
+ validate_strip_and_col_sep_options
prepare_quoted
prepare_unquoted
prepare_line
@@ -479,9 +482,9 @@ class CSV
begin
StringScanner.new("x").scan("x")
rescue TypeError
- @@string_scanner_scan_accept_string = false
+ STRING_SCANNER_SCAN_ACCEPT_STRING = false
else
- @@string_scanner_scan_accept_string = true
+ STRING_SCANNER_SCAN_ACCEPT_STRING = true
end
def prepare_separators
@@ -505,7 +508,7 @@ class CSV
@first_column_separators = Regexp.new(@escaped_first_column_separator +
"+".encode(@encoding))
else
- if @@string_scanner_scan_accept_string
+ if STRING_SCANNER_SCAN_ACCEPT_STRING
@column_end = @column_separator
else
@column_end = Regexp.new(@escaped_column_separator)
@@ -526,10 +529,32 @@ class CSV
@cr = "\r".encode(@encoding)
@lf = "\n".encode(@encoding)
- @cr_or_lf = Regexp.new("[\r\n]".encode(@encoding))
+ @line_end = Regexp.new("\r\n|\n|\r".encode(@encoding))
@not_line_end = Regexp.new("[^\r\n]+".encode(@encoding))
end
+ # This method verifies that there are no (obvious) ambiguities with the
+ # provided +col_sep+ and +strip+ parsing options. For example, if +col_sep+
+ # and +strip+ were both equal to +\t+, then there would be no clear way to
+ # parse the input.
+ def validate_strip_and_col_sep_options
+ return unless @strip
+
+ if @strip.is_a?(String)
+ if @column_separator.start_with?(@strip) || @column_separator.end_with?(@strip)
+ raise ArgumentError,
+ "The provided strip (#{@escaped_strip}) and " \
+ "col_sep (#{@escaped_column_separator}) options are incompatible."
+ end
+ else
+ if Regexp.new("\\A[#{@escaped_strip}]|[#{@escaped_strip}]\\z").match?(@column_separator)
+ raise ArgumentError,
+ "The provided strip (true) and " \
+ "col_sep (#{@escaped_column_separator}) options are incompatible."
+ end
+ end
+ end
+
def prepare_quoted
if @quote_character
@quotes = Regexp.new(@escaped_quote_character +
@@ -605,7 +630,7 @@ class CSV
# do nothing: ensure will set default
end
end
- separator = $INPUT_RECORD_SEPARATOR if separator == :auto
+ separator = InputRecordSeparator.value if separator == :auto
end
separator.to_s.encode(@encoding)
end
@@ -724,6 +749,8 @@ class CSV
end
end
+ SCANNER_TEST_CHUNK_SIZE =
+ Integer((ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"), 10)
def build_scanner
inputs = @samples.collect do |sample|
UnoptimizedStringIO.new(sample)
@@ -733,17 +760,20 @@ class CSV
else
inputs << @input
end
- chunk_size = ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"
InputsScanner.new(inputs,
@encoding,
- chunk_size: Integer(chunk_size, 10))
+ @row_separator,
+ chunk_size: SCANNER_TEST_CHUNK_SIZE)
end
else
def build_scanner
string = nil
if @samples.empty? and @input.is_a?(StringIO)
string = @input.read
- elsif @samples.size == 1 and @input.respond_to?(:eof?) and @input.eof?
+ elsif @samples.size == 1 and
+ @input != ARGF and
+ @input.respond_to?(:eof?) and
+ @input.eof?
string = @samples[0]
end
if string
@@ -762,7 +792,7 @@ class CSV
StringIO.new(sample)
end
inputs << @input
- InputsScanner.new(inputs, @encoding)
+ InputsScanner.new(inputs, @encoding, @row_separator)
end
end
end
@@ -914,7 +944,7 @@ class CSV
message = "Any value after quoted field isn't allowed"
raise MalformedCSVError.new(message, @lineno)
elsif @unquoted_column_value and
- (new_line = @scanner.scan(@cr_or_lf))
+ (new_line = @scanner.scan(@line_end))
ignore_broken_line
message = "Unquoted fields do not allow new line " +
"<#{new_line.inspect}>"
@@ -923,7 +953,7 @@ class CSV
ignore_broken_line
message = "Illegal quoting"
raise MalformedCSVError.new(message, @lineno)
- elsif (new_line = @scanner.scan(@cr_or_lf))
+ elsif (new_line = @scanner.scan(@line_end))
ignore_broken_line
message = "New line must be <#{@row_separator.inspect}> " +
"not <#{new_line.inspect}>"
@@ -1089,7 +1119,7 @@ class CSV
def ignore_broken_line
@scanner.scan_all(@not_line_end)
- @scanner.scan_all(@cr_or_lf)
+ @scanner.scan_all(@line_end)
@lineno += 1
end
diff --git a/lib/csv/version.rb b/lib/csv/version.rb
index b509299d96e..d1d0dc0e020 100644
--- a/lib/csv/version.rb
+++ b/lib/csv/version.rb
@@ -2,5 +2,5 @@
class CSV
# The version of the installed library.
- VERSION = "3.2.0"
+ VERSION = "3.2.2"
end
diff --git a/lib/csv/writer.rb b/lib/csv/writer.rb
index d49115fccf8..4a9a35c5afc 100644
--- a/lib/csv/writer.rb
+++ b/lib/csv/writer.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require_relative "input_record_separator"
require_relative "match_p"
require_relative "row"
@@ -133,7 +134,7 @@ class CSV
@column_separator = @options[:column_separator].to_s.encode(@encoding)
row_separator = @options[:row_separator]
if row_separator == :auto
- @row_separator = $INPUT_RECORD_SEPARATOR.encode(@encoding)
+ @row_separator = InputRecordSeparator.value.encode(@encoding)
else
@row_separator = row_separator.to_s.encode(@encoding)
end
diff --git a/lib/delegate.rb b/lib/delegate.rb
index a5ae605e9ec..70d4e4ad1d0 100644
--- a/lib/delegate.rb
+++ b/lib/delegate.rb
@@ -44,7 +44,7 @@ class Delegator < BasicObject
kernel = ::Kernel.dup
kernel.class_eval do
alias __raise__ raise
- [:to_s, :inspect, :=~, :!~, :===, :<=>, :hash].each do |m|
+ [:to_s, :inspect, :!~, :===, :<=>, :hash].each do |m|
undef_method m
end
private_instance_methods.each do |m|
diff --git a/lib/delegate/delegate.gemspec b/lib/delegate/delegate.gemspec
index 05d9ced1754..1cfacfeb2fb 100644
--- a/lib/delegate/delegate.gemspec
+++ b/lib/delegate/delegate.gemspec
@@ -24,8 +24,6 @@ Gem::Specification.new do |spec|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z 2>/dev/null`.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"]
spec.required_ruby_version = '>= 2.7'
end
diff --git a/lib/did_you_mean.rb b/lib/did_you_mean.rb
index ab7e6b01a8e..2df238da06d 100644
--- a/lib/did_you_mean.rb
+++ b/lib/did_you_mean.rb
@@ -7,7 +7,8 @@ require_relative 'did_you_mean/spell_checkers/method_name_checker'
require_relative 'did_you_mean/spell_checkers/key_error_checker'
require_relative 'did_you_mean/spell_checkers/null_checker'
require_relative 'did_you_mean/spell_checkers/require_path_checker'
-require_relative 'did_you_mean/formatters/plain_formatter'
+require_relative 'did_you_mean/spell_checkers/pattern_key_name_checker'
+require_relative 'did_you_mean/formatter'
require_relative 'did_you_mean/tree_spell_checker'
# The +DidYouMean+ gem adds functionality to suggest possible method/class
@@ -85,28 +86,70 @@ require_relative 'did_you_mean/tree_spell_checker'
#
module DidYouMean
# Map of error types and spell checker objects.
- SPELL_CHECKERS = Hash.new(NullChecker)
+ @spell_checkers = Hash.new(NullChecker)
+
+ # Returns a sharable hash map of error types and spell checker objects.
+ def self.spell_checkers
+ @spell_checkers
+ end
# Adds +DidYouMean+ functionality to an error using a given spell checker
def self.correct_error(error_class, spell_checker)
- SPELL_CHECKERS[error_class.name] = spell_checker
- error_class.prepend(Correctable) unless error_class < Correctable
+ if defined?(Ractor)
+ new_mapping = { **@spell_checkers, error_class.to_s => spell_checker }
+ new_mapping.default = NullChecker
+
+ @spell_checkers = Ractor.make_shareable(new_mapping)
+ else
+ spell_checkers[error_class.to_s] = spell_checker
+ end
+
+ error_class.prepend(Correctable) if error_class.is_a?(Class) && !(error_class < Correctable)
end
correct_error NameError, NameErrorCheckers
correct_error KeyError, KeyErrorChecker
correct_error NoMethodError, MethodNameChecker
correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0'
+ correct_error NoMatchingPatternKeyError, PatternKeyNameChecker if defined?(::NoMatchingPatternKeyError)
+
+ # TODO: Remove on 3.3:
+ class DeprecatedMapping # :nodoc:
+ def []=(key, value)
+ warn "Calling `DidYouMean::SPELL_CHECKERS[#{key.to_s}] = #{value.to_s}' has been deprecated. " \
+ "Please call `DidYouMean.correct_error(#{key.to_s}, #{value.to_s})' instead."
+
+ DidYouMean.correct_error(key, value)
+ end
+
+ def merge!(hash)
+ warn "Calling `DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. " \
+ "Please call `DidYouMean.correct_error(error_name, spell_checker)' instead."
+
+ hash.each do |error_class, spell_checker|
+ DidYouMean.correct_error(error_class, spell_checker)
+ end
+ end
+ end
+
+ # TODO: Remove on 3.3:
+ SPELL_CHECKERS = DeprecatedMapping.new
+ deprecate_constant :SPELL_CHECKERS
+ private_constant :DeprecatedMapping
# Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+.
def self.formatter
- @@formatter
+ if defined?(Ractor)
+ Ractor.current[:__did_you_mean_formatter__] || Formatter
+ else
+ Formatter
+ end
end
# Updates the primary formatter used to format the suggestions.
def self.formatter=(formatter)
- @@formatter = formatter
+ if defined?(Ractor)
+ Ractor.current[:__did_you_mean_formatter__] = formatter
+ end
end
-
- self.formatter = PlainFormatter.new
end
diff --git a/lib/did_you_mean/core_ext/name_error.rb b/lib/did_you_mean/core_ext/name_error.rb
index 7e99282b41d..eb3ef117a04 100644
--- a/lib/did_you_mean/core_ext/name_error.rb
+++ b/lib/did_you_mean/core_ext/name_error.rb
@@ -26,7 +26,7 @@ module DidYouMean
end
def spell_checker
- SPELL_CHECKERS[self.class.to_s].new(self)
+ DidYouMean.spell_checkers[self.class.to_s].new(self)
end
end
end
diff --git a/lib/did_you_mean/formatter.rb b/lib/did_you_mean/formatter.rb
new file mode 100644
index 00000000000..c43748f707a
--- /dev/null
+++ b/lib/did_you_mean/formatter.rb
@@ -0,0 +1,44 @@
+# frozen-string-literal: true
+
+module DidYouMean
+ # The +DidYouMean::Formatter+ is the basic, default formatter for the
+ # gem. The formatter responds to the +message_for+ method and it returns a
+ # human readable string.
+ class Formatter
+
+ # Returns a human readable string that contains +corrections+. This
+ # formatter is designed to be less verbose to not take too much screen
+ # space while being helpful enough to the user.
+ #
+ # @example
+ #
+ # formatter = DidYouMean::Formatter.new
+ #
+ # # displays suggestions in two lines with the leading empty line
+ # puts formatter.message_for(["methods", "method"])
+ #
+ # Did you mean? methods
+ # method
+ # # => nil
+ #
+ # # displays an empty line
+ # puts formatter.message_for([])
+ #
+ # # => nil
+ #
+ def self.message_for(corrections)
+ corrections.empty? ? "" : "\nDid you mean? #{corrections.join("\n ")}"
+ end
+
+ def message_for(corrections)
+ warn "The instance method #message_for has been deprecated. Please use the class method " \
+ "DidYouMean::Formatter.message_for(...) instead."
+
+ self.class.message_for(corrections)
+ end
+ end
+
+ PlainFormatter = Formatter
+
+ deprecate_constant :PlainFormatter
+end
diff --git a/lib/did_you_mean/formatters/plain_formatter.rb b/lib/did_you_mean/formatters/plain_formatter.rb
index e2d995f5871..d669588e0f3 100644
--- a/lib/did_you_mean/formatters/plain_formatter.rb
+++ b/lib/did_you_mean/formatters/plain_formatter.rb
@@ -1,33 +1,4 @@
-# frozen-string-literal: true
+require_relative '../formatter'
-module DidYouMean
- # The +DidYouMean::PlainFormatter+ is the basic, default formatter for the
- # gem. The formatter responds to the +message_for+ method and it returns a
- # human readable string.
- class PlainFormatter
-
- # Returns a human readable string that contains +corrections+. This
- # formatter is designed to be less verbose to not take too much screen
- # space while being helpful enough to the user.
- #
- # @example
- #
- # formatter = DidYouMean::PlainFormatter.new
- #
- # # displays suggestions in two lines with the leading empty line
- # puts formatter.message_for(["methods", "method"])
- #
- # Did you mean? methods
- # method
- # # => nil
- #
- # # displays an empty line
- # puts formatter.message_for([])
- #
- # # => nil
- #
- def message_for(corrections)
- corrections.empty? ? "" : "\nDid you mean? #{corrections.join("\n ")}"
- end
- end
-end
+warn "`require 'did_you_mean/formatters/plain_formatter'` is deprecated. Please `require 'did_you_mean/formatter'` " \
+ "instead."
diff --git a/lib/did_you_mean/formatters/verbose_formatter.rb b/lib/did_you_mean/formatters/verbose_formatter.rb
index b8fe214d578..8ee98fa070e 100644
--- a/lib/did_you_mean/formatters/verbose_formatter.rb
+++ b/lib/did_you_mean/formatters/verbose_formatter.rb
@@ -1,49 +1,9 @@
-# frozen-string-literal: true
-
-module DidYouMean
- # The +DidYouMean::VerboseFormatter+ uses extra empty lines to make the
- # suggestion stand out more in the error message.
- #
- # In order to activate the verbose formatter,
- #
- # @example
- #
- # OBject
- # # => NameError: uninitialized constant OBject
- # # Did you mean? Object
- #
- # require 'did_you_mean/verbose'
- #
- # OBject
- # # => NameError: uninitialized constant OBject
- # #
- # # Did you mean? Object
- # #
- #
- class VerboseFormatter
+warn "`require 'did_you_mean/formatters/verbose_formatter'` is deprecated and falls back to the default formatter. "
- # Returns a human readable string that contains +corrections+. This
- # formatter is designed to be less verbose to not take too much screen
- # space while being helpful enough to the user.
- #
- # @example
- #
- # formatter = DidYouMean::PlainFormatter.new
- #
- # puts formatter.message_for(["methods", "method"])
- #
- #
- # Did you mean? methods
- # method
- #
- # # => nil
- #
- def message_for(corrections)
- return "" if corrections.empty?
+require_relative '../formatter'
- output = "\n\n Did you mean? ".dup
- output << corrections.join("\n ")
- output << "\n "
- end
- end
+# frozen-string-literal: true
+module DidYouMean
+ # For compatibility:
+ VerboseFormatter = Formatter
end
diff --git a/lib/did_you_mean/spell_checker.rb b/lib/did_you_mean/spell_checker.rb
index e5106abba2c..37da2fc7a6e 100644
--- a/lib/did_you_mean/spell_checker.rb
+++ b/lib/did_you_mean/spell_checker.rb
@@ -10,25 +10,25 @@ module DidYouMean
end
def correct(input)
- input = normalize(input)
- threshold = input.length > 3 ? 0.834 : 0.77
+ normalized_input = normalize(input)
+ threshold = normalized_input.length > 3 ? 0.834 : 0.77
- words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), input) >= threshold }
- words.reject! { |word| input == word.to_s }
- words.sort_by! { |word| JaroWinkler.distance(word.to_s, input) }
+ words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), normalized_input) >= threshold }
+ words.reject! { |word| input.to_s == word.to_s }
+ words.sort_by! { |word| JaroWinkler.distance(word.to_s, normalized_input) }
words.reverse!
# Correct mistypes
- threshold = (input.length * 0.25).ceil
- corrections = words.select { |c| Levenshtein.distance(normalize(c), input) <= threshold }
+ threshold = (normalized_input.length * 0.25).ceil
+ corrections = words.select { |c| Levenshtein.distance(normalize(c), normalized_input) <= threshold }
# Correct misspells
if corrections.empty?
corrections = words.select do |word|
word = normalize(word)
- length = input.length < word.length ? input.length : word.length
+ length = normalized_input.length < word.length ? normalized_input.length : word.length
- Levenshtein.distance(word, input) < length
+ Levenshtein.distance(word, normalized_input) < length
end.first(1)
end
diff --git a/lib/did_you_mean/spell_checkers/method_name_checker.rb b/lib/did_you_mean/spell_checkers/method_name_checker.rb
index 0483127d6f1..d8ebaa46168 100644
--- a/lib/did_you_mean/spell_checkers/method_name_checker.rb
+++ b/lib/did_you_mean/spell_checkers/method_name_checker.rb
@@ -6,6 +6,7 @@ module DidYouMean
NAMES_TO_EXCLUDE = { NilClass => nil.methods }
NAMES_TO_EXCLUDE.default = []
+ Ractor.make_shareable(NAMES_TO_EXCLUDE) if defined?(Ractor)
# +MethodNameChecker::RB_RESERVED_WORDS+ is the list of reserved words in
# Ruby that take an argument. Unlike
@@ -36,6 +37,8 @@ module DidYouMean
yield
)
+ Ractor.make_shareable(RB_RESERVED_WORDS) if defined?(Ractor)
+
def initialize(exception)
@method_name = exception.name
@receiver = exception.receiver
diff --git a/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb b/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb
index 3e51b4fa3ae..36d00349c67 100644
--- a/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb
+++ b/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb
@@ -8,6 +8,7 @@ module DidYouMean
NAMES_TO_EXCLUDE = { 'foo' => [:fork, :for] }
NAMES_TO_EXCLUDE.default = []
+ Ractor.make_shareable(NAMES_TO_EXCLUDE) if defined?(Ractor)
# +VariableNameChecker::RB_RESERVED_WORDS+ is the list of all reserved
# words in Ruby. They could be declared like methods are, and a typo would
@@ -62,6 +63,8 @@ module DidYouMean
__ENCODING__
)
+ Ractor.make_shareable(RB_RESERVED_WORDS) if defined?(Ractor)
+
def initialize(exception)
@name = exception.name.to_s.tr("@", "")
@lvar_names = exception.respond_to?(:local_variables) ? exception.local_variables : []
diff --git a/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb b/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb
new file mode 100644
index 00000000000..ed263c8f937
--- /dev/null
+++ b/lib/did_you_mean/spell_checkers/pattern_key_name_checker.rb
@@ -0,0 +1,20 @@
+require_relative "../spell_checker"
+
+module DidYouMean
+ class PatternKeyNameChecker
+ def initialize(no_matching_pattern_key_error)
+ @key = no_matching_pattern_key_error.key
+ @keys = no_matching_pattern_key_error.matchee.keys
+ end
+
+ def corrections
+ @corrections ||= exact_matches.empty? ? SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect) : exact_matches
+ end
+
+ private
+
+ def exact_matches
+ @exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect)
+ end
+ end
+end
diff --git a/lib/did_you_mean/spell_checkers/require_path_checker.rb b/lib/did_you_mean/spell_checkers/require_path_checker.rb
index e4cdb9f43d4..586ced37de2 100644
--- a/lib/did_you_mean/spell_checkers/require_path_checker.rb
+++ b/lib/did_you_mean/spell_checkers/require_path_checker.rb
@@ -9,7 +9,10 @@ module DidYouMean
attr_reader :path
INITIAL_LOAD_PATH = $LOAD_PATH.dup.freeze
- ENV_SPECIFIC_EXT = ".#{RbConfig::CONFIG["DLEXT"]}"
+ Ractor.make_shareable(INITIAL_LOAD_PATH) if defined?(Ractor)
+
+ ENV_SPECIFIC_EXT = ".#{RbConfig::CONFIG["DLEXT"]}"
+ Ractor.make_shareable(ENV_SPECIFIC_EXT) if defined?(Ractor)
private_constant :INITIAL_LOAD_PATH, :ENV_SPECIFIC_EXT
diff --git a/lib/did_you_mean/verbose.rb b/lib/did_you_mean/verbose.rb
index 4e86f167ea7..1ff19aef809 100644
--- a/lib/did_you_mean/verbose.rb
+++ b/lib/did_you_mean/verbose.rb
@@ -1,4 +1,2 @@
-require_relative '../did_you_mean'
-require_relative 'formatters/verbose_formatter'
-
-DidYouMean.formatter = DidYouMean::VerboseFormatter.new
+warn "The verbose formatter has been removed and now `require 'did_you_mean/verbose'` has no effect. Please " \
+ "remove this call."
diff --git a/lib/did_you_mean/version.rb b/lib/did_you_mean/version.rb
index 8df73e1d623..b5fe50b5ed1 100644
--- a/lib/did_you_mean/version.rb
+++ b/lib/did_you_mean/version.rb
@@ -1,3 +1,3 @@
module DidYouMean
- VERSION = "1.6.0-alpha"
+ VERSION = "1.6.1".freeze
end
diff --git a/lib/drb/drb.rb b/lib/drb/drb.rb
index 3e232139114..23181bb8342 100644
--- a/lib/drb/drb.rb
+++ b/lib/drb/drb.rb
@@ -1627,9 +1627,12 @@ module DRb
end
def perform
- @result = nil
- @succ = false
- setup_message
+ begin
+ setup_message
+ ensure
+ @result = nil
+ @succ = false
+ end
if @block
@result = perform_with_block
@@ -1718,6 +1721,7 @@ module DRb
client_uri = client.uri
@exported_uri << client_uri unless @exported_uri.include?(client_uri)
end
+ _last_invoke_method = nil
loop do
begin
succ = false
@@ -1730,6 +1734,7 @@ module DRb
rescue Exception => e
error_print(e) if verbose
ensure
+ _last_invoke_method = invoke_method
client.close unless succ
if Thread.current['DRb']['stop_service']
shutdown
diff --git a/lib/drb/extserv.rb b/lib/drb/extserv.rb
index a93d5d1576e..9523fe84e35 100644
--- a/lib/drb/extserv.rb
+++ b/lib/drb/extserv.rb
@@ -18,7 +18,7 @@ module DRb
@name = name
ro = DRbObject.new(nil, there)
synchronize do
- @invoker = ro.regist(name, DRbObject.new(self, @server.uri))
+ @invoker = ro.register(name, DRbObject.new(self, @server.uri))
end
end
attr_reader :server
@@ -29,7 +29,7 @@ module DRb
def stop_service
synchronize do
- @invoker.unregist(@name)
+ @invoker.unregister(@name)
server = @server
@server = nil
server.stop_service
diff --git a/lib/drb/extservm.rb b/lib/drb/extservm.rb
index 040e4e3e08e..9333a108e5d 100644
--- a/lib/drb/extservm.rb
+++ b/lib/drb/extservm.rb
@@ -44,19 +44,21 @@ module DRb
end
end
- def regist(name, ro)
+ def register(name, ro)
synchronize do
@servers[name] = ro
@cond.signal
end
self
end
+ alias regist register
- def unregist(name)
+ def unregister(name)
synchronize do
@servers.delete(name)
end
end
+ alias unregist unregister
private
def invoke_thread
diff --git a/lib/erb.rb b/lib/erb.rb
index 3045106c923..0e42425a605 100644
--- a/lib/erb.rb
+++ b/lib/erb.rb
@@ -724,7 +724,7 @@ class ERB
frozen = nil
s.scan(re) do
comment = $+
- comment = $1 if comment[/-\*-\s*(.*?)\s*-*-$/]
+ comment = $1 if comment[/-\*-\s*([^\s].*?)\s*-\*-$/]
case comment
when %r"coding\s*[=:]\s*([[:alnum:]\-_]+)"
enc = Encoding.find($1.sub(/-(?:mac|dos|unix)/i, ''))
diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb
index 1ae180aeac8..78cda8ace29 100644
--- a/lib/error_highlight/core_ext.rb
+++ b/lib/error_highlight/core_ext.rb
@@ -15,6 +15,7 @@ module ErrorHighlight
return msg unless locs
loc = locs.first
+ return msg unless loc
begin
node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
opts = {}
@@ -29,7 +30,9 @@ module ErrorHighlight
spot = ErrorHighlight.spot(node, **opts)
- rescue Errno::ENOENT, SyntaxError
+ rescue SyntaxError
+ rescue SystemCallError # file not found or something
+ rescue ArgumentError # eval'ed code
end
if spot
diff --git a/lib/error_highlight/formatter.rb b/lib/error_highlight/formatter.rb
index ce687fb2a28..20ca78d468a 100644
--- a/lib/error_highlight/formatter.rb
+++ b/lib/error_highlight/formatter.rb
@@ -1,6 +1,6 @@
module ErrorHighlight
class DefaultFormatter
- def message_for(spot)
+ def self.message_for(spot)
# currently only a one-line code snippet is supported
if spot[:first_lineno] == spot[:last_lineno]
indent = spot[:snippet][0...spot[:first_column]].gsub(/[^\t]/, " ")
@@ -14,12 +14,10 @@ module ErrorHighlight
end
def self.formatter
- @@formatter
+ Ractor.current[:__error_highlight_formatter__] || DefaultFormatter
end
def self.formatter=(formatter)
- @@formatter = formatter
+ Ractor.current[:__error_highlight_formatter__] = formatter
end
-
- self.formatter = DefaultFormatter.new
end
diff --git a/lib/error_highlight/version.rb b/lib/error_highlight/version.rb
index 0dda1ba375a..49a34502cb9 100644
--- a/lib/error_highlight/version.rb
+++ b/lib/error_highlight/version.rb
@@ -1,3 +1,3 @@
module ErrorHighlight
- VERSION = "0.2.0"
+ VERSION = "0.3.0"
end
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index 3333307e22a..c115005163b 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -102,7 +102,7 @@ end
# <tt>:verbose</tt> flags to methods in FileUtils.
#
module FileUtils
- VERSION = "1.5.0"
+ VERSION = "1.6.0"
def self.private_module_function(name) #:nodoc:
module_function name
diff --git a/lib/ipaddr.gemspec b/lib/ipaddr.gemspec
index 5187f3be11f..36e23000020 100644
--- a/lib/ipaddr.gemspec
+++ b/lib/ipaddr.gemspec
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Gem::Specification.new do |spec|
spec.name = "ipaddr"
- spec.version = "1.2.2"
+ spec.version = "1.2.3"
spec.authors = ["Akinori MUSHA", "Hajimu UMEMOTO"]
spec.email = ["knu@idaemons.org", "ume@mahoroba.org"]
diff --git a/lib/irb.rb b/lib/irb.rb
index 6b296448db6..6887d5596ec 100644
--- a/lib/irb.rb
+++ b/lib/irb.rb
@@ -62,6 +62,7 @@ require_relative "irb/easter-egg"
# -W[level=2] Same as `ruby -W`
# --context-mode n Set n[0-4] to method to create Binding Object,
# when new workspace was created
+# --extra-doc-dir Add an extra doc dir for the doc dialog
# --echo Show result (default)
# --noecho Don't show result
# --echo-on-assignment
@@ -823,17 +824,20 @@ module IRB
diff_size = output_width - Reline::Unicode.calculate_width(first_line, true)
if diff_size.positive? and output_width > winwidth
lines, _ = Reline::Unicode.split_by_width(first_line, winwidth - diff_size - 3)
- str = "%s...\e[0m" % lines.first
+ str = "%s..." % lines.first
+ str += "\e[0m" if @context.use_colorize
multiline_p = false
else
str = str.gsub(/(\A.*?\n).*/m, "\\1...")
+ str += "\e[0m" if @context.use_colorize
end
else
output_width = Reline::Unicode.calculate_width(@context.return_format % str, true)
diff_size = output_width - Reline::Unicode.calculate_width(str, true)
if diff_size.positive? and output_width > winwidth
lines, _ = Reline::Unicode.split_by_width(str, winwidth - diff_size - 3)
- str = "%s...\e[0m" % lines.first
+ str = "%s..." % lines.first
+ str += "\e[0m" if @context.use_colorize
end
end
end
diff --git a/lib/irb/cmd/info.rb b/lib/irb/cmd/info.rb
index 8ad0c2a438c..dd93352ff00 100644
--- a/lib/irb/cmd/info.rb
+++ b/lib/irb/cmd/info.rb
@@ -16,8 +16,9 @@ module IRB
str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n"
str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty?
str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty?
+ str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n"
if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
- codepage = `chcp`.sub(/.*: (\d+)\n/, '\1')
+ codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1')
str += "Code page: #{codepage}\n"
end
str
diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb
index dcba1d1c71e..8f203ef1250 100644
--- a/lib/irb/cmd/show_source.rb
+++ b/lib/irb/cmd/show_source.rb
@@ -64,7 +64,7 @@ module IRB
prev_tokens = []
# chunk with line number
- tokens.chunk { |tok| tok[0][0] }.each do |lnum, chunk|
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
code = lines[0..lnum].join
prev_tokens.concat chunk
continue = lex.process_continue(prev_tokens)
diff --git a/lib/irb/color.rb b/lib/irb/color.rb
index 40e9e04c975..f9204f4c984 100644
--- a/lib/irb/color.rb
+++ b/lib/irb/color.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'reline'
require 'ripper'
-require 'irb/ruby-lex'
+require_relative 'ruby-lex'
module IRB # :nodoc:
module Color
diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb
index 30c68257504..78f0b515203 100644
--- a/lib/irb/color_printer.rb
+++ b/lib/irb/color_printer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'pp'
-require 'irb/color'
+require_relative 'color'
module IRB
class ColorPrinter < ::PP
diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb
index 339e9e60840..d6fb19038d7 100644
--- a/lib/irb/extend-command.rb
+++ b/lib/irb/extend-command.rb
@@ -47,7 +47,7 @@ module IRB # :nodoc:
@EXTEND_COMMANDS = [
[
- :irb_current_working_workspace, :CurrentWorkingWorkspace, "irb/cmd/chws",
+ :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws",
[:irb_print_working_workspace, OVERRIDE_ALL],
[:irb_cwws, OVERRIDE_ALL],
[:irb_pwws, OVERRIDE_ALL],
@@ -59,7 +59,7 @@ module IRB # :nodoc:
[:irb_pwb, OVERRIDE_ALL],
],
[
- :irb_change_workspace, :ChangeWorkspace, "irb/cmd/chws",
+ :irb_change_workspace, :ChangeWorkspace, "cmd/chws",
[:irb_chws, OVERRIDE_ALL],
[:irb_cws, OVERRIDE_ALL],
[:chws, NO_OVERRIDE],
@@ -70,13 +70,13 @@ module IRB # :nodoc:
],
[
- :irb_workspaces, :Workspaces, "irb/cmd/pushws",
+ :irb_workspaces, :Workspaces, "cmd/pushws",
[:workspaces, NO_OVERRIDE],
[:irb_bindings, OVERRIDE_ALL],
[:bindings, NO_OVERRIDE],
],
[
- :irb_push_workspace, :PushWorkspace, "irb/cmd/pushws",
+ :irb_push_workspace, :PushWorkspace, "cmd/pushws",
[:irb_pushws, OVERRIDE_ALL],
[:pushws, NO_OVERRIDE],
[:irb_push_binding, OVERRIDE_ALL],
@@ -84,7 +84,7 @@ module IRB # :nodoc:
[:pushb, NO_OVERRIDE],
],
[
- :irb_pop_workspace, :PopWorkspace, "irb/cmd/pushws",
+ :irb_pop_workspace, :PopWorkspace, "cmd/pushws",
[:irb_popws, OVERRIDE_ALL],
[:popws, NO_OVERRIDE],
[:irb_pop_binding, OVERRIDE_ALL],
@@ -93,55 +93,55 @@ module IRB # :nodoc:
],
[
- :irb_load, :Load, "irb/cmd/load"],
+ :irb_load, :Load, "cmd/load"],
[
- :irb_require, :Require, "irb/cmd/load"],
+ :irb_require, :Require, "cmd/load"],
[
- :irb_source, :Source, "irb/cmd/load",
+ :irb_source, :Source, "cmd/load",
[:source, NO_OVERRIDE],
],
[
- :irb, :IrbCommand, "irb/cmd/subirb"],
+ :irb, :IrbCommand, "cmd/subirb"],
[
- :irb_jobs, :Jobs, "irb/cmd/subirb",
+ :irb_jobs, :Jobs, "cmd/subirb",
[:jobs, NO_OVERRIDE],
],
[
- :irb_fg, :Foreground, "irb/cmd/subirb",
+ :irb_fg, :Foreground, "cmd/subirb",
[:fg, NO_OVERRIDE],
],
[
- :irb_kill, :Kill, "irb/cmd/subirb",
+ :irb_kill, :Kill, "cmd/subirb",
[:kill, OVERRIDE_PRIVATE_ONLY],
],
[
- :irb_help, :Help, "irb/cmd/help",
+ :irb_help, :Help, "cmd/help",
[:help, NO_OVERRIDE],
],
[
- :irb_info, :Info, "irb/cmd/info"
+ :irb_info, :Info, "cmd/info"
],
[
- :irb_ls, :Ls, "irb/cmd/ls",
+ :irb_ls, :Ls, "cmd/ls",
[:ls, NO_OVERRIDE],
],
[
- :irb_measure, :Measure, "irb/cmd/measure",
+ :irb_measure, :Measure, "cmd/measure",
[:measure, NO_OVERRIDE],
],
[
- :irb_show_source, :ShowSource, "irb/cmd/show_source",
+ :irb_show_source, :ShowSource, "cmd/show_source",
[:show_source, NO_OVERRIDE],
],
[
- :irb_whereami, :Whereami, "irb/cmd/whereami",
+ :irb_whereami, :Whereami, "cmd/whereami",
[:whereami, NO_OVERRIDE],
],
@@ -187,7 +187,7 @@ module IRB # :nodoc:
kwargs = ", **kwargs" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0"
line = __LINE__; eval %[
def #{cmd_name}(*opts#{kwargs}, &b)
- require "#{load_file}"
+ require_relative "#{load_file}"
arity = ExtendCommand::#{cmd_class}.instance_method(:execute).arity
args = (1..(arity < 0 ? ~arity : arity)).map {|i| "arg" + i.to_s }
args << "*opts#{kwargs}" if arity < 0
@@ -262,10 +262,10 @@ module IRB # :nodoc:
CE = ContextExtender # :nodoc:
@EXTEND_COMMANDS = [
- [:eval_history=, "irb/ext/history.rb"],
- [:use_tracer=, "irb/ext/tracer.rb"],
- [:use_loader=, "irb/ext/use-loader.rb"],
- [:save_history=, "irb/ext/save-history.rb"],
+ [:eval_history=, "ext/history.rb"],
+ [:use_tracer=, "ext/tracer.rb"],
+ [:use_loader=, "ext/use-loader.rb"],
+ [:save_history=, "ext/save-history.rb"],
]
# Installs the default context extensions as irb commands:
@@ -288,7 +288,7 @@ module IRB # :nodoc:
line = __LINE__; Context.module_eval %[
def #{cmd_name}(*opts, &b)
Context.module_eval {remove_method(:#{cmd_name})}
- require "#{load_file}"
+ require_relative "#{load_file}"
__send__ :#{cmd_name}, *opts, &b
end
for ali in aliases
diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb
index dc95065f975..64276e61be2 100644
--- a/lib/irb/input-method.rb
+++ b/lib/irb/input-method.rb
@@ -84,8 +84,7 @@ module IRB
#
# See IO#eof? for more information.
def eof?
- rs, = IO.select([@stdin], [], [], 0.00001)
- if rs and rs[0]
+ if @stdin.wait_readable(0.00001)
c = @stdin.getc
result = c.nil? ? true : false
@stdin.ungetc(c) unless c.nil?
@@ -318,7 +317,9 @@ module IRB
dialog.trap_key = nil
alt_d = [
[Reline::Key.new(nil, 0xE4, true)], # Normal Alt+d.
- [195, 164] # The "ä" that appears when Alt+d is pressed on xterm.
+ [27, 100], # Normal Alt+d when convert-meta isn't used.
+ [195, 164], # The "ä" that appears when Alt+d is pressed on xterm.
+ [226, 136, 130] # The "∂" that appears when Alt+d in pressed on iTerm2.
]
if just_cursor_moving and completion_journey_data.nil?
diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb
index c2f3b605dbd..8c37c0f1741 100644
--- a/lib/irb/inspector.rb
+++ b/lib/irb/inspector.rb
@@ -114,7 +114,7 @@ module IRB # :nodoc:
end
result
}
- Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require "irb/color_printer"}){|v|
+ Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v|
if IRB.conf[:MAIN_CONTEXT]&.use_colorize?
IRB::ColorPrinter.pp(v, '').chomp
else
diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec
index 594fae7ff63..26d0fb018fa 100644
--- a/lib/irb/irb.gemspec
+++ b/lib/irb/irb.gemspec
@@ -36,5 +36,5 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = Gem::Requirement.new(">= 2.5")
- spec.add_dependency "reline", ">= 0.2.8.pre.11"
+ spec.add_dependency "reline", ">= 0.3.0"
end
diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb
index d7ac17bd790..29862f5507e 100644
--- a/lib/irb/ruby-lex.rb
+++ b/lib/irb/ruby-lex.rb
@@ -87,8 +87,8 @@ class RubyLex
tokens.each do |t|
partial_tokens << t
unprocessed_tokens << t
- if t[2].include?("\n")
- t_str = t[2]
+ if t.tok.include?("\n")
+ t_str = t.tok
t_str.each_line("\n") do |s|
code << s << "\n"
ltype, indent, continue, code_block_open = check_state(code, partial_tokens, context: context)
@@ -97,9 +97,10 @@ class RubyLex
end
unprocessed_tokens = []
else
- code << t[2]
+ code << t.tok
end
end
+
unless unprocessed_tokens.empty?
ltype, indent, continue, code_block_open = check_state(code, unprocessed_tokens, context: context)
result << @prompt.call(ltype, indent, continue || code_block_open, @line_no + line_num_offset)
@@ -107,6 +108,7 @@ class RubyLex
result
end
end
+
if p.respond_to?(:call)
@input = p
elsif block_given?
@@ -153,19 +155,19 @@ class RubyLex
pos_to_index = {}
lexer.scan.each do |t|
next if t.pos.first == 0
- if pos_to_index.has_key?(t[0])
- index = pos_to_index[t[0]]
+ if pos_to_index.has_key?(t.pos)
+ index = pos_to_index[t.pos]
found_tk = tokens[index]
- if ERROR_TOKENS.include?(found_tk[1]) && !ERROR_TOKENS.include?(t[1])
+ if ERROR_TOKENS.include?(found_tk.event) && !ERROR_TOKENS.include?(t.event)
tokens[index] = t
end
else
- pos_to_index[t[0]] = tokens.size
+ pos_to_index[t.pos] = tokens.size
tokens << t
end
end
else
- tokens = lexer.parse
+ tokens = lexer.parse.reject { |it| it.pos.first == 0 }
end
end
tokens
@@ -175,17 +177,17 @@ class RubyLex
def find_prev_spaces(line_index)
return 0 if @tokens.size == 0
- md = @tokens[0][2].match(/(\A +)/)
+ md = @tokens[0].tok.match(/(\A +)/)
prev_spaces = md.nil? ? 0 : md[1].count(' ')
line_count = 0
@tokens.each_with_index do |t, i|
- if t[2].include?("\n")
- line_count += t[2].count("\n")
+ if t.tok.include?("\n")
+ line_count += t.tok.count("\n")
if line_count >= line_index
return prev_spaces
end
if (@tokens.size - 1) > i
- md = @tokens[i + 1][2].match(/(\A +)/)
+ md = @tokens[i + 1].tok.match(/(\A +)/)
prev_spaces = md.nil? ? 0 : md[1].count(' ')
end
end
@@ -295,18 +297,18 @@ class RubyLex
def process_continue(tokens = @tokens)
# last token is always newline
- if tokens.size >= 2 and tokens[-2][1] == :on_regexp_end
+ if tokens.size >= 2 and tokens[-2].event == :on_regexp_end
# end of regexp literal
return false
- elsif tokens.size >= 2 and tokens[-2][1] == :on_semicolon
+ elsif tokens.size >= 2 and tokens[-2].event == :on_semicolon
return false
- elsif tokens.size >= 2 and tokens[-2][1] == :on_kw and ['begin', 'else', 'ensure'].include?(tokens[-2][2])
+ elsif tokens.size >= 2 and tokens[-2].event == :on_kw and ['begin', 'else', 'ensure'].include?(tokens[-2].tok)
return false
- elsif !tokens.empty? and tokens.last[2] == "\\\n"
+ elsif !tokens.empty? and tokens.last.tok == "\\\n"
return true
- elsif tokens.size >= 1 and tokens[-1][1] == :on_heredoc_end # "EOH\n"
+ elsif tokens.size >= 1 and tokens[-1].event == :on_heredoc_end # "EOH\n"
return false
- elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2][3].anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) and tokens[-2][2] !~ /\A\.\.\.?\z/
+ elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2].state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) and tokens[-2].tok !~ /\A\.\.\.?\z/
# end of literal except for regexp
# endless range at end of line is not a continue
return true
@@ -316,7 +318,7 @@ class RubyLex
def check_code_block(code, tokens = @tokens)
return true if tokens.empty?
- if tokens.last[1] == :on_heredoc_beg
+ if tokens.last.event == :on_heredoc_beg
return true
end
@@ -388,7 +390,7 @@ class RubyLex
end
if defined?(Ripper::EXPR_BEG)
- last_lex_state = tokens.last[3]
+ last_lex_state = tokens.last.state
if last_lex_state.allbits?(Ripper::EXPR_BEG)
return false
elsif last_lex_state.allbits?(Ripper::EXPR_DOT)
@@ -413,14 +415,14 @@ class RubyLex
tokens.each_with_index { |t, index|
# detecting one-liner method definition
if in_oneliner_def.nil?
- if t[3].allbits?(Ripper::EXPR_ENDFN)
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
in_oneliner_def = :ENDFN
end
else
- if t[3].allbits?(Ripper::EXPR_ENDFN)
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
# continuing
- elsif t[3].allbits?(Ripper::EXPR_BEG)
- if t[2] == '='
+ elsif t.state.allbits?(Ripper::EXPR_BEG)
+ if t.tok == '='
in_oneliner_def = :BODY
end
else
@@ -432,14 +434,14 @@ class RubyLex
end
end
- case t[1]
+ case t.event
when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
indent += 1
when :on_rbracket, :on_rbrace, :on_rparen
indent -= 1
when :on_kw
- next if index > 0 and tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
- case t[2]
+ next if index > 0 and tokens[index - 1].state.allbits?(Ripper::EXPR_FNAME)
+ case t.tok
when 'do'
syntax_of_do = take_corresponding_syntax_to_kw_do(tokens, index)
indent += 1 if syntax_of_do == :method_calling
@@ -447,7 +449,7 @@ class RubyLex
indent += 1
when 'if', 'unless', 'while', 'until'
# postfix if/unless/while/until must be Ripper::EXPR_LABEL
- indent += 1 unless t[3].allbits?(Ripper::EXPR_LABEL)
+ indent += 1 unless t.state.allbits?(Ripper::EXPR_LABEL)
when 'end'
indent -= 1
end
@@ -459,14 +461,14 @@ class RubyLex
def is_method_calling?(tokens, index)
tk = tokens[index]
- if tk[3].anybits?(Ripper::EXPR_CMDARG) and tk[1] == :on_ident
+ if tk.state.anybits?(Ripper::EXPR_CMDARG) and tk.event == :on_ident
# The target method call to pass the block with "do".
return true
- elsif tk[3].anybits?(Ripper::EXPR_ARG) and tk[1] == :on_ident
- non_sp_index = tokens[0..(index - 1)].rindex{ |t| t[1] != :on_sp }
+ elsif tk.state.anybits?(Ripper::EXPR_ARG) and tk.event == :on_ident
+ non_sp_index = tokens[0..(index - 1)].rindex{ |t| t.event != :on_sp }
if non_sp_index
prev_tk = tokens[non_sp_index]
- if prev_tk[3].anybits?(Ripper::EXPR_DOT) and prev_tk[1] == :on_period
+ if prev_tk.state.anybits?(Ripper::EXPR_DOT) and prev_tk.event == :on_period
# The target method call with receiver to pass the block with "do".
return true
end
@@ -481,17 +483,17 @@ class RubyLex
index.downto(0) do |i|
tk = tokens[i]
# In "continue", the token isn't the corresponding syntax to "do".
- non_sp_index = tokens[0..(i - 1)].rindex{ |t| t[1] != :on_sp }
+ non_sp_index = tokens[0..(i - 1)].rindex{ |t| t.event != :on_sp }
first_in_fomula = false
if non_sp_index.nil?
first_in_fomula = true
- elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index][1])
+ elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index].event)
first_in_fomula = true
end
if is_method_calling?(tokens, i)
syntax_of_do = :method_calling
break if first_in_fomula
- elsif tk[1] == :on_kw && %w{while until for}.include?(tk[2])
+ elsif tk.event == :on_kw && %w{while until for}.include?(tk.tok)
# A loop syntax in front of "do" found.
#
# while cond do # also "until" or "for"
@@ -512,14 +514,14 @@ class RubyLex
index.downto(0) do |i|
tk = tokens[i]
# In "continue", the token isn't the corresponding syntax to "do".
- non_sp_index = tokens[0..(i - 1)].rindex{ |t| t[1] != :on_sp }
+ non_sp_index = tokens[0..(i - 1)].rindex{ |t| t.event != :on_sp }
first_in_fomula = false
if non_sp_index.nil?
first_in_fomula = true
- elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index][1])
+ elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index].event)
first_in_fomula = true
end
- if tk[1] == :on_kw && tk[2] == 'for'
+ if tk.event == :on_kw && tk.tok == 'for'
# A loop syntax in front of "do" found.
#
# while cond do # also "until" or "for"
@@ -541,14 +543,14 @@ class RubyLex
@tokens.each_with_index do |t, index|
# detecting one-liner method definition
if in_oneliner_def.nil?
- if t[3].allbits?(Ripper::EXPR_ENDFN)
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
in_oneliner_def = :ENDFN
end
else
- if t[3].allbits?(Ripper::EXPR_ENDFN)
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
# continuing
- elsif t[3].allbits?(Ripper::EXPR_BEG)
- if t[2] == '='
+ elsif t.state.allbits?(Ripper::EXPR_BEG)
+ if t.tok == '='
in_oneliner_def = :BODY
end
else
@@ -560,7 +562,7 @@ class RubyLex
end
end
- case t[1]
+ case t.event
when :on_ignored_nl, :on_nl, :on_comment
if index != (@tokens.size - 1) and in_oneliner_def != :BODY
depth_difference = 0
@@ -570,15 +572,16 @@ class RubyLex
when :on_sp
next
end
- case t[1]
+
+ case t.event
when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
depth_difference += 1
open_brace_on_line += 1
when :on_rbracket, :on_rbrace, :on_rparen
depth_difference -= 1 if open_brace_on_line > 0
when :on_kw
- next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
- case t[2]
+ next if index > 0 and @tokens[index - 1].state.allbits?(Ripper::EXPR_FNAME)
+ case t.tok
when 'do'
syntax_of_do = take_corresponding_syntax_to_kw_do(@tokens, index)
depth_difference += 1 if syntax_of_do == :method_calling
@@ -586,7 +589,7 @@ class RubyLex
depth_difference += 1
when 'if', 'unless', 'while', 'until', 'rescue'
# postfix if/unless/while/until/rescue must be Ripper::EXPR_LABEL
- unless t[3].allbits?(Ripper::EXPR_LABEL)
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
depth_difference += 1
end
when 'else', 'elsif', 'ensure', 'when'
@@ -619,14 +622,14 @@ class RubyLex
@tokens.each_with_index do |t, index|
# detecting one-liner method definition
if in_oneliner_def.nil?
- if t[3].allbits?(Ripper::EXPR_ENDFN)
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
in_oneliner_def = :ENDFN
end
else
- if t[3].allbits?(Ripper::EXPR_ENDFN)
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
# continuing
- elsif t[3].allbits?(Ripper::EXPR_BEG)
- if t[2] == '='
+ elsif t.state.allbits?(Ripper::EXPR_BEG)
+ if t.tok == '='
in_oneliner_def = :BODY
end
else
@@ -643,7 +646,7 @@ class RubyLex
end
end
- case t[1]
+ case t.event
when :on_ignored_nl, :on_nl, :on_comment
if in_oneliner_def != :BODY
corresponding_token_depth = nil
@@ -654,11 +657,12 @@ class RubyLex
end
next
when :on_sp
- spaces_at_line_head = t[2].count(' ') if is_first_spaces_of_line
+ spaces_at_line_head = t.tok.count(' ') if is_first_spaces_of_line
is_first_spaces_of_line = false
next
end
- case t[1]
+
+ case t.event
when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
spaces_of_nest.push(spaces_at_line_head + open_brace_on_line * 2)
open_brace_on_line += 1
@@ -671,8 +675,8 @@ class RubyLex
end
open_brace_on_line -= 1
when :on_kw
- next if index > 0 and @tokens[index - 1][3].allbits?(Ripper::EXPR_FNAME)
- case t[2]
+ next if index > 0 and @tokens[index - 1].state.allbits?(Ripper::EXPR_FNAME)
+ case t.tok
when 'do'
syntax_of_do = take_corresponding_syntax_to_kw_do(@tokens, index)
if syntax_of_do == :method_calling
@@ -681,16 +685,20 @@ class RubyLex
when 'def', 'case', 'for', 'begin', 'class', 'module'
spaces_of_nest.push(spaces_at_line_head)
when 'rescue'
- unless t[3].allbits?(Ripper::EXPR_LABEL)
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
corresponding_token_depth = spaces_of_nest.last
end
when 'if', 'unless', 'while', 'until'
# postfix if/unless/while/until must be Ripper::EXPR_LABEL
- unless t[3].allbits?(Ripper::EXPR_LABEL)
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
spaces_of_nest.push(spaces_at_line_head)
end
- when 'else', 'elsif', 'ensure', 'when', 'in'
+ when 'else', 'elsif', 'ensure', 'when'
corresponding_token_depth = spaces_of_nest.last
+ when 'in'
+ if in_keyword_case_scope?
+ corresponding_token_depth = spaces_of_nest.last
+ end
when 'end'
if is_first_printable_of_line
corresponding_token_depth = spaces_of_nest.pop
@@ -712,7 +720,7 @@ class RubyLex
end_type = []
while i < tokens.size
t = tokens[i]
- case t[1]
+ case t.event
when *end_type.last
start_token.pop
end_type.pop
@@ -725,7 +733,7 @@ class RubyLex
when :on_symbeg
acceptable_single_tokens = %i{on_ident on_const on_op on_cvar on_ivar on_gvar on_kw on_int on_backtick}
if (i + 1) < tokens.size
- if acceptable_single_tokens.all?{ |st| tokens[i + 1][1] != st }
+ if acceptable_single_tokens.all?{ |st| tokens[i + 1].event != st }
start_token << t
end_type << :on_tstring_end
else
@@ -744,14 +752,16 @@ class RubyLex
end
i += 1
end
- start_token.last.nil? ? '' : start_token.last
+ start_token.last.nil? ? nil : start_token.last
end
def process_literal_type(tokens = @tokens)
start_token = check_string_literal(tokens)
- case start_token[1]
+ return nil if start_token == ""
+
+ case start_token&.event
when :on_tstring_beg
- case start_token[2]
+ case start_token&.tok
when ?" then ?"
when /^%.$/ then ?"
when /^%Q.$/ then ?"
@@ -766,7 +776,7 @@ class RubyLex
when :on_qsymbols_beg then ?]
when :on_symbols_beg then ?]
when :on_heredoc_beg
- start_token[2] =~ /<<[-~]?(['"`])[_a-zA-Z0-9]+\1/
+ start_token&.tok =~ /<<[-~]?(['"`])[_a-zA-Z0-9]+\1/
case $1
when ?" then ?"
when ?' then ?'
@@ -794,6 +804,7 @@ class RubyLex
false
end
end
+
if index
first_token = nil
last_line_tokens = tokens[(index + 1)..(tokens.size - 1)]
@@ -803,6 +814,7 @@ class RubyLex
break
end
end
+
if first_token.nil?
return false
elsif first_token && first_token.state == Ripper::EXPR_DOT
@@ -829,5 +841,21 @@ class RubyLex
heredoc_tokens = @tokens.select { |t| [:on_heredoc_beg, :on_heredoc_end].include?(t.event) }
heredoc_tokens[-1]&.event == :on_heredoc_beg
end
+
+ def in_keyword_case_scope?
+ kw_tokens = @tokens.select { |t| t.event == :on_kw && ['case', 'for', 'end'].include?(t.tok) }
+ counter = 0
+ kw_tokens.reverse.each do |t|
+ if t.tok == 'case'
+ return true if counter.zero?
+ counter += 1
+ elsif t.tok == 'for'
+ counter += 1
+ elsif t.tok == 'end'
+ counter -= 1
+ end
+ end
+ false
+ end
end
# :startdoc:
diff --git a/lib/irb/version.rb b/lib/irb/version.rb
index df58edd4a57..481d14ffd2d 100644
--- a/lib/irb/version.rb
+++ b/lib/irb/version.rb
@@ -11,7 +11,7 @@
#
module IRB # :nodoc:
- VERSION = "1.3.8.pre.11"
+ VERSION = "1.4.1"
@RELEASE_VERSION = VERSION
- @LAST_UPDATE_DATE = "2021-10-09"
+ @LAST_UPDATE_DATE = "2021-12-25"
end
diff --git a/lib/logger/formatter.rb b/lib/logger/formatter.rb
index 6a135b6fabb..62bff7097aa 100644
--- a/lib/logger/formatter.rb
+++ b/lib/logger/formatter.rb
@@ -3,7 +3,8 @@
class Logger
# Default formatter for log messages.
class Formatter
- Format = "%s, [%s#%d] %5s -- %s: %s\n"
+ Format = "%s, [%s #%d] %5s -- %s: %s\n"
+ DatetimeFormat = "%Y-%m-%dT%H:%M:%S.%6N"
attr_accessor :datetime_format
@@ -19,7 +20,7 @@ class Logger
private
def format_datetime(time)
- time.strftime(@datetime_format || "%Y-%m-%dT%H:%M:%S.%6N ")
+ time.strftime(@datetime_format || DatetimeFormat)
end
def msg2str(msg)
diff --git a/lib/logger/version.rb b/lib/logger/version.rb
index e0f71830d21..3cc001aa884 100644
--- a/lib/logger/version.rb
+++ b/lib/logger/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class Logger
- VERSION = "1.4.3"
+ VERSION = "1.5.0"
end
diff --git a/lib/mkmf.rb b/lib/mkmf.rb
index c9eac567026..118b81314c9 100644
--- a/lib/mkmf.rb
+++ b/lib/mkmf.rb
@@ -285,7 +285,7 @@ MESSAGE
def split_libs(*strs)
sep = $mswin ? /\s+/ : /\s+(?=-|\z)/
- strs.map {|s| s.lstrip.split(sep)}.flatten
+ strs.flat_map {|s| s.lstrip.split(sep)}
end
def merge_libs(*libs)
@@ -386,38 +386,64 @@ MESSAGE
end
end
- def xsystem command, opts = nil
+ def expand_command(commands, envs = libpath_env)
varpat = /\$\((\w+)\)|\$\{(\w+)\}/
- if varpat =~ command
- vars = Hash.new {|h, k| h[k] = ENV[k]}
- command = command.dup
- nil while command.gsub!(varpat) {vars[$1||$2]}
+ vars = nil
+ expand = proc do |command|
+ case command
+ when Array
+ command.map(&expand)
+ when String
+ if varpat =~ command
+ vars ||= Hash.new {|h, k| h[k] = ENV[k]}
+ command = command.dup
+ nil while command.gsub!(varpat) {vars[$1||$2]}
+ end
+ command
+ else
+ command
+ end
+ end
+ if Array === commands
+ env, *commands = commands if Hash === commands.first
+ envs.merge!(env) if env
end
+ return envs, expand[commands]
+ end
+
+ def env_quote(envs)
+ envs.map {|e, v| "#{e}=#{v.quote}"}
+ end
+
+ def xsystem command, opts = nil
+ env, command = expand_command(command)
Logging::open do
- puts command.quote
+ puts [env_quote(env), command.quote].join(' ')
if opts and opts[:werror]
result = nil
Logging.postpone do |log|
- output = IO.popen(libpath_env, command, &:read)
+ output = IO.popen(env, command, &:read)
result = ($?.success? and File.zero?(log.path))
output
end
result
else
- system(libpath_env, command)
+ system(env, *command)
end
end
end
def xpopen command, *mode, &block
+ env, commands = expand_command(command)
+ command = [env_quote(env), command].join(' ')
Logging::open do
case mode[0]
- when nil, /^r/
+ when nil, Hash, /^r/
puts "#{command} |"
else
puts "| #{command}"
end
- IO.popen(libpath_env, command, *mode, &block)
+ IO.popen(env, commands, *mode, &block)
end
end
@@ -1057,7 +1083,7 @@ SRC
def find_library(lib, func, *paths, &b)
dir_config(lib)
lib = with_config(lib+'lib', lib)
- paths = paths.collect {|path| path.split(File::PATH_SEPARATOR)}.flatten
+ paths = paths.flat_map {|path| path.split(File::PATH_SEPARATOR)}
checking_for checking_message(func && func.funcall_style, LIBARG%lib) do
libpath = $LIBPATH
libs = append_library($libs, lib)
@@ -1830,16 +1856,24 @@ SRC
# invoked with the option and a stripped output string is returned
# without modifying any of the global values mentioned above.
def pkg_config(pkg, option=nil)
+ _, ldir = dir_config(pkg)
+ if ldir
+ pkg_config_path = "#{ldir}/pkgconfig"
+ if File.directory?(pkg_config_path)
+ Logging.message("PKG_CONFIG_PATH = %s\n", pkg_config_path)
+ envs = ["PKG_CONFIG_PATH"=>[pkg_config_path, ENV["PKG_CONFIG_PATH"]].compact.join(File::PATH_SEPARATOR)]
+ end
+ end
if pkgconfig = with_config("#{pkg}-config") and find_executable0(pkgconfig)
# if and only if package specific config command is given
elsif ($PKGCONFIG ||=
(pkgconfig = with_config("pkg-config", ("pkg-config" unless CROSS_COMPILING))) &&
find_executable0(pkgconfig) && pkgconfig) and
- xsystem("#{$PKGCONFIG} --exists #{pkg}")
+ xsystem([*envs, $PKGCONFIG, "--exists", pkg])
# default to pkg-config command
pkgconfig = $PKGCONFIG
get = proc {|opt|
- opt = xpopen("#{$PKGCONFIG} --#{opt} #{pkg}", err:[:child, :out], &:read)
+ opt = xpopen([*envs, $PKGCONFIG, "--#{opt}", pkg], err:[:child, :out], &:read)
Logging.open {puts opt.each_line.map{|s|"=> #{s.inspect}"}}
opt.strip if $?.success?
}
@@ -1850,7 +1884,7 @@ SRC
end
if pkgconfig
get ||= proc {|opt|
- opt = xpopen("#{pkgconfig} --#{opt}", err:[:child, :out], &:read)
+ opt = xpopen([*envs, pkgconfig, "--#{opt}"], err:[:child, :out], &:read)
Logging.open {puts opt.each_line.map{|s|"=> #{s.inspect}"}}
opt.strip if $?.success?
}
diff --git a/lib/mutex_m.rb b/lib/mutex_m.rb
index 706d3c1b3c6..abd0fc6add3 100644
--- a/lib/mutex_m.rb
+++ b/lib/mutex_m.rb
@@ -41,6 +41,7 @@
module Mutex_m
VERSION = "0.1.1"
+ Ractor.make_shareable(VERSION) if defined?(Ractor)
def Mutex_m.define_aliases(cl) # :nodoc:
cl.module_eval %q{
diff --git a/lib/net/http.rb b/lib/net/http.rb
index 1fcf13530de..1e7c91298e9 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -132,7 +132,7 @@ module Net #:nodoc:
# puts res.class.name # => 'HTTPOK'
#
# # Body
- # puts res.body if res.response_body_permitted?
+ # puts res.body
#
# === Following Redirection
#
@@ -327,6 +327,8 @@ module Net #:nodoc:
# HTTPInformation:: 1xx
# HTTPContinue:: 100
# HTTPSwitchProtocol:: 101
+ # HTTPProcessing:: 102
+ # HTTPEarlyHints:: 103
# HTTPSuccess:: 2xx
# HTTPOK:: 200
# HTTPCreated:: 201
@@ -336,6 +338,7 @@ module Net #:nodoc:
# HTTPResetContent:: 205
# HTTPPartialContent:: 206
# HTTPMultiStatus:: 207
+ # HTTPAlreadyReported:: 208
# HTTPIMUsed:: 226
# HTTPRedirection:: 3xx
# HTTPMultipleChoices:: 300
@@ -345,6 +348,7 @@ module Net #:nodoc:
# HTTPNotModified:: 304
# HTTPUseProxy:: 305
# HTTPTemporaryRedirect:: 307
+ # HTTPPermanentRedirect:: 308
# HTTPClientError:: 4xx
# HTTPBadRequest:: 400
# HTTPUnauthorized:: 401
@@ -364,6 +368,7 @@ module Net #:nodoc:
# HTTPUnsupportedMediaType:: 415
# HTTPRequestedRangeNotSatisfiable:: 416
# HTTPExpectationFailed:: 417
+ # HTTPMisdirectedRequest:: 421
# HTTPUnprocessableEntity:: 422
# HTTPLocked:: 423
# HTTPFailedDependency:: 424
@@ -379,7 +384,10 @@ module Net #:nodoc:
# HTTPServiceUnavailable:: 503
# HTTPGatewayTimeOut:: 504
# HTTPVersionNotSupported:: 505
+ # HTTPVariantAlsoNegotiates:: 506
# HTTPInsufficientStorage:: 507
+ # HTTPLoopDetected:: 508
+ # HTTPNotExtended:: 510
# HTTPNetworkAuthenticationRequired:: 511
#
# There is also the Net::HTTPBadResponse exception which is raised when
@@ -388,7 +396,7 @@ module Net #:nodoc:
class HTTP < Protocol
# :stopdoc:
- VERSION = "0.1.1"
+ VERSION = "0.2.0"
Revision = %q$Revision$.split[1]
HTTPVersion = '1.1'
begin
@@ -985,7 +993,7 @@ module Net #:nodoc:
conn_port = port
end
- D "opening connection to #{conn_addr}:#{conn_port}..."
+ debug "opening connection to #{conn_addr}:#{conn_port}..."
begin
s = Socket.tcp conn_addr, conn_port, @local_host, @local_port, connect_timeout: @open_timeout
rescue => e
@@ -994,7 +1002,7 @@ module Net #:nodoc:
"#{conn_addr}:#{conn_port} (#{e.message})"
end
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
- D "opened"
+ debug "opened"
if use_ssl?
if proxy?
plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
@@ -1028,7 +1036,7 @@ module Net #:nodoc:
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
@ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess }
- D "starting SSL for #{conn_addr}:#{conn_port}..."
+ debug "starting SSL for #{conn_addr}:#{conn_port}..."
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
s.sync_close = true
# Server Name Indication (SNI) RFC 3546
@@ -1041,16 +1049,17 @@ module Net #:nodoc:
if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && @ssl_context.verify_hostname
s.post_connection_check(@address)
end
- D "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}"
+ debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}"
end
@socket = BufferedIO.new(s, read_timeout: @read_timeout,
write_timeout: @write_timeout,
continue_timeout: @continue_timeout,
debug_output: @debug_output)
+ @last_communicated = nil
on_connect
rescue => exception
if s
- D "Conn close because of connect error #{exception}"
+ debug "Conn close because of connect error #{exception}"
s.close
end
raise
@@ -1584,10 +1593,10 @@ module Net #:nodoc:
if count < max_retries && IDEMPOTENT_METHODS_.include?(req.method)
count += 1
@socket.close if @socket
- D "Conn close because of error #{exception}, and retry"
+ debug "Conn close because of error #{exception}, and retry"
retry
end
- D "Conn close because of error #{exception}"
+ debug "Conn close because of error #{exception}"
@socket.close if @socket
raise
end
@@ -1595,7 +1604,7 @@ module Net #:nodoc:
end_transport req, res
res
rescue => exception
- D "Conn close because of error #{exception}"
+ debug "Conn close because of error #{exception}"
@socket.close if @socket
raise exception
end
@@ -1605,11 +1614,11 @@ module Net #:nodoc:
connect
elsif @last_communicated
if @last_communicated + @keep_alive_timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC)
- D 'Conn close because of keep_alive_timeout'
+ debug 'Conn close because of keep_alive_timeout'
@socket.close
connect
elsif @socket.io.to_io.wait_readable(0) && @socket.eof?
- D "Conn close because of EOF"
+ debug "Conn close because of EOF"
@socket.close
connect
end
@@ -1627,15 +1636,15 @@ module Net #:nodoc:
@curr_http_version = res.http_version
@last_communicated = nil
if @socket.closed?
- D 'Conn socket closed'
+ debug 'Conn socket closed'
elsif not res.body and @close_on_empty_response
- D 'Conn close'
+ debug 'Conn close'
@socket.close
elsif keep_alive?(req, res)
- D 'Conn keep-alive'
+ debug 'Conn keep-alive'
@last_communicated = Process.clock_gettime(Process::CLOCK_MONOTONIC)
else
- D 'Conn close'
+ debug 'Conn close'
@socket.close
end
end
@@ -1690,11 +1699,14 @@ module Net #:nodoc:
default_port == port ? addr : "#{addr}:#{port}"
end
- def D(msg)
+ # Adds a message to debugging output
+ def debug(msg)
return unless @debug_output
@debug_output << msg
@debug_output << "\n"
end
+
+ alias_method :D, :debug
end
end
diff --git a/lib/net/http/backward.rb b/lib/net/http/backward.rb
index 9e24eae32c4..691e41e4f10 100644
--- a/lib/net/http/backward.rb
+++ b/lib/net/http/backward.rb
@@ -5,22 +5,36 @@
class Net::HTTP
ProxyMod = ProxyDelta
-end
-
-module Net
- HTTPSession = Net::HTTP
+ deprecate_constant :ProxyMod
end
module Net::NetPrivate
HTTPRequest = ::Net::HTTPRequest
+ deprecate_constant :HTTPRequest
end
-Net::HTTPInformationCode = Net::HTTPInformation
-Net::HTTPSuccessCode = Net::HTTPSuccess
-Net::HTTPRedirectionCode = Net::HTTPRedirection
-Net::HTTPRetriableCode = Net::HTTPRedirection
-Net::HTTPClientErrorCode = Net::HTTPClientError
-Net::HTTPFatalErrorCode = Net::HTTPClientError
-Net::HTTPServerErrorCode = Net::HTTPServerError
-Net::HTTPResponceReceiver = Net::HTTPResponse
+module Net
+ HTTPSession = HTTP
+ HTTPInformationCode = HTTPInformation
+ HTTPSuccessCode = HTTPSuccess
+ HTTPRedirectionCode = HTTPRedirection
+ HTTPRetriableCode = HTTPRedirection
+ HTTPClientErrorCode = HTTPClientError
+ HTTPFatalErrorCode = HTTPClientError
+ HTTPServerErrorCode = HTTPServerError
+ HTTPResponseReceiver = HTTPResponse
+
+ HTTPResponceReceiver = HTTPResponse # Typo since 2001
+
+ deprecate_constant :HTTPSession,
+ :HTTPInformationCode,
+ :HTTPSuccessCode,
+ :HTTPRedirectionCode,
+ :HTTPRetriableCode,
+ :HTTPClientErrorCode,
+ :HTTPFatalErrorCode,
+ :HTTPServerErrorCode,
+ :HTTPResponseReceiver,
+ :HTTPResponceReceiver
+end
diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb
index d6198cd6246..313de6ac923 100644
--- a/lib/net/http/generic_request.rb
+++ b/lib/net/http/generic_request.rb
@@ -31,12 +31,12 @@ class Net::HTTPGenericRequest
@decode_content = false
- if @response_has_body and Net::HTTP::HAVE_ZLIB then
+ if Net::HTTP::HAVE_ZLIB then
if !initheader ||
!initheader.keys.any? { |k|
%w[accept-encoding range].include? k.downcase
} then
- @decode_content = true
+ @decode_content = true if @response_has_body
initheader = initheader ? initheader.dup : {}
initheader["accept-encoding"] =
"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
diff --git a/lib/net/http/net-http.gemspec b/lib/net/http/net-http.gemspec
index f5ef4045e8f..dd4fc2b17ed 100644
--- a/lib/net/http/net-http.gemspec
+++ b/lib/net/http/net-http.gemspec
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
`git ls-files -z 2>/dev/null`.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"]
spec.add_dependency "net-protocol"
diff --git a/lib/net/net-protocol.gemspec b/lib/net/net-protocol.gemspec
index 767a7d48e28..021a5dcbf33 100644
--- a/lib/net/net-protocol.gemspec
+++ b/lib/net/net-protocol.gemspec
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
spec.summary = %q{The abstruct interface for net-* client.}
spec.description = %q{The abstruct interface for net-* client.}
spec.homepage = "https://github.com/ruby/net-protocol"
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
spec.licenses = ["Ruby", "BSD-2-Clause"]
spec.metadata["homepage_uri"] = spec.homepage
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
index 0afac2a02a6..add2c87a71a 100644
--- a/lib/net/protocol.rb
+++ b/lib/net/protocol.rb
@@ -26,7 +26,7 @@ require 'io/wait'
module Net # :nodoc:
class Protocol #:nodoc: internal use only
- VERSION = "0.1.1"
+ VERSION = "0.1.2"
private
def Protocol.protocol_param(name, val)
diff --git a/lib/open-uri.gemspec b/lib/open-uri.gemspec
index cc41fc60cf9..12f10ef3166 100644
--- a/lib/open-uri.gemspec
+++ b/lib/open-uri.gemspec
@@ -16,8 +16,7 @@ Gem::Specification.new do |spec|
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.executables = []
spec.require_paths = ["lib"]
spec.add_dependency "uri"
diff --git a/lib/open3.rb b/lib/open3.rb
index c574696bb12..9652b271946 100644
--- a/lib/open3.rb
+++ b/lib/open3.rb
@@ -29,8 +29,9 @@
# - Open3.pipeline : run a pipeline and wait for its completion
#
+require 'open3/version'
+
module Open3
- VERSION = "0.1.1"
# Open stdin, stdout, and stderr streams and start external executable.
# In addition, a thread to wait for the started process is created.
@@ -758,3 +759,6 @@ module Open3
end
end
+
+# JRuby uses different popen logic on Windows, require it here to reuse wrapper methods above.
+require 'open3/jruby_windows' if RUBY_ENGINE == 'jruby' && JRuby::Util::ON_WINDOWS
diff --git a/lib/open3.gemspec b/lib/open3/open3.gemspec
index ad9485adc72..a33fca74442 100644
--- a/lib/open3.gemspec
+++ b/lib/open3/open3.gemspec
@@ -1,8 +1,8 @@
# frozen_string_literal: true
name = File.basename(__FILE__, ".gemspec")
-version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir|
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
+version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}/version.rb")) do |line|
/^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
end rescue nil
end
diff --git a/lib/open3/version.rb b/lib/open3/version.rb
new file mode 100644
index 00000000000..5a8e84b4ae5
--- /dev/null
+++ b/lib/open3/version.rb
@@ -0,0 +1,3 @@
+module Open3
+ VERSION = "0.1.1"
+end
diff --git a/lib/optparse.rb b/lib/optparse.rb
index 060b13428a5..a61eff30c4f 100644
--- a/lib/optparse.rb
+++ b/lib/optparse.rb
@@ -50,7 +50,7 @@
#
# === New to \OptionParser?
#
-# See the {Tutorial}[./doc/optparse/tutorial_rdoc.html].
+# See the {Tutorial}[optparse/tutorial.rdoc].
#
# === Introduction
#
@@ -420,12 +420,12 @@
# === Further documentation
#
# The above examples, along with the accompanying
-# {Tutorial}[./doc/optparse/tutorial_rdoc.html],
+# {Tutorial}[optparse/tutorial.rdoc],
# should be enough to learn how to use this class.
# If you have any questions, file a ticket at http://bugs.ruby-lang.org.
#
class OptionParser
- OptionParser::Version = "0.1.1"
+ OptionParser::Version = "0.2.0"
# :stopdoc:
NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
diff --git a/lib/optparse/ac.rb b/lib/optparse/ac.rb
index 9d520101aad..0953725e46e 100644
--- a/lib/optparse/ac.rb
+++ b/lib/optparse/ac.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: false
-require 'optparse'
+require_relative '../optparse'
class OptionParser::AC < OptionParser
private
diff --git a/lib/optparse/date.rb b/lib/optparse/date.rb
index d6649c83f1a..7bbf12b77f8 100644
--- a/lib/optparse/date.rb
+++ b/lib/optparse/date.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: false
-require 'optparse'
+require_relative '../optparse'
require 'date'
OptionParser.accept(DateTime) do |s,|
diff --git a/lib/optparse/kwargs.rb b/lib/optparse/kwargs.rb
index ccf20c65e9e..992aca467eb 100644
--- a/lib/optparse/kwargs.rb
+++ b/lib/optparse/kwargs.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require 'optparse'
+require_relative '../optparse'
class OptionParser
# :call-seq:
diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec
index ae6596678c4..a4287ddeeed 100644
--- a/lib/optparse/optparse.gemspec
+++ b/lib/optparse/optparse.gemspec
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = spec.homepage
spec.files = Dir["{doc,lib,misc}/**/*"] + %w[README.md ChangeLog COPYING]
+ spec.rdoc_options = ["--main=README.md", "--op=rdoc", "--page-dir=doc"]
spec.bindir = "exe"
spec.executables = []
spec.require_paths = ["lib"]
diff --git a/lib/optparse/shellwords.rb b/lib/optparse/shellwords.rb
index bf31701b963..4feb1993d96 100644
--- a/lib/optparse/shellwords.rb
+++ b/lib/optparse/shellwords.rb
@@ -2,6 +2,6 @@
# -*- ruby -*-
require 'shellwords'
-require 'optparse'
+require_relative '../optparse'
OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)}
diff --git a/lib/optparse/time.rb b/lib/optparse/time.rb
index ffc6ff000d9..0ce651f6f68 100644
--- a/lib/optparse/time.rb
+++ b/lib/optparse/time.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: false
-require 'optparse'
+require_relative '../optparse'
require 'time'
OptionParser.accept(Time) do |s,|
diff --git a/lib/optparse/uri.rb b/lib/optparse/uri.rb
index 51550cf91b1..31d10593b15 100644
--- a/lib/optparse/uri.rb
+++ b/lib/optparse/uri.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
# -*- ruby -*-
-require 'optparse'
+require_relative '../optparse'
require 'uri'
OptionParser.accept(URI) {|s,| URI.parse(s) if s}
diff --git a/lib/ostruct.rb b/lib/ostruct.rb
index ecfacd10fbd..4dccfc4c632 100644
--- a/lib/ostruct.rb
+++ b/lib/ostruct.rb
@@ -93,21 +93,21 @@
# o.methods = [:foo, :bar]
# o.methods # => [:foo, :bar]
#
-# To help remedy clashes, OpenStruct uses only protected/private methods ending with `!`
-# and defines aliases for builtin public methods by adding a `!`:
+# To help remedy clashes, OpenStruct uses only protected/private methods ending with <code>!</code>
+# and defines aliases for builtin public methods by adding a <code>!</code>:
#
# o = OpenStruct.new(make: 'Bentley', class: :luxury)
# o.class # => :luxury
# o.class! # => OpenStruct
#
-# It is recommended (but not enforced) to not use fields ending in `!`;
+# It is recommended (but not enforced) to not use fields ending in <code>!</code>;
# Note that a subclass' methods may not be overwritten, nor can OpenStruct's own methods
-# ending with `!`.
+# ending with <code>!</code>.
#
# For all these reasons, consider not using OpenStruct at all.
#
class OpenStruct
- VERSION = "0.4.0"
+ VERSION = "0.5.2"
#
# Creates a new OpenStruct object. By default, the resulting OpenStruct
@@ -221,11 +221,14 @@ class OpenStruct
#
def new_ostruct_member!(name) # :nodoc:
unless @table.key?(name) || is_method_protected!(name)
- getter_proc = Proc.new { @table[name] }
- setter_proc = Proc.new {|x| @table[name] = x}
if defined?(::Ractor)
+ getter_proc = nil.instance_eval{ Proc.new { @table[name] } }
+ setter_proc = nil.instance_eval{ Proc.new {|x| @table[name] = x} }
::Ractor.make_shareable(getter_proc)
::Ractor.make_shareable(setter_proc)
+ else
+ getter_proc = Proc.new { @table[name] }
+ setter_proc = Proc.new {|x| @table[name] = x}
end
define_singleton_method!(name, &getter_proc)
define_singleton_method!("#{name}=", &setter_proc)
@@ -279,7 +282,7 @@ class OpenStruct
# :call-seq:
# ostruct[name] -> object
#
- # Returns the value of an attribute, or `nil` if there is no such attribute.
+ # Returns the value of an attribute, or +nil+ if there is no such attribute.
#
# require "ostruct"
# person = OpenStruct.new("name" => "John Smith", "age" => 70)
@@ -452,8 +455,13 @@ class OpenStruct
update_to_values!(h)
end
- # Make all public methods (builtin or our own) accessible with `!`:
- instance_methods.each do |method|
+ # Make all public methods (builtin or our own) accessible with <code>!</code>:
+ give_access = instance_methods
+ # See https://github.com/ruby/ostruct/issues/30
+ give_access -= %i[instance_exec instance_eval eval] if RUBY_ENGINE == 'jruby'
+ give_access.each do |method|
+ next if method.match(/\W$/)
+
new_name = "#{method}!"
alias_method new_name, method
end
diff --git a/lib/pp.gemspec b/lib/pp.gemspec
index 4d2400c31cc..d4b0be83dfe 100644
--- a/lib/pp.gemspec
+++ b/lib/pp.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "pp"
- spec.version = "0.2.0"
+ spec.version = "0.3.0"
spec.authors = ["Tanaka Akira"]
spec.email = ["akr@fsij.org"]
diff --git a/lib/pp.rb b/lib/pp.rb
index 72480e53047..f43356a3dfa 100644
--- a/lib/pp.rb
+++ b/lib/pp.rb
@@ -61,14 +61,34 @@ require 'prettyprint'
# Tanaka Akira <akr@fsij.org>
class PP < PrettyPrint
+ # Returns the usable width for +out+.
+ # As the width of +out+:
+ # 1. If +out+ is assigned to a tty device, its width is used.
+ # 2. Otherwise, or it could not get the value, the +COLUMN+
+ # environment variable is assumed to be set to the width.
+ # 3. If +COLUMN+ is not set to a non-zero number, 80 is assumed.
+ #
+ # And finally, returns the above width value - 1.
+ # * This -1 is for Windows command prompt, which moves the cursor to
+ # the next line if it reaches the last column.
+ def PP.width_for(out)
+ begin
+ require 'io/console'
+ _, width = out.winsize
+ rescue LoadError, NoMethodError, SystemCallError
+ end
+ (width || ENV['COLUMNS']&.to_i&.nonzero? || 80) - 1
+ end
+
# Outputs +obj+ to +out+ in pretty printed format of
# +width+ columns in width.
#
# If +out+ is omitted, <code>$></code> is assumed.
- # If +width+ is omitted, 79 is assumed.
+ # If +width+ is omitted, the width of +out+ is assumed (see
+ # width_for).
#
# PP.pp returns +out+.
- def PP.pp(obj, out=$>, width=79)
+ def PP.pp(obj, out=$>, width=width_for(out))
q = PP.new(out, width)
q.guard_inspect_key {q.pp obj}
q.flush
@@ -424,7 +444,7 @@ end
class File < IO # :nodoc:
class Stat # :nodoc:
def pretty_print(q) # :nodoc:
- require 'etc.so'
+ require 'etc'
q.object_group(self) {
q.breakable
q.text sprintf("dev=0x%x", self.dev); q.comma_breakable
@@ -530,37 +550,39 @@ class MatchData # :nodoc:
end
end
-class RubyVM::AbstractSyntaxTree::Node
- def pretty_print_children(q, names = [])
- children.zip(names) do |c, n|
- if n
- q.breakable
- q.text "#{n}:"
- end
- q.group(2) do
- q.breakable
- q.pp c
+if defined?(RubyVM::AbstractSyntaxTree)
+ class RubyVM::AbstractSyntaxTree::Node
+ def pretty_print_children(q, names = [])
+ children.zip(names) do |c, n|
+ if n
+ q.breakable
+ q.text "#{n}:"
+ end
+ q.group(2) do
+ q.breakable
+ q.pp c
+ end
end
end
- end
- def pretty_print(q)
- q.group(1, "(#{type}@#{first_lineno}:#{first_column}-#{last_lineno}:#{last_column}", ")") {
- case type
- when :SCOPE
- pretty_print_children(q, %w"tbl args body")
- when :ARGS
- pretty_print_children(q, %w[pre_num pre_init opt first_post post_num post_init rest kw kwrest block])
- when :DEFN
- pretty_print_children(q, %w[mid body])
- when :ARYPTN
- pretty_print_children(q, %w[const pre rest post])
- when :HSHPTN
- pretty_print_children(q, %w[const kw kwrest])
- else
- pretty_print_children(q)
- end
- }
+ def pretty_print(q)
+ q.group(1, "(#{type}@#{first_lineno}:#{first_column}-#{last_lineno}:#{last_column}", ")") {
+ case type
+ when :SCOPE
+ pretty_print_children(q, %w"tbl args body")
+ when :ARGS
+ pretty_print_children(q, %w[pre_num pre_init opt first_post post_num post_init rest kw kwrest block])
+ when :DEFN
+ pretty_print_children(q, %w[mid body])
+ when :ARYPTN
+ pretty_print_children(q, %w[const pre rest post])
+ when :HSHPTN
+ pretty_print_children(q, %w[const kw kwrest])
+ else
+ pretty_print_children(q)
+ end
+ }
+ end
end
end
diff --git a/lib/prettyprint.gemspec b/lib/prettyprint.gemspec
index 169267fb167..eae2227d604 100644
--- a/lib/prettyprint.gemspec
+++ b/lib/prettyprint.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "prettyprint"
- spec.version = "0.1.0"
+ spec.version = "0.1.1"
spec.authors = ["Tanaka Akira"]
spec.email = ["akr@fsij.org"]
diff --git a/lib/racc/grammar.rb b/lib/racc/grammar.rb
index 3444dfcce3e..01c4c3df69b 100644
--- a/lib/racc/grammar.rb
+++ b/lib/racc/grammar.rb
@@ -335,14 +335,14 @@ module Racc
if action
idbase = "#{type}@#{id}-#{@seqs[type] += 1}"
target = _wrap(idbase, "#{idbase}-core", action)
- _regist("#{idbase}-core", &block)
+ _register("#{idbase}-core", &block)
else
- target = _regist("#{type}@#{id}", &block)
+ target = _register("#{type}@#{id}", &block)
end
@grammar.intern(target)
end
- def _regist(target_name)
+ def _register(target_name)
target = target_name.intern
unless _added?(@grammar.intern(target))
yield(target).each_rule do |rule|
diff --git a/lib/racc/grammarfileparser.rb b/lib/racc/grammarfileparser.rb
index 419495113b8..c7d1207f0b5 100644
--- a/lib/racc/grammarfileparser.rb
+++ b/lib/racc/grammarfileparser.rb
@@ -260,9 +260,9 @@ module Racc
_, *blocks = *@scanner.epilogue.split(/^----/)
blocks.each do |block|
header, *body = block.lines.to_a
- label0, pathes = *header.sub(/\A-+/, '').split('=', 2)
+ label0, paths = *header.sub(/\A-+/, '').split('=', 2)
label = canonical_label(label0)
- (pathes ? pathes.strip.split(' ') : []).each do |path|
+ (paths ? paths.strip.split(' ') : []).each do |path|
add_user_code label, SourceText.new(File.read(path), path, 1)
end
add_user_code label, SourceText.new(body.join(''), @filename, line + 1)
diff --git a/lib/racc/info.rb b/lib/racc/info.rb
index f599b13cbb2..bb1f100adc0 100644
--- a/lib/racc/info.rb
+++ b/lib/racc/info.rb
@@ -11,7 +11,7 @@
#++
module Racc
- VERSION = '1.5.2'
+ VERSION = '1.6.0'
Version = VERSION
Copyright = 'Copyright (c) 1999-2006 Minero Aoki'
end
diff --git a/lib/random/formatter.rb b/lib/random/formatter.rb
new file mode 100644
index 00000000000..744853a4b71
--- /dev/null
+++ b/lib/random/formatter.rb
@@ -0,0 +1,217 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: true
+
+# == Random number formatter.
+#
+# Formats generated random numbers in many manners.
+#
+# === Examples
+#
+# Generate random hexadecimal strings:
+#
+# require 'random/formatter'
+#
+# prng.hex(10) #=> "52750b30ffbc7de3b362"
+# prng.hex(10) #=> "92b15d6c8dc4beb5f559"
+# prng.hex(13) #=> "39b290146bea6ce975c37cfc23"
+#
+# Generate random base64 strings:
+#
+# prng.base64(10) #=> "EcmTPZwWRAozdA=="
+# prng.base64(10) #=> "KO1nIU+p9DKxGg=="
+# prng.base64(12) #=> "7kJSM/MzBJI+75j8"
+#
+# Generate random binary strings:
+#
+# prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
+# prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
+#
+# Generate alphanumeric strings:
+#
+# prng.alphanumeric(10) #=> "S8baxMJnPl"
+# prng.alphanumeric(10) #=> "aOxAg8BAJe"
+#
+# Generate UUIDs:
+#
+# prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
+# prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
+
+module Random::Formatter
+
+ # Random::Formatter#random_bytes generates a random binary string.
+ #
+ # The argument _n_ specifies the length of the result string.
+ #
+ # If _n_ is not specified or is nil, 16 is assumed.
+ # It may be larger in future.
+ #
+ # The result may contain any byte: "\x00" - "\xff".
+ #
+ # require 'random/formatter'
+ #
+ # prng.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
+ # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
+ def random_bytes(n=nil)
+ n = n ? n.to_int : 16
+ gen_random(n)
+ end
+
+ # Random::Formatter#hex generates a random hexadecimal string.
+ #
+ # The argument _n_ specifies the length, in bytes, of the random number to be generated.
+ # The length of the resulting hexadecimal string is twice of _n_.
+ #
+ # If _n_ is not specified or is nil, 16 is assumed.
+ # It may be larger in the future.
+ #
+ # The result may contain 0-9 and a-f.
+ #
+ # require 'random/formatter'
+ #
+ # prng.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
+ # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61"
+ def hex(n=nil)
+ random_bytes(n).unpack1("H*")
+ end
+
+ # Random::Formatter#base64 generates a random base64 string.
+ #
+ # The argument _n_ specifies the length, in bytes, of the random number
+ # to be generated. The length of the result string is about 4/3 of _n_.
+ #
+ # If _n_ is not specified or is nil, 16 is assumed.
+ # It may be larger in the future.
+ #
+ # The result may contain A-Z, a-z, 0-9, "+", "/" and "=".
+ #
+ # require 'random/formatter'
+ #
+ # prng.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
+ # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
+ #
+ # See RFC 3548 for the definition of base64.
+ def base64(n=nil)
+ [random_bytes(n)].pack("m0")
+ end
+
+ # Random::Formatter#urlsafe_base64 generates a random URL-safe base64 string.
+ #
+ # The argument _n_ specifies the length, in bytes, of the random number
+ # to be generated. The length of the result string is about 4/3 of _n_.
+ #
+ # If _n_ is not specified or is nil, 16 is assumed.
+ # It may be larger in the future.
+ #
+ # The boolean argument _padding_ specifies the padding.
+ # If it is false or nil, padding is not generated.
+ # Otherwise padding is generated.
+ # By default, padding is not generated because "=" may be used as a URL delimiter.
+ #
+ # The result may contain A-Z, a-z, 0-9, "-" and "_".
+ # "=" is also used if _padding_ is true.
+ #
+ # require 'random/formatter'
+ #
+ # prng.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
+ # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
+ #
+ # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
+ # prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="
+ #
+ # See RFC 3548 for the definition of URL-safe base64.
+ def urlsafe_base64(n=nil, padding=false)
+ s = [random_bytes(n)].pack("m0")
+ s.tr!("+/", "-_")
+ s.delete!("=") unless padding
+ s
+ end
+
+ # Random::Formatter#uuid generates a random v4 UUID (Universally Unique IDentifier).
+ #
+ # require 'random/formatter'
+ #
+ # prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
+ # prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
+ # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
+ #
+ # The version 4 UUID is purely random (except the version).
+ # It doesn't contain meaningful information such as MAC addresses, timestamps, etc.
+ #
+ # The result contains 122 random bits (15.25 random bytes).
+ #
+ # See RFC 4122 for details of UUID.
+ #
+ def uuid
+ ary = random_bytes(16).unpack("NnnnnN")
+ ary[2] = (ary[2] & 0x0fff) | 0x4000
+ ary[3] = (ary[3] & 0x3fff) | 0x8000
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
+ end
+
+ private def gen_random(n)
+ self.bytes(n)
+ end
+
+ # Random::Formatter#choose generates a string that randomly draws from a
+ # source array of characters.
+ #
+ # The argument _source_ specifies the array of characters from which
+ # to generate the string.
+ # The argument _n_ specifies the length, in characters, of the string to be
+ # generated.
+ #
+ # The result may contain whatever characters are in the source array.
+ #
+ # require 'random/formatter'
+ #
+ # prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
+ # prng.choose([*'0'..'9'], 5) #=> "27309"
+ private def choose(source, n)
+ size = source.size
+ m = 1
+ limit = size
+ while limit * size <= 0x100000000
+ limit *= size
+ m += 1
+ end
+ result = ''.dup
+ while m <= n
+ rs = random_number(limit)
+ is = rs.digits(size)
+ (m-is.length).times { is << 0 }
+ result << source.values_at(*is).join('')
+ n -= m
+ end
+ if 0 < n
+ rs = random_number(limit)
+ is = rs.digits(size)
+ if is.length < n
+ (n-is.length).times { is << 0 }
+ else
+ is.pop while n < is.length
+ end
+ result.concat source.values_at(*is).join('')
+ end
+ result
+ end
+
+ ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9']
+ # Random::Formatter#alphanumeric generates a random alphanumeric string.
+ #
+ # The argument _n_ specifies the length, in characters, of the alphanumeric
+ # string to be generated.
+ #
+ # If _n_ is not specified or is nil, 16 is assumed.
+ # It may be larger in the future.
+ #
+ # The result may contain A-Z, a-z and 0-9.
+ #
+ # require 'random/formatter'
+ #
+ # prng.alphanumeric #=> "2BuBuLf3WfSKyQbR"
+ # prng.alphanumeric(10) #=> "i6K93NdqiH"
+ def alphanumeric(n=nil)
+ n = 16 if n.nil?
+ choose(ALPHANUMERIC, n)
+ end
+end
diff --git a/lib/rdoc/cross_reference.rb b/lib/rdoc/cross_reference.rb
index 4a6abfa3ac0..ef8e21bde84 100644
--- a/lib/rdoc/cross_reference.rb
+++ b/lib/rdoc/cross_reference.rb
@@ -19,7 +19,7 @@ class RDoc::CrossReference
#
# See CLASS_REGEXP_STR
- METHOD_REGEXP_STR = '([a-z]\w*[!?=]?|%|===?|\[\]=?|<<|>>|\+@|-@|-|\+|\*)(?:\([\w.+*/=<>-]*\))?'
+ METHOD_REGEXP_STR = '([A-Za-z]\w*[!?=]?|%|===?|\[\]=?|<<|>>|\+@|-@|-|\+|\*)(?:\([\w.+*/=<>-]*\))?'
##
# Regular expressions matching text that should potentially have
@@ -34,12 +34,6 @@ class RDoc::CrossReference
# A::B::C.meth
#{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR}
- # Stand-alone method (preceded by a #)
- | \\?\##{METHOD_REGEXP_STR}
-
- # Stand-alone method (preceded by ::)
- | ::#{METHOD_REGEXP_STR}
-
# A::B::C
# The stuff after CLASS_REGEXP_STR is a
# nasty hack. CLASS_REGEXP_STR unfortunately matches
@@ -56,6 +50,12 @@ class RDoc::CrossReference
# marker.
| #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z)
+ # Stand-alone method (preceded by a #)
+ | \\?\##{METHOD_REGEXP_STR}
+
+ # Stand-alone method (preceded by ::)
+ | ::#{METHOD_REGEXP_STR}
+
# Things that look like filenames
# The key thing is that there must be at least
# one special character (period, slash, or
@@ -82,12 +82,12 @@ class RDoc::CrossReference
# A::B::C.meth
#{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR}
- # Stand-alone method
- | \\?#{METHOD_REGEXP_STR}
-
# A::B::C
| #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z)
+ # Stand-alone method
+ | \\?#{METHOD_REGEXP_STR}
+
# Things that look like filenames
| (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+
@@ -115,15 +115,8 @@ class RDoc::CrossReference
@seen = {}
end
- ##
- # Returns a reference to +name+.
- #
- # If the reference is found and +name+ is not documented +text+ will be
- # returned. If +name+ is escaped +name+ is returned. If +name+ is not
- # found +text+ is returned.
-
- def resolve name, text
- return @seen[name] if @seen.include? name
+ def resolve_method name
+ ref = nil
if /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then
type = $2
@@ -165,12 +158,27 @@ class RDoc::CrossReference
end
end
+ ref
+ end
+
+ ##
+ # Returns a reference to +name+.
+ #
+ # If the reference is found and +name+ is not documented +text+ will be
+ # returned. If +name+ is escaped +name+ is returned. If +name+ is not
+ # found +text+ is returned.
+
+ def resolve name, text
+ return @seen[name] if @seen.include? name
+
ref = case name
when /^\\(#{CLASS_REGEXP_STR})$/o then
@context.find_symbol $1
else
@context.find_symbol name
- end unless ref
+ end
+
+ ref = resolve_method name unless ref
# Try a page name
ref = @store.page name if not ref and name =~ /^[\w.]+$/
diff --git a/lib/rdoc/version.rb b/lib/rdoc/version.rb
index a3ef62d6be2..86c5b360fd6 100644
--- a/lib/rdoc/version.rb
+++ b/lib/rdoc/version.rb
@@ -3,6 +3,6 @@ module RDoc
##
# RDoc version you are using
- VERSION = '6.3.2'
+ VERSION = '6.4.0'
end
diff --git a/lib/readline.gemspec b/lib/readline.gemspec
index 69d76954fa9..3a18f9edb62 100644
--- a/lib/readline.gemspec
+++ b/lib/readline.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = 'readline'
- spec.version = '0.0.2'
+ spec.version = '0.0.3'
spec.authors = ['aycabta']
spec.email = ['aycabta@gmail.com']
diff --git a/lib/readline.rb b/lib/readline.rb
index 6cc923cb2fb..29cdf3a14f0 100644
--- a/lib/readline.rb
+++ b/lib/readline.rb
@@ -2,5 +2,6 @@ begin
require 'readline.so'
rescue LoadError
require 'reline' unless defined? Reline
+ Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline)
Readline = Reline
end
diff --git a/lib/reline.rb b/lib/reline.rb
index b872fe69455..a9fc49b416f 100644
--- a/lib/reline.rb
+++ b/lib/reline.rb
@@ -60,7 +60,7 @@ module Reline
def initialize
self.output = STDOUT
- @dialog_proc_list = []
+ @dialog_proc_list = {}
yield self
@completion_quote_character = nil
@bracketed_paste_finished = false
@@ -155,10 +155,15 @@ module Reline
@dig_perfect_match_proc = p
end
+ DialogProc = Struct.new(:dialog_proc, :context)
def add_dialog_proc(name_sym, p, context = nil)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
raise ArgumentError unless name_sym.instance_of?(Symbol)
- @dialog_proc_list << [name_sym, p, context]
+ @dialog_proc_list[name_sym] = DialogProc.new(p, context)
+ end
+
+ def dialog_proc(name_sym)
+ @dialog_proc_list[name_sym]
end
def input=(val)
@@ -301,9 +306,8 @@ module Reline
line_editor.auto_indent_proc = auto_indent_proc
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
line_editor.pre_input_hook = pre_input_hook
- @dialog_proc_list.each do |d|
- name_sym, dialog_proc, context = d
- line_editor.add_dialog_proc(name_sym, dialog_proc, context)
+ @dialog_proc_list.each_pair do |name_sym, d|
+ line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
end
unless config.test_mode
@@ -315,6 +319,7 @@ module Reline
line_editor.rerender
begin
+ line_editor.set_signal_handlers
prev_pasting_state = false
loop do
prev_pasting_state = Reline::IOGate.in_pasting?
@@ -343,6 +348,11 @@ module Reline
line_editor.finalize
Reline::IOGate.deprep(otio)
raise e
+ rescue Exception
+ # Including Interrupt
+ line_editor.finalize
+ Reline::IOGate.deprep(otio)
+ raise
end
line_editor.finalize
@@ -516,6 +526,7 @@ module Reline
def_single_delegators :core, :last_incremental_search
def_single_delegators :core, :last_incremental_search=
def_single_delegators :core, :add_dialog_proc
+ def_single_delegators :core, :dialog_proc
def_single_delegators :core, :autocompletion, :autocompletion=
def_single_delegators :core, :readmultiline
diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb
index ae6961e29fa..ab147a61857 100644
--- a/lib/reline/ansi.rb
+++ b/lib/reline/ansi.rb
@@ -4,6 +4,19 @@ require 'timeout'
require_relative 'terminfo'
class Reline::ANSI
+ CAPNAME_KEY_BINDINGS = {
+ 'khome' => :ed_move_to_beg,
+ 'kend' => :ed_move_to_end,
+ 'kcuu1' => :ed_prev_history,
+ 'kcud1' => :ed_next_history,
+ 'kcuf1' => :ed_next_char,
+ 'kcub1' => :ed_prev_char,
+ 'cuu' => :ed_prev_history,
+ 'cud' => :ed_next_history,
+ 'cuf' => :ed_next_char,
+ 'cub' => :ed_prev_char,
+ }
+
if Reline::Terminfo.enabled?
Reline::Terminfo.setupterm(0, 2)
end
@@ -34,30 +47,38 @@ class Reline::ANSI
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
{
+ [27, 91, 90] => :completion_journey_up, # S-Tab
+ }.each_pair do |key, func|
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
+ end
+ {
# default bindings
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x
- [27, 91, 90] => :completion_journey_up, # S-Tab
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
end
end
def self.set_default_key_bindings_terminfo(config)
- {
- Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
- Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
- Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
- Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
- Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
- Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
- # Escape sequences that omit the move distance and are set to defaults
- # value 1 may be sometimes sent by pressing the arrow-key.
- Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
- Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
- Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
- Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
- }.each_pair do |key, func|
+ key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
+ begin
+ key_code = Reline::Terminfo.tigetstr(capname)
+ case capname
+ # Escape sequences that omit the move distance and are set to defaults
+ # value 1 may be sometimes sent by pressing the arrow-key.
+ when 'cuu', 'cud', 'cuf', 'cub'
+ [ key_code.sub(/%p1%d/, '').bytes, key_binding ]
+ else
+ [ key_code.bytes, key_binding ]
+ end
+ rescue Reline::Terminfo::TerminfoError
+ # capname is undefined
+ end
+ end.compact.to_h
+
+ key_bindings.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
@@ -126,7 +147,7 @@ class Reline::ANSI
unless @@buf.empty?
return @@buf.shift
end
- until c = @@input.raw(intr: true) { select([@@input], [], [], 0.1) && @@input.getbyte }
+ until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte }
Reline.core.line_editor.resize
end
(c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
@@ -261,7 +282,7 @@ class Reline::ANSI
def self.move_cursor_up(x)
if x > 0
- @@output.write "\e[#{x}A" if x > 0
+ @@output.write "\e[#{x}A"
elsif x < 0
move_cursor_down(-x)
end
@@ -269,7 +290,7 @@ class Reline::ANSI
def self.move_cursor_down(x)
if x > 0
- @@output.write "\e[#{x}B" if x > 0
+ @@output.write "\e[#{x}B"
elsif x < 0
move_cursor_up(-x)
end
@@ -277,7 +298,11 @@ class Reline::ANSI
def self.hide_cursor
if Reline::Terminfo.enabled?
- @@output.write Reline::Terminfo.tigetstr('civis')
+ begin
+ @@output.write Reline::Terminfo.tigetstr('civis')
+ rescue Reline::Terminfo::TerminfoError
+ # civis is undefined
+ end
else
# ignored
end
@@ -285,7 +310,11 @@ class Reline::ANSI
def self.show_cursor
if Reline::Terminfo.enabled?
- @@output.write Reline::Terminfo.tigetstr('cnorm')
+ begin
+ @@output.write Reline::Terminfo.tigetstr('cnorm')
+ rescue Reline::Terminfo::TerminfoError
+ # cnorm is undefined
+ end
else
# ignored
end
@@ -316,8 +345,6 @@ class Reline::ANSI
end
def self.deprep(otio)
- int_handle = Signal.trap('INT', 'IGNORE')
- Signal.trap('INT', int_handle)
Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
end
end
diff --git a/lib/reline/config.rb b/lib/reline/config.rb
index 4b2655d8eb5..5ef5ce4e8d4 100644
--- a/lib/reline/config.rb
+++ b/lib/reline/config.rb
@@ -67,6 +67,7 @@ class Reline::Config
@keyseq_timeout = 500
@test_mode = false
@autocompletion = false
+ @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
end
def reset
@@ -387,4 +388,8 @@ class Reline::Config
end
ret
end
+
+ private def seven_bit_encoding?(encoding)
+ encoding == Encoding::US_ASCII
+ end
end
diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb
index 5df346882a3..3fafad5c6ef 100644
--- a/lib/reline/general_io.rb
+++ b/lib/reline/general_io.rb
@@ -1,4 +1,5 @@
require 'timeout'
+require 'io/wait'
class Reline::GeneralIO
def self.reset(encoding: nil)
@@ -36,7 +37,7 @@ class Reline::GeneralIO
end
c = nil
loop do
- result = select([@@input], [], [], 0.1)
+ result = @@input.wait_readable(0.1)
next if result.nil?
c = @@input.read(1)
break
diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb
index cad97a7d042..a561feee578 100644
--- a/lib/reline/key_actor/emacs.rb
+++ b/lib/reline/key_actor/emacs.rb
@@ -43,7 +43,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 20 ^T
:ed_transpose_chars,
# 21 ^U
- :em_kill_line,
+ :unix_line_discard,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb
index 16e5f69914b..c1c61513a9e 100644
--- a/lib/reline/key_stroke.rb
+++ b/lib/reline/key_stroke.rb
@@ -4,6 +4,7 @@ class Reline::KeyStroke
end
def compress_meta_key(ary)
+ return ary unless @config.convert_meta
ary.inject([]) { |result, key|
if result.size > 0 and result.last == "\e".ord
result[result.size - 1] = Reline::Key.new(key, key | 0b10000000, true)
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index f6facc9da81..135432a5d1d 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -93,7 +93,7 @@ class Reline::LineEditor
mode_string
end
- private def check_multiline_prompt(buffer, prompt)
+ private def check_multiline_prompt(buffer)
if @vi_arg
prompt = "(arg: #{@vi_arg}) "
@rerender_all = true
@@ -121,7 +121,7 @@ class Reline::LineEditor
if use_cached_prompt_list
prompt_list = @cached_prompt_list
else
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
@prompt_cache_time = Time.now.to_f
end
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
@@ -151,33 +151,6 @@ class Reline::LineEditor
@screen_size = Reline::IOGate.get_screen_size
@screen_height = @screen_size.first
reset_variables(prompt, encoding: encoding)
- @old_trap = Signal.trap('INT') {
- clear_dialog
- if @scroll_partial_screen
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
- else
- move_cursor_down(@highest_in_all - @line_index - 1)
- end
- Reline::IOGate.move_cursor_column(0)
- scroll_down(1)
- case @old_trap
- when 'DEFAULT', 'SYSTEM_DEFAULT'
- raise Interrupt
- when 'IGNORE'
- # Do nothing
- when 'EXIT'
- exit
- else
- @old_trap.call
- end
- }
- begin
- @old_tstp_trap = Signal.trap('TSTP') {
- Reline::IOGate.ungetc("\C-z".ord)
- @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
- }
- rescue ArgumentError
- end
Reline::IOGate.set_winch_handler do
@resized = true
end
@@ -217,7 +190,7 @@ class Reline::LineEditor
else
back = 0
new_buffer = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
new_buffer.each_with_index do |line, index|
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
width = prompt_width + calculate_width(line)
@@ -244,6 +217,36 @@ class Reline::LineEditor
end
end
+ def set_signal_handlers
+ @old_trap = Signal.trap('INT') {
+ clear_dialog
+ if @scroll_partial_screen
+ move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
+ else
+ move_cursor_down(@highest_in_all - @line_index - 1)
+ end
+ Reline::IOGate.move_cursor_column(0)
+ scroll_down(1)
+ case @old_trap
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
+ raise Interrupt
+ when 'IGNORE'
+ # Do nothing
+ when 'EXIT'
+ exit
+ else
+ @old_trap.call if @old_trap.respond_to?(:call)
+ end
+ }
+ begin
+ @old_tstp_trap = Signal.trap('TSTP') {
+ Reline::IOGate.ungetc("\C-z".ord)
+ @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
+ }
+ rescue ArgumentError
+ end
+ end
+
def finalize
Signal.trap('INT', @old_trap)
begin
@@ -257,7 +260,7 @@ class Reline::LineEditor
end
def reset_variables(prompt = '', encoding:)
- @prompt = prompt
+ @prompt = prompt.gsub("\n", "\\n")
@mark_pointer = nil
@encoding = encoding
@is_multiline = false
@@ -435,7 +438,7 @@ class Reline::LineEditor
show_menu
@menu_info = nil
end
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
if @cleared
clear_screen_buffer(prompt, prompt_list, prompt_width)
@cleared = false
@@ -446,7 +449,7 @@ class Reline::LineEditor
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
Reline::IOGate.move_cursor_column(0)
@scroll_partial_screen = nil
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
if @previous_line_index
new_lines = whole_lines(index: @previous_line_index, line: @line)
else
@@ -463,15 +466,17 @@ class Reline::LineEditor
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
rendered = false
if @add_newline_to_end_of_buffer
+ clear_dialog_with_content
rerender_added_newline(prompt, prompt_width)
@add_newline_to_end_of_buffer = false
else
if @just_cursor_moving and not @rerender_all
+ clear_dialog_with_content
rendered = just_move_cursor
- render_dialog((prompt_width + @cursor) % @screen_size.last)
@just_cursor_moving = false
return
elsif @previous_line_index or new_highest_in_this != @highest_in_this
+ clear_dialog_with_content
rerender_changed_current_line
@previous_line_index = nil
rendered = true
@@ -492,7 +497,7 @@ class Reline::LineEditor
end
line = modify_lines(new_lines)[@line_index]
clear_dialog
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
render_partial(prompt, prompt_width, line, @first_line_started_from)
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
scroll_down(1)
@@ -501,7 +506,7 @@ class Reline::LineEditor
else
if not rendered and not @in_pasting
line = modify_lines(whole_lines)[@line_index]
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
render_partial(prompt, prompt_width, line, @first_line_started_from)
end
render_dialog((prompt_width + @cursor) % @screen_size.last)
@@ -634,8 +639,12 @@ class Reline::LineEditor
end
def add_dialog_proc(name, p, context = nil)
- return if @dialogs.any? { |d| d.name == name }
- @dialogs << Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
+ dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
+ if index = @dialogs.find_index { |d| d.name == name }
+ @dialogs[index] = dialog
+ else
+ @dialogs << dialog
+ end
end
DIALOG_DEFAULT_HEIGHT = 20
@@ -651,6 +660,7 @@ class Reline::LineEditor
private def render_each_dialog(dialog, cursor_column)
if @in_pasting
+ clear_each_dialog(dialog)
dialog.contents = nil
dialog.trap_key = nil
return
@@ -706,19 +716,18 @@ class Reline::LineEditor
dialog.scrollbar_pos = nil
end
upper_space = @first_line_started_from - @started_from
- lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
dialog.column = dialog_render_info.pos.x
dialog.width += @block_elem_width if dialog.scrollbar_pos
diff = (dialog.column + dialog.width) - (@screen_size.last)
if diff > 0
dialog.column -= diff
end
- if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
+ if (@rest_height - dialog_render_info.pos.y) >= height
dialog.vertical_offset = dialog_render_info.pos.y + 1
elsif upper_space >= height
dialog.vertical_offset = dialog_render_info.pos.y - height
else
- if (lower_space + @rest_height - dialog_render_info.pos.y) < height
+ if (@rest_height - dialog_render_info.pos.y) < height
scroll_down(height + dialog_render_info.pos.y)
move_cursor_up(height + dialog_render_info.pos.y)
end
@@ -776,7 +785,7 @@ class Reline::LineEditor
private def reset_dialog(dialog, old_dialog)
return if dialog.lines_backup.nil? or old_dialog.contents.nil?
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
visual_lines = []
visual_start = nil
dialog.lines_backup[:lines].each_with_index { |l, i|
@@ -858,7 +867,8 @@ class Reline::LineEditor
s = ' ' * width
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
- s = padding_space_with_escape_sequences(s, dialog.width)
+ rerender_width = old_dialog.width - dialog.width
+ s = padding_space_with_escape_sequences(s, rerender_width)
end
Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
@output.write "\e[0m#{s}\e[0m"
@@ -875,10 +885,18 @@ class Reline::LineEditor
end
end
+ private def clear_dialog_with_content
+ @dialogs.each do |dialog|
+ clear_each_dialog(dialog)
+ dialog.contents = nil
+ dialog.trap_key = nil
+ end
+ end
+
private def clear_each_dialog(dialog)
dialog.trap_key = nil
return unless dialog.contents
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
visual_lines = []
visual_lines_under_dialog = []
visual_start = nil
@@ -963,7 +981,7 @@ class Reline::LineEditor
end
def just_move_cursor
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
move_cursor_up(@started_from)
new_first_line_started_from =
if @line_index.zero?
@@ -1000,7 +1018,7 @@ class Reline::LineEditor
else
new_lines = whole_lines
end
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
diff = all_height - @highest_in_all
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
@@ -1047,7 +1065,7 @@ class Reline::LineEditor
Reline::IOGate.move_cursor_column(0)
back = 0
new_buffer = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
new_buffer.each_with_index do |line, index|
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
width = prompt_width + calculate_width(line)
@@ -2021,8 +2039,16 @@ class Reline::LineEditor
last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer += bytesize
last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
- if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
- width = 0
+ combined_char = last_mbchar + str
+ if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
+ # combined char
+ last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
+ combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
+ if combined_char_width > last_mbchar_width
+ width = combined_char_width - last_mbchar_width
+ else
+ width = 0
+ end
end
@cursor += width
@cursor_max += width
@@ -2207,6 +2233,8 @@ class Reline::LineEditor
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = @buffer_of_lines.size - 1
@line = @buffer_of_lines.last
+ @byte_pointer = @line.bytesize
+ @cursor = @cursor_max = calculate_width(@line)
@rerender_all = true
@searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
else
@@ -2577,6 +2605,11 @@ class Reline::LineEditor
end
alias_method :backward_delete_char, :em_delete_prev_char
+ # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
+ # +Ctrl-U+) + Kill from the cursor to the end of the line.
+ # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
+ # the line. With a negative numeric argument, kill backward
+ # from the cursor to the beginning of the current line.
private def ed_kill_line(key)
if @line.bytesize > @byte_pointer
@line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
@@ -2595,7 +2628,12 @@ class Reline::LineEditor
end
alias_method :kill_line, :ed_kill_line
- private def em_kill_line(key)
+ # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
+ # beginning of the edit buffer to the cursor and save it to the
+ # cut buffer.
+ # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
+ # to the beginning of the current line.
+ private def vi_kill_line_prev(key)
if @byte_pointer > 0
@line, deleted = byteslice!(@line, 0, @byte_pointer)
@byte_pointer = 0
@@ -2604,8 +2642,22 @@ class Reline::LineEditor
@cursor = 0
end
end
- alias_method :unix_line_discard, :em_kill_line
- alias_method :vi_kill_line_prev, :em_kill_line
+ alias_method :unix_line_discard, :vi_kill_line_prev
+
+ # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
+ # edit buffer and save it to the cut buffer. +vi-kill-line-prev+
+ # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
+ # current line, no matter where point is.
+ private def em_kill_line(key)
+ if @line.size > 0
+ @kill_ring.append(@line.dup, true)
+ @line.clear
+ @byte_pointer = 0
+ @cursor_max = 0
+ @cursor = 0
+ end
+ end
+ alias_method :kill_whole_line, :em_kill_line
private def em_delete(key)
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
diff --git a/lib/reline/reline.gemspec b/lib/reline/reline.gemspec
index 400301fc705..26809db8738 100644
--- a/lib/reline/reline.gemspec
+++ b/lib/reline/reline.gemspec
@@ -1,7 +1,9 @@
-lib = File.expand_path('../lib', __FILE__)
-$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'reline/version'
+begin
+ require_relative 'lib/reline/version'
+rescue LoadError
+ require_relative 'version'
+end
Gem::Specification.new do |spec|
spec.name = 'reline'
diff --git a/lib/reline/terminfo.rb b/lib/reline/terminfo.rb
index b4d1d735f84..fd8a312d785 100644
--- a/lib/reline/terminfo.rb
+++ b/lib/reline/terminfo.rb
@@ -74,12 +74,27 @@ module Reline::Terminfo
#extern 'char *tparm(const char *str, ...)'
@tiparm = Fiddle::Function.new(curses_dl['tparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
end
- # TODO: add int tigetflag(char *capname) and int tigetnum(char *capname)
+ begin
+ #extern 'int tigetflag(char *str)'
+ @tigetflag = Fiddle::Function.new(curses_dl['tigetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
+ rescue Fiddle::DLError
+ # OpenBSD lacks tigetflag
+ #extern 'int tgetflag(char *str)'
+ @tigetflag = Fiddle::Function.new(curses_dl['tgetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
+ end
+ begin
+ #extern 'int tigetnum(char *str)'
+ @tigetnum = Fiddle::Function.new(curses_dl['tigetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
+ rescue Fiddle::DLError
+ # OpenBSD lacks tigetnum
+ #extern 'int tgetnum(char *str)'
+ @tigetnum = Fiddle::Function.new(curses_dl['tgetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
+ end
def self.setupterm(term, fildes)
- errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
+ errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
ret = @setupterm.(term, fildes, errret_int)
- errret = errret_int.unpack1('i')
+ errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i')
case ret
when 0 # OK
0
@@ -106,6 +121,7 @@ module Reline::Terminfo
end
def self.tigetstr(capname)
+ raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
capability = @tigetstr.(capname)
case capability.to_i
when 0, -1
@@ -122,6 +138,30 @@ module Reline::Terminfo
@tiparm.(str, *new_args).to_s
end
+ def self.tigetflag(capname)
+ raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
+ flag = @tigetflag.(capname).to_i
+ case flag
+ when -1
+ raise TerminfoError, "not boolean capability: #{capname}"
+ when 0
+ raise TerminfoError, "can't find capability: #{capname}"
+ end
+ flag
+ end
+
+ def self.tigetnum(capname)
+ raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
+ num = @tigetnum.(capname).to_i
+ case num
+ when -2
+ raise TerminfoError, "not numeric capability: #{capname}"
+ when -1
+ raise TerminfoError, "can't find capability: #{capname}"
+ end
+ num
+ end
+
def self.enabled?
true
end
diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb
index 80cc54a05e6..6000c9f82a7 100644
--- a/lib/reline/unicode.rb
+++ b/lib/reline/unicode.rb
@@ -79,6 +79,8 @@ class Reline::Unicode
require 'reline/unicode/east_asian_width'
+ HalfwidthDakutenHandakuten = /[\u{FF9E}\u{FF9F}]/
+
MBCharWidthRE = /
(?<width_2_1>
[#{ EscapedChars.map {|c| "\\x%02x" % c.ord }.join }] (?# ^ + char, such as ^M, ^H, ^[, ...)
@@ -93,6 +95,12 @@ class Reline::Unicode
#{ EastAsianWidth::TYPE_H }
| #{ EastAsianWidth::TYPE_NA }
| #{ EastAsianWidth::TYPE_N }
+ )(?!#{ HalfwidthDakutenHandakuten })
+ | (?<width_2_3>
+ (?: #{ EastAsianWidth::TYPE_H }
+ | #{ EastAsianWidth::TYPE_NA }
+ | #{ EastAsianWidth::TYPE_N })
+ #{ HalfwidthDakutenHandakuten }
)
| (?<ambiguous_width>
#{EastAsianWidth::TYPE_A}
@@ -109,7 +117,7 @@ class Reline::Unicode
m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
case
when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
- when m[:width_2_1], m[:width_2_2] then 2
+ when m[:width_2_1], m[:width_2_2], m[:width_2_3] then 2
when m[:width_3] then 3
when m[:width_0] then 0
when m[:width_1] then 1
diff --git a/lib/reline/version.rb b/lib/reline/version.rb
index 8425d5e14af..1bb1c02f3d1 100644
--- a/lib/reline/version.rb
+++ b/lib/reline/version.rb
@@ -1,3 +1,3 @@
module Reline
- VERSION = '0.2.8.pre.11'
+ VERSION = '0.3.1'
end
diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb
index 73653b2de2c..f064472ce76 100644
--- a/lib/reline/windows.rb
+++ b/lib/reline/windows.rb
@@ -168,7 +168,9 @@ class Reline::Windows
@@input_buf = []
@@output_buf = []
- def self.msys_tty?(io=@@hConsoleInputHandle)
+ @@output = STDOUT
+
+ def self.msys_tty?(io = @@hConsoleInputHandle)
# check if fd is a pipe
if @@GetFileType.call(io) != FILE_TYPE_PIPE
return false
@@ -213,8 +215,29 @@ class Reline::Windows
[ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
]
+ @@hsg = nil
+
def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
+ # high-surrogate
+ if 0xD800 <= char_code and char_code <= 0xDBFF
+ @@hsg = char_code
+ return
+ end
+ # low-surrogate
+ if 0xDC00 <= char_code and char_code <= 0xDFFF
+ if @@hsg
+ char_code = 0x10000 + (@@hsg - 0xD800) * 0x400 + char_code - 0xDC00
+ @@hsg = nil
+ else
+ # no high-surrogate. ignored.
+ return
+ end
+ else
+ # ignore high-surrogate without low-surrogate if there
+ @@hsg = nil
+ end
+
key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
match = KEY_MAP.find { |args,| key.matches?(**args) }
@@ -235,25 +258,33 @@ class Reline::Windows
num_of_events = 0.chr * 8
while @@output_buf.empty?
Reline.core.line_editor.resize
- next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
+ if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
+ # prevent for background consolemode change
+ @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
+ next
+ end
next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
- input_record = 0.chr * 18
+ input_records = 0.chr * 20 * 80
read_event = 0.chr * 4
- if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
- event = input_record[0, 2].unpack1('s*')
- case event
- when WINDOW_BUFFER_SIZE_EVENT
- @@winch_handler.()
- when KEY_EVENT
- key_down = input_record[4, 4].unpack1('l*')
- repeat_count = input_record[8, 2].unpack1('s*')
- virtual_key_code = input_record[10, 2].unpack1('s*')
- virtual_scan_code = input_record[12, 2].unpack1('s*')
- char_code = input_record[14, 2].unpack1('S*')
- control_key_state = input_record[16, 2].unpack1('S*')
- is_key_down = key_down.zero? ? false : true
- if is_key_down
- process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
+ if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_records, 80, read_event) != 0
+ read_events = read_event.unpack1('L')
+ 0.upto(read_events) do |idx|
+ input_record = input_records[idx * 20, 20]
+ event = input_record[0, 2].unpack1('s*')
+ case event
+ when WINDOW_BUFFER_SIZE_EVENT
+ @@winch_handler.()
+ when KEY_EVENT
+ key_down = input_record[4, 4].unpack1('l*')
+ repeat_count = input_record[8, 2].unpack1('s*')
+ virtual_key_code = input_record[10, 2].unpack1('s*')
+ virtual_scan_code = input_record[12, 2].unpack1('s*')
+ char_code = input_record[14, 2].unpack1('S*')
+ control_key_state = input_record[16, 2].unpack1('S*')
+ is_key_down = key_down.zero? ? false : true
+ if is_key_down
+ process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
+ end
end
end
end
@@ -274,7 +305,7 @@ class Reline::Windows
end
def self.empty_buffer?
- if not @@input_buf.empty?
+ if not @@output_buf.empty?
false
elsif @@kbhit.call == 0
true
@@ -284,6 +315,18 @@ class Reline::Windows
end
def self.get_console_screen_buffer_info
+ # CONSOLE_SCREEN_BUFFER_INFO
+ # [ 0,2] dwSize.X
+ # [ 2,2] dwSize.Y
+ # [ 4,2] dwCursorPositions.X
+ # [ 6,2] dwCursorPositions.Y
+ # [ 8,2] wAttributes
+ # [10,2] srWindow.Left
+ # [12,2] srWindow.Top
+ # [14,2] srWindow.Right
+ # [16,2] srWindow.Bottom
+ # [18,2] dwMaximumWindowSize.X
+ # [20,2] dwMaximumWindowSize.Y
csbi = 0.chr * 22
return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
csbi
@@ -300,8 +343,8 @@ class Reline::Windows
unless csbi = get_console_screen_buffer_info
return Reline::CursorPos.new(0, 0)
end
- x = csbi[4, 2].unpack1('s*')
- y = csbi[6, 2].unpack1('s*')
+ x = csbi[4, 2].unpack1('s')
+ y = csbi[6, 2].unpack1('s')
Reline::CursorPos.new(x, y)
end
@@ -341,26 +384,45 @@ class Reline::Windows
end
def self.scroll_down(val)
- return if val.zero?
- screen_height = get_screen_size.first
- val = screen_height - 1 if val > (screen_height - 1)
- scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
- destination_origin = 0 # y * 65536 + x
- fill = [' '.ord, 0].pack('SS')
- @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
+ return if val < 0
+ return unless csbi = get_console_screen_buffer_info
+ buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
+ screen_height = window_bottom - window_top + 1
+ val = screen_height if val > screen_height
+
+ if @@legacy_console || window_left != 0
+ # unless ENABLE_VIRTUAL_TERMINAL,
+ # if srWindow.Left != 0 then it's conhost.exe hosted console
+ # and puts "\n" causes horizontal scroll. its glitch.
+ # FYI irb write from culumn 1, so this gives no gain.
+ scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
+ destination_origin = 0 # y * 65536 + x
+ fill = [' '.ord, attributes].pack('SS')
+ @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
+ else
+ origin_x = x + 1
+ origin_y = y - window_top + 1
+ @@output.write [
+ (origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
+ "\n" * val,
+ (origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
+ ].join
+ end
end
def self.clear_screen
- return unless csbi = get_console_screen_buffer_info
- buffer_width = csbi[0, 2].unpack1('S')
- attributes = csbi[8, 2].unpack1('S')
- _window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
- fill_length = buffer_width * (window_bottom - window_top + 1)
- screen_topleft = window_top * 65536
- written = 0.chr * 4
- @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
- @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
+ if @@legacy_console
+ return unless csbi = get_console_screen_buffer_info
+ buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
+ fill_length = buffer_width * (window_bottom - window_top + 1)
+ screen_topleft = window_top * 65536
+ written = 0.chr * 4
+ @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
+ @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
+ @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
+ else
+ @@output.write "\e[2J" "\e[H"
+ end
end
def self.set_screen_size(rows, columns)
diff --git a/lib/resolv.rb b/lib/resolv.rb
index b69c7045ca9..61c9c7d5cf1 100644
--- a/lib/resolv.rb
+++ b/lib/resolv.rb
@@ -2464,7 +2464,7 @@ class Resolv
Regex_8HexLinkLocal = /\A
[Ff][Ee]80
(?::[0-9A-Fa-f]{1,4}){7}
- %[0-9A-Za-z]+
+ %[-0-9A-Za-z._~]+
\z/x
##
@@ -2478,7 +2478,7 @@ class Resolv
|
:((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
)?
- :[0-9A-Fa-f]{1,4}%[0-9A-Za-z.]+
+ :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+
\z/x
##
diff --git a/lib/rinda/rinda.gemspec b/lib/rinda/rinda.gemspec
index 1cc5a453d8f..0c13e3c2df1 100644
--- a/lib/rinda/rinda.gemspec
+++ b/lib/rinda/rinda.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "rinda"
- spec.version = "0.1.0"
+ spec.version = "0.1.1"
spec.authors = ["Masatoshi SEKI"]
spec.email = ["seki@ruby-lang.org"]
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 80708e2aa00..26fb93cbfbb 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -8,7 +8,7 @@
require 'rbconfig'
module Gem
- VERSION = "3.3.0.dev".freeze
+ VERSION = "3.4.0.dev".freeze
end
# Must be first since it unloads the prelude from 1.9.2
@@ -163,16 +163,6 @@ module Gem
specifications/default
].freeze
- ##
- # Exception classes used in a Gem.read_binary +rescue+ statement
-
- READ_BINARY_ERRORS = [Errno::EACCES, Errno::EROFS, Errno::ENOSYS, Errno::ENOTSUP].freeze
-
- ##
- # Exception classes used in Gem.write_binary +rescue+ statement
-
- WRITE_BINARY_ERRORS = [Errno::ENOSYS, Errno::ENOTSUP].freeze
-
@@win_platform = nil
@configuration = nil
@@ -272,9 +262,6 @@ module Gem
unless spec = specs.first
msg = "can't find gem #{dep} with executable #{exec_name}"
- if dep.filters_bundler? && bundler_message = Gem::BundlerVersionFinder.missing_version_message
- msg = bundler_message
- end
raise Gem::GemNotFoundException, msg
end
@@ -779,40 +766,42 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
# Safely read a file in binary mode on all platforms.
def self.read_binary(path)
- File.open path, 'rb+' do |f|
- f.flock(File::LOCK_EX)
- f.read
+ open_with_flock(path, 'rb+') do |io|
+ io.read
end
- rescue *READ_BINARY_ERRORS
- File.open path, 'rb' do |f|
- f.read
- end
- rescue Errno::ENOLCK # NFS
- if Thread.main != Thread.current
- raise
- else
- File.open path, 'rb' do |f|
- f.read
- end
+ rescue Errno::EACCES, Errno::EROFS
+ open_with_flock(path, 'rb') do |io|
+ io.read
end
end
##
# Safely write a file in binary mode on all platforms.
def self.write_binary(path, data)
- File.open(path, 'wb') do |io|
- begin
- io.flock(File::LOCK_EX)
- rescue *WRITE_BINARY_ERRORS
- end
+ open_with_flock(path, 'wb') do |io|
io.write data
end
+ end
+
+ ##
+ # Open a file with given flags, and protect access with flock
+
+ def self.open_with_flock(path, flags, &block)
+ File.open(path, flags) do |io|
+ if !java_platform? && !solaris_platform?
+ begin
+ io.flock(File::LOCK_EX)
+ rescue Errno::ENOSYS, Errno::ENOTSUP
+ end
+ end
+ yield io
+ end
rescue Errno::ENOLCK # NFS
if Thread.main != Thread.current
raise
else
- File.open(path, 'wb') do |io|
- io.write data
+ File.open(path, flags) do |io|
+ yield io
end
end
end
@@ -854,7 +843,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
fetcher = Gem::SpecFetcher.fetcher
spec_tuples, = fetcher.spec_for_dependency dependency
- spec, = spec_tuples.first
+ spec, = spec_tuples.last
spec
end
@@ -1027,6 +1016,13 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
end
##
+ # Is this platform Solaris?
+
+ def self.solaris_platform?
+ RUBY_PLATFORM =~ /solaris/
+ end
+
+ ##
# Load +plugins+ as Ruby files
def self.load_plugin_files(plugins) # :nodoc:
@@ -1293,7 +1289,12 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
end
def default_gem_load_paths
- @default_gem_load_paths ||= $LOAD_PATH[load_path_insert_index..-1]
+ @default_gem_load_paths ||= $LOAD_PATH[load_path_insert_index..-1].map do |lp|
+ expanded = File.expand_path(lp)
+ next expanded unless File.exist?(expanded)
+
+ File.realpath(expanded)
+ end
end
end
@@ -1310,19 +1311,18 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
autoload :Licenses, File.expand_path('rubygems/util/licenses', __dir__)
autoload :NameTuple, File.expand_path('rubygems/name_tuple', __dir__)
autoload :PathSupport, File.expand_path('rubygems/path_support', __dir__)
- autoload :Platform, File.expand_path('rubygems/platform', __dir__)
autoload :RequestSet, File.expand_path('rubygems/request_set', __dir__)
- autoload :Requirement, File.expand_path('rubygems/requirement', __dir__)
autoload :Resolver, File.expand_path('rubygems/resolver', __dir__)
autoload :Source, File.expand_path('rubygems/source', __dir__)
autoload :SourceList, File.expand_path('rubygems/source_list', __dir__)
autoload :SpecFetcher, File.expand_path('rubygems/spec_fetcher', __dir__)
- autoload :Specification, File.expand_path('rubygems/specification', __dir__)
+ autoload :SpecificationPolicy, File.expand_path('rubygems/specification_policy', __dir__)
autoload :Util, File.expand_path('rubygems/util', __dir__)
autoload :Version, File.expand_path('rubygems/version', __dir__)
end
require_relative 'rubygems/exceptions'
+require_relative 'rubygems/specification'
# REFACTOR: This should be pulled out into some kind of hacks file.
begin
diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb
index 9ce0a2378e0..14179aebf34 100644
--- a/lib/rubygems/bundler_version_finder.rb
+++ b/lib/rubygems/bundler_version_finder.rb
@@ -2,48 +2,18 @@
module Gem::BundlerVersionFinder
def self.bundler_version
- version, _ = bundler_version_with_reason
+ v = ENV["BUNDLER_VERSION"]
- return unless version
+ v ||= bundle_update_bundler_version
+ return if v == true
- Gem::Version.new(version)
- end
-
- def self.bundler_version_with_reason
- if v = ENV["BUNDLER_VERSION"]
- return [v, "`$BUNDLER_VERSION`"]
- end
- if v = bundle_update_bundler_version
- return if v == true
- return [v, "`bundle update --bundler`"]
- end
- v, lockfile = lockfile_version
- if v
- return [v, "your #{lockfile}"]
- end
- end
+ v ||= lockfile_version
+ return unless v
- def self.missing_version_message
- return unless vr = bundler_version_with_reason
- <<-EOS
-Could not find 'bundler' (#{vr.first}) required by #{vr.last}.
-To update to the latest version installed on your system, run `bundle update --bundler`.
-To install the missing version, run `gem install bundler:#{vr.first}`
- EOS
+ Gem::Version.new(v)
end
- def self.compatible?(spec)
- return true unless spec.name == "bundler".freeze
- return true unless bundler_version = self.bundler_version
-
- spec.version.segments.first == bundler_version.segments.first
- end
-
- def self.filter!(specs)
- return unless bundler_version = self.bundler_version
-
- specs.reject! {|spec| spec.version.segments.first != bundler_version.segments.first }
-
+ def self.prioritize!(specs)
exact_match_index = specs.find_index {|spec| spec.version == bundler_version }
return unless exact_match_index
@@ -68,12 +38,10 @@ To install the missing version, run `gem install bundler:#{vr.first}`
private_class_method :bundle_update_bundler_version
def self.lockfile_version
- return unless lockfile = lockfile_contents
- lockfile, contents = lockfile
- lockfile ||= "lockfile"
+ return unless contents = lockfile_contents
regexp = /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
return unless contents =~ regexp
- [$1, lockfile]
+ $1
end
private_class_method :lockfile_version
@@ -103,7 +71,7 @@ To install the missing version, run `gem install bundler:#{vr.first}`
return unless File.file?(lockfile)
- [lockfile, File.read(lockfile)]
+ File.read(lockfile)
end
private_class_method :lockfile_contents
end
diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb
index 303f54a7d79..abdaa8e7c60 100644
--- a/lib/rubygems/command.rb
+++ b/lib/rubygems/command.rb
@@ -5,7 +5,7 @@
# See LICENSE.txt for permissions.
#++
-require 'optparse'
+require_relative 'optparse'
require_relative 'requirement'
require_relative 'user_interaction'
@@ -19,7 +19,7 @@ require_relative 'user_interaction'
class Gem::Command
include Gem::UserInteraction
- OptionParser.accept Symbol do |value|
+ Gem::OptionParser.accept Symbol do |value|
value.to_sym
end
@@ -344,7 +344,7 @@ class Gem::Command
##
# Add a command-line option and handler to the command.
#
- # See OptionParser#make_switch for an explanation of +opts+.
+ # See Gem::OptionParser#make_switch for an explanation of +opts+.
#
# +handler+ will be called with two values, the value of the argument and
# the options hash.
@@ -540,7 +540,7 @@ class Gem::Command
# command.
def create_option_parser
- @parser = OptionParser.new
+ @parser = Gem::OptionParser.new
add_parser_options
diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb
index 39bf8bd30d1..03cdd6a4bb9 100644
--- a/lib/rubygems/command_manager.rb
+++ b/lib/rubygems/command_manager.rb
@@ -73,7 +73,9 @@ class Gem::CommandManager
].freeze
ALIAS_COMMANDS = {
- 'i' => 'install',
+ 'i' => 'install',
+ 'login' => 'signin',
+ 'logout' => 'signout',
}.freeze
##
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index bdfeb0ba6e8..b59564d5759 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -43,10 +43,15 @@ class Gem::Commands::CertCommand < Gem::Command
options[:key] = open_private_key(key_file)
end
+ add_option('-A', '--key-algorithm ALGORITHM',
+ 'Select which key algorithm to use for --build') do |algorithm, options|
+ options[:key_algorithm] = algorithm
+ end
+
add_option('-s', '--sign CERT',
'Signs CERT with the key from -K',
'and the certificate from -C') do |cert_file, options|
- raise OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless
+ raise Gem::OptionParser::InvalidArgument, "#{cert_file}: does not exist" unless
File.file? cert_file
options[:sign] << cert_file
@@ -80,23 +85,23 @@ class Gem::Commands::CertCommand < Gem::Command
check_openssl
OpenSSL::X509::Certificate.new File.read certificate_file
rescue Errno::ENOENT
- raise OptionParser::InvalidArgument, "#{certificate_file}: does not exist"
+ raise Gem::OptionParser::InvalidArgument, "#{certificate_file}: does not exist"
rescue OpenSSL::X509::CertificateError
- raise OptionParser::InvalidArgument,
+ raise Gem::OptionParser::InvalidArgument,
"#{certificate_file}: invalid X509 certificate"
end
def open_private_key(key_file)
check_openssl
passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- key = OpenSSL::PKey::RSA.new File.read(key_file), passphrase
- raise OptionParser::InvalidArgument,
+ key = OpenSSL::PKey.read File.read(key_file), passphrase
+ raise Gem::OptionParser::InvalidArgument,
"#{key_file}: private key not found" unless key.private?
key
rescue Errno::ENOENT
- raise OptionParser::InvalidArgument, "#{key_file}: does not exist"
- rescue OpenSSL::PKey::RSAError
- raise OptionParser::InvalidArgument, "#{key_file}: invalid RSA key"
+ raise Gem::OptionParser::InvalidArgument, "#{key_file}: does not exist"
+ rescue OpenSSL::PKey::PKeyError, ArgumentError
+ raise Gem::OptionParser::InvalidArgument, "#{key_file}: invalid RSA, DSA, or EC key"
end
def execute
@@ -170,7 +175,8 @@ class Gem::Commands::CertCommand < Gem::Command
raise Gem::CommandLineError,
"Passphrase and passphrase confirmation don't match" unless passphrase == passphrase_confirmation
- key = Gem::Security.create_key
+ algorithm = options[:key_algorithm] || Gem::Security::DEFAULT_KEY_ALGORITHM
+ key = Gem::Security.create_key(algorithm)
key_path = Gem::Security.write key, "gem-private_key.pem", 0600, passphrase
return key, key_path
@@ -255,13 +261,14 @@ For further reading on signing gems see `ri Gem::Security`.
key_file = File.join Gem.default_key_path
key = File.read key_file
passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- options[:key] = OpenSSL::PKey::RSA.new key, passphrase
+ options[:key] = OpenSSL::PKey.read key, passphrase
+
rescue Errno::ENOENT
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem does not exist"
terminate_interaction 1
- rescue OpenSSL::PKey::RSAError
+ rescue OpenSSL::PKey::PKeyError
alert_error \
"--private-key not specified and ~/.gem/gem-private_key.pem is not valid"
diff --git a/lib/rubygems/commands/fetch_command.rb b/lib/rubygems/commands/fetch_command.rb
index 619f56a5da6..c8ecb0d48c4 100644
--- a/lib/rubygems/commands/fetch_command.rb
+++ b/lib/rubygems/commands/fetch_command.rb
@@ -8,7 +8,12 @@ class Gem::Commands::FetchCommand < Gem::Command
include Gem::VersionOption
def initialize
- super 'fetch', 'Download a gem and place it in the current directory'
+ defaults = {
+ :suggest_alternate => true,
+ :version => Gem::Requirement.default,
+ }
+
+ super 'fetch', 'Download a gem and place it in the current directory', defaults
add_bulk_threshold_option
add_proxy_option
@@ -18,6 +23,10 @@ class Gem::Commands::FetchCommand < Gem::Command
add_version_option
add_platform_option
add_prerelease_option
+
+ add_option '--[no-]suggestions', 'Suggest alternates when gems are not found' do |value, options|
+ options[:suggest_alternate] = value
+ end
end
def arguments # :nodoc:
@@ -42,15 +51,27 @@ then repackaging it.
"#{program_name} GEMNAME [GEMNAME ...]"
end
+ def check_version # :nodoc:
+ if options[:version] != Gem::Requirement.default and
+ get_all_gem_names.size > 1
+ alert_error "Can't use --version with multiple gems. You can specify multiple gems with" \
+ " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ terminate_interaction 1
+ end
+ end
+
def execute
- version = options[:version] || Gem::Requirement.default
+ check_version
+ version = options[:version]
platform = Gem.platforms.last
- gem_names = get_all_gem_names
+ gem_names = get_all_gem_names_and_versions
- gem_names.each do |gem_name|
- dep = Gem::Dependency.new gem_name, version
+ gem_names.each do |gem_name, gem_version|
+ gem_version ||= version
+ dep = Gem::Dependency.new gem_name, gem_version
dep.prerelease = options[:prerelease]
+ suppress_suggestions = !options[:suggest_alternate]
specs_and_sources, errors =
Gem::SpecFetcher.fetcher.spec_for_dependency dep
@@ -60,15 +81,13 @@ then repackaging it.
specs_and_sources = filtered unless filtered.empty?
end
- spec, source = specs_and_sources.max_by {|s,| s.version }
+ spec, source = specs_and_sources.max_by {|s,| s }
if spec.nil?
- show_lookup_failure gem_name, version, errors, options[:domain]
+ show_lookup_failure gem_name, gem_version, errors, suppress_suggestions, options[:domain]
next
end
-
source.download spec
-
say "Downloaded #{spec.full_name}"
end
end
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index 41547ce73bf..13979b0a590 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -50,6 +50,11 @@ class Gem::Commands::PristineCommand < Gem::Command
options[:env_shebang] = value
end
+ add_option('-i', '--install-dir DIR',
+ 'Gem repository to get binstubs and plugins installed') do |value, options|
+ options[:install_dir] = File.expand_path(value)
+ end
+
add_option('-n', '--bindir DIR',
'Directory where executables are',
'located') do |value, options|
@@ -163,11 +168,12 @@ extensions will be restored.
end
bin_dir = options[:bin_dir] if options[:bin_dir]
+ install_dir = options[:install_dir] if options[:install_dir]
installer_options = {
:wrappers => true,
:force => true,
- :install_dir => spec.base_dir,
+ :install_dir => install_dir || spec.base_dir,
:env_shebang => env_shebang,
:build_args => spec.build_args,
:bin_dir => bin_dir,
@@ -177,7 +183,7 @@ extensions will be restored.
installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_bin
elsif options[:only_plugins]
- installer = Gem::Installer.for_spec(spec)
+ installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_plugins
else
installer = Gem::Installer.at(gem, installer_options)
diff --git a/lib/rubygems/commands/server_command.rb b/lib/rubygems/commands/server_command.rb
index f3c08efb5eb..f8cad3b5db7 100644
--- a/lib/rubygems/commands/server_command.rb
+++ b/lib/rubygems/commands/server_command.rb
@@ -1,88 +1,25 @@
# frozen_string_literal: true
require_relative '../command'
-require_relative '../server'
-require_relative '../deprecate'
-class Gem::Commands::ServerCommand < Gem::Command
- extend Gem::Deprecate
- rubygems_deprecate_command
-
- def initialize
- super 'server', 'Documentation and gem repository HTTP server',
- :port => 8808, :gemdir => [], :daemon => false
-
- OptionParser.accept :Port do |port|
- if port =~ /\A\d+\z/
- port = Integer port
- raise OptionParser::InvalidArgument, "#{port}: not a port number" if
- port > 65535
-
- port
- else
- begin
- Socket.getservbyname port
- rescue SocketError
- raise OptionParser::InvalidArgument, "#{port}: no such named service"
- end
+unless defined? Gem::Commands::ServerCommand
+ class Gem::Commands::ServerCommand < Gem::Command
+ def initialize
+ super('server', 'Starts up a web server that hosts the RDoc (requires rubygems-server)')
+ begin
+ Gem::Specification.find_by_name('rubygems-server').activate
+ rescue Gem::LoadError
+ # no-op
end
end
- add_option '-p', '--port=PORT', :Port,
- 'port to listen on' do |port, options|
- options[:port] = port
- end
-
- add_option '-d', '--dir=GEMDIR',
- 'directories from which to serve gems',
- 'multiple directories may be provided' do |gemdir, options|
- options[:gemdir] << File.expand_path(gemdir)
+ def description # :nodoc:
+ <<-EOF
+The server command has been moved to the rubygems-server gem.
+ EOF
end
- add_option '--[no-]daemon', 'run as a daemon' do |daemon, options|
- options[:daemon] = daemon
+ def execute
+ alert_error "Install the rubygems-server gem for the server command"
end
-
- add_option '-b', '--bind=HOST,HOST',
- 'addresses to bind', Array do |address, options|
- options[:addresses] ||= []
- options[:addresses].push(*address)
- end
-
- add_option '-l', '--launch[=COMMAND]',
- 'launches a browser window',
- "COMMAND defaults to 'start' on Windows",
- "and 'open' on all other platforms" do |launch, options|
- launch ||= Gem.win_platform? ? 'start' : 'open'
- options[:launch] = launch
- end
- end
-
- def defaults_str # :nodoc:
- "--port 8808 --dir #{Gem.dir} --no-daemon"
- end
-
- def description # :nodoc:
- <<-EOF
-The server command starts up a web server that hosts the RDoc for your
-installed gems and can operate as a server for installation of gems on other
-machines.
-
-The cache files for installed gems must exist to use the server as a source
-for gem installation.
-
-To install gems from a running server, use `gem install GEMNAME --source
-http://gem_server_host:8808`
-
-You can set up a shortcut to gem server documentation using the URL:
-
- http://localhost:8808/rdoc?q=%s - Firefox
- http://localhost:8808/rdoc?q=* - LaunchBar
-
- EOF
- end
-
- def execute
- options[:gemdir] = Gem.path if options[:gemdir].empty?
- Gem::Server.run options
end
end
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 00953a8f4b1..0601dccb075 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -12,8 +12,6 @@ class Gem::Commands::SetupCommand < Gem::Command
ENV_PATHS = %w[/usr/bin/env /bin/env].freeze
def initialize
- require 'tmpdir'
-
super 'setup', 'Install RubyGems',
:format_executable => false, :document => %w[ri],
:force => true,
@@ -149,13 +147,6 @@ By default, this RubyGems will install gem as:
def execute
@verbose = Gem.configuration.really_verbose
- install_destdir = options[:destdir]
-
- unless install_destdir.empty?
- ENV['GEM_HOME'] ||= File.join(install_destdir,
- Gem.default_dir.gsub(/^[a-zA-Z]:/, ''))
- end
-
check_ruby_version
require 'fileutils'
@@ -166,8 +157,8 @@ By default, this RubyGems will install gem as:
end
extend MakeDirs
- lib_dir, bin_dir = make_destination_dirs install_destdir
- man_dir = generate_default_man_dir install_destdir
+ lib_dir, bin_dir = make_destination_dirs
+ man_dir = generate_default_man_dir
install_lib lib_dir
@@ -189,8 +180,8 @@ By default, this RubyGems will install gem as:
say "RubyGems #{Gem::VERSION} installed"
- regenerate_binstubs if options[:regenerate_binstubs]
- regenerate_plugins if options[:regenerate_plugins]
+ regenerate_binstubs(bin_dir) if options[:regenerate_binstubs]
+ regenerate_plugins(bin_dir) if options[:regenerate_plugins]
uninstall_old_gemcutter
@@ -258,35 +249,34 @@ By default, this RubyGems will install gem as:
say "Installing #{tool} executable" if @verbose
Dir.chdir path do
- bin_files = Dir['*']
+ bin_file = "gem"
- bin_files -= %w[update_rubygems]
+ require 'tmpdir'
- bin_files.each do |bin_file|
- dest_file = target_bin_path(bin_dir, bin_file)
- bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
+ dest_file = target_bin_path(bin_dir, bin_file)
+ bin_tmp_file = File.join Dir.tmpdir, "#{bin_file}.#{$$}"
- begin
- bin = File.readlines bin_file
- bin[0] = shebang
+ begin
+ bin = File.readlines bin_file
+ bin[0] = shebang
- File.open bin_tmp_file, 'w' do |fp|
- fp.puts bin.join
- end
-
- install bin_tmp_file, dest_file, :mode => prog_mode
- bin_file_names << dest_file
- ensure
- rm bin_tmp_file
+ File.open bin_tmp_file, 'w' do |fp|
+ fp.puts bin.join
end
- next unless Gem.win_platform?
+ install bin_tmp_file, dest_file, :mode => prog_mode
+ bin_file_names << dest_file
+ ensure
+ rm bin_tmp_file
+ end
+
+ next unless Gem.win_platform?
- begin
- bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
+ begin
+ bin_cmd_file = File.join Dir.tmpdir, "#{bin_file}.bat"
- File.open bin_cmd_file, 'w' do |file|
- file.puts <<-TEXT
+ File.open bin_cmd_file, 'w' do |file|
+ file.puts <<-TEXT
@ECHO OFF
IF NOT "%~f0" == "~f0" GOTO :WinNT
@"#{File.basename(Gem.ruby).chomp('"')}" "#{dest_file}" %1 %2 %3 %4 %5 %6 %7 %8 %9
@@ -294,12 +284,11 @@ By default, this RubyGems will install gem as:
:WinNT
@"#{File.basename(Gem.ruby).chomp('"')}" "%~dpn0" %*
TEXT
- end
-
- install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode
- ensure
- rm bin_cmd_file
end
+
+ install bin_cmd_file, "#{dest_file}.bat", :mode => prog_mode
+ ensure
+ rm bin_cmd_file
end
end
end
@@ -371,8 +360,7 @@ By default, this RubyGems will install gem as:
end
def install_default_bundler_gem(bin_dir)
- specs_dir = Gem.default_specifications_dir
- specs_dir = File.join(options[:destdir], specs_dir) unless Gem.win_platform?
+ specs_dir = File.join(default_dir, "specifications", "default")
mkdir_p specs_dir, :mode => 0755
bundler_spec = Dir.chdir("bundler") { Gem::Specification.load("bundler.gemspec") }
@@ -387,8 +375,20 @@ By default, this RubyGems will install gem as:
bundler_spec = Gem::Specification.load(default_spec_path)
+ # The base_dir value for a specification is inferred by walking up from the
+ # folder where the spec was `loaded_from`. In the case of default gems, we
+ # walk up two levels, because they live at `specifications/default/`, whereas
+ # in the case of regular gems we walk up just one level because they live at
+ # `specifications/`. However, in this case, the gem we are installing is
+ # misdetected as a regular gem, when it's a default gem in reality. This is
+ # because when there's a `:destdir`, the `loaded_from` path has changed and
+ # doesn't match `Gem.default_specifications_dir` which is the criteria to
+ # tag a gem as a default gem. So, in that case, write the correct
+ # `@base_dir` directly.
+ bundler_spec.instance_variable_set(:@base_dir, File.dirname(File.dirname(specs_dir)))
+
# Remove gemspec that was same version of vendored bundler.
- normal_gemspec = File.join(Gem.default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec")
+ normal_gemspec = File.join(default_dir, "specifications", "bundler-#{bundler_spec.version}.gemspec")
if File.file? normal_gemspec
File.delete normal_gemspec
end
@@ -401,7 +401,6 @@ By default, this RubyGems will install gem as:
end
bundler_bin_dir = bundler_spec.bin_dir
- bundler_bin_dir = File.join(options[:destdir], bundler_bin_dir) unless Gem.win_platform?
mkdir_p bundler_bin_dir, :mode => 0755
bundler_spec.executables.each do |e|
cp File.join("bundler", bundler_spec.bindir, e), File.join(bundler_bin_dir, e)
@@ -412,8 +411,16 @@ By default, this RubyGems will install gem as:
Dir.chdir("bundler") do
built_gem = Gem::Package.build(bundler_spec)
begin
- 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, wrappers: true)
- installer.install
+ 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
ensure
FileUtils.rm_f built_gem
end
@@ -424,11 +431,11 @@ By default, this RubyGems will install gem as:
say "Bundler #{bundler_spec.version} installed"
end
- def make_destination_dirs(install_destdir)
+ def make_destination_dirs
lib_dir, bin_dir = Gem.default_rubygems_dirs
unless lib_dir
- lib_dir, bin_dir = generate_default_dirs(install_destdir)
+ lib_dir, bin_dir = generate_default_dirs
end
mkdir_p lib_dir, :mode => 0755
@@ -437,7 +444,7 @@ By default, this RubyGems will install gem as:
return lib_dir, bin_dir
end
- def generate_default_man_dir(install_destdir)
+ def generate_default_man_dir
prefix = options[:prefix]
if prefix.empty?
@@ -447,14 +454,10 @@ By default, this RubyGems will install gem as:
man_dir = File.join prefix, 'man'
end
- unless install_destdir.empty?
- man_dir = File.join install_destdir, man_dir.gsub(/^[a-zA-Z]:/, '')
- end
-
- man_dir
+ prepend_destdir_if_present(man_dir)
end
- def generate_default_dirs(install_destdir)
+ def generate_default_dirs
prefix = options[:prefix]
site_or_vendor = options[:site_or_vendor]
@@ -466,12 +469,7 @@ By default, this RubyGems will install gem as:
bin_dir = File.join prefix, 'bin'
end
- unless install_destdir.empty?
- lib_dir = File.join install_destdir, lib_dir.gsub(/^[a-zA-Z]:/, '')
- bin_dir = File.join install_destdir, bin_dir.gsub(/^[a-zA-Z]:/, '')
- end
-
- [lib_dir, bin_dir]
+ [prepend_destdir_if_present(lib_dir), prepend_destdir_if_present(bin_dir)]
end
def files_in(dir)
@@ -592,11 +590,12 @@ abort "#{deprecation_message}"
rescue Gem::InstallError
end
- def regenerate_binstubs
+ def regenerate_binstubs(bindir)
require_relative "pristine_command"
say "Regenerating binstubs"
args = %w[--all --only-executables --silent]
+ args << "--bindir=#{bindir}"
if options[:env_shebang]
args << "--env-shebang"
end
@@ -605,11 +604,13 @@ abort "#{deprecation_message}"
command.invoke(*args)
end
- def regenerate_plugins
+ def regenerate_plugins(bindir)
require_relative "pristine_command"
say "Regenerating plugins"
args = %w[--all --only-plugins --silent]
+ args << "--bindir=#{bindir}"
+ args << "--install-dir=#{default_dir}"
command = Gem::Commands::PristineCommand.new
command.invoke(*args)
@@ -617,6 +618,25 @@ abort "#{deprecation_message}"
private
+ def default_dir
+ prefix = options[:prefix]
+
+ if prefix.empty?
+ dir = Gem.default_dir
+ else
+ dir = prefix
+ end
+
+ prepend_destdir_if_present(dir)
+ end
+
+ def prepend_destdir_if_present(path)
+ destdir = options[:destdir]
+ return path if destdir.empty?
+
+ File.join(options[:destdir], path.gsub(/^[a-zA-Z]:/, ''))
+ end
+
def install_file_list(files, dest_dir)
files.each do |file|
install_file file, dest_dir
diff --git a/lib/rubygems/commands/uninstall_command.rb b/lib/rubygems/commands/uninstall_command.rb
index 20781b224d5..467c8bf7ed6 100644
--- a/lib/rubygems/commands/uninstall_command.rb
+++ b/lib/rubygems/commands/uninstall_command.rb
@@ -81,7 +81,7 @@ class Gem::Commands::UninstallCommand < Gem::Command
'Uninstall gem from the vendor directory.',
'Only for use by gem repackagers.') do |value, options|
unless Gem.vendor_dir
- raise OptionParser::InvalidOption.new 'your platform is not supported'
+ raise Gem::OptionParser::InvalidOption.new 'your platform is not supported'
end
alert_warning 'Use your OS package manager to uninstall vendor gems'
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index d104488c943..99208e5cb2d 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -29,7 +29,7 @@ class Gem::Commands::UpdateCommand < Gem::Command
add_install_update_options
- OptionParser.accept Gem::Version do |value|
+ Gem::OptionParser.accept Gem::Version do |value|
Gem::Version.new value
value
@@ -286,10 +286,11 @@ command to remove old versions.
check_oldest_rubygems version
- update_gem 'rubygems-update', version
-
installed_gems = Gem::Specification.find_all_by_name 'rubygems-update', requirement
- version = installed_gems.first.version
+ installed_gems = update_gem('rubygems-update', version) if installed_gems.empty? || installed_gems.first.version != version
+ return if installed_gems.empty?
+
+ version = installed_gems.first.version
install_rubygems version
end
diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb
index 3721204ab2c..36403623643 100644
--- a/lib/rubygems/dependency.rb
+++ b/lib/rubygems/dependency.rb
@@ -277,7 +277,7 @@ class Gem::Dependency
requirement.satisfied_by?(spec.version) && env_req.satisfied_by?(spec.version)
end.map(&:to_spec)
- Gem::BundlerVersionFinder.filter!(matches) if filters_bundler?
+ Gem::BundlerVersionFinder.prioritize!(matches) if prioritizes_bundler?
if platform_only
matches.reject! do |spec|
@@ -295,7 +295,7 @@ class Gem::Dependency
@requirement.specific?
end
- def filters_bundler?
+ def prioritizes_bundler?
name == "bundler".freeze && !specific?
end
@@ -325,11 +325,11 @@ class Gem::Dependency
active = matches.find {|spec| spec.activated? }
return active if active
- return matches.first if prerelease?
-
- # Move prereleases to the end of the list for >= 0 requirements
- pre, matches = matches.partition {|spec| spec.version.prerelease? }
- matches += pre if requirement == Gem::Requirement.default
+ unless prerelease?
+ # Move prereleases to the end of the list for >= 0 requirements
+ pre, matches = matches.partition {|spec| spec.version.prerelease? }
+ matches += pre if requirement == Gem::Requirement.default
+ end
matches.first
end
diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb
index 8f61869874d..10e08fc703c 100644
--- a/lib/rubygems/dependency_list.rb
+++ b/lib/rubygems/dependency_list.rb
@@ -5,7 +5,7 @@
# See LICENSE.txt for permissions.
#++
-require 'tsort'
+require_relative 'tsort'
require_relative 'deprecate'
##
@@ -20,7 +20,7 @@ class Gem::DependencyList
attr_reader :specs
include Enumerable
- include TSort
+ include Gem::TSort
##
# Allows enabling/disabling use of development dependencies
diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb
index 86f0d1da14b..f115ce23d0b 100644
--- a/lib/rubygems/errors.rb
+++ b/lib/rubygems/errors.rb
@@ -59,9 +59,6 @@ module Gem
private
def build_message
- if name == "bundler" && message = Gem::BundlerVersionFinder.missing_version_message
- return message
- end
names = specs.map(&:full_name)
"Could not find '#{name}' (#{requirement}) - did find: [#{names.join ','}]\n"
end
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index f0debb646ff..18068690980 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -24,10 +24,14 @@ class Gem::UnknownCommandError < Gem::Exception
return if defined?(@attached)
if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable)
- DidYouMean::SPELL_CHECKERS['Gem::UnknownCommandError'] =
- Gem::UnknownCommandSpellChecker
+ if DidYouMean.respond_to?(:correct_error)
+ DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker)
+ else
+ DidYouMean::SPELL_CHECKERS['Gem::UnknownCommandError'] =
+ Gem::UnknownCommandSpellChecker
- prepend DidYouMean::Correctable
+ prepend DidYouMean::Correctable
+ end
end
@attached = true
@@ -284,3 +288,4 @@ end
# Backwards compatible typo'd exception class for early RubyGems 2.0.x
Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError # :nodoc:
+Gem.deprecate_constant :UnsatisfiableDepedencyError
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index 7934309901c..6d32be6515b 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -24,13 +24,14 @@ class Gem::Ext::Builder
# try to find make program from Ruby configure arguments first
RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
- make_program = ENV['MAKE'] || ENV['make'] || $1
- unless make_program
- make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
+ make_program_name = ENV['MAKE'] || ENV['make'] || $1
+ unless make_program_name
+ make_program_name = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
end
- make_program = Shellwords.split(make_program)
+ make_program = Shellwords.split(make_program_name)
- destdir = 'DESTDIR=%s' % ENV['DESTDIR']
+ # 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'] != "") ? 'DESTDIR=%s' % ENV['DESTDIR'] : ''
['clean', '', 'install'].each do |target|
# Pass DESTDIR via command line to override what's in MAKEFLAGS
diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb
index 269e876cfac..e47cabef842 100644
--- a/lib/rubygems/ext/cmake_builder.rb
+++ b/lib/rubygems/ext/cmake_builder.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative '../command'
class Gem::Ext::CmakeBuilder < Gem::Ext::Builder
def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd)
unless File.exist?(File.join(cmake_dir, 'Makefile'))
+ require_relative '../command'
cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args]
run cmd, results, class_name, cmake_dir
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index cafa63544f9..2203b17f8e2 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -51,7 +51,7 @@ module Gem::InstallUpdateOptions
'Install gem into the vendor directory.',
'Only for use by gem repackagers.') do |value, options|
unless Gem.vendor_dir
- raise OptionParser::InvalidOption.new 'your platform is not supported'
+ raise Gem::OptionParser::InvalidOption.new 'your platform is not supported'
end
options[:vendor] = true
@@ -143,7 +143,7 @@ module Gem::InstallUpdateOptions
unless v
message = v ? v : "(tried #{Gem::GEM_DEP_FILES.join ', '})"
- raise OptionParser::InvalidArgument,
+ raise Gem::OptionParser::InvalidArgument,
"cannot find gem dependencies file #{message}"
end
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index cf5ed7b5024..4cda09f200b 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -5,7 +5,6 @@
# See LICENSE.txt for permissions.
#++
-require_relative 'command'
require_relative 'installer_uninstaller_utils'
require_relative 'exceptions'
require_relative 'deprecate'
@@ -68,19 +67,28 @@ class Gem::Installer
@path_warning = false
- @install_lock = Thread::Mutex.new
-
class << self
- ##
- # True if we've warned about PATH not including Gem.bindir
+ #
+ # 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.
- attr_accessor :path_warning
+ def inherited(klass)
+ if klass.name == "Bundler::RubyGemsGemInstaller"
+ require "rubygems/command"
+ end
+
+ super(klass)
+ end
##
- # Certain aspects of the install process are not thread-safe. This lock is
- # used to allow multiple threads to install Gems at the same time.
+ # True if we've warned about PATH not including Gem.bindir
- attr_reader :install_lock
+ attr_accessor :path_warning
##
# Overrides the executable format.
@@ -193,8 +201,8 @@ class Gem::Installer
#
# If +@force+ is set +filename+ is overwritten.
#
- # If +filename+ exists and is a RubyGems wrapper for different gem the user
- # is consulted.
+ # If +filename+ exists and it is a RubyGems wrapper for a different gem, then
+ # the user is consulted.
#
# If +filename+ exists and +@bin_dir+ is Gem.default_bindir (/usr/local) the
# user is consulted.
@@ -212,7 +220,17 @@ class Gem::Installer
existing = nil
File.open generated_bin, 'rb' do |io|
- next unless io.gets =~ /^#!/ # shebang
+ line = io.gets
+ shebang = /^#!.*ruby/
+
+ if load_relative_enabled?
+ until line.nil? || line =~ shebang do
+ line = io.gets
+ end
+ end
+
+ next unless line =~ shebang
+
io.gets # blankline
# TODO detect a specially formatted comment instead of trying
@@ -285,8 +303,6 @@ class Gem::Installer
def install
pre_install_checks
- FileUtils.rm_f File.join gem_home, 'specifications', spec.spec_name
-
run_pre_install_hooks
# Set loaded_from to ensure extension_dir is correct
@@ -326,7 +342,7 @@ class Gem::Installer
say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil?
- Gem::Installer.install_lock.synchronize { Gem::Specification.reset }
+ Gem::Specification.reset
run_post_install_hooks
@@ -440,13 +456,9 @@ class Gem::Installer
# specifications directory.
def write_spec
- File.open spec_file, 'w' do |file|
- spec.installed_by_version = Gem.rubygems_version
-
- file.puts spec.to_ruby_for_cache
+ spec.installed_by_version = Gem.rubygems_version
- file.fsync rescue nil # for filesystems without fsync(2)
- end
+ Gem.write_binary(spec_file, spec.to_ruby_for_cache)
end
##
@@ -454,9 +466,7 @@ class Gem::Installer
# specifications/default directory.
def write_default_spec
- File.open(default_spec_file, "w") do |file|
- file.puts spec.to_ruby
- end
+ Gem.write_binary(default_spec_file, spec.to_ruby)
end
##
@@ -511,7 +521,7 @@ class Gem::Installer
end
def generate_plugins # :nodoc:
- latest = Gem::Installer.install_lock.synchronize { Gem::Specification.latest_spec_for(spec.name) }
+ latest = Gem::Specification.latest_spec_for(spec.name)
return if latest && latest.version > spec.version
ensure_writable_dir @plugins_dir
@@ -585,7 +595,6 @@ class Gem::Installer
#
def shebang(bin_file_name)
- ruby_name = RbConfig::CONFIG['ruby_install_name'] if @env_shebang
path = File.join gem_dir, spec.bindir, bin_file_name
first_line = File.open(path, "rb") {|file| file.gets } || ""
@@ -598,7 +607,7 @@ class Gem::Installer
if which = Gem.configuration[:custom_shebang]
# replace bin_file_name with "ruby" to avoid endless loops
- which = which.gsub(/ #{bin_file_name}$/," #{RbConfig::CONFIG['ruby_install_name']}")
+ which = which.gsub(/ #{bin_file_name}$/," #{ruby_install_name}")
which = which.gsub(/\$(\w+)/) do
case $1
@@ -614,14 +623,12 @@ class Gem::Installer
end
"#!#{which}"
- elsif not ruby_name
- "#!#{Gem.ruby}#{opts}"
- elsif opts
- "#!/bin/sh\n'exec' #{ruby_name.dump} '-x' \"$0\" \"$@\"\n#{shebang}"
- else
+ elsif @env_shebang
# Create a plain shebang line.
@env_path ||= ENV_PATHS.find {|env_path| File.executable? env_path }
- "#!#{@env_path} #{ruby_name}"
+ "#!#{@env_path} #{ruby_install_name}"
+ else
+ "#{bash_prolog_script}#!#{Gem.ruby}#{opts}"
end
end
@@ -676,7 +683,7 @@ class Gem::Installer
@development = options[:development]
@build_root = options[:build_root]
- @build_args = options[:build_args] || Gem::Command.build_args
+ @build_args = options[:build_args]
unless @build_root.nil?
@bin_dir = File.join(@build_root, @bin_dir.gsub(/^[a-zA-Z]:/, ''))
@@ -768,7 +775,7 @@ str = ARGV.first
if str
str = str.b[/\\A_(.*)_\\z/, 1]
if str and Gem::Version.correct?(str)
- version = str
+ #{explicit_version_requirement(spec.name)}
ARGV.shift
end
end
@@ -791,11 +798,20 @@ Gem.use_gemdeps
TEXT
end
+ def explicit_version_requirement(name)
+ code = "version = str"
+ return code unless name == "bundler"
+
+ code += <<-TEXT
+
+ ENV['BUNDLER_VERSION'] = str
+TEXT
+ end
+
##
# return the stub script text used to launch the true Ruby script
def windows_stub_script(bindir, bin_file_name)
- rb_config = RbConfig::CONFIG
rb_topdir = RbConfig::TOPDIR || File.dirname(rb_config["bindir"])
# get ruby executable file name from RbConfig
@@ -832,7 +848,7 @@ TEXT
# configure scripts and rakefiles or mkrf_conf files.
def build_extensions
- builder = Gem::Ext::Builder.new spec, @build_args
+ builder = Gem::Ext::Builder.new spec, build_args
builder.build_extensions
end
@@ -919,7 +935,7 @@ TEXT
# extensions.
def write_build_info_file
- return if @build_args.empty?
+ return if build_args.empty?
build_info_dir = File.join gem_home, 'build_info'
@@ -929,7 +945,7 @@ TEXT
build_info_file = File.join build_info_dir, "#{spec.full_name}.info"
File.open build_info_file, 'w' do |io|
- @build_args.each do |arg|
+ build_args.each do |arg|
io.puts arg
end
end
@@ -954,4 +970,46 @@ TEXT
raise Gem::FilePermissionError.new(dir) unless File.writable? dir
end
+
+ private
+
+ def build_args
+ @build_args ||= begin
+ require_relative "command"
+ Gem::Command.build_args
+ end
+ end
+
+ def rb_config
+ RbConfig::CONFIG
+ end
+
+ def ruby_install_name
+ rb_config["ruby_install_name"]
+ end
+
+ def load_relative_enabled?
+ rb_config["LIBRUBY_RELATIVE"] == 'yes'
+ end
+
+ def bash_prolog_script
+ if load_relative_enabled?
+ script = +<<~EOS
+ bindir="${0%/*}"
+ EOS
+
+ script << %Q(exec "$bindir/#{ruby_install_name}" "-x" "$0" "$@"\n)
+
+ <<~EOS
+ #!/bin/sh
+ # -*- ruby -*-
+ _=_\\
+ =begin
+ #{script.chomp}
+ =end
+ EOS
+ else
+ ""
+ end
+ end
end
diff --git a/lib/rubygems/local_remote_options.rb b/lib/rubygems/local_remote_options.rb
index 8acd98d2e42..0b8b0ee1a60 100644
--- a/lib/rubygems/local_remote_options.rb
+++ b/lib/rubygems/local_remote_options.rb
@@ -14,14 +14,14 @@ require_relative '../rubygems'
module Gem::LocalRemoteOptions
##
- # Allows OptionParser to handle HTTP URIs.
+ # Allows Gem::OptionParser to handle HTTP URIs.
def accept_uri_http
- OptionParser.accept URI::HTTP do |value|
+ Gem::OptionParser.accept URI::HTTP do |value|
begin
uri = URI.parse value
rescue URI::InvalidURIError
- raise OptionParser::InvalidArgument, value
+ raise Gem::OptionParser::InvalidArgument, value
end
valid_uri_schemes = ["http", "https", "file", "s3"]
diff --git a/lib/rubygems/optparse.rb b/lib/rubygems/optparse.rb
new file mode 100644
index 00000000000..65be9f6b74b
--- /dev/null
+++ b/lib/rubygems/optparse.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+require_relative 'optparse/lib/optparse'
diff --git a/lib/rubygems/optparse/.document b/lib/rubygems/optparse/.document
new file mode 100644
index 00000000000..0c43bbd6b38
--- /dev/null
+++ b/lib/rubygems/optparse/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/optparse/lib/optionparser.rb b/lib/rubygems/optparse/lib/optionparser.rb
new file mode 100644
index 00000000000..4b9b40d82a7
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optionparser.rb
@@ -0,0 +1,2 @@
+# frozen_string_literal: false
+require_relative 'optparse'
diff --git a/lib/rubygems/optparse/lib/optparse.rb b/lib/rubygems/optparse/lib/optparse.rb
new file mode 100644
index 00000000000..e4b1c61f79d
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse.rb
@@ -0,0 +1,2230 @@
+# frozen_string_literal: true
+#
+# optparse.rb - command-line option analysis with the Gem::OptionParser class.
+#
+# Author:: Nobu Nakada
+# Documentation:: Nobu Nakada and Gavin Sinclair.
+#
+# See Gem::OptionParser for documentation.
+#
+
+
+#--
+# == Developer Documentation (not for RDoc output)
+#
+# === Class tree
+#
+# - Gem::OptionParser:: front end
+# - Gem::OptionParser::Switch:: each switches
+# - Gem::OptionParser::List:: options list
+# - Gem::OptionParser::ParseError:: errors on parsing
+# - Gem::OptionParser::AmbiguousOption
+# - Gem::OptionParser::NeedlessArgument
+# - Gem::OptionParser::MissingArgument
+# - Gem::OptionParser::InvalidOption
+# - Gem::OptionParser::InvalidArgument
+# - Gem::OptionParser::AmbiguousArgument
+#
+# === Object relationship diagram
+#
+# +--------------+
+# | Gem::OptionParser |<>-----+
+# +--------------+ | +--------+
+# | ,-| Switch |
+# on_head -------->+---------------+ / +--------+
+# accept/reject -->| List |<|>-
+# | |<|>- +----------+
+# on ------------->+---------------+ `-| argument |
+# : : | class |
+# +---------------+ |==========|
+# on_tail -------->| | |pattern |
+# +---------------+ |----------|
+# Gem::OptionParser.accept ->| DefaultList | |converter |
+# reject |(shared between| +----------+
+# | all instances)|
+# +---------------+
+#
+#++
+#
+# == Gem::OptionParser
+#
+# === New to \Gem::OptionParser?
+#
+# See the {Tutorial}[./doc/optparse/tutorial_rdoc.html].
+#
+# === Introduction
+#
+# Gem::OptionParser is a class for command-line option analysis. It is much more
+# advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented
+# solution.
+#
+# === Features
+#
+# 1. The argument specification and the code to handle it are written in the
+# same place.
+# 2. It can output an option summary; you don't need to maintain this string
+# separately.
+# 3. Optional and mandatory arguments are specified very gracefully.
+# 4. Arguments can be automatically converted to a specified class.
+# 5. Arguments can be restricted to a certain set.
+#
+# All of these features are demonstrated in the examples below. See
+# #make_switch for full documentation.
+#
+# === Minimal example
+#
+# require 'rubygems/optparse/lib/optparse'
+#
+# options = {}
+# Gem::OptionParser.new do |parser|
+# parser.banner = "Usage: example.rb [options]"
+#
+# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
+# options[:verbose] = v
+# end
+# end.parse!
+#
+# p options
+# p ARGV
+#
+# === Generating Help
+#
+# Gem::OptionParser can be used to automatically generate help for the commands you
+# write:
+#
+# require 'rubygems/optparse/lib/optparse'
+#
+# Options = Struct.new(:name)
+#
+# class Parser
+# def self.parse(options)
+# args = Options.new("world")
+#
+# opt_parser = Gem::OptionParser.new do |parser|
+# parser.banner = "Usage: example.rb [options]"
+#
+# parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n|
+# args.name = n
+# end
+#
+# parser.on("-h", "--help", "Prints this help") do
+# puts parser
+# exit
+# end
+# end
+#
+# opt_parser.parse!(options)
+# return args
+# end
+# end
+# options = Parser.parse %w[--help]
+#
+# #=>
+# # Usage: example.rb [options]
+# # -n, --name=NAME Name to say hello to
+# # -h, --help Prints this help
+#
+# === Required Arguments
+#
+# For options that require an argument, option specification strings may include an
+# option name in all caps. If an option is used without the required argument,
+# an exception will be raised.
+#
+# require 'rubygems/optparse/lib/optparse'
+#
+# options = {}
+# Gem::OptionParser.new do |parser|
+# parser.on("-r", "--require LIBRARY",
+# "Require the LIBRARY before executing your script") do |lib|
+# puts "You required #{lib}!"
+# end
+# end.parse!
+#
+# Used:
+#
+# $ ruby optparse-test.rb -r
+# optparse-test.rb:9:in `<main>': missing argument: -r (Gem::OptionParser::MissingArgument)
+# $ ruby optparse-test.rb -r my-library
+# You required my-library!
+#
+# === Type Coercion
+#
+# Gem::OptionParser supports the ability to coerce command line arguments
+# into objects for us.
+#
+# Gem::OptionParser comes with a few ready-to-use kinds of type
+# coercion. They are:
+#
+# - Date -- Anything accepted by +Date.parse+
+# - DateTime -- Anything accepted by +DateTime.parse+
+# - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+
+# - URI -- Anything accepted by +URI.parse+
+# - Shellwords -- Anything accepted by +Shellwords.shellwords+
+# - String -- Any non-empty string
+# - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040)
+# - Float -- Any float. (e.g. 10, 3.14, -100E+13)
+# - Numeric -- Any integer, float, or rational (1, 3.4, 1/3)
+# - DecimalInteger -- Like +Integer+, but no octal format.
+# - OctalInteger -- Like +Integer+, but no decimal format.
+# - DecimalNumeric -- Decimal integer or float.
+# - TrueClass -- Accepts '+, yes, true, -, no, false' and
+# defaults as +true+
+# - FalseClass -- Same as +TrueClass+, but defaults to +false+
+# - Array -- Strings separated by ',' (e.g. 1,2,3)
+# - Regexp -- Regular expressions. Also includes options.
+#
+# We can also add our own coercions, which we will cover below.
+#
+# ==== Using Built-in Conversions
+#
+# As an example, the built-in +Time+ conversion is used. The other built-in
+# conversions behave in the same way.
+# Gem::OptionParser will attempt to parse the argument
+# as a +Time+. If it succeeds, that time will be passed to the
+# handler block. Otherwise, an exception will be raised.
+#
+# require 'rubygems/optparse/lib/optparse'
+# require 'rubygems/optparse/lib/optparse/time'
+# Gem::OptionParser.new do |parser|
+# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
+# p time
+# end
+# end.parse!
+#
+# Used:
+#
+# $ ruby optparse-test.rb -t nonsense
+# ... invalid argument: -t nonsense (Gem::OptionParser::InvalidArgument)
+# $ ruby optparse-test.rb -t 10-11-12
+# 2010-11-12 00:00:00 -0500
+# $ ruby optparse-test.rb -t 9:30
+# 2014-08-13 09:30:00 -0400
+#
+# ==== Creating Custom Conversions
+#
+# The +accept+ method on Gem::OptionParser may be used to create converters.
+# It specifies which conversion block to call whenever a class is specified.
+# The example below uses it to fetch a +User+ object before the +on+ handler receives it.
+#
+# require 'rubygems/optparse/lib/optparse'
+#
+# User = Struct.new(:id, :name)
+#
+# def find_user id
+# not_found = ->{ raise "No User Found for id #{id}" }
+# [ User.new(1, "Sam"),
+# User.new(2, "Gandalf") ].find(not_found) do |u|
+# u.id == id
+# end
+# end
+#
+# op = Gem::OptionParser.new
+# op.accept(User) do |user_id|
+# find_user user_id.to_i
+# end
+#
+# op.on("--user ID", User) do |user|
+# puts user
+# end
+#
+# op.parse!
+#
+# Used:
+#
+# $ ruby optparse-test.rb --user 1
+# #<struct User id=1, name="Sam">
+# $ 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)
+#
+# === Store options to a Hash
+#
+# The +into+ option of +order+, +parse+ and so on methods stores command line options into a Hash.
+#
+# require 'rubygems/optparse/lib/optparse'
+#
+# options = {}
+# Gem::OptionParser.new do |parser|
+# parser.on('-a')
+# parser.on('-b NUM', Integer)
+# parser.on('-v', '--verbose')
+# end.parse!(into: options)
+#
+# p options
+#
+# Used:
+#
+# $ ruby optparse-test.rb -a
+# {:a=>true}
+# $ ruby optparse-test.rb -a -v
+# {:a=>true, :verbose=>true}
+# $ ruby optparse-test.rb -a -b 100
+# {:a=>true, :b=>100}
+#
+# === Complete example
+#
+# The following example is a complete Ruby program. You can run it and see the
+# effect of specifying various options. This is probably the best way to learn
+# the features of +optparse+.
+#
+# require 'rubygems/optparse/lib/optparse'
+# require 'rubygems/optparse/lib/optparse/time'
+# require 'ostruct'
+# require 'pp'
+#
+# class OptparseExample
+# Version = '1.0.0'
+#
+# CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
+# CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
+#
+# class ScriptOptions
+# attr_accessor :library, :inplace, :encoding, :transfer_type,
+# :verbose, :extension, :delay, :time, :record_separator,
+# :list
+#
+# def initialize
+# self.library = []
+# self.inplace = false
+# self.encoding = "utf8"
+# self.transfer_type = :auto
+# self.verbose = false
+# end
+#
+# def define_options(parser)
+# parser.banner = "Usage: example.rb [options]"
+# parser.separator ""
+# parser.separator "Specific options:"
+#
+# # add additional options
+# perform_inplace_option(parser)
+# delay_execution_option(parser)
+# execute_at_time_option(parser)
+# specify_record_separator_option(parser)
+# list_example_option(parser)
+# specify_encoding_option(parser)
+# optional_option_argument_with_keyword_completion_option(parser)
+# boolean_verbose_option(parser)
+#
+# parser.separator ""
+# parser.separator "Common options:"
+# # No argument, shows at tail. This will print an options summary.
+# # Try it and see!
+# parser.on_tail("-h", "--help", "Show this message") do
+# puts parser
+# exit
+# end
+# # Another typical switch to print the version.
+# parser.on_tail("--version", "Show version") do
+# puts Version
+# exit
+# end
+# end
+#
+# def perform_inplace_option(parser)
+# # Specifies an optional option argument
+# parser.on("-i", "--inplace [EXTENSION]",
+# "Edit ARGV files in place",
+# "(make backup if EXTENSION supplied)") do |ext|
+# self.inplace = true
+# self.extension = ext || ''
+# self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot.
+# end
+# end
+#
+# def delay_execution_option(parser)
+# # Cast 'delay' argument to a Float.
+# parser.on("--delay N", Float, "Delay N seconds before executing") do |n|
+# self.delay = n
+# end
+# end
+#
+# def execute_at_time_option(parser)
+# # Cast 'time' argument to a Time object.
+# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
+# self.time = time
+# end
+# end
+#
+# def specify_record_separator_option(parser)
+# # Cast to octal integer.
+# parser.on("-F", "--irs [OCTAL]", Gem::OptionParser::OctalInteger,
+# "Specify record separator (default \\0)") do |rs|
+# self.record_separator = rs
+# end
+# end
+#
+# def list_example_option(parser)
+# # List of arguments.
+# parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list|
+# self.list = list
+# end
+# end
+#
+# def specify_encoding_option(parser)
+# # Keyword completion. We are specifying a specific set of arguments (CODES
+# # and CODE_ALIASES - notice the latter is a Hash), and the user may provide
+# # the shortest unambiguous text.
+# code_list = (CODE_ALIASES.keys + CODES).join(', ')
+# parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding",
+# "(#{code_list})") do |encoding|
+# self.encoding = encoding
+# end
+# end
+#
+# def optional_option_argument_with_keyword_completion_option(parser)
+# # Optional '--type' option argument with keyword completion.
+# parser.on("--type [TYPE]", [:text, :binary, :auto],
+# "Select transfer type (text, binary, auto)") do |t|
+# self.transfer_type = t
+# end
+# end
+#
+# def boolean_verbose_option(parser)
+# # Boolean switch.
+# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
+# self.verbose = v
+# end
+# end
+# end
+#
+# #
+# # Return a structure describing the options.
+# #
+# def parse(args)
+# # The options specified on the command line will be collected in
+# # *options*.
+#
+# @options = ScriptOptions.new
+# @args = Gem::OptionParser.new do |parser|
+# @options.define_options(parser)
+# parser.parse!(args)
+# end
+# @options
+# end
+#
+# attr_reader :parser, :options
+# end # class OptparseExample
+#
+# example = OptparseExample.new
+# options = example.parse(ARGV)
+# pp options # example.options
+# pp ARGV
+#
+# === Shell Completion
+#
+# For modern shells (e.g. bash, zsh, etc.), you can use shell
+# completion for command line options.
+#
+# === Further documentation
+#
+# The above examples, along with the accompanying
+# {Tutorial}[./doc/optparse/tutorial_rdoc.html],
+# should be enough to learn how to use this class.
+# If you have any questions, file a ticket at http://bugs.ruby-lang.org.
+#
+class Gem::OptionParser
+ Gem::OptionParser::Version = "0.2.0"
+
+ # :stopdoc:
+ NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
+ RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze
+ OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze
+ # :startdoc:
+
+ #
+ # Keyword completion module. This allows partial arguments to be specified
+ # and resolved against a list of acceptable values.
+ #
+ module Completion
+ def self.regexp(key, icase)
+ Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase)
+ end
+
+ def self.candidate(key, icase = false, pat = nil, &block)
+ pat ||= Completion.regexp(key, icase)
+ candidates = []
+ block.call do |k, *v|
+ (if Regexp === k
+ kn = ""
+ k === key
+ else
+ kn = defined?(k.id2name) ? k.id2name : k
+ pat === kn
+ end) or next
+ v << k if v.empty?
+ candidates << [k, v, kn]
+ end
+ candidates
+ end
+
+ def candidate(key, icase = false, pat = nil)
+ 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
+ canon, sw, * = candidates[0]
+ elsif candidates.size > 1
+ canon, sw, cn = candidates.shift
+ candidates.each do |k, v, kn|
+ next if sw == v
+ if String === cn and String === kn
+ if cn.rindex(kn, 0)
+ canon, sw, cn = k, v, kn
+ next
+ elsif kn.rindex(cn, 0)
+ next
+ end
+ end
+ throw :ambiguous, key
+ end
+ end
+ if canon
+ block_given? or return key, *sw
+ yield(key, *sw)
+ end
+ end
+
+ def convert(opt = nil, val = nil, *)
+ val
+ end
+ end
+
+
+ #
+ # Map from option/keyword string to object with completion.
+ #
+ class OptionMap < Hash
+ include Completion
+ end
+
+
+ #
+ # Individual switch class. Not important to the user.
+ #
+ # Defined within Switch are several Switch-derived classes: NoArgument,
+ # RequiredArgument, etc.
+ #
+ class Switch
+ attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block
+
+ #
+ # Guesses argument style from +arg+. Returns corresponding
+ # Gem::OptionParser::Switch class (OptionalArgument, etc.).
+ #
+ def self.guess(arg)
+ case arg
+ when ""
+ t = self
+ when /\A=?\[/
+ t = Switch::OptionalArgument
+ when /\A\s+\[/
+ t = Switch::PlacedArgument
+ else
+ t = Switch::RequiredArgument
+ end
+ self >= t or incompatible_argument_styles(arg, t)
+ t
+ end
+
+ def self.incompatible_argument_styles(arg, t)
+ raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}",
+ ParseError.filter_backtrace(caller(2)))
+ end
+
+ def self.pattern
+ NilClass
+ end
+
+ def initialize(pattern = nil, conv = nil,
+ short = nil, long = nil, arg = nil,
+ desc = ([] if short or long), block = nil, &_block)
+ raise if Array === pattern
+ block ||= _block
+ @pattern, @conv, @short, @long, @arg, @desc, @block =
+ pattern, conv, short, long, arg, desc, block
+ end
+
+ #
+ # 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:
+ pattern or return nil, [arg]
+ unless m = pattern.match(arg)
+ yield(InvalidArgument, arg)
+ return arg, []
+ end
+ if String === m
+ m = [s = m]
+ else
+ m = m.to_a
+ s = m[0]
+ return nil, m unless String === s
+ end
+ raise InvalidArgument, arg unless arg.rindex(s, 0)
+ return nil, m if s.length == arg.length
+ 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:
+ if conv
+ val = conv.call(*val)
+ else
+ val = proc {|v| v}.call(*val)
+ end
+ return arg, block, val
+ end
+ private :conv_arg
+
+ #
+ # Produces the summary text. Each line of the summary is yielded to the
+ # block (without newline).
+ #
+ # +sdone+:: Already summarized short style options keyed hash.
+ # +ldone+:: Already summarized long style options keyed hash.
+ # +width+:: Width of left side (option part). In other words, the right
+ # side (description part) starts after +width+ columns.
+ # +max+:: Maximum width of left side -> the options are filled within
+ # +max+ columns.
+ # +indent+:: Prefix string indents all summarized lines.
+ #
+ def summarize(sdone = {}, ldone = {}, width = 1, max = width - 1, indent = "")
+ sopts, lopts = [], [], nil
+ @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
+ @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
+ return if sopts.empty? and lopts.empty? # completely hidden
+
+ left = [sopts.join(', ')]
+ right = desc.dup
+
+ while s = lopts.shift
+ l = left[-1].length + s.length
+ l += arg.length if left.size == 1 && arg
+ l < max or sopts.empty? or left << +''
+ left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s
+ end
+
+ if arg
+ left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg)
+ end
+ mlen = left.collect {|ss| ss.length}.max.to_i
+ while mlen > width and l = left.shift
+ mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen
+ if l.length < width and (r = right[0]) and !r.empty?
+ l = l.to_s.ljust(width) + ' ' + r
+ right.shift
+ end
+ yield(indent + l)
+ end
+
+ while begin l = left.shift; r = right.shift; l or r end
+ l = l.to_s.ljust(width) + ' ' + r if r and !r.empty?
+ yield(indent + l)
+ end
+
+ self
+ end
+
+ def add_banner(to) # :nodoc:
+ unless @short or @long
+ s = desc.join
+ to << " [" + s + "]..." unless s.empty?
+ end
+ to
+ end
+
+ def match_nonswitch?(str) # :nodoc:
+ @pattern =~ str unless @short or @long
+ end
+
+ #
+ # Main name of the switch.
+ #
+ def switch_name
+ (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '')
+ end
+
+ def compsys(sdone, ldone) # :nodoc:
+ sopts, lopts = [], []
+ @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
+ @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
+ return if sopts.empty? and lopts.empty? # completely hidden
+
+ (sopts+lopts).each do |opt|
+ # "(-x -c -r)-l[left justify]"
+ if /^--\[no-\](.+)$/ =~ opt
+ o = $1
+ yield("--#{o}", desc.join(""))
+ yield("--no-#{o}", desc.join(""))
+ else
+ yield("#{opt}", desc.join(""))
+ end
+ end
+ end
+
+ #
+ # Switch that takes no arguments.
+ #
+ class NoArgument < self
+
+ #
+ # Raises an exception if any arguments given.
+ #
+ def parse(arg, argv)
+ yield(NeedlessArgument, arg) if arg
+ conv_arg(arg)
+ end
+
+ def self.incompatible_argument_styles(*)
+ end
+
+ def self.pattern
+ Object
+ end
+ end
+
+ #
+ # Switch that takes an argument.
+ #
+ class RequiredArgument < self
+
+ #
+ # Raises an exception if argument is not present.
+ #
+ def parse(arg, argv)
+ unless arg
+ raise MissingArgument if argv.empty?
+ arg = argv.shift
+ end
+ conv_arg(*parse_arg(arg, &method(:raise)))
+ end
+ end
+
+ #
+ # Switch that can omit argument.
+ #
+ class OptionalArgument < self
+
+ #
+ # Parses argument if given, or uses default value.
+ #
+ def parse(arg, argv, &error)
+ if arg
+ conv_arg(*parse_arg(arg, &error))
+ else
+ conv_arg(arg)
+ end
+ end
+ end
+
+ #
+ # Switch that takes an argument, which does not begin with '-'.
+ #
+ class PlacedArgument < self
+
+ #
+ # Returns nil if argument is not present or begins with '-'.
+ #
+ def parse(arg, argv, &error)
+ if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
+ return nil, block, nil
+ end
+ opt = (val = parse_arg(val, &error))[1]
+ val = conv_arg(*val)
+ if opt and !arg
+ argv.shift
+ else
+ val[0] = nil
+ end
+ val
+ end
+ end
+ end
+
+ #
+ # Simple option list providing mapping from short and/or long option
+ # string to Gem::OptionParser::Switch and mapping from acceptable argument to
+ # matching pattern and converter pair. Also provides summary feature.
+ #
+ class List
+ # Map from acceptable argument types to pattern and converter pairs.
+ attr_reader :atype
+
+ # Map from short style option switches to actual switch objects.
+ attr_reader :short
+
+ # Map from long style option switches to actual switch objects.
+ attr_reader :long
+
+ # List of all switches and summary string.
+ attr_reader :list
+
+ #
+ # Just initializes all instance variables.
+ #
+ def initialize
+ @atype = {}
+ @short = OptionMap.new
+ @long = OptionMap.new
+ @list = []
+ end
+
+ #
+ # See Gem::OptionParser.accept.
+ #
+ def accept(t, pat = /.*/m, &block)
+ if pat
+ pat.respond_to?(:match) or
+ raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2))
+ else
+ pat = t if t.respond_to?(:match)
+ end
+ unless block
+ block = pat.method(:convert).to_proc if pat.respond_to?(:convert)
+ end
+ @atype[t] = [pat, block]
+ end
+
+ #
+ # See Gem::OptionParser.reject.
+ #
+ def reject(t)
+ @atype.delete(t)
+ end
+
+ #
+ # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+.
+ #
+ # +sw+:: Gem::OptionParser::Switch instance to be added.
+ # +sopts+:: Short style option list.
+ # +lopts+:: Long style option list.
+ # +nlopts+:: Negated long style options list.
+ #
+ 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
+ # and negated long options. Arguments are:
+ #
+ # +switch+:: Gem::OptionParser::Switch instance to be inserted.
+ # +short_opts+:: List of short style options.
+ # +long_opts+:: List of long style options.
+ # +nolong_opts+:: List of long style options with "no-" prefix.
+ #
+ # prepend(switch, short_opts, long_opts, nolong_opts)
+ #
+ def prepend(*args)
+ update(*args)
+ @list.unshift(args[0])
+ end
+
+ #
+ # Appends +switch+ at the tail of the list, and associates short, long
+ # and negated long options. Arguments are:
+ #
+ # +switch+:: Gem::OptionParser::Switch instance to be inserted.
+ # +short_opts+:: List of short style options.
+ # +long_opts+:: List of long style options.
+ # +nolong_opts+:: List of long style options with "no-" prefix.
+ #
+ # append(switch, short_opts, long_opts, nolong_opts)
+ #
+ def append(*args)
+ update(*args)
+ @list.push(args[0])
+ end
+
+ #
+ # Searches +key+ in +id+ list. The result is returned or yielded if a
+ # block is given. If it isn't found, nil is returned.
+ #
+ def search(id, key)
+ if list = __send__(id)
+ val = list.fetch(key) {return nil}
+ block_given? ? yield(val) : val
+ end
+ end
+
+ #
+ # Searches list +id+ for +opt+ and the optional patterns for completion
+ # +pat+. If +icase+ is true, the search is case insensitive. The result
+ # is returned or yielded if a block is given. If it isn't found, nil is
+ # returned.
+ #
+ def complete(id, opt, icase = false, *pat, &block)
+ __send__(id).complete(opt, icase, *pat, &block)
+ end
+
+ def get_candidates(id)
+ yield __send__(id).keys
+ end
+
+ #
+ # Iterates over each option, passing the option to the +block+.
+ #
+ def each_option(&block)
+ list.each(&block)
+ end
+
+ #
+ # Creates the summary table, passing each line to the +block+ (without
+ # newline). The arguments +args+ are passed along to the summarize
+ # method which is called on every option.
+ #
+ def summarize(*args, &block)
+ sum = []
+ list.reverse_each do |opt|
+ if opt.respond_to?(:summarize) # perhaps Gem::OptionParser::Switch
+ s = []
+ opt.summarize(*args) {|l| s << l}
+ sum.concat(s.reverse)
+ elsif !opt or opt.empty?
+ sum << ""
+ elsif opt.respond_to?(:each_line)
+ sum.concat([*opt.each_line].reverse)
+ else
+ sum.concat([*opt.each].reverse)
+ end
+ end
+ sum.reverse_each(&block)
+ end
+
+ def add_banner(to) # :nodoc:
+ list.each do |opt|
+ if opt.respond_to?(:add_banner)
+ opt.add_banner(to)
+ end
+ end
+ to
+ end
+
+ def compsys(*args, &block) # :nodoc:
+ list.each do |opt|
+ if opt.respond_to?(:compsys)
+ opt.compsys(*args, &block)
+ end
+ end
+ end
+ end
+
+ #
+ # Hash with completion search feature. See Gem::OptionParser::Completion.
+ #
+ class CompletingHash < Hash
+ include Completion
+
+ #
+ # Completion for hash key.
+ #
+ def match(key)
+ *values = fetch(key) {
+ raise AmbiguousArgument, catch(:ambiguous) {return complete(key)}
+ }
+ return key, *values
+ end
+ end
+
+ # :stopdoc:
+
+ #
+ # Enumeration of acceptable argument styles. Possible values are:
+ #
+ # NO_ARGUMENT:: The switch takes no arguments. (:NONE)
+ # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED)
+ # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL)
+ #
+ # Use like --switch=argument (long style) or -Xargument (short style). For
+ # short style, only portion matched to argument pattern is treated as
+ # argument.
+ #
+ ArgumentStyle = {}
+ NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument}
+ RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument}
+ OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument}
+ ArgumentStyle.freeze
+
+ #
+ # Switches common used such as '--', and also provides default
+ # argument classes
+ #
+ DefaultList = List.new
+ DefaultList.short['-'] = Switch::NoArgument.new {}
+ DefaultList.long[''] = Switch::NoArgument.new {throw :terminate}
+
+
+ COMPSYS_HEADER = <<'XXX' # :nodoc:
+
+typeset -A opt_args
+local context state line
+
+_arguments -s -S \
+XXX
+
+ def compsys(to, name = File.basename($0)) # :nodoc:
+ to << "#compdef #{name}\n"
+ to << COMPSYS_HEADER
+ visit(:compsys, {}, {}) {|o, d|
+ to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n]
+ }
+ to << " '*:file:_files' && return 0\n"
+ end
+
+ #
+ # Default options for ARGV, which never appear in option summary.
+ #
+ Officious = {}
+
+ #
+ # --help
+ # Shows option summary.
+ #
+ Officious['help'] = proc do |parser|
+ Switch::NoArgument.new do |arg|
+ puts parser.help
+ exit
+ end
+ end
+
+ #
+ # --*-completion-bash=WORD
+ # Shows candidates for command line completion.
+ #
+ Officious['*-completion-bash'] = proc do |parser|
+ Switch::RequiredArgument.new do |arg|
+ puts parser.candidate(arg)
+ exit
+ end
+ end
+
+ #
+ # --*-completion-zsh[=NAME:FILE]
+ # Creates zsh completion file.
+ #
+ Officious['*-completion-zsh'] = proc do |parser|
+ Switch::OptionalArgument.new do |arg|
+ parser.compsys(STDOUT, arg)
+ exit
+ end
+ end
+
+ #
+ # --version
+ # Shows version string if Version is defined.
+ #
+ Officious['version'] = proc do |parser|
+ Switch::OptionalArgument.new do |pkg|
+ if pkg
+ begin
+ require 'rubygems/optparse/lib/optparse/version'
+ rescue LoadError
+ else
+ show_version(*pkg.split(/,/)) or
+ abort("#{parser.program_name}: no version found in package #{pkg}")
+ exit
+ end
+ end
+ v = parser.ver or abort("#{parser.program_name}: version unknown")
+ puts v
+ exit
+ end
+ end
+
+ # :startdoc:
+
+ #
+ # Class methods
+ #
+
+ #
+ # Initializes a new instance and evaluates the optional block in context
+ # of the instance. Arguments +args+ are passed to #new, see there for
+ # description of parameters.
+ #
+ # This method is *deprecated*, its behavior corresponds to the older #new
+ # method.
+ #
+ def self.with(*args, &block)
+ opts = new(*args)
+ opts.instance_eval(&block)
+ opts
+ end
+
+ #
+ # Returns an incremented value of +default+ according to +arg+.
+ #
+ def self.inc(arg, default = nil)
+ case arg
+ when Integer
+ arg.nonzero?
+ when nil
+ default.to_i + 1
+ end
+ end
+ def inc(*args)
+ self.class.inc(*args)
+ end
+
+ #
+ # Initializes the instance and yields itself if called with a block.
+ #
+ # +banner+:: Banner message.
+ # +width+:: Summary width.
+ # +indent+:: Summary indent.
+ #
+ def initialize(banner = nil, width = 32, indent = ' ' * 4)
+ @stack = [DefaultList, List.new, List.new]
+ @program_name = nil
+ @banner = banner
+ @summary_width = width
+ @summary_indent = indent
+ @default_argv = ARGV
+ @require_exact = false
+ add_officious
+ yield self if block_given?
+ end
+
+ def add_officious # :nodoc:
+ list = base()
+ Officious.each do |opt, block|
+ list.long[opt] ||= block.call(self)
+ end
+ end
+
+ #
+ # Terminates option parsing. Optional parameter +arg+ is a string pushed
+ # back to be the first non-option argument.
+ #
+ def terminate(arg = nil)
+ self.class.terminate(arg)
+ end
+ def self.terminate(arg = nil)
+ throw :terminate, arg
+ end
+
+ @stack = [DefaultList]
+ def self.top() DefaultList end
+
+ #
+ # Directs to accept specified class +t+. The argument string is passed to
+ # the block in which it should be converted to the desired class.
+ #
+ # +t+:: Argument class specifier, any object including Class.
+ # +pat+:: Pattern for argument, defaults to +t+ if it responds to match.
+ #
+ # accept(t, pat, &block)
+ #
+ def accept(*args, &blk) top.accept(*args, &blk) end
+ #
+ # See #accept.
+ #
+ def self.accept(*args, &blk) top.accept(*args, &blk) end
+
+ #
+ # Directs to reject specified class argument.
+ #
+ # +t+:: Argument class specifier, any object including Class.
+ #
+ # reject(t)
+ #
+ def reject(*args, &blk) top.reject(*args, &blk) end
+ #
+ # See #reject.
+ #
+ def self.reject(*args, &blk) top.reject(*args, &blk) end
+
+ #
+ # Instance methods
+ #
+
+ # Heading banner preceding summary.
+ attr_writer :banner
+
+ # Program name to be emitted in error message and default banner,
+ # defaults to $0.
+ attr_writer :program_name
+
+ # Width for option list portion of summary. Must be Numeric.
+ attr_accessor :summary_width
+
+ # Indentation for summary. Must be String (or have + String method).
+ attr_accessor :summary_indent
+
+ # Strings to be parsed in default.
+ attr_accessor :default_argv
+
+ # Whether to require that options match exactly (disallows providing
+ # abbreviated long option as short option).
+ attr_accessor :require_exact
+
+ #
+ # Heading banner preceding summary.
+ #
+ def banner
+ unless @banner
+ @banner = +"Usage: #{program_name} [options]"
+ visit(:add_banner, @banner)
+ end
+ @banner
+ end
+
+ #
+ # Program name to be emitted in error message and default banner, defaults
+ # to $0.
+ #
+ def program_name
+ @program_name || File.basename($0, '.*')
+ end
+
+ # for experimental cascading :-)
+ alias set_banner banner=
+ alias set_program_name program_name=
+ alias set_summary_width summary_width=
+ alias set_summary_indent summary_indent=
+
+ # Version
+ attr_writer :version
+ # Release code
+ attr_writer :release
+
+ #
+ # Version
+ #
+ def version
+ (defined?(@version) && @version) || (defined?(::Version) && ::Version)
+ end
+
+ #
+ # Release code
+ #
+ def release
+ (defined?(@release) && @release) || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE)
+ end
+
+ #
+ # Returns version string from program_name, version and release.
+ #
+ def ver
+ if v = version
+ str = +"#{program_name} #{[v].join('.')}"
+ str << " (#{v})" if v = release
+ str
+ end
+ end
+
+ def warn(mesg = $!)
+ super("#{program_name}: #{mesg}")
+ end
+
+ def abort(mesg = $!)
+ super("#{program_name}: #{mesg}")
+ end
+
+ #
+ # Subject of #on / #on_head, #accept / #reject
+ #
+ def top
+ @stack[-1]
+ end
+
+ #
+ # Subject of #on_tail.
+ #
+ def base
+ @stack[1]
+ end
+
+ #
+ # Pushes a new List.
+ #
+ def new
+ @stack.push(List.new)
+ if block_given?
+ yield self
+ else
+ self
+ end
+ end
+
+ #
+ # Removes the last List.
+ #
+ def remove
+ @stack.pop
+ end
+
+ #
+ # Puts option summary into +to+ and returns +to+. Yields each line if
+ # a block is given.
+ #
+ # +to+:: Output destination, which must have method <<. Defaults to [].
+ # +width+:: Width of left side, defaults to @summary_width.
+ # +max+:: Maximum length allowed for left side, defaults to +width+ - 1.
+ # +indent+:: Indentation, defaults to @summary_indent.
+ #
+ def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk)
+ nl = "\n"
+ blk ||= proc {|l| to << (l.index(nl, -1) ? l : l + nl)}
+ visit(:summarize, {}, {}, width, max, indent, &blk)
+ to
+ end
+
+ #
+ # Returns option summary string.
+ #
+ def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end
+ alias to_s help
+
+ #
+ # Returns option summary list.
+ #
+ def to_a; summarize("#{banner}".split(/^/)) end
+
+ #
+ # Checks if an argument is given twice, in which case an ArgumentError is
+ # raised. Called from Gem::OptionParser#switch only.
+ #
+ # +obj+:: New argument.
+ # +prv+:: Previously specified argument.
+ # +msg+:: Exception message.
+ #
+ 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:
+
+ # :call-seq:
+ # make_switch(params, block = nil)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def make_switch(opts, block = nil)
+ short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], []
+ ldesc, sdesc, desc, arg = [], [], []
+ default_style = Switch::NoArgument
+ default_pattern = nil
+ klass = nil
+ q, a = nil
+ has_arg = false
+
+ opts.each do |o|
+ # argument class
+ next if search(:atype, o) do |pat, c|
+ klass = notwice(o, klass, 'type')
+ if not_style and not_style != Switch::NoArgument
+ not_pattern, not_conv = pat, c
+ else
+ default_pattern, conv = pat, c
+ end
+ end
+
+ # directly specified pattern(any object possible to match)
+ if (!(String === o || Symbol === o)) and o.respond_to?(:match)
+ pattern = notwice(o, pattern, 'pattern')
+ if pattern.respond_to?(:convert)
+ conv = pattern.method(:convert).to_proc
+ else
+ conv = SPLAT_PROC
+ end
+ next
+ end
+
+ # anything others
+ case o
+ when Proc, Method
+ block = notwice(o, block, 'block')
+ when Array, Hash
+ case pattern
+ when CompletingHash
+ when nil
+ pattern = CompletingHash.new
+ conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert)
+ else
+ raise ArgumentError, "argument pattern given twice"
+ end
+ o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}}
+ 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]*)(.+)?/
+ q, a = $1, $2
+ o = notwice(a ? Object : TrueClass, klass, 'type')
+ not_pattern, not_conv = search(:atype, o) unless not_style
+ not_style = (not_style || default_style).guess(arg = a) if a
+ default_style = Switch::NoArgument
+ default_pattern, conv = search(:atype, FalseClass) unless default_pattern
+ ldesc << "--no-#{q}"
+ (q = q.downcase).tr!('_', '-')
+ long << "no-#{q}"
+ nolong << q
+ when /^--\[no-\]([^\[\]=\s]*)(.+)?/
+ q, a = $1, $2
+ o = notwice(a ? Object : TrueClass, klass, 'type')
+ if a
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ end
+ ldesc << "--[no-]#{q}"
+ (o = q.downcase).tr!('_', '-')
+ long << o
+ not_pattern, not_conv = search(:atype, FalseClass) unless not_style
+ not_style = Switch::NoArgument
+ nolong << "no-#{o}"
+ when /^--([^\[\]=\s]*)(.+)?/
+ q, a = $1, $2
+ if a
+ o = notwice(NilClass, klass, 'type')
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ end
+ ldesc << "--#{q}"
+ (o = q.downcase).tr!('_', '-')
+ long << o
+ when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
+ q, a = $1, $2
+ o = notwice(Object, klass, 'type')
+ if a
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ else
+ has_arg = true
+ end
+ sdesc << "-#{q}"
+ short << Regexp.new(q)
+ when /^-(.)(.+)?/
+ q, a = $1, $2
+ if a
+ o = notwice(NilClass, klass, 'type')
+ default_style = default_style.guess(arg = a)
+ default_pattern, conv = search(:atype, o) unless default_pattern
+ end
+ sdesc << "-#{q}"
+ short << q
+ when /^=/
+ style = notwice(default_style.guess(arg = o), style, 'style')
+ default_pattern, conv = search(:atype, Object) unless default_pattern
+ else
+ desc.push(o)
+ end
+ end
+
+ default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
+ 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)
+ elsif !block
+ if style or pattern
+ raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
+ end
+ s = desc
+ else
+ short << pattern
+ s = (style || default_style).new(pattern,
+ conv, nil, nil, arg, desc, block)
+ end
+ return s, short, long,
+ (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
+ nolong
+ end
+
+ # :call-seq:
+ # define(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def define(*opts, &block)
+ top.append(*(sw = make_switch(opts, block)))
+ sw[0]
+ end
+
+ # :call-seq:
+ # on(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def on(*opts, &block)
+ define(*opts, &block)
+ self
+ end
+ alias def_option define
+
+ # :call-seq:
+ # define_head(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def define_head(*opts, &block)
+ top.prepend(*(sw = make_switch(opts, block)))
+ sw[0]
+ end
+
+ # :call-seq:
+ # on_head(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ # The new option is added at the head of the summary.
+ #
+ def on_head(*opts, &block)
+ define_head(*opts, &block)
+ self
+ end
+ alias def_head_option define_head
+
+ # :call-seq:
+ # define_tail(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ def define_tail(*opts, &block)
+ base.append(*(sw = make_switch(opts, block)))
+ sw[0]
+ end
+
+ #
+ # :call-seq:
+ # on_tail(*params, &block)
+ #
+ # :include: ../doc/optparse/creates_option.rdoc
+ #
+ # The new option is added at the tail of the summary.
+ #
+ def on_tail(*opts, &block)
+ define_tail(*opts, &block)
+ self
+ end
+ alias def_tail_option define_tail
+
+ #
+ # Add separator in summary.
+ #
+ def separator(string)
+ top.append(string, nil, nil)
+ end
+
+ #
+ # Parses command line arguments +argv+ in order. When a block is given,
+ # each non-option argument is yielded. When optional +into+ keyword
+ # argument is provided, the parsed option values are stored there via
+ # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other
+ # similar object).
+ #
+ # Returns the rest of +argv+ left unparsed.
+ #
+ def order(*argv, into: nil, &nonopt)
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
+ order!(argv, into: into, &nonopt)
+ end
+
+ #
+ # Same as #order, but removes switches destructively.
+ # Non-option arguments remain in +argv+.
+ #
+ def order!(argv = default_argv, into: nil, &nonopt)
+ setter = ->(name, val) {into[name.to_sym] = val} if into
+ parse_in_order(argv, setter, &nonopt)
+ end
+
+ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
+ opt, arg, val, rest = nil
+ nonopt ||= proc {|a| throw :terminate, a}
+ argv.unshift(arg) if arg = catch(:terminate) {
+ while arg = argv.shift
+ case arg
+ # long option
+ when /\A--([^=]*)(?:=(.*))?/m
+ opt, rest = $1, $2
+ opt.tr!('_', '-')
+ begin
+ sw, = complete(:long, opt, true)
+ if require_exact && !sw.long.include?(arg)
+ raise InvalidOption, arg
+ end
+ rescue ParseError
+ raise $!.set_option(arg, true)
+ end
+ begin
+ opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
+ val = cb.call(val) if cb
+ setter.call(sw.switch_name, val) if setter
+ rescue ParseError
+ raise $!.set_option(arg, rest)
+ end
+
+ # short option
+ when /\A-(.)((=).*|.+)?/m
+ eq, rest, opt = $3, $2, $1
+ has_arg, val = eq, rest
+ begin
+ sw, = search(:short, opt)
+ unless sw
+ begin
+ sw, = complete(:short, opt)
+ # short option matched.
+ val = arg.delete_prefix('-')
+ has_arg = true
+ rescue InvalidOption
+ raise if require_exact
+ # if no short options match, try completion with long
+ # options.
+ sw, = complete(:long, opt)
+ eq ||= !rest
+ end
+ end
+ rescue ParseError
+ raise $!.set_option(arg, true)
+ end
+ begin
+ opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
+ rescue ParseError
+ raise $!.set_option(arg, arg.length > 2)
+ else
+ raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
+ end
+ begin
+ argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
+ val = cb.call(val) if cb
+ setter.call(sw.switch_name, val) if setter
+ rescue ParseError
+ raise $!.set_option(arg, arg.length > 2)
+ end
+
+ # non-option argument
+ else
+ catch(:prune) do
+ visit(:each_option) do |sw0|
+ sw = sw0
+ sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg)
+ end
+ nonopt.call(arg)
+ end
+ end
+ end
+
+ nil
+ }
+
+ visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern}
+
+ argv
+ end
+ private :parse_in_order
+
+ #
+ # Parses command line arguments +argv+ in permutation mode and returns
+ # list of non-option arguments. When optional +into+ keyword
+ # argument is provided, the parsed option values are stored there via
+ # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other
+ # similar object).
+ #
+ def permute(*argv, into: nil)
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
+ permute!(argv, into: into)
+ end
+
+ #
+ # Same as #permute, but removes switches destructively.
+ # Non-option arguments remain in +argv+.
+ #
+ def permute!(argv = default_argv, into: nil)
+ nonopts = []
+ order!(argv, into: into, &nonopts.method(:<<))
+ argv[0, 0] = nonopts
+ argv
+ end
+
+ #
+ # Parses command line arguments +argv+ in order when environment variable
+ # POSIXLY_CORRECT is set, and in permutation mode otherwise.
+ # When optional +into+ keyword argument is provided, the parsed option
+ # values are stored there via <code>[]=</code> method (so it can be Hash,
+ # or OpenStruct, or other similar object).
+ #
+ def parse(*argv, into: nil)
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
+ parse!(argv, into: into)
+ end
+
+ #
+ # Same as #parse, but removes switches destructively.
+ # Non-option arguments remain in +argv+.
+ #
+ def parse!(argv = default_argv, into: nil)
+ if ENV.include?('POSIXLY_CORRECT')
+ order!(argv, into: into)
+ else
+ permute!(argv, into: into)
+ end
+ end
+
+ #
+ # Wrapper method for getopts.rb.
+ #
+ # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option")
+ # # params["a"] = true # -a
+ # # params["b"] = "1" # -b1
+ # # params["foo"] = "1" # --foo
+ # # params["bar"] = "x" # --bar x
+ # # params["zot"] = "z" # --zot Z
+ #
+ def getopts(*args)
+ argv = Array === args.first ? args.shift : default_argv
+ single_options, *long_options = *args
+
+ result = {}
+
+ single_options.scan(/(.)(:)?/) do |opt, val|
+ if val
+ result[opt] = nil
+ define("-#{opt} VAL")
+ else
+ result[opt] = false
+ define("-#{opt}")
+ end
+ end if single_options
+
+ long_options.each do |arg|
+ arg, desc = arg.split(';', 2)
+ opt, val = arg.split(':', 2)
+ if val
+ result[opt] = val.empty? ? nil : val
+ define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact)
+ else
+ result[opt] = false
+ define("--#{opt}", *[desc].compact)
+ end
+ end
+
+ parse_in_order(argv, result.method(:[]=))
+ result
+ end
+
+ #
+ # See #getopts.
+ #
+ def self.getopts(*args)
+ new.getopts(*args)
+ end
+
+ #
+ # Traverses @stack, sending each element method +id+ with +args+ and
+ # +block+.
+ #
+ 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:
+ 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
+ # canonical switch and switch descriptor Gem::OptionParser::Switch.
+ #
+ # +typ+:: Searching table.
+ # +opt+:: Searching key.
+ # +icase+:: Search case insensitive if true.
+ # +pat+:: Optional pattern for completion.
+ #
+ def complete(typ, opt, icase = false, *pat) # :nodoc:
+ if pat.empty?
+ search(typ, opt) {|sw| return [sw, opt]} # exact match or...
+ end
+ ambiguous = catch(:ambiguous) {
+ 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])
+ end
+ private :complete
+
+ #
+ # Returns additional info.
+ #
+ def additional_message(typ, opt)
+ return unless typ and opt and defined?(DidYouMean::SpellChecker)
+ all_candidates = []
+ visit(:get_candidates, typ) do |candidates|
+ all_candidates.concat(candidates)
+ end
+ all_candidates.select! {|cand| cand.is_a?(String) }
+ checker = DidYouMean::SpellChecker.new(dictionary: all_candidates)
+ suggestions = all_candidates & checker.correct(opt)
+ if DidYouMean.respond_to?(:formatter)
+ DidYouMean.formatter.message_for(suggestions)
+ else
+ "\nDid you mean? #{suggestions.join("\n ")}"
+ end
+ end
+
+ def candidate(word)
+ list = []
+ case word
+ when '-'
+ long = short = true
+ when /\A--/
+ word, arg = word.split(/=/, 2)
+ argpat = Completion.regexp(arg, false) if arg and !arg.empty?
+ long = true
+ when /\A-/
+ short = true
+ end
+ pat = Completion.regexp(word, long)
+ visit(:each_option) do |opt|
+ next unless Switch === opt
+ opts = (long ? opt.long : []) + (short ? opt.short : [])
+ opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat
+ if /\A=/ =~ opt.arg
+ opts.map! {|sw| sw + "="}
+ if arg and CompletingHash === opt.pattern
+ if opts = opt.pattern.candidate(arg, false, argpat)
+ opts.map!(&:last)
+ end
+ end
+ end
+ list.concat(opts)
+ end
+ list
+ end
+
+ #
+ # Loads options from file names as +filename+. Does nothing when the file
+ # is not present. Returns whether successfully loaded.
+ #
+ # +filename+ defaults to basename of the program without suffix in a
+ # directory ~/.options, then the basename with '.options' suffix
+ # under XDG and Haiku standard places.
+ #
+ def load(filename = nil)
+ unless filename
+ basename = File.basename($0, '.*')
+ return true if load(File.expand_path(basename, '~/.options')) rescue nil
+ basename << ".options"
+ return [
+ # XDG
+ ENV['XDG_CONFIG_HOME'],
+ '~/.config',
+ *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR),
+
+ # Haiku
+ '~/config/settings',
+ ].any? {|dir|
+ next if !dir or dir.empty?
+ load(File.expand_path(basename, dir)) rescue nil
+ }
+ end
+ begin
+ parse(*IO.readlines(filename).each {|s| s.chomp!})
+ true
+ rescue Errno::ENOENT, Errno::ENOTDIR
+ false
+ end
+ end
+
+ #
+ # Parses environment variable +env+ or its uppercase with splitting like a
+ # shell.
+ #
+ # +env+ defaults to the basename of the program.
+ #
+ def environment(env = File.basename($0, '.*'))
+ env = ENV[env] || ENV[env.upcase] or return
+ require 'shellwords'
+ parse(*Shellwords.shellwords(env))
+ end
+
+ #
+ # Acceptable argument classes
+ #
+
+ #
+ # Any string and no conversion. This is fall-back.
+ #
+ accept(Object) {|s,|s or s.nil?}
+
+ accept(NilClass) {|s,|s}
+
+ #
+ # Any non-empty string, and no conversion.
+ #
+ accept(String, /.+/m) {|s,*|s}
+
+ #
+ # Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal
+ # for 0x, and decimal for others; with optional sign prefix. Converts to
+ # Integer.
+ #
+ decimal = '\d+(?:_\d+)*'
+ binary = 'b[01]+(?:_[01]+)*'
+ hex = 'x[\da-f]+(?:_[\da-f]+)*'
+ octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?"
+ integer = "#{octal}|#{decimal}"
+
+ accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,|
+ begin
+ Integer(s)
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Float number format, and converts to Float.
+ #
+ float = "(?:#{decimal}(?=(.)?)(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?"
+ floatpat = %r"\A[-+]?#{float}\z"io
+ accept(Float, floatpat) {|s,| s.to_f if s}
+
+ #
+ # Generic numeric format, converts to Integer for integer format, Float
+ # for float format, and Rational for rational format.
+ #
+ real = "[-+]?(?:#{octal}|#{float})"
+ accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, f, n,|
+ if n
+ Rational(d, n)
+ elsif f
+ Float(s)
+ else
+ Integer(s)
+ end
+ }
+
+ #
+ # Decimal integer format, to be converted to Integer.
+ #
+ DecimalInteger = /\A[-+]?#{decimal}\z/io
+ accept(DecimalInteger, DecimalInteger) {|s,|
+ begin
+ Integer(s, 10)
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Ruby/C like octal/hexadecimal/binary integer format, to be converted to
+ # Integer.
+ #
+ OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io
+ accept(OctalInteger, OctalInteger) {|s,|
+ begin
+ Integer(s, 8)
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Decimal integer/float number format, to be converted to Integer for
+ # integer format, Float for float format.
+ #
+ DecimalNumeric = floatpat # decimal integer is allowed as float also.
+ accept(DecimalNumeric, floatpat) {|s, f|
+ begin
+ if f
+ Float(s)
+ else
+ Integer(s)
+ end
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end if s
+ }
+
+ #
+ # Boolean switch, which means whether it is present or not, whether it is
+ # absent or not with prefix no-, or it takes an argument
+ # yes/no/true/false/+/-.
+ #
+ yesno = CompletingHash.new
+ %w[- no false].each {|el| yesno[el] = false}
+ %w[+ yes true].each {|el| yesno[el] = true}
+ yesno['nil'] = false # should be nil?
+ accept(TrueClass, yesno) {|arg, val| val == nil or val}
+ #
+ # Similar to TrueClass, but defaults to false.
+ #
+ accept(FalseClass, yesno) {|arg, val| val != nil and val}
+
+ #
+ # List of strings separated by ",".
+ #
+ accept(Array) do |s, |
+ if s
+ s = s.split(',').collect {|ss| ss unless ss.empty?}
+ end
+ s
+ end
+
+ #
+ # Regular expression with options.
+ #
+ accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o|
+ f = 0
+ if o
+ f |= Regexp::IGNORECASE if /i/ =~ o
+ f |= Regexp::MULTILINE if /m/ =~ o
+ f |= Regexp::EXTENDED if /x/ =~ o
+ k = o.delete("imx")
+ k = nil if k.empty?
+ end
+ Regexp.new(s || all, f, k)
+ end
+
+ #
+ # Exceptions
+ #
+
+ #
+ # Base class of exceptions from Gem::OptionParser.
+ #
+ class ParseError < RuntimeError
+ # Reason which caused the error.
+ Reason = 'parse error'
+
+ def initialize(*args, additional: nil)
+ @additional = additional
+ @arg0, = args
+ @args = args
+ @reason = nil
+ end
+
+ attr_reader :args
+ attr_writer :reason
+ attr_accessor :additional
+
+ #
+ # Pushes back erred argument(s) to +argv+.
+ #
+ def recover(argv)
+ argv[0, 0] = @args
+ argv
+ end
+
+ def self.filter_backtrace(array)
+ unless $DEBUG
+ array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~))
+ end
+ array
+ end
+
+ def set_backtrace(array)
+ super(self.class.filter_backtrace(array))
+ end
+
+ def set_option(opt, eq)
+ if eq
+ @args[0] = opt
+ else
+ @args.unshift(opt)
+ end
+ self
+ end
+
+ #
+ # Returns error reason. Override this for I18N.
+ #
+ def reason
+ @reason || self.class::Reason
+ end
+
+ def inspect
+ "#<#{self.class}: #{args.join(' ')}>"
+ end
+
+ #
+ # Default stringizing method to emit standard error message.
+ #
+ def message
+ "#{reason}: #{args.join(' ')}#{additional[@arg0] if additional}"
+ end
+
+ alias to_s message
+ end
+
+ #
+ # Raises when ambiguously completable string is encountered.
+ #
+ class AmbiguousOption < ParseError
+ const_set(:Reason, 'ambiguous option')
+ end
+
+ #
+ # Raises when there is an argument for a switch which takes no argument.
+ #
+ class NeedlessArgument < ParseError
+ const_set(:Reason, 'needless argument')
+ end
+
+ #
+ # Raises when a switch with mandatory argument has no argument.
+ #
+ class MissingArgument < ParseError
+ const_set(:Reason, 'missing argument')
+ end
+
+ #
+ # Raises when switch is undefined.
+ #
+ class InvalidOption < ParseError
+ const_set(:Reason, 'invalid option')
+ end
+
+ #
+ # Raises when the given argument does not match required format.
+ #
+ class InvalidArgument < ParseError
+ const_set(:Reason, 'invalid argument')
+ end
+
+ #
+ # Raises when the given argument word can't be completed uniquely.
+ #
+ class AmbiguousArgument < InvalidArgument
+ const_set(:Reason, 'ambiguous argument')
+ end
+
+ #
+ # Miscellaneous
+ #
+
+ #
+ # Extends command line arguments array (ARGV) to parse itself.
+ #
+ module Arguable
+
+ #
+ # Sets Gem::OptionParser object, when +opt+ is +false+ or +nil+, methods
+ # Gem::OptionParser::Arguable#options and Gem::OptionParser::Arguable#options= are
+ # undefined. Thus, there is no ways to access the Gem::OptionParser object
+ # via the receiver object.
+ #
+ def options=(opt)
+ unless @optparse = opt
+ class << self
+ undef_method(:options)
+ undef_method(:options=)
+ end
+ end
+ end
+
+ #
+ # Actual Gem::OptionParser object, automatically created if nonexistent.
+ #
+ # If called with a block, yields the Gem::OptionParser object and returns the
+ # result of the block. If an Gem::OptionParser::ParseError exception occurs
+ # in the block, it is rescued, a error message printed to STDERR and
+ # +nil+ returned.
+ #
+ def options
+ @optparse ||= Gem::OptionParser.new
+ @optparse.default_argv = self
+ block_given? or return @optparse
+ begin
+ yield @optparse
+ rescue ParseError
+ @optparse.warn $!
+ nil
+ end
+ end
+
+ #
+ # Parses +self+ destructively in order and returns +self+ containing the
+ # rest arguments left unparsed.
+ #
+ def order!(&blk) options.order!(self, &blk) end
+
+ #
+ # Parses +self+ destructively in permutation mode and returns +self+
+ # containing the rest arguments left unparsed.
+ #
+ def permute!() options.permute!(self) end
+
+ #
+ # Parses +self+ destructively and returns +self+ containing the
+ # rest arguments left unparsed.
+ #
+ def parse!() options.parse!(self) end
+
+ #
+ # Substitution of getopts is possible as follows. Also see
+ # Gem::OptionParser#getopts.
+ #
+ # def getopts(*args)
+ # ($OPT = ARGV.getopts(*args)).each do |opt, val|
+ # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val"
+ # end
+ # rescue Gem::OptionParser::ParseError
+ # end
+ #
+ def getopts(*args)
+ options.getopts(self, *args)
+ end
+
+ #
+ # Initializes instance variable.
+ #
+ def self.extend_object(obj)
+ super
+ obj.instance_eval {@optparse = nil}
+ end
+ def initialize(*args)
+ super
+ @optparse = nil
+ end
+ end
+
+ #
+ # Acceptable argument classes. Now contains DecimalInteger, OctalInteger
+ # and DecimalNumeric. See Acceptable argument classes (in source code).
+ #
+ module Acceptables
+ const_set(:DecimalInteger, Gem::OptionParser::DecimalInteger)
+ const_set(:OctalInteger, Gem::OptionParser::OctalInteger)
+ const_set(:DecimalNumeric, Gem::OptionParser::DecimalNumeric)
+ end
+end
+
+# ARGV is arguable by Gem::OptionParser
+ARGV.extend(Gem::OptionParser::Arguable)
diff --git a/lib/rubygems/optparse/lib/optparse/ac.rb b/lib/rubygems/optparse/lib/optparse/ac.rb
new file mode 100644
index 00000000000..ff2f4c2dc3e
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse/ac.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: false
+require 'rubygems/optparse/lib/optparse'
+
+class Gem::OptionParser::AC < Gem::OptionParser
+ private
+
+ def _check_ac_args(name, block)
+ unless /\A\w[-\w]*\z/ =~ name
+ raise ArgumentError, name
+ end
+ unless block
+ raise ArgumentError, "no block given", ParseError.filter_backtrace(caller)
+ end
+ end
+
+ ARG_CONV = proc {|val| val.nil? ? true : val}
+
+ def _ac_arg_enable(prefix, name, help_string, block)
+ _check_ac_args(name, block)
+
+ sdesc = []
+ ldesc = ["--#{prefix}-#{name}"]
+ desc = [help_string]
+ q = name.downcase
+ ac_block = proc {|val| block.call(ARG_CONV.call(val))}
+ enable = Switch::PlacedArgument.new(nil, ARG_CONV, sdesc, ldesc, nil, desc, ac_block)
+ disable = Switch::NoArgument.new(nil, proc {false}, sdesc, ldesc, nil, desc, ac_block)
+ top.append(enable, [], ["enable-" + q], disable, ['disable-' + q])
+ enable
+ end
+
+ public
+
+ def ac_arg_enable(name, help_string, &block)
+ _ac_arg_enable("enable", name, help_string, block)
+ end
+
+ def ac_arg_disable(name, help_string, &block)
+ _ac_arg_enable("disable", name, help_string, block)
+ end
+
+ def ac_arg_with(name, help_string, &block)
+ _check_ac_args(name, block)
+
+ sdesc = []
+ ldesc = ["--with-#{name}"]
+ desc = [help_string]
+ q = name.downcase
+ with = Switch::PlacedArgument.new(*search(:atype, String), sdesc, ldesc, nil, desc, block)
+ without = Switch::NoArgument.new(nil, proc {}, sdesc, ldesc, nil, desc, block)
+ top.append(with, [], ["with-" + q], without, ['without-' + q])
+ with
+ end
+end
diff --git a/lib/rubygems/optparse/lib/optparse/date.rb b/lib/rubygems/optparse/lib/optparse/date.rb
new file mode 100644
index 00000000000..11131e92c22
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse/date.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: false
+require 'rubygems/optparse/lib/optparse'
+require 'date'
+
+Gem::OptionParser.accept(DateTime) do |s,|
+ begin
+ DateTime.parse(s) if s
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end
+end
+Gem::OptionParser.accept(Date) do |s,|
+ begin
+ Date.parse(s) if s
+ rescue ArgumentError
+ raise Gem::OptionParser::InvalidArgument, s
+ end
+end
diff --git a/lib/rubygems/optparse/lib/optparse/kwargs.rb b/lib/rubygems/optparse/lib/optparse/kwargs.rb
new file mode 100644
index 00000000000..9290344c39a
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse/kwargs.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+require 'rubygems/optparse/lib/optparse'
+
+class Gem::OptionParser
+ # :call-seq:
+ # define_by_keywords(options, method, **params)
+ #
+ # :include: ../../doc/optparse/creates_option.rdoc
+ #
+ def define_by_keywords(options, meth, **opts)
+ meth.parameters.each do |type, name|
+ case type
+ when :key, :keyreq
+ op, cl = *(type == :key ? %w"[ ]" : ["", ""])
+ define("--#{name}=#{op}#{name.upcase}#{cl}", *opts[name]) do |o|
+ options[name] = o
+ end
+ end
+ end
+ options
+ end
+end
diff --git a/lib/rubygems/optparse/lib/optparse/shellwords.rb b/lib/rubygems/optparse/lib/optparse/shellwords.rb
new file mode 100644
index 00000000000..60dd91990c1
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse/shellwords.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: false
+# -*- ruby -*-
+
+require 'shellwords'
+require 'rubygems/optparse/lib/optparse'
+
+Gem::OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)}
diff --git a/lib/rubygems/optparse/lib/optparse/time.rb b/lib/rubygems/optparse/lib/optparse/time.rb
new file mode 100644
index 00000000000..cb19f6e998e
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse/time.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: false
+require 'rubygems/optparse/lib/optparse'
+require 'time'
+
+Gem::OptionParser.accept(Time) do |s,|
+ begin
+ (Time.httpdate(s) rescue Time.parse(s)) if s
+ rescue
+ raise Gem::OptionParser::InvalidArgument, s
+ end
+end
diff --git a/lib/rubygems/optparse/lib/optparse/uri.rb b/lib/rubygems/optparse/lib/optparse/uri.rb
new file mode 100644
index 00000000000..088f309992f
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse/uri.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: false
+# -*- ruby -*-
+
+require 'rubygems/optparse/lib/optparse'
+require 'uri'
+
+Gem::OptionParser.accept(URI) {|s,| URI.parse(s) if s}
diff --git a/lib/rubygems/optparse/lib/optparse/version.rb b/lib/rubygems/optparse/lib/optparse/version.rb
new file mode 100644
index 00000000000..5d79e9db44d
--- /dev/null
+++ b/lib/rubygems/optparse/lib/optparse/version.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: false
+# Gem::OptionParser internal utility
+
+class << Gem::OptionParser
+ def show_version(*pkgs)
+ progname = ARGV.options.program_name
+ result = false
+ show = proc do |klass, cname, version|
+ str = "#{progname}"
+ unless klass == ::Object and cname == :VERSION
+ version = version.join(".") if Array === version
+ str << ": #{klass}" unless klass == Object
+ str << " version #{version}"
+ end
+ [:Release, :RELEASE].find do |rel|
+ if klass.const_defined?(rel)
+ str << " (#{klass.const_get(rel)})"
+ end
+ end
+ puts str
+ result = true
+ end
+ if pkgs.size == 1 and pkgs[0] == "all"
+ self.search_const(::Object, /\AV(?:ERSION|ersion)\z/) do |klass, cname, version|
+ unless cname[1] == ?e and klass.const_defined?(:Version)
+ show.call(klass, cname.intern, version)
+ end
+ end
+ else
+ pkgs.each do |pkg|
+ begin
+ pkg = pkg.split(/::|\//).inject(::Object) {|m, c| m.const_get(c)}
+ v = case
+ when pkg.const_defined?(:Version)
+ pkg.const_get(n = :Version)
+ when pkg.const_defined?(:VERSION)
+ pkg.const_get(n = :VERSION)
+ else
+ n = nil
+ "unknown"
+ end
+ show.call(pkg, n, v)
+ rescue NameError
+ end
+ end
+ end
+ result
+ end
+
+ def each_const(path, base = ::Object)
+ path.split(/::|\//).inject(base) do |klass, name|
+ raise NameError, path unless Module === klass
+ klass.constants.grep(/#{name}/i) do |c|
+ klass.const_defined?(c) or next
+ klass.const_get(c)
+ end
+ end
+ end
+
+ def search_const(klass, name)
+ klasses = [klass]
+ while klass = klasses.shift
+ klass.constants.each do |cname|
+ klass.const_defined?(cname) or next
+ const = klass.const_get(cname)
+ yield klass, cname, const if name === cname
+ klasses << const if Module === const and const != ::Object
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index 9286c542219..01b01599a8f 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require 'tsort'
+require_relative 'tsort'
##
# A RequestSet groups a request to activate a set of dependencies.
@@ -15,7 +15,7 @@ require 'tsort'
# #=> ["nokogiri-1.6.0", "mini_portile-0.5.1", "pg-0.17.0"]
class Gem::RequestSet
- include TSort
+ include Gem::TSort
##
# Array of gems to install even if already installed
diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb
index d2e28fab5b4..9edd6aa7d3c 100644
--- a/lib/rubygems/requirement.rb
+++ b/lib/rubygems/requirement.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require_relative "deprecate"
+require_relative "version"
##
# A Requirement is a set of one or more version restrictions. It supports a
diff --git a/lib/rubygems/resolver/installer_set.rb b/lib/rubygems/resolver/installer_set.rb
index 237bc3f782a..45252ed2412 100644
--- a/lib/rubygems/resolver/installer_set.rb
+++ b/lib/rubygems/resolver/installer_set.rb
@@ -76,21 +76,21 @@ class Gem::Resolver::InstallerSet < Gem::Resolver::Set
newest = found.last
+ unless newest
+ exc = Gem::UnsatisfiableDependencyError.new request
+ exc.errors = errors
+
+ raise exc
+ end
+
unless @force
found_matching_metadata = found.reverse.find do |spec|
metadata_satisfied?(spec)
end
if found_matching_metadata.nil?
- if newest
- ensure_required_ruby_version_met(newest.spec)
- ensure_required_rubygems_version_met(newest.spec)
- else
- exc = Gem::UnsatisfiableDependencyError.new request
- exc.errors = errors
-
- raise exc
- end
+ ensure_required_ruby_version_met(newest.spec)
+ ensure_required_rubygems_version_met(newest.spec)
else
newest = found_matching_metadata
end
diff --git a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb
index 16430a79f5b..95f8416b967 100644
--- a/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb
+++ b/lib/rubygems/resolver/molinillo/lib/molinillo/dependency_graph.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'tsort'
+require_relative '../../../../tsort'
require_relative 'dependency_graph/log'
require_relative 'dependency_graph/vertex'
@@ -17,7 +17,7 @@ module Gem::Resolver::Molinillo
vertices.values.each { |v| yield v }
end
- include TSort
+ include Gem::TSort
# @!visibility private
alias tsort_each_node each
diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb
index da2af5f3268..4d1deee9972 100644
--- a/lib/rubygems/s3_uri_signer.rb
+++ b/lib/rubygems/s3_uri_signer.rb
@@ -1,4 +1,3 @@
-require 'digest'
require_relative 'openssl'
##
@@ -87,7 +86,7 @@ class Gem::S3URISigner
"AWS4-HMAC-SHA256",
date_time,
credential_info,
- Digest::SHA256.hexdigest(canonical_request),
+ OpenSSL::Digest::SHA256.hexdigest(canonical_request),
].join("\n")
end
diff --git a/lib/rubygems/security.rb b/lib/rubygems/security.rb
index 28f705549c8..f21c1756422 100644
--- a/lib/rubygems/security.rb
+++ b/lib/rubygems/security.rb
@@ -152,6 +152,7 @@ require_relative 'openssl'
# certificate for EMAIL_ADDR
# -C, --certificate CERT Signing certificate for --sign
# -K, --private-key KEY Key for --sign or --build
+# -A, --key-algorithm ALGORITHM Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA.
# -s, --sign CERT Signs CERT with the key from -K
# and the certificate from -C
# -d, --days NUMBER_OF_DAYS Days before the certificate expires
@@ -317,7 +318,6 @@ require_relative 'openssl'
# * Honor extension restrictions
# * Might be better to store the certificate chain as a PKCS#7 or PKCS#12
# file, instead of an array embedded in the metadata.
-# * Flexible signature and key algorithms, not hard-coded to RSA and SHA1.
#
# == Original author
#
@@ -337,17 +337,19 @@ module Gem::Security
DIGEST_NAME = 'SHA256' # :nodoc:
##
- # Algorithm for creating the key pair used to sign gems
+ # Length of keys created by RSA and DSA keys
- KEY_ALGORITHM =
- if defined?(OpenSSL::PKey::RSA)
- OpenSSL::PKey::RSA
- end
+ RSA_DSA_KEY_LENGTH = 3072
##
- # Length of keys created by KEY_ALGORITHM
+ # Default algorithm to use when building a key pair
- KEY_LENGTH = 3072
+ DEFAULT_KEY_ALGORITHM = 'RSA'
+
+ ##
+ # Named curve used for Elliptic Curve
+
+ EC_NAME = 'secp384r1'
##
# Cipher used to encrypt the key pair used to sign gems.
@@ -400,7 +402,7 @@ module Gem::Security
serial = 1)
cert = OpenSSL::X509::Certificate.new
- cert.public_key = key.public_key
+ cert.public_key = get_public_key(key)
cert.version = 2
cert.serial = serial
@@ -419,6 +421,26 @@ module Gem::Security
end
##
+ # Gets the right public key from a PKey instance
+
+ def self.get_public_key(key)
+ # Ruby 3.0 (Ruby/OpenSSL 2.2) or later
+ return OpenSSL::PKey.read(key.public_to_der) if key.respond_to?(:public_to_der)
+ return key.public_key unless key.is_a?(OpenSSL::PKey::EC)
+
+ ec_key = OpenSSL::PKey::EC.new(key.group.curve_name)
+ ec_key.public_key = key.public_key
+ ec_key
+ end
+
+ ##
+ # In Ruby 2.3 EC doesn't implement the private_key? but not the private? method
+
+ if defined?(OpenSSL::PKey::EC) && Gem::Version.new(String.new(RUBY_VERSION)) < Gem::Version.new("2.4.0")
+ OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)
+ end
+
+ ##
# Creates a self-signed certificate with an issuer and subject from +email+,
# a subject alternative name of +email+ and the given +extensions+ for the
# +key+.
@@ -459,11 +481,29 @@ module Gem::Security
end
##
- # Creates a new key pair of the specified +length+ and +algorithm+. The
- # default is a 3072 bit RSA key.
-
- def self.create_key(length = KEY_LENGTH, algorithm = KEY_ALGORITHM)
- algorithm.new length
+ # Creates a new key pair of the specified +algorithm+. RSA, DSA, and EC
+ # are supported.
+
+ def self.create_key(algorithm)
+ if defined?(OpenSSL::PKey)
+ case algorithm.downcase
+ when 'dsa'
+ OpenSSL::PKey::DSA.new(RSA_DSA_KEY_LENGTH)
+ when 'rsa'
+ OpenSSL::PKey::RSA.new(RSA_DSA_KEY_LENGTH)
+ when 'ec'
+ if RUBY_VERSION >= "2.4.0"
+ OpenSSL::PKey::EC.generate(EC_NAME)
+ else
+ domain_key = OpenSSL::PKey::EC.new(EC_NAME)
+ domain_key.generate_key
+ domain_key
+ end
+ else
+ raise Gem::Security::Exception,
+ "#{algorithm} algorithm not found. RSA, DSA, and EC algorithms are supported."
+ end
+ end
end
##
@@ -476,9 +516,10 @@ module Gem::Security
dcs = dcs.split '.'
- name = "CN=#{cn}/#{dcs.map {|dc| "DC=#{dc}" }.join '/'}"
-
- OpenSSL::X509::Name.parse name
+ OpenSSL::X509::Name.new([
+ ["CN", cn],
+ *dcs.map {|dc| ["DC", dc] },
+ ])
end
##
@@ -492,7 +533,7 @@ module Gem::Security
raise Gem::Security::Exception,
"incorrect signing key for re-signing " +
"#{expired_certificate.subject}" unless
- expired_certificate.public_key.to_pem == private_key.public_key.to_pem
+ expired_certificate.check_private_key(private_key)
unless expired_certificate.subject.to_s ==
expired_certificate.issuer.to_s
diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb
index 9683e55b327..06eae073f4a 100644
--- a/lib/rubygems/security/policy.rb
+++ b/lib/rubygems/security/policy.rb
@@ -117,7 +117,7 @@ class Gem::Security::Policy
raise Gem::Security::Exception,
"certificate #{signer.subject} does not match the signing key" unless
- signer.public_key.to_pem == key.public_key.to_pem
+ signer.check_private_key(key)
true
end
@@ -164,9 +164,9 @@ class Gem::Security::Policy
end
save_cert = OpenSSL::X509::Certificate.new File.read path
- save_dgst = digester.digest save_cert.public_key.to_s
+ save_dgst = digester.digest save_cert.public_key.to_pem
- pkey_str = root.public_key.to_s
+ pkey_str = root.public_key.to_pem
cert_dgst = digester.digest pkey_str
raise Gem::Security::Exception,
diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb
index c5c2c4f2200..968cf889730 100644
--- a/lib/rubygems/security/signer.rb
+++ b/lib/rubygems/security/signer.rb
@@ -83,8 +83,8 @@ class Gem::Security::Signer
@digest_name = Gem::Security::DIGEST_NAME
@digest_algorithm = Gem::Security.create_digest(@digest_name)
- if @key && !@key.is_a?(OpenSSL::PKey::RSA)
- @key = OpenSSL::PKey::RSA.new(File.read(@key), @passphrase)
+ if @key && !@key.is_a?(OpenSSL::PKey::PKey)
+ @key = OpenSSL::PKey.read(File.read(@key), @passphrase)
end
if @cert_chain
@@ -177,8 +177,7 @@ class Gem::Security::Signer
disk_cert = File.read(disk_cert_path) rescue nil
disk_key_path = File.join(Gem.default_key_path)
- disk_key =
- OpenSSL::PKey::RSA.new(File.read(disk_key_path), @passphrase) rescue nil
+ disk_key = OpenSSL::PKey.read(File.read(disk_key_path), @passphrase) rescue nil
return unless disk_key
diff --git a/lib/rubygems/security_option.rb b/lib/rubygems/security_option.rb
index 6c75d6ecb4f..a4c570ded5c 100644
--- a/lib/rubygems/security_option.rb
+++ b/lib/rubygems/security_option.rb
@@ -19,16 +19,16 @@ end
module Gem::SecurityOption
def add_security_option
- OptionParser.accept Gem::Security::Policy do |value|
+ Gem::OptionParser.accept Gem::Security::Policy do |value|
require_relative 'security'
- raise OptionParser::InvalidArgument, 'OpenSSL not installed' unless
+ raise Gem::OptionParser::InvalidArgument, 'OpenSSL not installed' unless
defined?(Gem::Security::HighSecurity)
policy = Gem::Security::Policies[value]
unless policy
valid = Gem::Security::Policies.keys.sort
- raise OptionParser::InvalidArgument, "#{value} (#{valid.join ', '} are valid)"
+ raise Gem::OptionParser::InvalidArgument, "#{value} (#{valid.join ', '} are valid)"
end
policy
end
diff --git a/lib/rubygems/server.rb b/lib/rubygems/server.rb
deleted file mode 100644
index 45be05bcda7..00000000000
--- a/lib/rubygems/server.rb
+++ /dev/null
@@ -1,882 +0,0 @@
-# frozen_string_literal: true
-require 'zlib'
-require 'erb'
-require 'uri'
-
-require_relative '../rubygems'
-require_relative 'rdoc'
-
-##
-# Gem::Server and allows users to serve gems for consumption by
-# `gem --remote-install`.
-#
-# gem_server starts an HTTP server on the given port and serves the following:
-# * "/" - Browsing of gem spec files for installed gems
-# * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index
-# * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs
-# name/version/platform index
-# * "/quick/" - Individual gemspecs
-# * "/gems" - Direct access to download the installable gems
-# * "/rdoc?q=" - Search for installed rdoc documentation
-#
-# == Usage
-#
-# gem_server = Gem::Server.new Gem.dir, 8089, false
-# gem_server.run
-#
-#--
-# TODO Refactor into a real WEBrick servlet to remove code duplication.
-
-class Gem::Server
- attr_reader :spec_dirs
-
- include ERB::Util
- include Gem::UserInteraction
-
- SEARCH = <<-ERB.freeze
- <form class="headerSearch" name="headerSearchForm" method="get" action="/rdoc">
- <div id="search" style="float:right">
- <label for="q">Filter/Search</label>
- <input id="q" type="text" style="width:10em" name="q">
- <button type="submit" style="display:none"></button>
- </div>
- </form>
- ERB
-
- DOC_TEMPLATE = <<-'ERB'.freeze
- <?xml version="1.0" encoding="iso-8859-1"?>
- <!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>RubyGems Documentation Index</title>
- <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
- </head>
- <body>
- <div id="fileHeader">
-<%= SEARCH %>
- <h1>RubyGems Documentation Index</h1>
- </div>
- <!-- banner header -->
-
- <div id="bodyContent">
- <div id="contextContent">
- <div id="description">
- <h1>Summary</h1>
- <p>There are <%=values["gem_count"]%> gems installed:</p>
- <p>
- <%= values["specs"].map { |v| "<a href=\"##{u v["name"]}\">#{h v["name"]}</a>" }.join ', ' %>.
- <h1>Gems</h1>
-
- <dl>
- <% values["specs"].each do |spec| %>
- <dt>
- <% if spec["first_name_entry"] then %>
- <a name="<%=h spec["name"]%>"></a>
- <% end %>
-
- <b><%=h spec["name"]%> <%=h spec["version"]%></b>
-
- <% if spec["ri_installed"] || spec["rdoc_installed"] then %>
- <a href="<%=spec["doc_path"]%>">[rdoc]</a>
- <% else %>
- <span title="rdoc not installed">[rdoc]</span>
- <% end %>
-
- <% if spec["homepage"] then %>
- <a href="<%=uri_encode spec["homepage"]%>" title="<%=h spec["homepage"]%>">[www]</a>
- <% else %>
- <span title="no homepage available">[www]</span>
- <% end %>
-
- <% if spec["has_deps"] then %>
- - depends on
- <%= spec["dependencies"].map { |v| "<a href=\"##{u v["name"]}\">#{h v["name"]}</a>" }.join ', ' %>.
- <% end %>
- </dt>
- <dd>
- <%=spec["summary"]%>
- <% if spec["executables"] then %>
- <br/>
-
- <% if spec["only_one_executable"] then %>
- Executable is
- <% else %>
- Executables are
- <%end%>
-
- <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{h v["executable"]}</span>"}.join ', ' %>.
-
- <%end%>
- <br/>
- <br/>
- </dd>
- <% end %>
- </dl>
-
- </div>
- </div>
- </div>
- <div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
- </div>
- </body>
- </html>
- ERB
-
- # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
- RDOC_CSS = <<-CSS.freeze
-body {
- font-family: Verdana,Arial,Helvetica,sans-serif;
- font-size: 90%;
- margin: 0;
- margin-left: 40px;
- padding: 0;
- background: white;
-}
-
-h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
-h1 { font-size: 150%; }
-h2,h3,h4 { margin-top: 1em; }
-
-a { background: #eef; color: #039; text-decoration: none; }
-a:hover { background: #039; color: #eef; }
-
-/* Override the base stylesheets Anchor inside a table cell */
-td > a {
- background: transparent;
- color: #039;
- text-decoration: none;
-}
-
-/* and inside a section title */
-.section-title > a {
- background: transparent;
- color: #eee;
- text-decoration: none;
-}
-
-/* === Structural elements =================================== */
-
-div#index {
- margin: 0;
- margin-left: -40px;
- padding: 0;
- font-size: 90%;
-}
-
-
-div#index a {
- margin-left: 0.7em;
-}
-
-div#index .section-bar {
- margin-left: 0px;
- padding-left: 0.7em;
- background: #ccc;
- font-size: small;
-}
-
-
-div#classHeader, div#fileHeader {
- width: auto;
- color: white;
- padding: 0.5em 1.5em 0.5em 1.5em;
- margin: 0;
- margin-left: -40px;
- border-bottom: 3px solid #006;
-}
-
-div#classHeader a, div#fileHeader a {
- background: inherit;
- color: white;
-}
-
-div#classHeader td, div#fileHeader td {
- background: inherit;
- color: white;
-}
-
-
-div#fileHeader {
- background: #057;
-}
-
-div#classHeader {
- background: #048;
-}
-
-
-.class-name-in-header {
- font-size: 180%;
- font-weight: bold;
-}
-
-
-div#bodyContent {
- padding: 0 1.5em 0 1.5em;
-}
-
-div#description {
- padding: 0.5em 1.5em;
- background: #efefef;
- border: 1px dotted #999;
-}
-
-div#description h1,h2,h3,h4,h5,h6 {
- color: #125;;
- background: transparent;
-}
-
-div#validator-badges {
- text-align: center;
-}
-div#validator-badges img { border: 0; }
-
-div#copyright {
- color: #333;
- background: #efefef;
- font: 0.75em sans-serif;
- margin-top: 5em;
- margin-bottom: 0;
- padding: 0.5em 2em;
-}
-
-
-/* === Classes =================================== */
-
-table.header-table {
- color: white;
- font-size: small;
-}
-
-.type-note {
- font-size: small;
- color: #DEDEDE;
-}
-
-.xxsection-bar {
- background: #eee;
- color: #333;
- padding: 3px;
-}
-
-.section-bar {
- color: #333;
- border-bottom: 1px solid #999;
- margin-left: -20px;
-}
-
-
-.section-title {
- background: #79a;
- color: #eee;
- padding: 3px;
- margin-top: 2em;
- margin-left: -30px;
- border: 1px solid #999;
-}
-
-.top-aligned-row { vertical-align: top }
-.bottom-aligned-row { vertical-align: bottom }
-
-/* --- Context section classes ----------------------- */
-
-.context-row { }
-.context-item-name { font-family: monospace; font-weight: bold; color: black; }
-.context-item-value { font-size: small; color: #448; }
-.context-item-desc { color: #333; padding-left: 2em; }
-
-/* --- Method classes -------------------------- */
-.method-detail {
- background: #efefef;
- padding: 0;
- margin-top: 0.5em;
- margin-bottom: 1em;
- border: 1px dotted #ccc;
-}
-.method-heading {
- color: black;
- background: #ccc;
- border-bottom: 1px solid #666;
- padding: 0.2em 0.5em 0 0.5em;
-}
-.method-signature { color: black; background: inherit; }
-.method-name { font-weight: bold; }
-.method-args { font-style: italic; }
-.method-description { padding: 0 0.5em 0 0.5em; }
-
-/* --- Source code sections -------------------- */
-
-a.source-toggle { font-size: 90%; }
-div.method-source-code {
- background: #262626;
- color: #ffdead;
- margin: 1em;
- padding: 0.5em;
- border: 1px dashed #999;
- overflow: hidden;
-}
-
-div.method-source-code pre { color: #ffdead; overflow: hidden; }
-
-/* --- Ruby keyword styles --------------------- */
-
-.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
-
-.ruby-constant { color: #7fffd4; background: transparent; }
-.ruby-keyword { color: #00ffff; background: transparent; }
-.ruby-ivar { color: #eedd82; background: transparent; }
-.ruby-operator { color: #00ffee; background: transparent; }
-.ruby-identifier { color: #ffdead; background: transparent; }
-.ruby-node { color: #ffa07a; background: transparent; }
-.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
-.ruby-regexp { color: #ffa07a; background: transparent; }
-.ruby-value { color: #7fffd4; background: transparent; }
- CSS
-
- RDOC_NO_DOCUMENTATION = <<-'ERB'.freeze
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>Found documentation</title>
- <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
- </head>
- <body>
- <div id="fileHeader">
-<%= SEARCH %>
- <h1>No documentation found</h1>
- </div>
-
- <div id="bodyContent">
- <div id="contextContent">
- <div id="description">
- <p>No gems matched <%= h query.inspect %></p>
-
- <p>
- Back to <a href="/">complete gem index</a>
- </p>
-
- </div>
- </div>
- </div>
- <div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
- </div>
- </body>
-</html>
- ERB
-
- RDOC_SEARCH_TEMPLATE = <<-'ERB'.freeze
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>Found documentation</title>
- <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
- </head>
- <body>
- <div id="fileHeader">
-<%= SEARCH %>
- <h1>Found documentation</h1>
- </div>
- <!-- banner header -->
-
- <div id="bodyContent">
- <div id="contextContent">
- <div id="description">
- <h1>Summary</h1>
- <p><%=doc_items.length%> documentation topics found.</p>
- <h1>Topics</h1>
-
- <dl>
- <% doc_items.each do |doc_item| %>
- <dt>
- <b><%=doc_item[:name]%></b>
- <a href="<%=u doc_item[:url]%>">[rdoc]</a>
- </dt>
- <dd>
- <%=h doc_item[:summary]%>
- <br/>
- <br/>
- </dd>
- <% end %>
- </dl>
-
- <p>
- Back to <a href="/">complete gem index</a>
- </p>
-
- </div>
- </div>
- </div>
- <div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
- </div>
- </body>
-</html>
- ERB
-
- def self.run(options)
- new(options[:gemdir], options[:port], options[:daemon],
- options[:launch], options[:addresses]).run
- end
-
- def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
- begin
- require 'webrick'
- rescue LoadError
- abort "webrick is not found. You may need to `gem install webrick` to install webrick."
- end
-
- Gem::RDoc.load_rdoc
- Socket.do_not_reverse_lookup = true
-
- @gem_dirs = Array gem_dirs
- @port = port
- @daemon = daemon
- @launch = launch
- @addresses = addresses
-
- logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
- @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
-
- @spec_dirs = @gem_dirs.map {|gem_dir| File.join gem_dir, 'specifications' }
- @spec_dirs.reject! {|spec_dir| !File.directory? spec_dir }
-
- reset_gems
-
- @have_rdoc_4_plus = nil
- end
-
- def add_date(res)
- res['date'] = @spec_dirs.map do |spec_dir|
- File.stat(spec_dir).mtime
- end.max
- end
-
- def uri_encode(str)
- str.gsub(URI::UNSAFE) do |match|
- match.each_byte.map {|c| sprintf('%%%02X', c.ord) }.join
- end
- end
-
- def doc_root(gem_name)
- if have_rdoc_4_plus?
- "/doc_root/#{u gem_name}/"
- else
- "/doc_root/#{u gem_name}/rdoc/index.html"
- end
- end
-
- def have_rdoc_4_plus?
- @have_rdoc_4_plus ||=
- Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version
- end
-
- def latest_specs(req, res)
- reset_gems
-
- res['content-type'] = 'application/x-gzip'
-
- add_date res
-
- latest_specs = Gem::Specification.latest_specs
-
- specs = latest_specs.sort.map do |spec|
- platform = spec.original_platform || Gem::Platform::RUBY
- [spec.name, spec.version, platform]
- end
-
- specs = Marshal.dump specs
-
- if req.path =~ /\.gz$/
- specs = Gem::Util.gzip specs
- res['content-type'] = 'application/x-gzip'
- else
- res['content-type'] = 'application/octet-stream'
- end
-
- if req.request_method == 'HEAD'
- res['content-length'] = specs.length
- else
- res.body << specs
- end
- end
-
- ##
- # Creates server sockets based on the addresses option. If no addresses
- # were given a server socket for all interfaces is created.
-
- def listen(addresses = @addresses)
- addresses = [nil] unless addresses
-
- listeners = 0
-
- addresses.each do |address|
- begin
- @server.listen address, @port
- @server.listeners[listeners..-1].each do |listener|
- host, port = listener.addr.values_at 2, 1
- host = "[#{host}]" if host =~ /:/ # we don't reverse lookup
- say "Server started at http://#{host}:#{port}"
- end
-
- listeners = @server.listeners.length
- rescue SystemCallError
- next
- end
- end
-
- if @server.listeners.empty?
- say "Unable to start a server."
- say "Check for running servers or your --bind and --port arguments"
- terminate_interaction 1
- end
- end
-
- def prerelease_specs(req, res)
- reset_gems
-
- res['content-type'] = 'application/x-gzip'
-
- add_date res
-
- specs = Gem::Specification.select do |spec|
- spec.version.prerelease?
- end.sort.map do |spec|
- platform = spec.original_platform || Gem::Platform::RUBY
- [spec.name, spec.version, platform]
- end
-
- specs = Marshal.dump specs
-
- if req.path =~ /\.gz$/
- specs = Gem::Util.gzip specs
- res['content-type'] = 'application/x-gzip'
- else
- res['content-type'] = 'application/octet-stream'
- end
-
- if req.request_method == 'HEAD'
- res['content-length'] = specs.length
- else
- res.body << specs
- end
- end
-
- def quick(req, res)
- reset_gems
-
- res['content-type'] = 'text/plain'
- add_date res
-
- case req.request_uri.path
- when %r{^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)\.gemspec\.rz$} then
- marshal_format, full_name = $1, $2
- specs = Gem::Specification.find_all_by_full_name(full_name)
-
- selector = full_name.inspect
-
- if specs.empty?
- res.status = 404
- res.body = "No gems found matching #{selector}"
- elsif specs.length > 1
- res.status = 500
- res.body = "Multiple gems found matching #{selector}"
- elsif marshal_format
- res['content-type'] = 'application/x-deflate'
- res.body << Gem.deflate(Marshal.dump(specs.first))
- end
- else
- raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
- end
- end
-
- def root(req, res)
- reset_gems
-
- add_date res
-
- raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
- req.path == '/'
-
- specs = []
- total_file_count = 0
-
- Gem::Specification.each do |spec|
- total_file_count += spec.files.size
- deps = spec.dependencies.map do |dep|
- {
- "name" => dep.name,
- "type" => dep.type,
- "version" => dep.requirement.to_s,
- }
- end
-
- deps = deps.sort_by {|dep| [dep["name"].downcase, dep["version"]] }
- deps.last["is_last"] = true unless deps.empty?
-
- # executables
- executables = spec.executables.sort.collect {|exec| {"executable" => exec} }
- executables = nil if executables.empty?
- executables.last["is_last"] = true if executables
-
- # Pre-process spec homepage for safety reasons
- begin
- homepage_uri = URI.parse(spec.homepage)
- if [URI::HTTP, URI::HTTPS].member? homepage_uri.class
- homepage_uri = spec.homepage
- else
- homepage_uri = "."
- end
- rescue URI::InvalidURIError
- homepage_uri = "."
- end
-
- specs << {
- "authors" => spec.authors.sort.join(", "),
- "date" => spec.date.to_s,
- "dependencies" => deps,
- "doc_path" => doc_root(spec.full_name),
- "executables" => executables,
- "only_one_executable" => (executables && executables.size == 1),
- "full_name" => spec.full_name,
- "has_deps" => !deps.empty?,
- "homepage" => homepage_uri,
- "name" => spec.name,
- "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?,
- "ri_installed" => Gem::RDoc.new(spec).ri_installed?,
- "summary" => spec.summary,
- "version" => spec.version.to_s,
- }
- end
-
- specs << {
- "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
- "dependencies" => [],
- "doc_path" => doc_root("rubygems-#{Gem::VERSION}"),
- "executables" => [{"executable" => 'gem', "is_last" => true}],
- "only_one_executable" => true,
- "full_name" => "rubygems-#{Gem::VERSION}",
- "has_deps" => false,
- "homepage" => "https://guides.rubygems.org/",
- "name" => 'rubygems',
- "ri_installed" => true,
- "summary" => "RubyGems itself",
- "version" => Gem::VERSION,
- }
-
- specs = specs.sort_by {|spec| [spec["name"].downcase, spec["version"]] }
- specs.last["is_last"] = true
-
- # tag all specs with first_name_entry
- last_spec = nil
- specs.each do |spec|
- is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
- spec["first_name_entry"] = is_first
- last_spec = spec
- end
-
- # create page from template
- template = ERB.new(DOC_TEMPLATE)
- res['content-type'] = 'text/html'
-
- values = { "gem_count" => specs.size.to_s, "specs" => specs,
- "total_file_count" => total_file_count.to_s }
-
- # suppress 1.9.3dev warning about unused variable
- values = values
-
- result = template.result binding
- res.body = result
- end
-
- ##
- # Can be used for quick navigation to the rdoc documentation. You can then
- # define a search shortcut for your browser. E.g. in Firefox connect
- # 'shortcut:rdoc' to http://localhost:8808/rdoc?q=%s template. Then you can
- # directly open the ActionPack documentation by typing 'rdoc actionp'. If
- # there are multiple hits for the search term, they are presented as a list
- # with links.
- #
- # Search algorithm aims for an intuitive search:
- # 1. first try to find the gems and documentation folders which name
- # starts with the search term
- # 2. search for entries, that *contain* the search term
- # 3. show all the gems
- #
- # If there is only one search hit, user is immediately redirected to the
- # documentation for the particular gem, otherwise a list with results is
- # shown.
- #
- # === Additional trick - install documentation for Ruby core
- #
- # Note: please adjust paths accordingly use for example 'locate yaml.rb' and
- # 'gem environment' to identify directories, that are specific for your
- # local installation
- #
- # 1. install Ruby sources
- # cd /usr/src
- # sudo apt-get source ruby
- #
- # 2. generate documentation
- # rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc \
- # /usr/lib/ruby/1.8 ruby1.8-1.8.7.72
- #
- # By typing 'rdoc core' you can now access the core documentation
-
- def rdoc(req, res)
- query = req.query['q']
- show_rdoc_for_pattern("#{query}*", res) && return
- show_rdoc_for_pattern("*#{query}*", res) && return
-
- template = ERB.new RDOC_NO_DOCUMENTATION
-
- res['content-type'] = 'text/html'
- res.body = template.result binding
- end
-
- ##
- # Updates the server to use the latest installed gems.
-
- def reset_gems # :nodoc:
- Gem::Specification.dirs = @gem_dirs
- end
-
- ##
- # Returns true and prepares http response, if rdoc for the requested gem
- # name pattern was found.
- #
- # The search is based on the file system content, not on the gems metadata.
- # This allows additional documentation folders like 'core' for the Ruby core
- # documentation - just put it underneath the main doc folder.
-
- def show_rdoc_for_pattern(pattern, res)
- found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select do |path|
- File.exist? File.join(path, 'rdoc/index.html')
- end
- case found_gems.length
- when 0
- return false
- when 1
- new_path = File.basename(found_gems[0])
- res.status = 302
- res['Location'] = doc_root new_path
- return true
- else
- doc_items = []
- found_gems.each do |file_name|
- base_name = File.basename(file_name)
- doc_items << {
- :name => base_name,
- :url => doc_root(new_path),
- :summary => '',
- }
- end
-
- template = ERB.new(RDOC_SEARCH_TEMPLATE)
- res['content-type'] = 'text/html'
- result = template.result binding
- res.body = result
- return true
- end
- end
-
- def run
- listen
-
- WEBrick::Daemon.start if @daemon
-
- @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
- @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
-
- @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
- method(:latest_specs)
- @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
- method(:latest_specs)
-
- @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}",
- method(:prerelease_specs)
- @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}.gz",
- method(:prerelease_specs)
-
- @server.mount_proc "/quick/", method(:quick)
-
- @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
- res['content-type'] = 'text/css'
- add_date res
- res.body << RDOC_CSS
- end
-
- @server.mount_proc "/", method(:root)
-
- @server.mount_proc "/rdoc", method(:rdoc)
-
- file_handlers = {
- '/gems' => '/cache/',
- }
-
- if have_rdoc_4_plus?
- @server.mount '/doc_root', RDoc::Servlet, '/doc_root'
- else
- file_handlers['/doc_root'] = '/doc/'
- end
-
- @gem_dirs.each do |gem_dir|
- file_handlers.each do |mount_point, mount_dir|
- @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
- File.join(gem_dir, mount_dir), true)
- end
- end
-
- trap("INT") { @server.shutdown; exit! }
- trap("TERM") { @server.shutdown; exit! }
-
- launch if @launch
-
- @server.start
- end
-
- def specs(req, res)
- reset_gems
-
- add_date res
-
- specs = Gem::Specification.sort_by(&:sort_obj).map do |spec|
- platform = spec.original_platform || Gem::Platform::RUBY
- [spec.name, spec.version, platform]
- end
-
- specs = Marshal.dump specs
-
- if req.path =~ /\.gz$/
- specs = Gem::Util.gzip specs
- res['content-type'] = 'application/x-gzip'
- else
- res['content-type'] = 'application/octet-stream'
- end
-
- if req.request_method == 'HEAD'
- res['content-length'] = specs.length
- else
- res.body << specs
- end
- end
-
- def launch
- listeners = @server.listeners.map{|l| l.addr[2] }
-
- # TODO: 0.0.0.0 == any, not localhost.
- host = listeners.any?{|l| l == '0.0.0.0' } ? 'localhost' : listeners.first
-
- say "Launching browser to http://#{host}:#{@port}"
-
- system("#{@launch} http://#{host}:#{@port}")
- end
-end
diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb
index 9876adc24e9..cda5aa80738 100644
--- a/lib/rubygems/source/git.rb
+++ b/lib/rubygems/source/git.rb
@@ -225,7 +225,7 @@ class Gem::Source::Git < Gem::Source
# A hash for the git gem based on the git repository URI.
def uri_hash # :nodoc:
- require 'digest' # required here to avoid deadlocking in Gem.activate_bin_path (because digest is a gem on 2.5+)
+ require_relative '../openssl'
normalized =
if @repository =~ %r{^\w+://(\w+@)?}
@@ -235,6 +235,6 @@ class Gem::Source::Git < Gem::Source
@repository
end
- Digest::SHA1.hexdigest normalized
+ OpenSSL::Digest::SHA1.hexdigest normalized
end
end
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index 68ff605a832..b97bd496923 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -121,7 +121,7 @@ class Gem::SpecFetcher
end
end
- tuples = tuples.sort_by {|x| x[0] }
+ tuples = tuples.sort_by {|x| x[0].version }
return [tuples, errors]
end
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index bafc5e26194..fe3dc431c88 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -9,7 +9,8 @@
require_relative 'deprecate'
require_relative 'basic_specification'
require_relative 'stub_specification'
-require_relative 'specification_policy'
+require_relative 'platform'
+require_relative 'requirement'
require_relative 'util/list'
##
@@ -102,12 +103,8 @@ class Gem::Specification < Gem::BasicSpecification
today = Time.now.utc
TODAY = Time.utc(today.year, today.month, today.day) # :nodoc:
- # rubocop:disable Style/MutableConstant
- LOAD_CACHE = {} # :nodoc:
- # rubocop:enable Style/MutableConstant
- LOAD_CACHE_MUTEX = Thread::Mutex.new
-
- private_constant :LOAD_CACHE if defined? private_constant
+ @load_cache = {} # :nodoc:
+ @load_cache_mutex = Thread::Mutex.new
VALID_NAME_PATTERN = /\A[a-zA-Z0-9\.\-\_]+\z/.freeze # :nodoc:
@@ -182,13 +179,19 @@ class Gem::Specification < Gem::BasicSpecification
@@default_value[k].nil?
end
- @@stubs = nil
- @@stubs_by_name = {}
+ def self.clear_specs # :nodoc:
+ @@all = nil
+ @@stubs = nil
+ @@stubs_by_name = {}
+ @@spec_with_requirable_file = {}
+ @@active_stub_with_requirable_file = {}
+ end
+ private_class_method :clear_specs
+
+ clear_specs
# Sentinel object to represent "not found" stubs
NOT_FOUND = Struct.new(:to_spec, :this).new # :nodoc:
- @@spec_with_requirable_file = {}
- @@active_stub_with_requirable_file = {}
# Tracking removed method calls to warn users during build time.
REMOVED_METHODS = [:rubyforge_project=].freeze # :nodoc:
@@ -222,7 +225,7 @@ class Gem::Specification < Gem::BasicSpecification
attr_reader :version
##
- # A short summary of this gem's description. Displayed in `gem list -d`.
+ # A short summary of this gem's description. Displayed in <tt>gem list -d</tt>.
#
# The #description should be more detailed than the summary.
#
@@ -268,7 +271,7 @@ class Gem::Specification < Gem::BasicSpecification
# A list of authors for this gem.
#
# Alternatively, a single author can be specified by assigning a string to
- # `spec.author`
+ # +spec.author+
#
# Usage:
#
@@ -282,6 +285,15 @@ class Gem::Specification < Gem::BasicSpecification
# :section: Recommended gemspec attributes
##
+ # The version of Ruby required by this gem
+ #
+ # Usage:
+ #
+ # spec.required_ruby_version = '>= 2.7.0'
+
+ attr_reader :required_ruby_version
+
+ ##
# A long description of this gem
#
# The description should be more detailed than the summary but not
@@ -324,17 +336,21 @@ class Gem::Specification < Gem::BasicSpecification
# This should just be the name of your license. The full text of the license
# should be inside of the gem (at the top level) when you build it.
#
- # The simplest way, is to specify the standard SPDX ID
+ # The simplest way is to specify the standard SPDX ID
# https://spdx.org/licenses/ for the license.
- # Ideally you should pick one that is OSI (Open Source Initiative)
+ # Ideally, you should pick one that is OSI (Open Source Initiative)
# http://opensource.org/licenses/alphabetical approved.
#
- # The most commonly used OSI approved licenses are MIT and Apache-2.0.
+ # The most commonly used OSI-approved licenses are MIT and Apache-2.0.
# GitHub also provides a license picker at http://choosealicense.com/.
#
+ # You can also use a custom license file along with your gemspec and specify
+ # a LicenseRef-<idstring>, where idstring is the name of the file containing
+ # the license text.
+ #
# You should specify a license for your gem so that people know how they are
- # permitted to use it, and any restrictions you're placing on it. Not
- # specifying a license means all rights are reserved; others have no rights
+ # permitted to use it and any restrictions you're placing on it. Not
+ # specifying a license means all rights are reserved; others have no right
# to use the code for any purpose.
#
# You can set multiple licenses with #licenses=
@@ -513,11 +529,6 @@ class Gem::Specification < Gem::BasicSpecification
end
##
- # The version of Ruby required by this gem
-
- attr_reader :required_ruby_version
-
- ##
# The RubyGems version required by this gem
attr_reader :required_rubygems_version
@@ -744,23 +755,15 @@ class Gem::Specification < Gem::BasicSpecification
attr_accessor :specification_version
def self._all # :nodoc:
- unless defined?(@@all) && @@all
- @@all = stubs.map(&:to_spec)
-
- # After a reset, make sure already loaded specs
- # are still marked as activated.
- specs = {}
- Gem.loaded_specs.each_value{|s| specs[s] = true }
- @@all.each{|s| s.activated = true if specs[s] }
- end
- @@all
+ @@all ||= Gem.loaded_specs.values | stubs.map(&:to_spec)
end
- def self._clear_load_cache # :nodoc:
- LOAD_CACHE_MUTEX.synchronize do
- LOAD_CACHE.clear
+ def self.clear_load_cache # :nodoc:
+ @load_cache_mutex.synchronize do
+ @load_cache.clear
end
end
+ private_class_method :clear_load_cache
def self.each_gemspec(dirs) # :nodoc:
dirs.each do |dir|
@@ -991,7 +994,6 @@ class Gem::Specification < Gem::BasicSpecification
def self.find_by_path(path)
path = path.dup.freeze
spec = @@spec_with_requirable_file[path] ||= (stubs.find do |s|
- next unless Gem::BundlerVersionFinder.compatible?(s)
s.contains_requirable_file? path
end || NOT_FOUND)
spec.to_spec
@@ -1004,7 +1006,6 @@ class Gem::Specification < Gem::BasicSpecification
def self.find_inactive_by_path(path)
stub = stubs.find do |s|
next if s.activated?
- next unless Gem::BundlerVersionFinder.compatible?(s)
s.contains_requirable_file? path
end
stub && stub.to_spec
@@ -1080,7 +1081,7 @@ class Gem::Specification < Gem::BasicSpecification
# +prerelease+ is true.
def self.latest_specs(prerelease = false)
- _latest_specs Gem::Specification._all, prerelease
+ _latest_specs Gem::Specification.stubs, prerelease
end
##
@@ -1108,13 +1109,13 @@ class Gem::Specification < Gem::BasicSpecification
def self.load(file)
return unless file
- _spec = LOAD_CACHE_MUTEX.synchronize { LOAD_CACHE[file] }
+ _spec = @load_cache_mutex.synchronize { @load_cache[file] }
return _spec if _spec
file = file.dup.tap(&Gem::UNTAINT)
return unless File.file?(file)
- code = File.read file, :mode => 'r:UTF-8:-'
+ code = Gem.open_with_flock(file, 'r:UTF-8:-', &:read)
code.tap(&Gem::UNTAINT)
@@ -1123,12 +1124,12 @@ class Gem::Specification < Gem::BasicSpecification
if Gem::Specification === _spec
_spec.loaded_from = File.expand_path file.to_s
- LOAD_CACHE_MUTEX.synchronize do
- prev = LOAD_CACHE[file]
+ @load_cache_mutex.synchronize do
+ prev = @load_cache[file]
if prev
_spec = prev
else
- LOAD_CACHE[file] = _spec
+ @load_cache[file] = _spec
end
end
return _spec
@@ -1226,12 +1227,8 @@ class Gem::Specification < Gem::BasicSpecification
def self.reset
@@dirs = nil
Gem.pre_reset_hooks.each {|hook| hook.call }
- @@all = nil
- @@stubs = nil
- @@stubs_by_name = {}
- @@spec_with_requirable_file = {}
- @@active_stub_with_requirable_file = {}
- _clear_load_cache
+ clear_specs
+ clear_load_cache
unresolved = unresolved_deps
unless unresolved.empty?
w = "W" + "ARN"
diff --git a/lib/rubygems/stub_specification.rb b/lib/rubygems/stub_specification.rb
index 4246f9de862..ac7fc6109a8 100644
--- a/lib/rubygems/stub_specification.rb
+++ b/lib/rubygems/stub_specification.rb
@@ -110,7 +110,7 @@ class Gem::StubSpecification < Gem::BasicSpecification
begin
saved_lineno = $.
- File.open loaded_from, OPEN_MODE do |file|
+ Gem.open_with_flock loaded_from, OPEN_MODE do |file|
begin
file.readline # discard encoding line
stubline = file.readline.chomp
@@ -190,9 +190,6 @@ class Gem::StubSpecification < Gem::BasicSpecification
end
@spec ||= Gem::Specification.load(loaded_from)
- @spec.ignored = @ignored if @spec
-
- @spec
end
##
diff --git a/lib/rubygems/tsort.rb b/lib/rubygems/tsort.rb
new file mode 100644
index 00000000000..ebe7c3364b7
--- /dev/null
+++ b/lib/rubygems/tsort.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+require_relative 'tsort/lib/tsort'
diff --git a/lib/rubygems/tsort/.document b/lib/rubygems/tsort/.document
new file mode 100644
index 00000000000..0c43bbd6b38
--- /dev/null
+++ b/lib/rubygems/tsort/.document
@@ -0,0 +1 @@
+# Vendored files do not need to be documented
diff --git a/lib/rubygems/tsort/lib/tsort.rb b/lib/rubygems/tsort/lib/tsort.rb
new file mode 100644
index 00000000000..f68c5947d30
--- /dev/null
+++ b/lib/rubygems/tsort/lib/tsort.rb
@@ -0,0 +1,454 @@
+# frozen_string_literal: true
+
+#--
+# tsort.rb - provides a module for topological sorting and strongly connected components.
+#++
+#
+
+#
+# Gem::TSort implements topological sorting using Tarjan's algorithm for
+# strongly connected components.
+#
+# Gem::TSort is designed to be able to be used with any object which can be
+# interpreted as a directed graph.
+#
+# Gem::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
+# Gem::TSort uses Hash internally.
+#
+# == A Simple Example
+#
+# The following example demonstrates how to mix the Gem::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 'rubygems/tsort/lib/tsort'
+#
+# class Hash
+# include Gem::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 'rubygems/tsort/lib/tsort'
+#
+# class Make
+# def initialize
+# @dep = {}
+# @dep.default = []
+# end
+#
+# def rule(outputs, inputs=[], &block)
+# triple = [outputs, inputs, block]
+# outputs.each {|f| @dep[f] = [triple]}
+# @dep[triple] = inputs
+# end
+#
+# def build(target)
+# each_strongly_connected_component_from(target) {|ns|
+# if ns.length != 1
+# fs = ns.delete_if {|n| Array === n}
+# raise Gem::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 Gem::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 Gem
+ module TSort
+ 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, Gem::TSort::Cyclic is raised.
+ #
+ # class G
+ # include Gem::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 Gem::TSort::Cyclic
+ #
+ def tsort
+ each_node = method(:tsort_each_node)
+ each_child = method(:tsort_each_child)
+ Gem::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, Gem::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 Gem::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 Gem::TSort.tsort(each_node, each_child) # raises Gem::TSort::Cyclic
+ #
+ def TSort.tsort(each_node, each_child)
+ Gem::TSort.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, Gem::TSort::Cyclic is raised.
+ #
+ # class G
+ # include Gem::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)
+ Gem::TSort.tsort_each(each_node, each_child, &block)
+ end
+
+ # The iterator version of the Gem::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) }
+ # Gem::TSort.tsort_each(each_node, each_child) {|n| p n }
+ # #=> 4
+ # # 2
+ # # 3
+ # # 1
+ #
+ def TSort.tsort_each(each_node, each_child) # :yields: node
+ return to_enum(__method__, each_node, each_child) unless block_given?
+
+ Gem::TSort.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 Gem::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)
+ Gem::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 Gem::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 Gem::TSort.strongly_connected_components(each_node, each_child)
+ # #=> [[4], [2, 3], [1]]
+ #
+ def TSort.strongly_connected_components(each_node, each_child)
+ Gem::TSort.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 Gem::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)
+ Gem::TSort.each_strongly_connected_component(each_node, each_child, &block)
+ end
+
+ # The iterator version of the Gem::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) }
+ # Gem::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) }
+ # Gem::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
+ # #=> [4]
+ # # [2, 3]
+ # # [1]
+ #
+ def TSort.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
+ Gem::TSort.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 Gem::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
+ Gem::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.
+ #
+ # #Gem::TSort.each_strongly_connected_component_from is a class method and
+ # it doesn't need a class to represent a graph which includes Gem::TSort.
+ #
+ # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
+ # each_child = lambda {|n, &b| graph[n].each(&b) }
+ # Gem::TSort.each_strongly_connected_component_from(1, each_child) {|scc|
+ # p scc
+ # }
+ # #=> [4]
+ # # [2, 3]
+ # # [1]
+ #
+ def TSort.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 =
+ Gem::TSort.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
+end
diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb
index 8de5b11ae38..3f4178c6e05 100644
--- a/lib/rubygems/util/licenses.rb
+++ b/lib/rubygems/util/licenses.rb
@@ -5,6 +5,7 @@ class Gem::Licenses
extend Gem::Text
NONSTANDARD = 'Nonstandard'.freeze
+ LICENSE_REF = 'LicenseRef-.+'.freeze
# Software Package Data Exchange (SPDX) standard open-source software
# license identifiers
@@ -523,6 +524,7 @@ class Gem::Licenses
\+?
(?:\s WITH \s #{Regexp.union(EXCEPTION_IDENTIFIERS)})?
| #{NONSTANDARD}
+ | #{LICENSE_REF}
)
\Z
}ox.freeze
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index 20bbff4fdd5..9a26f721d60 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -149,6 +149,8 @@
# For the last example, single-digit versions are automatically extended with
# a zero to give a sensible result.
+require_relative "deprecate"
+
class Gem::Version
autoload :Requirement, File.expand_path('requirement', __dir__)
@@ -339,9 +341,11 @@ 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> return +nil+.
+ # <tt>Gem::Version</tt> or a valid version String return +nil+.
def <=>(other)
+ return self <=> self.class.new(other) if (String === other) && self.class.correct?(other)
+
return unless Gem::Version === other
return 0 if @version == other._version || canonical_segments == other.canonical_segments
diff --git a/lib/rubygems/version_option.rb b/lib/rubygems/version_option.rb
index eabe4fdb576..1db382fa7f9 100644
--- a/lib/rubygems/version_option.rb
+++ b/lib/rubygems/version_option.rb
@@ -16,7 +16,7 @@ module Gem::VersionOption
# Add the --platform option to the option parser.
def add_platform_option(task = command, *wrap)
- OptionParser.accept Gem::Platform do |value|
+ Gem::OptionParser.accept Gem::Platform do |value|
if value == Gem::Platform::RUBY
value
else
@@ -51,7 +51,7 @@ module Gem::VersionOption
# Add the --version option to the option parser.
def add_version_option(task = command, *wrap)
- OptionParser.accept Gem::Requirement do |value|
+ Gem::OptionParser.accept Gem::Requirement do |value|
Gem::Requirement.new(*value.split(/\s*,\s*/))
end
diff --git a/lib/securerandom.gemspec b/lib/securerandom.gemspec
index 358dc580568..b8f019f9aea 100644
--- a/lib/securerandom.gemspec
+++ b/lib/securerandom.gemspec
@@ -1,13 +1,13 @@
Gem::Specification.new do |spec|
spec.name = "securerandom"
- spec.version = "0.1.0"
+ spec.version = "0.1.1"
spec.authors = ["Tanaka Akira"]
spec.email = ["akr@fsij.org"]
spec.summary = %q{Interface for secure random number generator.}
spec.description = %q{Interface for secure random number generator.}
spec.homepage = "https://github.com/ruby/securerandom"
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
spec.licenses = ["Ruby", "BSD-2-Clause"]
spec.metadata["homepage_uri"] = spec.homepage
diff --git a/lib/securerandom.rb b/lib/securerandom.rb
index 323d4af3b71..9cbf4ea7894 100644
--- a/lib/securerandom.rb
+++ b/lib/securerandom.rb
@@ -1,6 +1,8 @@
# -*- coding: us-ascii -*-
# frozen_string_literal: true
+require 'random/formatter'
+
# == Secure random number generator interface.
#
# This library is an interface to secure random number generators which are
@@ -33,37 +35,8 @@
# These methods are usable as class methods of SecureRandom such as
# +SecureRandom.hex+.
#
-# === Examples
-#
-# Generate random hexadecimal strings:
-#
-# require 'securerandom'
-#
-# SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
-# SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
-# SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
-#
-# Generate random base64 strings:
-#
-# SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
-# SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
-# SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
-#
-# Generate random binary strings:
-#
-# SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
-# SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
-#
-# Generate alphanumeric strings:
-#
-# SecureRandom.alphanumeric(10) #=> "S8baxMJnPl"
-# SecureRandom.alphanumeric(10) #=> "aOxAg8BAJe"
-#
-# Generate UUIDs:
-#
-# SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
-# SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
-#
+# If a secure random number generator is not available,
+# +NotImplementedError+ is raised.
module SecureRandom
class << self
@@ -116,202 +89,4 @@ module SecureRandom
end
end
-module Random::Formatter
-
- # SecureRandom.random_bytes generates a random binary string.
- #
- # The argument _n_ specifies the length of the result string.
- #
- # If _n_ is not specified or is nil, 16 is assumed.
- # It may be larger in future.
- #
- # The result may contain any byte: "\x00" - "\xff".
- #
- # require 'securerandom'
- #
- # SecureRandom.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
- # SecureRandom.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
- #
- # If a secure random number generator is not available,
- # +NotImplementedError+ is raised.
- def random_bytes(n=nil)
- n = n ? n.to_int : 16
- gen_random(n)
- end
-
- # SecureRandom.hex generates a random hexadecimal string.
- #
- # The argument _n_ specifies the length, in bytes, of the random number to be generated.
- # The length of the resulting hexadecimal string is twice of _n_.
- #
- # If _n_ is not specified or is nil, 16 is assumed.
- # It may be larger in the future.
- #
- # The result may contain 0-9 and a-f.
- #
- # require 'securerandom'
- #
- # SecureRandom.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
- # SecureRandom.hex #=> "91dc3bfb4de5b11d029d376634589b61"
- #
- # If a secure random number generator is not available,
- # +NotImplementedError+ is raised.
- def hex(n=nil)
- random_bytes(n).unpack("H*")[0]
- end
-
- # SecureRandom.base64 generates a random base64 string.
- #
- # The argument _n_ specifies the length, in bytes, of the random number
- # to be generated. The length of the result string is about 4/3 of _n_.
- #
- # If _n_ is not specified or is nil, 16 is assumed.
- # It may be larger in the future.
- #
- # The result may contain A-Z, a-z, 0-9, "+", "/" and "=".
- #
- # require 'securerandom'
- #
- # SecureRandom.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
- # SecureRandom.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
- #
- # If a secure random number generator is not available,
- # +NotImplementedError+ is raised.
- #
- # See RFC 3548 for the definition of base64.
- def base64(n=nil)
- [random_bytes(n)].pack("m0")
- end
-
- # SecureRandom.urlsafe_base64 generates a random URL-safe base64 string.
- #
- # The argument _n_ specifies the length, in bytes, of the random number
- # to be generated. The length of the result string is about 4/3 of _n_.
- #
- # If _n_ is not specified or is nil, 16 is assumed.
- # It may be larger in the future.
- #
- # The boolean argument _padding_ specifies the padding.
- # If it is false or nil, padding is not generated.
- # Otherwise padding is generated.
- # By default, padding is not generated because "=" may be used as a URL delimiter.
- #
- # The result may contain A-Z, a-z, 0-9, "-" and "_".
- # "=" is also used if _padding_ is true.
- #
- # require 'securerandom'
- #
- # SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
- # SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
- #
- # SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
- # SecureRandom.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="
- #
- # If a secure random number generator is not available,
- # +NotImplementedError+ is raised.
- #
- # See RFC 3548 for the definition of URL-safe base64.
- def urlsafe_base64(n=nil, padding=false)
- s = [random_bytes(n)].pack("m0")
- s.tr!("+/", "-_")
- s.delete!("=") unless padding
- s
- end
-
- # SecureRandom.uuid generates a random v4 UUID (Universally Unique IDentifier).
- #
- # require 'securerandom'
- #
- # SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
- # SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
- # SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
- #
- # The version 4 UUID is purely random (except the version).
- # It doesn't contain meaningful information such as MAC addresses, timestamps, etc.
- #
- # The result contains 122 random bits (15.25 random bytes).
- #
- # See RFC 4122 for details of UUID.
- #
- def uuid
- ary = random_bytes(16).unpack("NnnnnN")
- ary[2] = (ary[2] & 0x0fff) | 0x4000
- ary[3] = (ary[3] & 0x3fff) | 0x8000
- "%08x-%04x-%04x-%04x-%04x%08x" % ary
- end
-
- private def gen_random(n)
- self.bytes(n)
- end
-
- # SecureRandom.choose generates a string that randomly draws from a
- # source array of characters.
- #
- # The argument _source_ specifies the array of characters from which
- # to generate the string.
- # The argument _n_ specifies the length, in characters, of the string to be
- # generated.
- #
- # The result may contain whatever characters are in the source array.
- #
- # require 'securerandom'
- #
- # SecureRandom.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
- # SecureRandom.choose([*'0'..'9'], 5) #=> "27309"
- #
- # If a secure random number generator is not available,
- # +NotImplementedError+ is raised.
- private def choose(source, n)
- size = source.size
- m = 1
- limit = size
- while limit * size <= 0x100000000
- limit *= size
- m += 1
- end
- result = ''.dup
- while m <= n
- rs = random_number(limit)
- is = rs.digits(size)
- (m-is.length).times { is << 0 }
- result << source.values_at(*is).join('')
- n -= m
- end
- if 0 < n
- rs = random_number(limit)
- is = rs.digits(size)
- if is.length < n
- (n-is.length).times { is << 0 }
- else
- is.pop while n < is.length
- end
- result.concat source.values_at(*is).join('')
- end
- result
- end
-
- ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9']
- # SecureRandom.alphanumeric generates a random alphanumeric string.
- #
- # The argument _n_ specifies the length, in characters, of the alphanumeric
- # string to be generated.
- #
- # If _n_ is not specified or is nil, 16 is assumed.
- # It may be larger in the future.
- #
- # The result may contain A-Z, a-z and 0-9.
- #
- # require 'securerandom'
- #
- # SecureRandom.alphanumeric #=> "2BuBuLf3WfSKyQbR"
- # SecureRandom.alphanumeric(10) #=> "i6K93NdqiH"
- #
- # If a secure random number generator is not available,
- # +NotImplementedError+ is raised.
- def alphanumeric(n=nil)
- n = 16 if n.nil?
- choose(ALPHANUMERIC, n)
- end
-end
-
SecureRandom.extend(Random::Formatter)
diff --git a/lib/set/set.gemspec b/lib/set/set.gemspec
index d14febaad4a..83100df7850 100644
--- a/lib/set/set.gemspec
+++ b/lib/set/set.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "set"
- spec.version = "1.0.1"
+ spec.version = "1.0.2"
spec.authors = ["Akinori MUSHA"]
spec.email = ["knu@idaemons.org"]
diff --git a/lib/tempfile.gemspec b/lib/tempfile.gemspec
index 4917b997e7f..c4f28b2ef70 100644
--- a/lib/tempfile.gemspec
+++ b/lib/tempfile.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "tempfile"
- spec.version = "0.1.1"
+ spec.version = "0.1.2"
spec.authors = ["Yukihiro Matsumoto"]
spec.email = ["matz@ruby-lang.org"]
diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb
index bf7db5282ac..3b671640392 100644
--- a/lib/tmpdir.rb
+++ b/lib/tmpdir.rb
@@ -69,7 +69,7 @@ class Dir
#
# Dir.mktmpdir {|dir|
# # use the directory...
- # open("#{dir}/foo", "w") { ... }
+ # open("#{dir}/foo", "w") { something using the file }
# }
#
# If a block is not given,
@@ -79,7 +79,7 @@ class Dir
# dir = Dir.mktmpdir
# begin
# # use the directory...
- # open("#{dir}/foo", "w") { ... }
+ # open("#{dir}/foo", "w") { something using the file }
# ensure
# # remove the directory.
# FileUtils.remove_entry dir
diff --git a/lib/uri/http.rb b/lib/uri/http.rb
index 6e9c963ef10..306daf19656 100644
--- a/lib/uri/http.rb
+++ b/lib/uri/http.rb
@@ -80,6 +80,45 @@ module URI
url = @query ? "#@path?#@query" : @path.dup
url.start_with?(?/.freeze) ? url : ?/ + url
end
+
+ #
+ # == Description
+ #
+ # Returns the authority for an HTTP uri, as defined in
+ # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.
+ #
+ #
+ # Example:
+ #
+ # URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com"
+ # URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000"
+ # URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com"
+ #
+ def authority
+ if port == default_port
+ host
+ else
+ "#{host}:#{port}"
+ end
+ end
+
+ #
+ # == Description
+ #
+ # Returns the origin for an HTTP uri, as defined in
+ # https://datatracker.ietf.org/doc/html/rfc6454.
+ #
+ #
+ # Example:
+ #
+ # URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com"
+ # URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000"
+ # URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com"
+ # URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com"
+ #
+ def origin
+ "#{scheme}://#{authority}"
+ end
end
register_scheme 'HTTP', HTTP
diff --git a/lib/uri/version.rb b/lib/uri/version.rb
index 41da51d844c..82188e25ada 100644
--- a/lib/uri/version.rb
+++ b/lib/uri/version.rb
@@ -1,6 +1,6 @@
module URI
# :stopdoc:
- VERSION_CODE = '001001'.freeze
+ VERSION_CODE = '001100'.freeze
VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
# :startdoc:
end