summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/English.gemspec11
-rw-r--r--lib/English.rb50
-rw-r--r--lib/bundled_gems.rb92
-rw-r--r--lib/bundler.rb60
-rw-r--r--lib/bundler/build_metadata.rb18
-rw-r--r--lib/bundler/bundler.gemspec10
-rw-r--r--lib/bundler/capistrano.rb20
-rw-r--r--lib/bundler/checksum.rb6
-rw-r--r--lib/bundler/cli.rb285
-rw-r--r--lib/bundler/cli/add.rb10
-rw-r--r--lib/bundler/cli/cache.rb13
-rw-r--r--lib/bundler/cli/common.rb25
-rw-r--r--lib/bundler/cli/config.rb3
-rw-r--r--lib/bundler/cli/console.rb5
-rw-r--r--lib/bundler/cli/doctor/diagnose.rb2
-rw-r--r--lib/bundler/cli/exec.rb33
-rw-r--r--lib/bundler/cli/gem.rb144
-rw-r--r--lib/bundler/cli/inject.rb60
-rw-r--r--lib/bundler/cli/install.rb105
-rw-r--r--lib/bundler/cli/issue.rb4
-rw-r--r--lib/bundler/cli/list.rb35
-rw-r--r--lib/bundler/cli/lock.rb10
-rw-r--r--lib/bundler/cli/outdated.rb2
-rw-r--r--lib/bundler/cli/plugin.rb6
-rw-r--r--lib/bundler/cli/pristine.rb4
-rw-r--r--lib/bundler/cli/show.rb10
-rw-r--r--lib/bundler/cli/update.rb10
-rw-r--r--lib/bundler/cli/viz.rb31
-rw-r--r--lib/bundler/compact_index_client.rb1
-rw-r--r--lib/bundler/current_ruby.rb20
-rw-r--r--lib/bundler/definition.rb292
-rw-r--r--lib/bundler/dependency.rb2
-rw-r--r--lib/bundler/deployment.rb65
-rw-r--r--lib/bundler/digest.rb2
-rw-r--r--lib/bundler/dsl.rb66
-rw-r--r--lib/bundler/environment_preserver.rb1
-rw-r--r--lib/bundler/errors.rb13
-rw-r--r--lib/bundler/feature_flag.rb40
-rw-r--r--lib/bundler/fetcher.rb67
-rw-r--r--lib/bundler/fetcher/compact_index.rb2
-rw-r--r--lib/bundler/fetcher/dependency.rb3
-rw-r--r--lib/bundler/fetcher/downloader.rb40
-rw-r--r--lib/bundler/fetcher/gem_remote_fetcher.rb6
-rw-r--r--lib/bundler/friendly_errors.rb7
-rw-r--r--lib/bundler/gem_helpers.rb144
-rw-r--r--lib/bundler/graph.rb152
-rw-r--r--lib/bundler/index.rb16
-rw-r--r--lib/bundler/inline.rb10
-rw-r--r--lib/bundler/installer.rb6
-rw-r--r--lib/bundler/installer/gem_installer.rb11
-rw-r--r--lib/bundler/lazy_specification.rb67
-rw-r--r--lib/bundler/lockfile_generator.rb2
-rw-r--r--lib/bundler/lockfile_parser.rb16
-rw-r--r--lib/bundler/man/bundle-add.12
-rw-r--r--lib/bundler/man/bundle-binstubs.19
-rw-r--r--lib/bundler/man/bundle-binstubs.1.ronn10
-rw-r--r--lib/bundler/man/bundle-cache.116
-rw-r--r--lib/bundler/man/bundle-cache.1.ronn15
-rw-r--r--lib/bundler/man/bundle-check.17
-rw-r--r--lib/bundler/man/bundle-check.1.ronn5
-rw-r--r--lib/bundler/man/bundle-clean.12
-rw-r--r--lib/bundler/man/bundle-config.1333
-rw-r--r--lib/bundler/man/bundle-config.1.ronn228
-rw-r--r--lib/bundler/man/bundle-console.12
-rw-r--r--lib/bundler/man/bundle-doctor.147
-rw-r--r--lib/bundler/man/bundle-doctor.1.ronn52
-rw-r--r--lib/bundler/man/bundle-env.12
-rw-r--r--lib/bundler/man/bundle-exec.17
-rw-r--r--lib/bundler/man/bundle-exec.1.ronn6
-rw-r--r--lib/bundler/man/bundle-fund.12
-rw-r--r--lib/bundler/man/bundle-gem.1108
-rw-r--r--lib/bundler/man/bundle-gem.1.ronn19
-rw-r--r--lib/bundler/man/bundle-help.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.131
-rw-r--r--lib/bundler/man/bundle-inject.1.ronn32
-rw-r--r--lib/bundler/man/bundle-install.175
-rw-r--r--lib/bundler/man/bundle-install.1.ronn135
-rw-r--r--lib/bundler/man/bundle-issue.12
-rw-r--r--lib/bundler/man/bundle-licenses.12
-rw-r--r--lib/bundler/man/bundle-list.17
-rw-r--r--lib/bundler/man/bundle-list.1.ronn5
-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-plugin.148
-rw-r--r--lib/bundler/man/bundle-plugin.1.ronn51
-rw-r--r--lib/bundler/man/bundle-pristine.12
-rw-r--r--lib/bundler/man/bundle-remove.110
-rw-r--r--lib/bundler/man/bundle-remove.1.ronn9
-rw-r--r--lib/bundler/man/bundle-show.17
-rw-r--r--lib/bundler/man/bundle-show.1.ronn4
-rw-r--r--lib/bundler/man/bundle-update.110
-rw-r--r--lib/bundler/man/bundle-update.1.ronn8
-rw-r--r--lib/bundler/man/bundle-version.12
-rw-r--r--lib/bundler/man/bundle-viz.130
-rw-r--r--lib/bundler/man/bundle-viz.1.ronn36
-rw-r--r--lib/bundler/man/bundle.111
-rw-r--r--lib/bundler/man/bundle.1.ronn9
-rw-r--r--lib/bundler/man/gemfile.533
-rw-r--r--lib/bundler/man/gemfile.5.ronn28
-rw-r--r--lib/bundler/man/index.txt2
-rw-r--r--lib/bundler/match_platform.rb43
-rw-r--r--lib/bundler/materialization.rb6
-rw-r--r--lib/bundler/plugin.rb2
-rw-r--r--lib/bundler/plugin/installer.rb10
-rw-r--r--lib/bundler/plugin/source_list.rb2
-rw-r--r--lib/bundler/resolver.rb6
-rw-r--r--lib/bundler/resolver/package.rb3
-rw-r--r--lib/bundler/ruby_dsl.rb29
-rw-r--r--lib/bundler/ruby_version.rb4
-rw-r--r--lib/bundler/rubygems_ext.rb238
-rw-r--r--lib/bundler/rubygems_gem_installer.rb6
-rw-r--r--lib/bundler/rubygems_integration.rb25
-rw-r--r--lib/bundler/runtime.rb17
-rw-r--r--lib/bundler/self_manager.rb76
-rw-r--r--lib/bundler/settings.rb44
-rw-r--r--lib/bundler/settings/validator.rb23
-rw-r--r--lib/bundler/shared_helpers.rb33
-rw-r--r--lib/bundler/similarity_detector.rb63
-rw-r--r--lib/bundler/source.rb2
-rw-r--r--lib/bundler/source/gemspec.rb4
-rw-r--r--lib/bundler/source/git.rb5
-rw-r--r--lib/bundler/source/git/git_proxy.rb18
-rw-r--r--lib/bundler/source/path.rb19
-rw-r--r--lib/bundler/source/rubygems.rb28
-rw-r--r--lib/bundler/source_list.rb55
-rw-r--r--lib/bundler/source_map.rb7
-rw-r--r--lib/bundler/spec_set.rb23
-rw-r--r--lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt136
-rw-r--r--lib/bundler/templates/newgem/Rakefile.tt5
-rw-r--r--lib/bundler/templates/newgem/circleci/config.yml.tt12
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt2
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt11
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/go.mod.tt5
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt2
-rw-r--r--lib/bundler/templates/newgem/ext/newgem/newgem.go.tt31
-rw-r--r--lib/bundler/templates/newgem/github/workflows/main.yml.tt8
-rw-r--r--lib/bundler/templates/newgem/gitlab-ci.yml.tt9
-rw-r--r--lib/bundler/templates/newgem/lib/newgem.rb.tt2
-rw-r--r--lib/bundler/templates/newgem/newgem.gemspec.tt16
-rw-r--r--lib/bundler/ui/shell.rb20
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool.rb59
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb65
-rw-r--r--lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb2
-rw-r--r--lib/bundler/vendor/fileutils/lib/fileutils.rb109
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb126
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb3
-rw-r--r--lib/bundler/vendor/thor/lib/thor.rb2
-rw-r--r--lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb48
-rw-r--r--lib/bundler/vendor/thor/lib/thor/parser/options.rb2
-rw-r--r--lib/bundler/vendor/thor/lib/thor/runner.rb4
-rw-r--r--lib/bundler/vendor/thor/lib/thor/shell/basic.rb10
-rw-r--r--lib/bundler/vendor/thor/lib/thor/version.rb2
-rw-r--r--lib/bundler/vendor/uri/lib/uri/common.rb72
-rw-r--r--lib/bundler/vendor/uri/lib/uri/file.rb2
-rw-r--r--lib/bundler/vendor/uri/lib/uri/generic.rb55
-rw-r--r--lib/bundler/vendor/uri/lib/uri/http.rb12
-rw-r--r--lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb17
-rw-r--r--lib/bundler/vendor/uri/lib/uri/version.rb4
-rw-r--r--lib/bundler/version.rb12
-rw-r--r--lib/bundler/vlad.rb15
-rw-r--r--lib/cgi.rb300
-rw-r--r--lib/cgi/cgi.gemspec43
-rw-r--r--lib/cgi/cookie.rb210
-rw-r--r--lib/cgi/core.rb900
-rw-r--r--lib/cgi/escape.rb232
-rw-r--r--lib/cgi/html.rb1035
-rw-r--r--lib/cgi/session.rb562
-rw-r--r--lib/cgi/session/pstore.rb91
-rw-r--r--lib/cgi/util.rb261
-rw-r--r--lib/delegate.rb45
-rw-r--r--lib/erb.rb1270
-rw-r--r--lib/erb/compiler.rb3
-rw-r--r--lib/erb/def_method.rb2
-rw-r--r--lib/erb/erb.gemspec (renamed from lib/erb.gemspec)11
-rw-r--r--lib/erb/util.rb68
-rw-r--r--lib/erb/version.rb4
-rw-r--r--lib/error_highlight/base.rb196
-rw-r--r--lib/error_highlight/core_ext.rb35
-rw-r--r--lib/error_highlight/error_highlight.gemspec2
-rw-r--r--lib/error_highlight/formatter.rb4
-rw-r--r--lib/error_highlight/version.rb2
-rw-r--r--lib/fileutils.rb109
-rw-r--r--lib/find.rb1
-rw-r--r--lib/forwardable.rb51
-rw-r--r--lib/forwardable/forwardable.gemspec2
-rw-r--r--lib/forwardable/impl.rb17
-rw-r--r--lib/ipaddr.gemspec2
-rw-r--r--lib/ipaddr.rb42
-rw-r--r--lib/mkmf.rb8
-rw-r--r--lib/net/http.rb88
-rw-r--r--lib/net/http/exceptions.rb3
-rw-r--r--lib/net/http/generic_request.rb45
-rw-r--r--lib/net/http/header.rb12
-rw-r--r--lib/net/http/net-http.gemspec9
-rw-r--r--lib/net/http/requests.rb16
-rw-r--r--lib/net/http/response.rb3
-rw-r--r--lib/net/http/responses.rb72
-rw-r--r--lib/net/net-protocol.gemspec5
-rw-r--r--lib/net/protocol.rb15
-rw-r--r--lib/open-uri.rb10
-rw-r--r--lib/open3/version.rb1
-rw-r--r--lib/optparse.rb109
-rw-r--r--lib/optparse/optparse.gemspec7
-rw-r--r--lib/pathname.rb74
-rw-r--r--lib/pp.rb70
-rw-r--r--lib/prettyprint.rb1
-rw-r--r--lib/prism.rb31
-rw-r--r--lib/prism/desugar_compiler.rb1
-rw-r--r--lib/prism/ffi.rb26
-rw-r--r--lib/prism/lex_compat.rb189
-rw-r--r--lib/prism/lex_ripper.rb62
-rw-r--r--lib/prism/node_ext.rb3
-rw-r--r--lib/prism/pack.rb2
-rw-r--r--lib/prism/parse_result.rb27
-rw-r--r--lib/prism/parse_result/comments.rb1
-rw-r--r--lib/prism/parse_result/errors.rb1
-rw-r--r--lib/prism/parse_result/newlines.rb1
-rw-r--r--lib/prism/pattern.rb1
-rw-r--r--lib/prism/polyfill/scan_byte.rb14
-rw-r--r--lib/prism/polyfill/warn.rb38
-rw-r--r--lib/prism/prism.gemspec14
-rw-r--r--lib/prism/relocation.rb1
-rw-r--r--lib/prism/string_query.rb1
-rw-r--r--lib/prism/translation.rb9
-rw-r--r--lib/prism/translation/parser.rb16
-rw-r--r--lib/prism/translation/parser/builder.rb1
-rw-r--r--lib/prism/translation/parser/compiler.rb33
-rw-r--r--lib/prism/translation/parser/lexer.rb48
-rw-r--r--lib/prism/translation/parser33.rb12
-rw-r--r--lib/prism/translation/parser34.rb12
-rw-r--r--lib/prism/translation/parser35.rb12
-rw-r--r--lib/prism/translation/parser_current.rb10
-rw-r--r--lib/prism/translation/parser_versions.rb36
-rw-r--r--lib/prism/translation/ripper.rb127
-rw-r--r--lib/prism/translation/ripper/filter.rb53
-rw-r--r--lib/prism/translation/ripper/lexer.rb135
-rw-r--r--lib/prism/translation/ripper/sexp.rb1
-rw-r--r--lib/prism/translation/ruby_parser.rb364
-rw-r--r--lib/resolv.gemspec5
-rw-r--r--lib/resolv.rb60
-rw-r--r--lib/rubygems.rb136
-rw-r--r--lib/rubygems/basic_specification.rb19
-rw-r--r--lib/rubygems/bundler_version_finder.rb41
-rw-r--r--lib/rubygems/command.rb2
-rw-r--r--lib/rubygems/command_manager.rb7
-rw-r--r--lib/rubygems/commands/build_command.rb7
-rw-r--r--lib/rubygems/commands/cert_command.rb2
-rw-r--r--lib/rubygems/commands/install_command.rb6
-rw-r--r--lib/rubygems/commands/pristine_command.rb25
-rw-r--r--lib/rubygems/commands/query_command.rb43
-rw-r--r--lib/rubygems/commands/rebuild_command.rb1
-rw-r--r--lib/rubygems/commands/setup_command.rb14
-rw-r--r--lib/rubygems/commands/sources_command.rb140
-rw-r--r--lib/rubygems/compatibility.rb41
-rw-r--r--lib/rubygems/config_file.rb2
-rw-r--r--lib/rubygems/core_ext/kernel_require.rb7
-rw-r--r--lib/rubygems/defaults.rb2
-rw-r--r--lib/rubygems/dependency.rb2
-rw-r--r--lib/rubygems/dependency_installer.rb79
-rw-r--r--lib/rubygems/dependency_list.rb3
-rw-r--r--lib/rubygems/deprecate.rb146
-rw-r--r--lib/rubygems/doctor.rb2
-rw-r--r--lib/rubygems/errors.rb2
-rw-r--r--lib/rubygems/exceptions.rb20
-rw-r--r--lib/rubygems/ext/builder.rb32
-rw-r--r--lib/rubygems/ext/cargo_builder.rb6
-rw-r--r--lib/rubygems/ext/cmake_builder.rb105
-rw-r--r--lib/rubygems/ext/configure_builder.rb6
-rw-r--r--lib/rubygems/ext/ext_conf_builder.rb10
-rw-r--r--lib/rubygems/ext/rake_builder.rb4
-rw-r--r--lib/rubygems/gem_runner.rb1
-rw-r--r--lib/rubygems/gemcutter_utilities.rb7
-rw-r--r--lib/rubygems/install_default_message.rb13
-rw-r--r--lib/rubygems/install_update_options.rb12
-rw-r--r--lib/rubygems/installer.rb177
-rw-r--r--lib/rubygems/name_tuple.rb8
-rw-r--r--lib/rubygems/package.rb33
-rw-r--r--lib/rubygems/package/tar_header.rb8
-rw-r--r--lib/rubygems/package/tar_reader.rb2
-rw-r--r--lib/rubygems/package/tar_writer.rb9
-rw-r--r--lib/rubygems/platform.rb223
-rw-r--r--lib/rubygems/psych_tree.rb2
-rw-r--r--lib/rubygems/remote_fetcher.rb22
-rw-r--r--lib/rubygems/request/connection_pools.rb7
-rw-r--r--lib/rubygems/request/http_pool.rb15
-rw-r--r--lib/rubygems/request_set.rb9
-rw-r--r--lib/rubygems/request_set/lockfile.rb2
-rw-r--r--lib/rubygems/resolver.rb5
-rw-r--r--lib/rubygems/resolver/best_set.rb2
-rw-r--r--lib/rubygems/resolver/conflict.rb2
-rw-r--r--lib/rubygems/resolver/source_set.rb2
-rw-r--r--lib/rubygems/s3_uri_signer.rb69
-rw-r--r--lib/rubygems/security/policy.rb2
-rw-r--r--lib/rubygems/security/signer.rb2
-rw-r--r--lib/rubygems/source.rb56
-rw-r--r--lib/rubygems/source_list.rb36
-rw-r--r--lib/rubygems/spec_fetcher.rb8
-rw-r--r--lib/rubygems/specification.rb106
-rw-r--r--lib/rubygems/specification_policy.rb36
-rw-r--r--lib/rubygems/specification_record.rb2
-rw-r--r--lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem (renamed from lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem)0
-rw-r--r--lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem21
-rw-r--r--lib/rubygems/text.rb2
-rw-r--r--lib/rubygems/uninstaller.rb23
-rw-r--r--lib/rubygems/uri_formatter.rb3
-rw-r--r--lib/rubygems/user_interaction.rb15
-rw-r--r--lib/rubygems/util.rb22
-rw-r--r--lib/rubygems/util/atomic_file_writer.rb76
-rw-r--r--lib/rubygems/util/licenses.rb21
-rw-r--r--lib/rubygems/util/list.rb40
-rw-r--r--lib/rubygems/validator.rb2
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http.rb148
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb3
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb45
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/header.rb12
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/requests.rb16
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/response.rb3
-rw-r--r--lib/rubygems/vendor/net-http/lib/net/http/responses.rb72
-rw-r--r--lib/rubygems/vendor/optparse/lib/optparse.rb123
-rw-r--r--lib/rubygems/vendor/resolv/lib/resolv.rb74
-rw-r--r--lib/rubygems/vendor/timeout/lib/timeout.rb5
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/common.rb72
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/file.rb2
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/generic.rb55
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/http.rb12
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb17
-rw-r--r--lib/rubygems/vendor/uri/lib/uri/version.rb4
-rw-r--r--lib/rubygems/version.rb28
-rw-r--r--lib/rubygems/win_platform.rb31
-rw-r--r--lib/set/sorted_set.rb6
-rw-r--r--lib/set/subclass_compatible.rb347
-rw-r--r--lib/shellwords.gemspec2
-rw-r--r--lib/singleton.rb11
-rw-r--r--lib/syntax_suggest/api.rb6
-rw-r--r--lib/syntax_suggest/code_line.rb19
-rw-r--r--lib/syntax_suggest/version.rb2
-rw-r--r--lib/tempfile.rb8
-rw-r--r--lib/time.gemspec1
-rw-r--r--lib/time.rb15
-rw-r--r--lib/timeout.rb245
-rw-r--r--lib/tsort.gemspec29
-rw-r--r--lib/tsort.rb455
-rw-r--r--lib/unicode_normalize/normalize.rb31
-rw-r--r--lib/unicode_normalize/tables.rb49
-rw-r--r--lib/uri/common.rb68
-rw-r--r--lib/uri/generic.rb55
-rw-r--r--lib/uri/http.rb12
-rw-r--r--lib/uri/rfc2396_parser.rb17
-rw-r--r--lib/uri/version.rb4
-rw-r--r--lib/weakref.rb3
-rw-r--r--lib/yaml.rb1
355 files changed, 7832 insertions, 8947 deletions
diff --git a/lib/English.gemspec b/lib/English.gemspec
index 5f4eb420c2..9c09555ca1 100644
--- a/lib/English.gemspec
+++ b/lib/English.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "english"
- spec.version = "0.8.0"
+ spec.version = "0.8.1"
spec.authors = ["Yukihiro Matsumoto"]
spec.email = ["matz@ruby-lang.org"]
@@ -15,8 +15,13 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
+ excludes = %W[
+ :^/test :^/spec :^/feature :^/bin
+ :^/Rakefile :^/Gemfile\* :^/.git*
+ :^/#{File.basename(__FILE__)}
+ ]
+ spec.files = IO.popen(%W[git ls-files -z --] + excludes, err: IO::NULL) do |f|
+ f.readlines("\x0", chomp: true)
end
spec.require_paths = ["lib"]
end
diff --git a/lib/English.rb b/lib/English.rb
index 03fe721991..bf7896dcd6 100644
--- a/lib/English.rb
+++ b/lib/English.rb
@@ -9,7 +9,7 @@
# "waterbuffalo" =~ /buff/
# print $', $$, "\n"
#
-# With English:
+# With 'English':
#
# require "English"
#
@@ -20,30 +20,30 @@
# Below is a full list of descriptive aliases and their associated global
# variable:
#
-# $ERROR_INFO:: $!
-# $ERROR_POSITION:: $@
-# $FS:: $;
-# $FIELD_SEPARATOR:: $;
-# $OFS:: $,
-# $OUTPUT_FIELD_SEPARATOR:: $,
-# $RS:: $/
-# $INPUT_RECORD_SEPARATOR:: $/
-# $ORS:: $\
-# $OUTPUT_RECORD_SEPARATOR:: $\
-# $INPUT_LINE_NUMBER:: $.
-# $NR:: $.
-# $LAST_READ_LINE:: $_
-# $DEFAULT_OUTPUT:: $>
-# $DEFAULT_INPUT:: $<
-# $PID:: $$
-# $PROCESS_ID:: $$
-# $CHILD_STATUS:: $?
-# $LAST_MATCH_INFO:: $~
-# $ARGV:: $*
-# $MATCH:: $&
-# $PREMATCH:: $`
-# $POSTMATCH:: $'
-# $LAST_PAREN_MATCH:: $+
+# <tt>$ERROR_INFO</tt>:: <tt>$!</tt>
+# <tt>$ERROR_POSITION</tt>:: <tt>$@</tt>
+# <tt>$FS</tt>:: <tt>$;</tt>
+# <tt>$FIELD_SEPARATOR</tt>:: <tt>$;</tt>
+# <tt>$OFS</tt>:: <tt>$,</tt>
+# <tt>$OUTPUT_FIELD_SEPARATOR</tt>:: <tt>$,</tt>
+# <tt>$RS</tt>:: <tt>$/</tt>
+# <tt>$INPUT_RECORD_SEPARATOR</tt>:: <tt>$/</tt>
+# <tt>$ORS</tt>:: <tt>$\</tt>
+# <tt>$OUTPUT_RECORD_SEPARATOR</tt>:: <tt>$\</tt>
+# <tt>$NR</tt>:: <tt>$.</tt>
+# <tt>$INPUT_LINE_NUMBER</tt>:: <tt>$.</tt>
+# <tt>$LAST_READ_LINE</tt>:: <tt>$_</tt>
+# <tt>$DEFAULT_OUTPUT</tt>:: <tt>$></tt>
+# <tt>$DEFAULT_INPUT</tt>:: <tt>$<</tt>
+# <tt>$PID</tt>:: <tt>$$</tt>
+# <tt>$PROCESS_ID</tt>:: <tt>$$</tt>
+# <tt>$CHILD_STATUS</tt>:: <tt>$?</tt>
+# <tt>$LAST_MATCH_INFO</tt>:: <tt>$~</tt>
+# <tt>$ARGV</tt>:: <tt>$*</tt>
+# <tt>$MATCH</tt>:: <tt>$&</tt>
+# <tt>$PREMATCH</tt>:: <tt>$`</tt>
+# <tt>$POSTMATCH</tt>:: <tt>$'</tt>
+# <tt>$LAST_PAREN_MATCH</tt>:: <tt>$+</tt>
#
module English end if false
diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb
index fab79f42a9..85f23b2596 100644
--- a/lib/bundled_gems.rb
+++ b/lib/bundled_gems.rb
@@ -1,5 +1,10 @@
# -*- frozen-string-literal: true -*-
+# :stopdoc:
+module Gem
+end
+# :startdoc:
+
module Gem::BUNDLED_GEMS # :nodoc:
SINCE = {
"racc" => "3.3.0",
@@ -15,16 +20,17 @@ module Gem::BUNDLED_GEMS # :nodoc:
"resolv-replace" => "3.4.0",
"rinda" => "3.4.0",
"syslog" => "3.4.0",
- "ostruct" => "3.5.0",
- "pstore" => "3.5.0",
- "rdoc" => "3.5.0",
- "win32ole" => "3.5.0",
- "fiddle" => "3.5.0",
- "logger" => "3.5.0",
- "benchmark" => "3.5.0",
- "irb" => "3.5.0",
- "reline" => "3.5.0",
- # "readline" => "3.5.0", # This is wrapper for reline. We don't warn for this.
+ "ostruct" => "4.0.0",
+ "pstore" => "4.0.0",
+ "rdoc" => "4.0.0",
+ "win32ole" => "4.0.0",
+ "fiddle" => "4.0.0",
+ "logger" => "4.0.0",
+ "benchmark" => "4.0.0",
+ "irb" => "4.0.0",
+ "reline" => "4.0.0",
+ # "readline" => "4.0.0", # This is wrapper for reline. We don't warn for this.
+ "tsort" => "4.1.0",
}.freeze
EXACT = {
@@ -49,12 +55,7 @@ module Gem::BUNDLED_GEMS # :nodoc:
kernel_class.send(:alias_method, :no_warning_require, :require)
kernel_class.send(:define_method, :require) do |name|
if message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names)
- uplevel = ::Gem::BUNDLED_GEMS.uplevel
- if uplevel > 0
- Kernel.warn message, uplevel: uplevel
- else
- Kernel.warn message
- end
+ Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel
end
kernel_class.send(:no_warning_require, name)
end
@@ -86,11 +87,10 @@ module Gem::BUNDLED_GEMS # :nodoc:
uplevel += 1
# Don't show script name when bundle exec and call ruby script directly.
if cl.path.end_with?("bundle")
- frame_count = 0
- break
+ return
end
end
- require_found ? 1 : frame_count - 1
+ require_found ? 1 : (frame_count - 1).nonzero?
end
def self.warning?(name, specs: nil)
@@ -105,11 +105,14 @@ module Gem::BUNDLED_GEMS # :nodoc:
# and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`.
feature.delete_prefix!(ARCHDIR)
feature.delete_prefix!(LIBDIR)
- segments = feature.split("/")
+ # 1. A segment for the EXACT mapping and SINCE check
+ # 2. A segment for the SINCE check for dashed names
+ # 3. A segment to check if there's a subfeature
+ segments = feature.split("/", 3)
name = segments.shift
name = EXACT[name] || name
if !SINCE[name]
- name = [name, segments.shift].join("-")
+ name = "#{name}-#{segments.shift}"
return unless SINCE[name]
end
segments.any?
@@ -119,29 +122,43 @@ module Gem::BUNDLED_GEMS # :nodoc:
false
end
+ if suppress_list = Thread.current[:__bundled_gems_warning_suppression]
+ return if suppress_list.include?(name) || suppress_list.include?(feature)
+ end
+
return if specs.include?(name)
+ # Don't warn if a hyphenated gem provides this feature
+ # (e.g., benchmark-ips provides benchmark/ips, not the benchmark gem)
+ if subfeature
+ feature_parts = feature.split("/")
+ if feature_parts.size >= 2
+ hyphenated_gem = "#{feature_parts[0]}-#{feature_parts[1]}"
+ return if specs.include?(hyphenated_gem)
+ end
+ end
+
return if WARNED[name]
WARNED[name] = true
- level = RUBY_VERSION < SINCE[name] ? "warning" : "error"
+ level = RUBY_VERSION < SINCE[name] ? :warning : :error
if subfeature
"#{feature} is found in #{name}, which"
else
- "#{feature} #{level == "warning" ? "was loaded" : "used to be loaded"} from the standard library, but"
+ "#{feature} #{level == :warning ? "was loaded" : "used to be loaded"} from the standard library, but"
end + build_message(name, level)
end
def self.build_message(name, level)
- msg = if level == "warning"
+ msg = if level == :warning
" will no longer be part of the default gems starting from Ruby #{SINCE[name]}"
else
" is not part of the default gems since Ruby #{SINCE[name]}."
end
if defined?(Bundler)
- motivation = level == "warning" ? "silence this warning" : "fix this error"
+ motivation = level == :warning ? "silence this warning" : "fix this error"
msg += "\nYou can add #{name} to your Gemfile or gemspec to #{motivation}."
# We detect the gem name from caller_locations. First we walk until we find `require`
@@ -194,19 +211,28 @@ module Gem::BUNDLED_GEMS # :nodoc:
require "bundler"
Bundler.reset!
+ # Build and activate a temporary definition containing the original gems + the requested gem
builder = Bundler::Dsl.new
- if Bundler::SharedHelpers.in_bundle?
- if Bundler.locked_gems
- Bundler.locked_gems.specs.each{|spec| builder.gem spec.name, spec.version.to_s }
- elsif Bundler.definition.gemfiles.size > 0
- Bundler.definition.gemfiles.each{|gemfile| builder.eval_gemfile(gemfile) }
+ lockfile = nil
+ if Bundler::SharedHelpers.in_bundle? && Bundler.definition.gemfiles.size > 0
+ Bundler.definition.gemfiles.each {|gemfile| builder.eval_gemfile(gemfile) }
+ lockfile = begin
+ Bundler.default_lockfile
+ rescue Bundler::GemfileNotFound
+ nil
end
+ else
+ # Fake BUNDLE_GEMFILE and BUNDLE_LOCKFILE to let checks pass
+ orig_gemfile = ENV["BUNDLE_GEMFILE"]
+ orig_lockfile = ENV["BUNDLE_LOCKFILE"]
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
+ Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock"
end
builder.gem gem
- definition = builder.to_definition(nil, true)
+ definition = builder.to_definition(lockfile, nil)
definition.validate_runtime!
begin
@@ -222,6 +248,8 @@ module Gem::BUNDLED_GEMS # :nodoc:
rescue Bundler::GemNotFound
warn "Failed to activate #{gem}, please install it with 'gem install #{gem}'"
ensure
+ ENV['BUNDLE_GEMFILE'] = orig_gemfile if orig_gemfile
+ ENV['BUNDLE_LOCKFILE'] = orig_lockfile if orig_lockfile
Bundler.ui = orig_ui
Bundler::Definition.no_lock = orig_no_lock
end
@@ -236,7 +264,7 @@ class LoadError
name = path.tr("/", "-")
if !defined?(Bundler) && Gem::BUNDLED_GEMS::SINCE[name] && !Gem::BUNDLED_GEMS::WARNED[name]
- warn name + Gem::BUNDLED_GEMS.build_message(name, "error"), uplevel: Gem::BUNDLED_GEMS.uplevel
+ warn name + Gem::BUNDLED_GEMS.build_message(name, :error), uplevel: Gem::BUNDLED_GEMS.uplevel
end
super
end
diff --git a/lib/bundler.rb b/lib/bundler.rb
index eea3b0cf17..51ea3beeb0 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
+require_relative "bundler/rubygems_ext"
require_relative "bundler/vendored_fileutils"
-require "pathname"
+autoload :Pathname, "pathname" unless defined?(Pathname)
require "rbconfig"
require_relative "bundler/errors"
require_relative "bundler/environment_preserver"
require_relative "bundler/plugin"
-require_relative "bundler/rubygems_ext"
require_relative "bundler/rubygems_integration"
require_relative "bundler/version"
require_relative "bundler/current_ruby"
@@ -53,9 +53,7 @@ module Bundler
autoload :FeatureFlag, File.expand_path("bundler/feature_flag", __dir__)
autoload :FREEBSD, File.expand_path("bundler/constants", __dir__)
autoload :GemHelper, File.expand_path("bundler/gem_helper", __dir__)
- autoload :GemHelpers, File.expand_path("bundler/gem_helpers", __dir__)
autoload :GemVersionPromoter, File.expand_path("bundler/gem_version_promoter", __dir__)
- autoload :Graph, File.expand_path("bundler/graph", __dir__)
autoload :Index, File.expand_path("bundler/index", __dir__)
autoload :Injector, File.expand_path("bundler/injector", __dir__)
autoload :Installer, File.expand_path("bundler/installer", __dir__)
@@ -114,13 +112,13 @@ module Bundler
end
def configured_bundle_path
- @configured_bundle_path ||= settings.path.tap(&:validate!)
+ @configured_bundle_path ||= Bundler.settings.path.tap(&:validate!)
end
# Returns absolute location of where binstubs are installed to.
def bin_path
@bin_path ||= begin
- path = settings[:bin] || "bin"
+ path = Bundler.settings[:bin] || "bin"
path = Pathname.new(path).expand_path(root).expand_path
mkdir_p(path)
path
@@ -174,14 +172,14 @@ module Bundler
self_manager.restart_with_locked_bundler_if_needed
end
- # Automatically install dependencies if Bundler.settings[:auto_install] exists.
+ # Automatically install dependencies if settings[:auto_install] exists.
# This is set through config cmd `bundle config set --global auto_install 1`.
#
# Note that this method `nil`s out the global Definition object, so it
# should be called first, before you instantiate anything like an
# `Installer` that'll keep a reference to the old one instead.
def auto_install
- return unless settings[:auto_install]
+ return unless Bundler.settings[:auto_install]
begin
definition.specs
@@ -220,8 +218,7 @@ module Bundler
end
def environment
- SharedHelpers.major_deprecation 2, "Bundler.environment has been removed in favor of Bundler.load", print_caller_location: true
- load
+ SharedHelpers.feature_removed! "Bundler.environment has been removed in favor of Bundler.load"
end
# Returns an instance of Bundler::Definition for given Gemfile and lockfile
@@ -239,10 +236,10 @@ module Bundler
end
def frozen_bundle?
- frozen = settings[:frozen]
+ frozen = Bundler.settings[:frozen]
return frozen unless frozen.nil?
- settings[:deployment]
+ Bundler.settings[:deployment]
end
def locked_gems
@@ -343,7 +340,7 @@ module Bundler
def app_cache(custom_path = nil)
path = custom_path || root
- Pathname.new(path).join(settings.app_cache_path)
+ Pathname.new(path).join(Bundler.settings.app_cache_path)
end
def tmp(name = Process.pid.to_s)
@@ -366,16 +363,11 @@ module Bundler
ORIGINAL_ENV.clone
end
- # @deprecated Use `unbundled_env` instead
def clean_env
- message =
- "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \
- "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`"
removed_message =
"`Bundler.clean_env` has been removed in favor of `Bundler.unbundled_env`. " \
"If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`"
- Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true)
- unbundled_env
+ Bundler::SharedHelpers.feature_removed!(removed_message)
end
# @return [Hash] Environment with all bundler-related variables removed
@@ -393,16 +385,11 @@ module Bundler
with_env(original_env) { yield }
end
- # @deprecated Use `with_unbundled_env` instead
def with_clean_env
- message =
- "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \
- "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`"
removed_message =
"`Bundler.with_clean_env` has been removed in favor of `Bundler.with_unbundled_env`. " \
"If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`"
- Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true)
- with_env(unbundled_env) { yield }
+ Bundler::SharedHelpers.feature_removed!(removed_message)
end
# Run block with all bundler-related variables removed
@@ -415,16 +402,11 @@ module Bundler
with_original_env { Kernel.system(*args) }
end
- # @deprecated Use `unbundled_system` instead
def clean_system(*args)
- message =
- "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \
- "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`"
removed_message =
"`Bundler.clean_system` has been removed in favor of `Bundler.unbundled_system`. " \
"If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`"
- Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true)
- with_env(unbundled_env) { Kernel.system(*args) }
+ Bundler::SharedHelpers.feature_removed!(removed_message)
end
# Run subcommand in an environment with all bundler related variables removed
@@ -437,16 +419,11 @@ module Bundler
with_original_env { Kernel.exec(*args) }
end
- # @deprecated Use `unbundled_exec` instead
def clean_exec(*args)
- message =
- "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \
- "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`"
removed_message =
"`Bundler.clean_exec` has been removed in favor of `Bundler.unbundled_exec`. " \
"If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`"
- Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true)
- with_env(unbundled_env) { Kernel.exec(*args) }
+ Bundler::SharedHelpers.feature_removed!(removed_message)
end
# Run a `Kernel.exec` to a subcommand in an environment with all bundler related variables removed
@@ -455,10 +432,14 @@ module Bundler
end
def local_platform
- return Gem::Platform::RUBY if settings[:force_ruby_platform]
+ return Gem::Platform::RUBY if Bundler.settings[:force_ruby_platform]
Gem::Platform.local
end
+ def generic_local_platform
+ Gem::Platform.generic(local_platform)
+ end
+
def default_gemfile
SharedHelpers.default_gemfile
end
@@ -564,7 +545,7 @@ module Bundler
end
def feature_flag
- @feature_flag ||= FeatureFlag.new(VERSION)
+ @feature_flag ||= FeatureFlag.new(Bundler.settings[:simulate_version] || VERSION)
end
def reset!
@@ -580,7 +561,6 @@ module Bundler
def reset_paths!
@bin_path = nil
- @bundler_major_version = nil
@bundle_path = nil
@configure = nil
@configured_bundle_path = nil
diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb
index 5d2a8b53bb..49d2518078 100644
--- a/lib/bundler/build_metadata.rb
+++ b/lib/bundler/build_metadata.rb
@@ -4,21 +4,26 @@ module Bundler
# Represents metadata from when the Bundler gem was built.
module BuildMetadata
# begin ivars
- @release = false
+ @built_at = nil
# end ivars
# A hash representation of the build metadata.
def self.to_h
{
- "Built At" => built_at,
+ "Timestamp" => timestamp,
"Git SHA" => git_commit_sha,
- "Released Version" => release?,
}
end
+ # A timestamp representing the date the bundler gem was built, or the
+ # current time if never built
+ def self.timestamp
+ @timestamp ||= @built_at || Time.now.utc.strftime("%Y-%m-%d").freeze
+ end
+
# A string representing the date the bundler gem was built.
def self.built_at
- @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze
+ @built_at
end
# The SHA for the git commit the bundler gem was built from.
@@ -34,10 +39,5 @@ module Bundler
@git_commit_sha ||= "unknown"
end
-
- # Whether this is an official release build of Bundler.
- def self.release?
- @release
- end
end
end
diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec
index 88411f295d..49319e81b4 100644
--- a/lib/bundler/bundler.gemspec
+++ b/lib/bundler/bundler.gemspec
@@ -23,16 +23,16 @@ Gem::Specification.new do |s|
s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably"
s.metadata = {
- "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler",
- "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md",
+ "bug_tracker_uri" => "https://github.com/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler",
+ "changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md",
"homepage_uri" => "https://bundler.io/",
- "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler",
+ "source_code_uri" => "https://github.com/ruby/rubygems/tree/master/bundler",
}
- s.required_ruby_version = ">= 3.1.0"
+ s.required_ruby_version = ">= 3.2.0"
# It should match the RubyGems version shipped with `required_ruby_version` above
- s.required_rubygems_version = ">= 3.3.3"
+ s.required_rubygems_version = ">= 3.4.1"
s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
diff --git a/lib/bundler/capistrano.rb b/lib/bundler/capistrano.rb
index 705840143f..6d2437d895 100644
--- a/lib/bundler/capistrano.rb
+++ b/lib/bundler/capistrano.rb
@@ -1,22 +1,4 @@
# frozen_string_literal: true
require_relative "shared_helpers"
-Bundler::SharedHelpers.major_deprecation 2,
- "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler"
-
-# Capistrano task for Bundler.
-#
-# Add "require 'bundler/capistrano'" in your Capistrano deploy.rb, and
-# Bundler will be activated after each new deployment.
-require_relative "deployment"
-require "capistrano/version"
-
-if defined?(Capistrano::Version) && Gem::Version.new(Capistrano::Version).release >= Gem::Version.new("3.0")
- raise "For Capistrano 3.x integration, please use https://github.com/capistrano/bundler"
-end
-
-Capistrano::Configuration.instance(:must_exist).load do
- before "deploy:finalize_update", "bundle:install"
- Bundler::Deployment.define_task(self, :task, except: { no_release: true })
- set :rake, lambda { "#{fetch(:bundle_cmd, "bundle")} exec rake" }
-end
+Bundler::SharedHelpers.feature_removed! "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler"
diff --git a/lib/bundler/checksum.rb b/lib/bundler/checksum.rb
index 356f4a48bc..ce05818bb0 100644
--- a/lib/bundler/checksum.rb
+++ b/lib/bundler/checksum.rb
@@ -205,6 +205,12 @@ module Bundler
@store[spec.lock_name].nil?
end
+ def empty?(spec)
+ return false unless spec.source.is_a?(Bundler::Source::Rubygems)
+
+ @store[spec.lock_name].empty?
+ end
+
def register(spec, checksum)
register_checksum(spec.lock_name, checksum)
end
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 51f71af501..1f6a65ca57 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -11,7 +11,7 @@ module Bundler
AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze
PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze
- EXTENSIONS = ["c", "rust"].freeze
+ EXTENSIONS = ["c", "rust", "go"].freeze
COMMAND_ALIASES = {
"check" => "c",
@@ -24,7 +24,7 @@ module Bundler
}.freeze
def self.start(*)
- check_deprecated_ext_option(ARGV) if ARGV.include?("--ext")
+ check_invalid_ext_option(ARGV) if ARGV.include?("--ext")
super
ensure
@@ -59,17 +59,29 @@ module Bundler
def initialize(*args)
super
+ current_cmd = args.last[:current_command].name
+
custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile]
if custom_gemfile && !custom_gemfile.empty?
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
- Bundler.reset_settings_and_root!
+ reset_settings = true
+ end
+
+ # lock --lockfile works differently than install --lockfile
+ unless current_cmd == "lock"
+ custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile]
+ if custom_lockfile && !custom_lockfile.empty?
+ Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile)
+ reset_settings = true
+ end
end
+ Bundler.reset_settings_and_root! if reset_settings
+
Bundler.auto_switch
Bundler.settings.set_command_option_if_given :retry, options[:retry]
- current_cmd = args.last[:current_command].name
Bundler.auto_install if AUTO_INSTALL_CMDS.include?(current_cmd)
rescue UnknownArgumentError => e
raise InvalidOption, e.message
@@ -77,7 +89,7 @@ module Bundler
self.options ||= {}
unprinted_warnings = Bundler.ui.unprinted_warnings
Bundler.ui = UI::Shell.new(options)
- Bundler.ui.level = "debug" if options["verbose"]
+ Bundler.ui.level = "debug" if options[:verbose] || Bundler.settings[:verbose]
unprinted_warnings.each {|w| Bundler.ui.warn(w) }
end
@@ -92,7 +104,7 @@ module Bundler
primary_commands = ["install", "update", "cache", "exec", "config", "help"]
list = self.class.printable_commands(true)
- by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] }
+ by_name = list.group_by {|name, _message| name.match(/^bundler? (\w+)/)[1] }
utilities = by_name.keys.sort - primary_commands
primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first }
utilities.map! {|name| by_name[name].first }
@@ -107,7 +119,33 @@ module Bundler
shell.say
self.class.send(:class_options_help, shell)
end
- default_task(Bundler.feature_flag.default_cli_command)
+
+ desc "install_or_cli_help", "Deprecated alias of install", hide: true
+ def install_or_cli_help
+ Bundler.ui.warn <<~MSG
+ `bundle install_or_cli_help` is a deprecated alias of `bundle install`.
+ It might be called due to the 'default_cli_command' being set to 'install_or_cli_help',
+ if so fix that by running `bundle config set default_cli_command install --global`.
+ MSG
+ invoke_other_command("install")
+ end
+
+ def self.default_command(meth = nil)
+ return super if meth
+
+ unless Bundler.settings[:default_cli_command]
+ Bundler.ui.info <<~MSG
+ In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`.
+ Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD.
+ You can use the future behavior now with `bundle config set default_cli_command cli_help --global`,
+ or you can continue to use the current behavior with `bundle config set default_cli_command install --global`.
+ This message will be removed after a default_cli_command value is set.
+
+ MSG
+ end
+
+ Bundler.settings[:default_cli_command] || "install"
+ end
class_option "no-color", type: :boolean, desc: "Disable colorization in output"
class_option "retry", type: :numeric, aliases: "-r", banner: "NUM",
@@ -130,7 +168,7 @@ module Bundler
if man_pages.include?(command)
man_page = man_pages[command]
- if Bundler.which("man") && !man_path.match?(%r{^file:/.+!/META-INF/jruby.home/.+})
+ if Bundler.which("man") && !man_path.match?(%r{^(?:file:/.+!|uri:classloader:)/META-INF/jruby.home/.+})
Kernel.exec("man", man_page)
else
puts File.read("#{man_path}/#{File.basename(man_page)}.ronn")
@@ -143,7 +181,7 @@ module Bundler
end
def self.handle_no_command_error(command, has_namespace = $thor_runner)
- if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command)
+ if Bundler.settings[:plugins] && Bundler::Plugin.command?(command)
return Bundler::Plugin.exec_command(command, ARGV[1..-1])
end
@@ -173,7 +211,7 @@ module Bundler
D
method_option "dry-run", type: :boolean, default: false, banner: "Lock the Gemfile"
method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile"
- method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}"
+ method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)"
def check
remembered_flag_deprecation("path")
@@ -187,12 +225,11 @@ module Bundler
long_desc <<-D
Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning.
D
- method_option "install", type: :boolean, banner: "Runs 'bundle install' after removing the gems from the Gemfile"
+ method_option "install", type: :boolean, banner: "Runs 'bundle install' after removing the gems from the Gemfile (removed)"
def remove(*gems)
if ARGV.include?("--install")
- message = "The `--install` flag has been deprecated. `bundle install` is triggered by default."
removed_message = "The `--install` flag has been removed. `bundle install` is triggered by default."
- SharedHelpers.major_deprecation(2, message, removed_message: removed_message)
+ raise InvalidOption, removed_message
end
require_relative "cli/remove"
@@ -210,42 +247,52 @@ module Bundler
If the bundle has already been installed, bundler will tell you so and then exit.
D
- method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin"
- method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install"
- method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments"
- method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install"
+ method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin (removed)"
+ method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install (removed)"
+ method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments (removed)"
+ method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install (removed)"
method_option "full-index", type: :boolean, banner: "Fall back to using the single-file index of all gems"
method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile"
method_option "jobs", aliases: "-j", type: :numeric, banner: "Specify the number of jobs to run in parallel"
method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead"
+ method_option "lockfile", type: :string, banner: "Use the specified lockfile instead of the default."
method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely"
method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache."
- method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem."
- method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache."
- method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}"
+ method_option "no-lock", type: :boolean, banner: "Don't create a lockfile."
+ method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed"
+ method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)."
+ method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)."
method_option "quiet", type: :boolean, banner: "Only output warnings and errors."
- method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default (usually 'ruby')"
+ method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default, usually 'ruby' (removed)"
method_option "standalone", type: :array, lazy_default: [], banner: "Make a bundle that can work without the Bundler runtime"
- method_option "system", type: :boolean, banner: "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application"
+ method_option "system", type: :boolean, banner: "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application (removed)"
method_option "trust-policy", alias: "P", type: :string, banner: "Gem trust policy (like gem install -P). Must be one of #{Bundler.rubygems.security_policy_keys.join("|")}"
method_option "target-rbconfig", type: :string, banner: "Path to rbconfig.rb for the deployment target platform"
- method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group."
- method_option "with", type: :array, banner: "Include gems that are part of the specified named group."
+ method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group (removed)."
+ method_option "with", type: :array, banner: "Include gems that are part of the specified named group (removed)."
def install
- SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force")
-
%w[clean deployment frozen no-prune path shebang without with].each do |option|
remembered_flag_deprecation(option)
end
print_remembered_flag_deprecation("--system", "path.system", "true") if ARGV.include?("--system")
- remembered_negative_flag_deprecation("no-deployment")
+ remembered_flag_deprecation("deployment", negative: true)
+
+ if ARGV.include?("--binstubs")
+ removed_message = "The --binstubs option has been removed in favor of `bundle binstubs --all`"
+ raise InvalidOption, removed_message
+ end
require_relative "cli/install"
+ options = self.options.dup
+ options["lockfile"] ||= ENV["BUNDLE_LOCKFILE"]
Bundler.settings.temporary(no_install: false) do
- Install.new(options.dup).run
+ Install.new(options).run
end
+ rescue GemfileNotFound => error
+ invoke_other_command("cli_help")
+ raise error # re-raise to show the error and get a failing exit status
end
map aliases_for("install")
@@ -263,7 +310,7 @@ module Bundler
method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead"
method_option "quiet", type: :boolean, banner: "Only output warnings and errors."
method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)"
- method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem."
+ method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed"
method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock"
method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler"
method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version"
@@ -274,7 +321,6 @@ module Bundler
method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated."
method_option "all", type: :boolean, banner: "Update everything."
def update(*gems)
- SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force")
require_relative "cli/update"
Bundler.settings.temporary(no_install: false) do
Update.new(options, gems).run
@@ -287,12 +333,11 @@ module Bundler
Calling show with [GEM] will list the exact location of that gem on your machine.
D
method_option "paths", type: :boolean, banner: "List the paths of all gems that are required by your Gemfile."
- method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated."
+ method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated (removed)."
def show(gem_name = nil)
if ARGV.include?("--outdated")
- message = "the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement"
- removed_message = "the `--outdated` flag to `bundle show` was undocumented and has been removed without replacement"
- SharedHelpers.major_deprecation(2, message, removed_message: removed_message)
+ removed_message = "the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`"
+ raise InvalidOption, removed_message
end
require_relative "cli/show"
Show.new(options, gem_name).run
@@ -302,6 +347,7 @@ module Bundler
method_option "name-only", type: :boolean, banner: "print only the gem names"
method_option "only-group", type: :array, default: [], banner: "print gems from a given set of groups"
method_option "without-group", type: :array, default: [], banner: "print all gems except from a given set of groups"
+ method_option "format", type: :string, banner: "format output ('json' is the only supported format)"
method_option "paths", type: :boolean, banner: "print the path to each gem in the bundle"
def list
require_relative "cli/list"
@@ -325,12 +371,14 @@ module Bundler
will create binstubs for all given gems.
D
method_option "force", type: :boolean, default: false, banner: "Overwrite existing binstubs if they exist"
- method_option "path", type: :string, lazy_default: "bin", banner: "Binstub destination directory (default bin)"
+ method_option "path", type: :string, lazy_default: "bin", banner: "Binstub destination directory, `bin` by default (removed)"
method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default (usually 'ruby')"
method_option "standalone", type: :boolean, banner: "Make binstubs that can work without the Bundler runtime"
method_option "all", type: :boolean, banner: "Install binstubs for all gems"
method_option "all-platforms", type: :boolean, default: false, banner: "Install binstubs for all platforms"
def binstubs(*gems)
+ remembered_flag_deprecation("path", option_name: "bin")
+
require_relative "cli/binstubs"
Binstubs.new(options, gems).run
end
@@ -396,15 +444,15 @@ module Bundler
end
desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
- method_option "all", type: :boolean, default: Bundler.feature_flag.cache_all?, banner: "Include all sources (including path and git)."
+ method_option "all", type: :boolean, default: Bundler.settings[:cache_all], banner: "Include all sources (including path and git) (removed)."
method_option "all-platforms", type: :boolean, banner: "Include gems for all platforms present in the lockfile, not only the current one"
method_option "cache-path", type: :string, banner: "Specify a different cache path than the default (vendor/cache)."
method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile"
method_option "no-install", type: :boolean, banner: "Don't install the gems, only update the cache."
- method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache."
- method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}"
+ method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)."
+ method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)."
method_option "quiet", type: :boolean, banner: "Only output warnings and errors."
- method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install"
+ method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install (removed)"
long_desc <<-D
The cache command will copy the .gem files for every gem in the bundle into the
directory ./vendor/cache. If you then check that directory into your source
@@ -413,18 +461,19 @@ module Bundler
D
def cache
print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all")
+ print_remembered_flag_deprecation("--no-all", "cache_all", "false") if ARGV.include?("--no-all")
- if ARGV.include?("--path")
- message =
- "The `--path` flag is deprecated because its semantics are unclear. " \
- "Use `bundle config cache_path` to configure the path of your cache of gems, " \
- "and `bundle config path` to configure the path where your gems are installed, " \
- "and stop using this flag"
+ %w[frozen no-prune].each do |option|
+ remembered_flag_deprecation(option)
+ end
+
+ if flag_passed?("--path")
removed_message =
"The `--path` flag has been removed because its semantics were unclear. " \
"Use `bundle config cache_path` to configure the path of your cache of gems, " \
- "and `bundle config path` to configure the path where your gems are installed."
- SharedHelpers.major_deprecation 2, message, removed_message: removed_message
+ "and `bundle config path` to configure the path where your gems are installed, " \
+ "and stop using this flag"
+ raise InvalidOption, removed_message
end
require_relative "cli/cache"
@@ -434,7 +483,7 @@ module Bundler
map aliases_for("cache")
desc "exec [OPTIONS]", "Run the command in context of the bundle"
- method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is deprecated"
+ method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is not permitted (removed)."
method_option :gemfile, type: :string, required: false, banner: "Use the specified gemfile instead of Gemfile"
long_desc <<-D
Exec runs a command, providing it access to the gems in the bundle. While using
@@ -443,9 +492,8 @@ module Bundler
D
def exec(*args)
if ARGV.include?("--no-keep-file-descriptors")
- message = "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to"
removed_message = "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to"
- SharedHelpers.major_deprecation(2, message, removed_message: removed_message)
+ raise InvalidOption, removed_message
end
require_relative "cli/exec"
@@ -486,13 +534,13 @@ module Bundler
def version
cli_help = current_command.name == "cli_help"
if cli_help || ARGV.include?("version")
- build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})"
+ build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})"
end
- if !cli_help && Bundler.feature_flag.print_only_version_number?
- Bundler.ui.info "#{Bundler::VERSION}#{build_info}"
+ if !cli_help
+ Bundler.ui.info "#{Bundler.verbose_version}#{build_info}"
else
- Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}"
+ Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}"
end
end
@@ -512,41 +560,32 @@ module Bundler
end
end
- unless Bundler.feature_flag.bundler_3_mode?
- desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true
- long_desc <<-D
- Viz generates a PNG file of the current Gemfile as a dependency graph.
- Viz requires the ruby-graphviz gem (and its dependencies).
- The associated gems must also be installed via 'bundle install'.
- D
- method_option :file, type: :string, default: "gem_graph", aliases: "-f", desc: "The name to use for the generated file. see format option"
- method_option :format, type: :string, default: "png", aliases: "-F", desc: "This is output format option. Supported format is png, jpg, svg, dot ..."
- method_option :requirements, type: :boolean, default: false, aliases: "-R", desc: "Set to show the version of each required dependency."
- method_option :version, type: :boolean, default: false, aliases: "-v", desc: "Set to show each gem version."
- method_option :without, type: :array, default: [], aliases: "-W", banner: "GROUP[ GROUP...]", desc: "Exclude gems that are part of the specified named group."
- def viz
- SharedHelpers.major_deprecation 2, "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph"
- require_relative "cli/viz"
- Viz.new(options.dup).run
- end
+ desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true
+ def viz
+ SharedHelpers.feature_removed! "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph"
end
desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem"
- method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], desc: "Generate a binary executable for your library."
- method_option :coc, type: :boolean, desc: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`."
- method_option :edit, type: :string, aliases: "-e", required: false, banner: "EDITOR", lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, desc: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)"
- method_option :ext, type: :string, desc: "Generate the boilerplate for C extension code.", enum: EXTENSIONS
- method_option :git, type: :boolean, default: true, desc: "Initialize a git repo inside your library."
- method_option :mit, type: :boolean, desc: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`."
- method_option :rubocop, type: :boolean, desc: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`."
- method_option :changelog, type: :boolean, desc: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`."
+ method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], banner: "Generate a binary executable for your library."
+ method_option :coc, type: :boolean, banner: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`."
+ method_option :edit, type: :string, aliases: "-e", required: false, lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, banner: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)"
+ method_option :ext, type: :string, banner: "Generate the boilerplate for C extension code.", enum: EXTENSIONS
+ method_option :git, type: :boolean, default: true, banner: "Initialize a git repo inside your library."
+ method_option :mit, type: :boolean, banner: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`."
+ method_option :rubocop, type: :boolean, banner: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true` (removed)."
+ method_option :changelog, type: :boolean, banner: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`."
method_option :test, type: :string, lazy_default: Bundler.settings["gem.test"] || "", aliases: "-t", banner: "Use the specified test framework for your library", enum: %w[rspec minitest test-unit], desc: "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`."
- method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], desc: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`"
- method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], desc: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`"
+ method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], banner: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`"
+ method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], banner: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`"
method_option :github_username, type: :string, default: Bundler.settings["gem.github_username"], banner: "Set your username on GitHub", desc: "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`."
+ method_option :bundle, type: :boolean, default: Bundler.settings["gem.bundle"], banner: "Automatically run `bundle install` after creation. Set a default with `bundle config set --global gem.bundle true`"
def gem(name)
require_relative "cli/gem"
+
+ raise InvalidOption, "--rubocop has been removed, use --linter=rubocop" if ARGV.include?("--rubocop")
+ raise InvalidOption, "--no-rubocop has been removed, use --no-linter" if ARGV.include?("--no-rubocop")
+
cmd_args = args + [self]
cmd_args.unshift(options)
@@ -557,7 +596,7 @@ module Bundler
File.expand_path("templates", __dir__)
end
- desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", hide: true
+ desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory"
method_option "dry-run", type: :boolean, default: false, banner: "Only print out changes, do not clean gems"
method_option "force", type: :boolean, default: false, banner: "Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application."
def clean
@@ -573,12 +612,8 @@ module Bundler
end
desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", hide: true
- method_option "source", type: :string, banner: "Install gem from the given source"
- method_option "group", type: :string, banner: "Install gem into a bundler group"
- def inject(name, version)
- SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command"
- require_relative "cli/inject"
- Inject.new(options.dup, name, version).run
+ def inject(*)
+ SharedHelpers.feature_removed! "The `inject` command has been replaced by the `add` command"
end
desc "lock", "Creates a lockfile without installing"
@@ -632,7 +667,7 @@ module Bundler
end
end
- if Bundler.feature_flag.plugins?
+ if Bundler.settings[:plugins]
require_relative "cli/plugin"
desc "plugin", "Manage the bundler plugins"
subcommand "plugin", Plugin
@@ -666,18 +701,15 @@ module Bundler
end
end
- def self.check_deprecated_ext_option(arguments)
- # when deprecated version of `--ext` is called
- # print out deprecation warning and pretend `--ext=c` was provided
- if deprecated_ext_value?(arguments)
- message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been deprecated. Please select a language, e.g. `--ext=rust` to generate a Rust extension. This gem will now be generated as if `--ext=c` was used."
+ def self.check_invalid_ext_option(arguments)
+ # when invalid version of `--ext` is called
+ if invalid_ext_value?(arguments)
removed_message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension."
- SharedHelpers.major_deprecation 2, message, removed_message: removed_message
- arguments[arguments.index("--ext")] = "--ext=c"
+ raise InvalidOption, removed_message
end
end
- def self.deprecated_ext_value?(arguments)
+ def self.invalid_ext_value?(arguments)
index = arguments.index("--ext")
next_argument = arguments[index + 1]
@@ -685,15 +717,15 @@ module Bundler
# for example `bundle gem hello --ext c`
return false if EXTENSIONS.include?(next_argument)
- # deprecated call when --ext is called with no value in last position
+ # invalid call when --ext is called with no value in last position
# for example `bundle gem hello_gem --ext`
return true if next_argument.nil?
- # deprecated call when --ext is followed by other parameter
+ # invalid call when --ext is followed by other parameter
# for example `bundle gem --ext --no-ci hello_gem`
return true if next_argument.start_with?("-")
- # deprecated call when --ext is followed by gem name
+ # invalid call when --ext is followed by gem name
# for example `bundle gem --ext hello_gem`
return true if next_argument
@@ -707,6 +739,19 @@ module Bundler
config[:current_command]
end
+ def invoke_other_command(name)
+ _, _, config = @_initializer
+ original_command = config[:current_command]
+ command = self.class.all_commands[name]
+ config[:current_command] = command
+ send(name)
+ ensure
+ config[:current_command] = original_command
+ end
+
+ def current_command=(command)
+ end
+
def print_command
return unless Bundler.ui.debug?
cmd = current_command
@@ -720,7 +765,7 @@ module Bundler
end
command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip
command.reject!(&:empty?)
- Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}"
+ Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler.verbose_version}"
end
def warn_on_outdated_bundler
@@ -747,44 +792,30 @@ module Bundler
nil
end
- def remembered_negative_flag_deprecation(name)
- positive_name = name.gsub(/\Ano-/, "")
- option = current_command.options[positive_name]
- flag_name = "--no-" + option.switch_name.gsub(/\A--/, "")
-
- flag_deprecation(positive_name, flag_name, option)
- end
-
- def remembered_flag_deprecation(name)
+ def remembered_flag_deprecation(name, negative: false, option_name: nil)
option = current_command.options[name]
flag_name = option.switch_name
-
- flag_deprecation(name, flag_name, option)
- end
-
- def flag_deprecation(name, flag_name, option)
- name_index = ARGV.find {|arg| flag_name == arg.split("=")[0] }
- return unless name_index
+ flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative
+ return unless flag_passed?(flag_name)
value = options[name]
value = value.join(" ").to_s if option.type == :array
value = "'#{value}'" unless option.type == :boolean
- print_remembered_flag_deprecation(flag_name, name.tr("-", "_"), value)
+ print_remembered_flag_deprecation(flag_name, option_name || name.tr("-", "_"), value)
end
def print_remembered_flag_deprecation(flag_name, option_name, option_value)
- message =
- "The `#{flag_name}` flag is deprecated because it relies on being " \
- "remembered across bundler invocations, which bundler will no longer " \
- "do in future versions. Instead please use `bundle config set #{option_name} " \
- "#{option_value}`, and stop using this flag"
removed_message =
"The `#{flag_name}` flag has been removed because it relied on being " \
- "remembered across bundler invocations, which bundler will no longer " \
- "do. Instead please use `bundle config set #{option_name} " \
- "#{option_value}`, and stop using this flag"
- Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message
+ "remembered across bundler invocations, which bundler no longer does. " \
+ "Instead please use `bundle config set #{option_name} #{option_value}`, " \
+ "and stop using this flag"
+ raise InvalidOption, removed_message
+ end
+
+ def flag_passed?(name)
+ ARGV.any? {|arg| name == arg.split("=")[0] }
end
end
end
diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb
index 12a681a816..9f17604096 100644
--- a/lib/bundler/cli/add.rb
+++ b/lib/bundler/cli/add.rb
@@ -36,6 +36,16 @@ module Bundler
end
def validate_options!
+ raise InvalidOption, "You cannot specify `--git` and `--github` at the same time." if options["git"] && options["github"]
+
+ unless options["git"] || options["github"]
+ raise InvalidOption, "You cannot specify `--branch` unless `--git` or `--github` is specified." if options["branch"]
+
+ raise InvalidOption, "You cannot specify `--ref` unless `--git` or `--github` is specified." if options["ref"]
+ end
+
+ raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"]
+
raise InvalidOption, "You cannot specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic]
# raise error when no gems are specified
diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb
index 2e63a16ec3..59605df847 100644
--- a/lib/bundler/cli/cache.rb
+++ b/lib/bundler/cli/cache.rb
@@ -10,17 +10,12 @@ module Bundler
def run
Bundler.ui.level = "warn" if options[:quiet]
- Bundler.settings.set_command_option_if_given :path, options[:path]
Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"]
- setup_cache_all
install
- # TODO: move cache contents here now that all bundles are locked
- custom_path = Bundler.settings[:path] if options[:path]
-
Bundler.settings.temporary(cache_all_platforms: options["all-platforms"]) do
- Bundler.load.cache(custom_path)
+ Bundler.load.cache
end
end
@@ -33,11 +28,5 @@ module Bundler
options["no-cache"] = true
Bundler::CLI::Install.new(options).run
end
-
- def setup_cache_all
- all = options.fetch(:all, Bundler.feature_flag.cache_all? || nil)
-
- Bundler.settings.set_command_option_if_given :cache_all, all
- end
end
end
diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb
index 7ef6deb2cf..2f332ff364 100644
--- a/lib/bundler/cli/common.rb
+++ b/lib/bundler/cli/common.rb
@@ -94,11 +94,14 @@ module Bundler
end
def self.gem_not_found_message(missing_gem_name, alternatives)
- require_relative "../similarity_detector"
message = "Could not find gem '#{missing_gem_name}'."
alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a }
- suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name)
- message += "\nDid you mean #{suggestions}?" if suggestions
+ if alternate_names.include?(missing_gem_name.downcase)
+ message += "\nDid you mean '#{missing_gem_name.downcase}'?"
+ elsif defined?(DidYouMean::SpellChecker)
+ suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name)
+ message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty?
+ end
message
end
@@ -130,9 +133,23 @@ module Bundler
def self.clean_after_install?
clean = Bundler.settings[:clean]
return clean unless clean.nil?
- clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil?
+ clean ||= Bundler.feature_flag.bundler_5_mode? && Bundler.settings[:path].nil?
clean &&= !Bundler.use_system_gems?
clean
end
+
+ def self.word_list(words)
+ if words.empty?
+ return ""
+ end
+
+ words = words.map {|word| "'#{word}'" }
+
+ if words.length == 1
+ return words[0]
+ end
+
+ [words[0..-2].join(", "), words[-1]].join(" or ")
+ end
end
end
diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb
index 77b502fe60..6a77e4a65e 100644
--- a/lib/bundler/cli/config.rb
+++ b/lib/bundler/cli/config.rb
@@ -26,8 +26,7 @@ module Bundler
end
message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead."
- removed_message = "Using the `config` command without a subcommand [list, get, set, unset] is has been removed. Use `bundle #{new_args.join(" ")}` instead."
- SharedHelpers.major_deprecation 3, message, removed_message: removed_message
+ SharedHelpers.feature_deprecated! message
Base.new(options, name, value, self).run
end
diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb
index f6389e8ea0..2d1a2ce458 100644
--- a/lib/bundler/cli/console.rb
+++ b/lib/bundler/cli/console.rb
@@ -21,6 +21,11 @@ module Bundler
get_constant(name)
rescue LoadError
if name == "irb"
+ if defined?(Gem::BUNDLED_GEMS) && Gem::BUNDLED_GEMS.respond_to?(:force_activate)
+ Gem::BUNDLED_GEMS.force_activate "irb"
+ require name
+ return get_constant(name)
+ end
Bundler.ui.error "#{name} is not available"
exit 1
else
diff --git a/lib/bundler/cli/doctor/diagnose.rb b/lib/bundler/cli/doctor/diagnose.rb
index c5da23acb8..a878025dda 100644
--- a/lib/bundler/cli/doctor/diagnose.rb
+++ b/lib/bundler/cli/doctor/diagnose.rb
@@ -24,7 +24,7 @@ module Bundler
def dylibs_darwin(path)
output = `/usr/bin/otool -L #{path.shellescape}`.chomp
- dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq
+ dylibs = output.split("\n")[1..-1].filter_map {|l| l.match(DARWIN_REGEX)&.match(1) }.uniq
# ignore @rpath and friends
dylibs.reject {|dylib| dylib.start_with? "@" }
end
diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb
index 9428e9db3b..2fdc416286 100644
--- a/lib/bundler/cli/exec.rb
+++ b/lib/bundler/cli/exec.rb
@@ -19,11 +19,13 @@ module Bundler
validate_cmd!
SharedHelpers.set_bundle_environment
if bin_path = Bundler.which(cmd)
- if !Bundler.settings[:disable_exec_load] && ruby_shebang?(bin_path)
- return kernel_load(bin_path, *args)
+ if !Bundler.settings[:disable_exec_load] && directly_loadable?(bin_path)
+ bin_path.delete_suffix!(".bat") if Gem.win_platform?
+ kernel_load(bin_path, *args)
+ else
+ bin_path = "./" + bin_path unless File.absolute_path?(bin_path)
+ kernel_exec(bin_path, *args)
end
- bin_path = "./" + bin_path unless File.absolute_path?(bin_path)
- kernel_exec(bin_path, *args)
else
# exec using the given command
kernel_exec(cmd, *args)
@@ -69,6 +71,29 @@ module Bundler
"#{file} #{args.join(" ")}".strip
end
+ def directly_loadable?(file)
+ if Gem.win_platform?
+ script_wrapper?(file)
+ else
+ ruby_shebang?(file)
+ end
+ end
+
+ def script_wrapper?(file)
+ script_file = file.delete_suffix(".bat")
+ return false unless File.exist?(script_file)
+
+ if File.zero?(script_file)
+ Bundler.ui.warn "#{script_file} is empty"
+ return false
+ end
+
+ header = File.open(file, "r") {|f| f.read(32) }
+ ruby_exe = "#{RbConfig::CONFIG["RUBY_INSTALL_NAME"]}#{RbConfig::CONFIG["EXEEXT"]}"
+ ruby_exe = "ruby.exe" if ruby_exe.empty?
+ header.include?(ruby_exe)
+ end
+
def ruby_shebang?(file)
possibilities = [
"#!/usr/bin/env ruby\n",
diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb
index 22bcf0e47a..236ce530ec 100644
--- a/lib/bundler/cli/gem.rb
+++ b/lib/bundler/cli/gem.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "pathname"
-
module Bundler
class CLI
Bundler.require_thor_actions
@@ -15,6 +13,8 @@ module Bundler
"test-unit" => "3.0",
}.freeze
+ DEFAULT_GITHUB_USERNAME = "[USERNAME]"
+
attr_reader :options, :gem_name, :thor, :name, :target, :extension
def initialize(options, gem_name, thor)
@@ -26,12 +26,11 @@ module Bundler
thor.destination_root = nil
@name = @gem_name
- @target = SharedHelpers.pwd.join(gem_name)
+ @target = Pathname.new(SharedHelpers.pwd).join(gem_name)
@extension = options[:ext]
validate_ext_name if @extension
- validate_rust_builder_rubygems_version if @extension == "rust"
end
def run
@@ -48,13 +47,16 @@ module Bundler
git_author_name = use_git ? `git config user.name`.chomp : ""
git_username = use_git ? `git config github.user`.chomp : ""
git_user_email = use_git ? `git config user.email`.chomp : ""
+ github_username = github_username(git_username)
- github_username = if options[:github_username].nil?
- git_username
- elsif options[:github_username] == false
- ""
+ if github_username.empty?
+ homepage_uri = "TODO: Put your gem's website or public repo URL here."
+ source_code_uri = "TODO: Put your gem's public repo URL here."
+ changelog_uri = "TODO: Put your gem's CHANGELOG.md URL here."
else
- options[:github_username]
+ homepage_uri = "https://github.com/#{github_username}/#{name}"
+ source_code_uri = "https://github.com/#{github_username}/#{name}"
+ changelog_uri = "https://github.com/#{github_username}/#{name}/blob/main/CHANGELOG.md"
end
config = {
@@ -69,12 +71,17 @@ module Bundler
test: options[:test],
ext: extension,
exe: options[:exe],
+ bundle: options[:bundle],
bundler_version: bundler_dependency_version,
git: use_git,
- github_username: github_username.empty? ? "[USERNAME]" : github_username,
+ github_username: github_username.empty? ? DEFAULT_GITHUB_USERNAME : github_username,
required_ruby_version: required_ruby_version,
rust_builder_required_rubygems_version: rust_builder_required_rubygems_version,
minitest_constant_name: minitest_constant_name,
+ ignore_paths: %w[bin/],
+ homepage_uri: homepage_uri,
+ source_code_uri: source_code_uri,
+ changelog_uri: changelog_uri,
}
ensure_safe_gem_name(name, constant_array)
@@ -95,7 +102,18 @@ module Bundler
bin/setup
]
- templates.merge!("gitignore.tt" => ".gitignore") if use_git
+ case Bundler.preferred_gemfile_name
+ when "Gemfile"
+ config[:ignore_paths] << "Gemfile"
+ when "gems.rb"
+ config[:ignore_paths] << "gems.rb"
+ config[:ignore_paths] << "gems.locked"
+ end
+
+ if use_git
+ templates.merge!("gitignore.tt" => ".gitignore")
+ config[:ignore_paths] << ".gitignore"
+ end
if test_framework = ask_and_set_test_framework
config[:test] = test_framework
@@ -109,6 +127,8 @@ module Bundler
"spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb"
)
config[:test_task] = :spec
+ config[:ignore_paths] << ".rspec"
+ config[:ignore_paths] << "spec/"
when "minitest"
# Generate path for minitest target file (FileList["test/**/test_*.rb"])
# foo => test/test_foo.rb
@@ -123,12 +143,14 @@ module Bundler
"test/minitest/test_newgem.rb.tt" => "test/#{minitest_namespaced_path}.rb"
)
config[:test_task] = :test
+ config[:ignore_paths] << "test/"
when "test-unit"
templates.merge!(
"test/test-unit/test_helper.rb.tt" => "test/test_helper.rb",
"test/test-unit/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb"
)
config[:test_task] = :test
+ config[:ignore_paths] << "test/"
end
end
@@ -136,19 +158,19 @@ module Bundler
case config[:ci]
when "github"
templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml")
- config[:ci_config_path] = ".github "
+ config[:ignore_paths] << ".github/"
when "gitlab"
templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml")
- config[:ci_config_path] = ".gitlab-ci.yml "
+ config[:ignore_paths] << ".gitlab-ci.yml"
when "circle"
templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml")
- config[:ci_config_path] = ".circleci "
+ config[:ignore_paths] << ".circleci/"
end
if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?",
- "This means that any other developer or company will be legally allowed to use your code " \
- "for free as long as they admit you created it. You can read more about the MIT license " \
- "at https://choosealicense.com/licenses/mit.")
+ "Using a MIT license means that any other developer or company will be legally allowed " \
+ "to use your code for free as long as they admit you created it. You can read more about " \
+ "the MIT license at https://choosealicense.com/licenses/mit.")
config[:mit] = true
Bundler.ui.info "MIT License enabled in config"
templates.merge!("LICENSE.txt.tt" => "LICENSE.txt")
@@ -156,12 +178,8 @@ module Bundler
if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?",
"Codes of conduct can increase contributions to your project by contributors who " \
- "prefer collaborative, safe spaces. You can read more about the code of conduct at " \
- "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \
- "of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \
- "address is specified as a contact in the generated code of conduct so that people know " \
- "who to contact in case of a violation. For suggestions about " \
- "how to enforce codes of conduct, see https://bit.ly/coc-enforcement.")
+ "prefer safe, respectful, productive, and collaborative spaces. \n" \
+ "See https://github.com/ruby/rubygems/blob/master/CODE_OF_CONDUCT.md")
config[:coc] = true
Bundler.ui.info "Code of conduct enabled in config"
templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md")
@@ -185,10 +203,12 @@ module Bundler
config[:linter_version] = rubocop_version
Bundler.ui.info "RuboCop enabled in config"
templates.merge!("rubocop.yml.tt" => ".rubocop.yml")
+ config[:ignore_paths] << ".rubocop.yml"
when "standard"
config[:linter_version] = standard_version
Bundler.ui.info "Standard enabled in config"
templates.merge!("standard.yml.tt" => ".standard.yml")
+ config[:ignore_paths] << ".standard.yml"
end
if config[:exe]
@@ -213,13 +233,25 @@ module Bundler
)
end
+ if extension == "go"
+ templates.merge!(
+ "ext/newgem/go.mod.tt" => "ext/#{name}/go.mod",
+ "ext/newgem/extconf-go.rb.tt" => "ext/#{name}/extconf.rb",
+ "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h",
+ "ext/newgem/newgem.go.tt" => "ext/#{name}/#{underscored_name}.go",
+ "ext/newgem/newgem-go.c.tt" => "ext/#{name}/#{underscored_name}.c",
+ )
+
+ config[:go_module_username] = config[:github_username] == DEFAULT_GITHUB_USERNAME ? "username" : config[:github_username]
+ end
+
if target.exist? && !target.directory?
Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`."
exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError]
end
if use_git
- Bundler.ui.info "Initializing git repo in #{target}"
+ Bundler.ui.info "\nInitializing git repo in #{target}"
require "shellwords"
`git init #{target.to_s.shellescape}`
@@ -241,26 +273,33 @@ module Bundler
IO.popen(%w[git add .], { chdir: target }, &:read)
end
+ if config[:bundle]
+ Bundler.ui.info "Running bundle install in the new gem directory."
+ Dir.chdir(target) do
+ system("bundle install")
+ end
+ end
+
# Open gemspec in editor
open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit]
- Bundler.ui.info "Gem '#{name}' was successfully created. " \
+ Bundler.ui.info "\nGem '#{name}' was successfully created. " \
"For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html"
end
private
def resolve_name(name)
- SharedHelpers.pwd.join(name).basename.to_s
+ Pathname.new(SharedHelpers.pwd).join(name).basename.to_s
end
- def ask_and_set(key, header, message)
+ def ask_and_set(key, prompt, explanation)
choice = options[key]
choice = Bundler.settings["gem.#{key}"] if choice.nil?
if choice.nil?
- Bundler.ui.confirm header
- choice = Bundler.ui.yes? "#{message} y/(n):"
+ Bundler.ui.info "\n#{explanation}"
+ choice = Bundler.ui.yes? "#{prompt} y/(n):"
Bundler.settings.set_global("gem.#{key}", choice)
end
@@ -282,7 +321,7 @@ module Bundler
test_framework = options[:test] || Bundler.settings["gem.test"]
if test_framework.to_s.empty?
- Bundler.ui.confirm "Do you want to generate tests with your gem?"
+ Bundler.ui.info "\nDo you want to generate tests with your gem?"
Bundler.ui.info hint_text("test")
result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):"
@@ -322,12 +361,11 @@ module Bundler
ci_template = options[:ci] || Bundler.settings["gem.ci"]
if ci_template.to_s.empty?
- Bundler.ui.confirm "Do you want to set up continuous integration for your gem? " \
+ Bundler.ui.info "\nDo you want to set up continuous integration for your gem? " \
"Supported services:\n" \
"* CircleCI: https://circleci.com/\n" \
"* GitHub Actions: https://github.com/features/actions\n" \
- "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" \
- "\n"
+ "* GitLab CI: https://docs.gitlab.com/ee/ci/\n"
Bundler.ui.info hint_text("ci")
result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):"
@@ -352,14 +390,12 @@ module Bundler
def ask_and_set_linter
return if skip?(:linter)
linter_template = options[:linter] || Bundler.settings["gem.linter"]
- linter_template = deprecated_rubocop_option if linter_template.nil?
if linter_template.to_s.empty?
- Bundler.ui.confirm "Do you want to add a code linter and formatter to your gem? " \
+ Bundler.ui.info "\nDo you want to add a code linter and formatter to your gem? " \
"Supported Linters:\n" \
"* RuboCop: https://rubocop.org\n" \
- "* Standard: https://github.com/standardrb/standard\n" \
- "\n"
+ "* Standard: https://github.com/standardrb/standard\n"
Bundler.ui.info hint_text("linter")
result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):"
@@ -386,27 +422,6 @@ module Bundler
linter_template
end
- def deprecated_rubocop_option
- if !options[:rubocop].nil?
- if options[:rubocop]
- Bundler::SharedHelpers.major_deprecation 2,
- "--rubocop is deprecated, use --linter=rubocop",
- removed_message: "--rubocop has been removed, use --linter=rubocop"
- "rubocop"
- else
- Bundler::SharedHelpers.major_deprecation 2,
- "--no-rubocop is deprecated, use --linter",
- removed_message: "--no-rubocop has been removed, use --linter"
- false
- end
- elsif !Bundler.settings["gem.rubocop"].nil?
- Bundler::SharedHelpers.major_deprecation 2,
- "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead",
- removed_message: "config gem.rubocop has been removed; we've updated your config to use gem.linter instead"
- Bundler.settings["gem.rubocop"] ? "rubocop" : false
- end
- end
-
def bundler_dependency_version
v = Gem::Version.new(Bundler::VERSION)
req = v.segments[0..1]
@@ -446,7 +461,7 @@ module Bundler
end
def required_ruby_version
- "3.1.0"
+ "3.2.0"
end
def rubocop_version
@@ -457,10 +472,13 @@ module Bundler
"1.3"
end
- def validate_rust_builder_rubygems_version
- if Gem::Version.new(rust_builder_required_rubygems_version) > Gem.rubygems_version
- Bundler.ui.error "Your RubyGems version (#{Gem.rubygems_version}) is too old to build Rust extension. Please update your RubyGems using `gem update --system` or any other way and try again."
- exit 1
+ def github_username(git_username)
+ if options[:github_username].nil?
+ git_username
+ elsif options[:github_username] == false
+ ""
+ else
+ options[:github_username]
end
end
end
diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb
deleted file mode 100644
index a09d5c6bda..0000000000
--- a/lib/bundler/cli/inject.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-module Bundler
- class CLI::Inject
- attr_reader :options, :name, :version, :group, :source, :gems
- def initialize(options, name, version)
- @options = options
- @name = name
- @version = version || last_version_number
- @group = options[:group].split(",") unless options[:group].nil?
- @source = options[:source]
- @gems = []
- end
-
- def run
- # The required arguments allow Thor to give useful feedback when the arguments
- # are incorrect. This adds those first two arguments onto the list as a whole.
- gems.unshift(source).unshift(group).unshift(version).unshift(name)
-
- # Build an array of Dependency objects out of the arguments
- deps = []
- # when `inject` support addition of more than one gem, then this loop will
- # help. Currently this loop is running once.
- gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source|
- ops = Gem::Requirement::OPS.map {|key, _val| key }
- has_op = ops.any? {|op| gem_version.start_with? op }
- gem_version = "~> #{gem_version}" unless has_op
- deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source)
- end
-
- added = Injector.inject(deps, options)
-
- if added.any?
- Bundler.ui.confirm "Added to Gemfile:"
- Bundler.ui.confirm(added.map do |d|
- name = "'#{d.name}'"
- requirement = ", '#{d.requirement}'"
- group = ", group: #{d.groups.inspect}" if d.groups != Array(:default)
- source = ", source: '#{d.source}'" unless d.source.nil?
- %(gem #{name}#{requirement}#{group}#{source})
- end.join("\n"))
- else
- Bundler.ui.confirm "All gems were already present in the Gemfile"
- end
- end
-
- private
-
- def last_version_number
- definition = Bundler.definition(true)
- definition.remotely!
- specs = definition.index[name].sort_by(&:version)
- unless options[:pre]
- specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
- end
- spec = specs.last
- spec.version.to_s
- end
- end
-end
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
index b0b354cf10..67feba84bd 100644
--- a/lib/bundler/cli/install.rb
+++ b/lib/bundler/cli/install.rb
@@ -20,54 +20,32 @@ module Bundler
Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Gem.freebsd_platform?
- # Disable color in deployment mode
- Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment]
-
if target_rbconfig_path = options[:"target-rbconfig"]
Bundler.rubygems.set_target_rbconfig(target_rbconfig_path)
end
- check_for_options_conflicts
-
check_trust_policy
- if options[:deployment] || options[:frozen] || Bundler.frozen_bundle?
- unless Bundler.default_lockfile.exist?
- flag = "--deployment flag" if options[:deployment]
- flag ||= "--frozen flag" if options[:frozen]
- flag ||= "deployment setting" if Bundler.settings[:deployment]
- flag ||= "frozen setting" if Bundler.settings[:frozen]
- raise ProductionError, "The #{flag} requires a lockfile. Please make " \
- "sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \
- "before deploying."
- end
-
- options[:local] = true if Bundler.app_cache.exist?
-
- Bundler.settings.set_command_option :deployment, true if options[:deployment]
- Bundler.settings.set_command_option :frozen, true if options[:frozen]
- end
-
- # When install is called with --no-deployment, disable deployment mode
- if options[:deployment] == false
- Bundler.settings.set_command_option :frozen, nil
- options[:system] = true
+ if Bundler.frozen_bundle? && !Bundler.default_lockfile.exist?
+ flag = "deployment setting" if Bundler.settings[:deployment]
+ flag = "frozen setting" if Bundler.settings[:frozen]
+ raise ProductionError, "The #{flag} requires a lockfile. Please make " \
+ "sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \
+ "before deploying."
end
normalize_settings
Bundler::Fetcher.disable_endpoint = options["full-index"]
- if options["binstubs"]
- Bundler::SharedHelpers.major_deprecation 2,
- "The --binstubs option will be removed in favor of `bundle binstubs --all`",
- removed_message: "The --binstubs option have been removed in favor of `bundle binstubs --all`"
- end
-
- Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
- definition = Bundler.definition
+ # For install we want to enable strict validation
+ # (rather than some optimizations we perform at app runtime).
+ definition = Bundler.definition(strict: true)
definition.validate_runtime!
+ definition.lockfile = options["lockfile"] if options["lockfile"]
+ definition.lockfile = false if options["no-lock"]
installer = Installer.install(Bundler.root, definition, options)
@@ -87,8 +65,6 @@ module Bundler
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages
- warn_ambiguous_gems
-
if CLI::Common.clean_after_install?
require_relative "clean"
Bundler::CLI::Clean.new(options).run
@@ -114,26 +90,10 @@ module Bundler
end
def gems_installed_for(definition)
- count = definition.specs.count
+ count = definition.specs.count {|spec| spec.name != "bundler" }
"#{count} #{count == 1 ? "gem" : "gems"} now installed"
end
- def check_for_group_conflicts_in_cli_options
- conflicting_groups = Array(options[:without]) & Array(options[:with])
- return if conflicting_groups.empty?
- raise InvalidOption, "You can't list a group in both with and without." \
- " The offending groups are: #{conflicting_groups.join(", ")}."
- end
-
- def check_for_options_conflicts
- if (options[:path] || options[:deployment]) && options[:system]
- error_message = String.new
- error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path]
- error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment]
- raise InvalidOption.new(error_message)
- end
- end
-
def check_trust_policy
trust_policy = options["trust-policy"]
unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy)
@@ -143,30 +103,11 @@ module Bundler
Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy
end
- def normalize_groups
- check_for_group_conflicts_in_cli_options
-
- # need to nil them out first to get around validation for backwards compatibility
- Bundler.settings.set_command_option :without, nil
- Bundler.settings.set_command_option :with, nil
- Bundler.settings.set_command_option :without, options[:without]
- Bundler.settings.set_command_option :with, options[:with]
- end
-
def normalize_settings
- Bundler.settings.set_command_option :path, nil if options[:system]
- Bundler.settings.set_command_option_if_given :path, options[:path]
-
if options["standalone"] && Bundler.settings[:path].nil? && !options["local"]
- Bundler.settings.temporary(path_relative_to_cwd: false) do
- Bundler.settings.set_command_option :path, "bundle"
- end
+ Bundler.settings.set_command_option :path, "bundle"
end
- bin_option = options["binstubs"]
- bin_option = nil if bin_option&.empty?
- Bundler.settings.set_command_option :bin, bin_option if options["binstubs"]
-
Bundler.settings.set_command_option_if_given :shebang, options["shebang"]
Bundler.settings.set_command_option_if_given :jobs, options["jobs"]
@@ -177,23 +118,7 @@ module Bundler
Bundler.settings.set_command_option_if_given :clean, options["clean"]
- normalize_groups if options[:without] || options[:with]
-
- options[:force] = options[:redownload]
- end
-
- def warn_ambiguous_gems
- # TODO: remove this when we drop Bundler 1.x support
- Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris|
- Bundler.ui.warn "Warning: the gem '#{name}' was found in multiple sources."
- Bundler.ui.warn "Installed from: #{installed_from_uri}"
- Bundler.ui.warn "Also found in:"
- also_found_in_uris.each {|uri| Bundler.ui.warn " * #{uri}" }
- Bundler.ui.warn "You should add a source requirement to restrict this gem to your preferred source."
- Bundler.ui.warn "For example:"
- Bundler.ui.warn " gem '#{name}', :source => '#{installed_from_uri}'"
- Bundler.ui.warn "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again."
- end
+ options[:force] = options[:redownload] if options[:redownload]
end
end
end
diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb
index fbe9184d12..cbfb7da2d8 100644
--- a/lib/bundler/cli/issue.rb
+++ b/lib/bundler/cli/issue.rb
@@ -10,7 +10,7 @@ module Bundler
be sure to check out these resources:
1. Check out our troubleshooting guide for quick fixes to common issues:
- https://github.com/rubygems/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md
+ https://github.com/ruby/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md
2. Instructions for common Bundler uses can be found on the documentation
site: https://bundler.io/
@@ -22,7 +22,7 @@ module Bundler
still aren't working the way you expect them to, please let us know so
that we can diagnose and help fix the problem you're having, by filling
in the new issue form located at
- https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md,
+ https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md,
and copy and pasting the information below.
EOS
diff --git a/lib/bundler/cli/list.rb b/lib/bundler/cli/list.rb
index f56bf5b86a..6a467f45a9 100644
--- a/lib/bundler/cli/list.rb
+++ b/lib/bundler/cli/list.rb
@@ -1,11 +1,14 @@
# frozen_string_literal: true
+require "json"
+
module Bundler
class CLI::List
def initialize(options)
@options = options
@without_group = options["without-group"].map(&:to_sym)
@only_group = options["only-group"].map(&:to_sym)
+ @format = options["format"]
end
def run
@@ -25,6 +28,36 @@ module Bundler
end
end.reject {|s| s.name == "bundler" }.sort_by(&:name)
+ case @format
+ when "json"
+ print_json(specs: specs)
+ when nil
+ print_human(specs: specs)
+ else
+ raise InvalidOption, "Unknown option`--format=#{@format}`. Supported formats: `json`"
+ end
+ end
+
+ private
+
+ def print_json(specs:)
+ gems = if @options["name-only"]
+ specs.map {|s| { name: s.name } }
+ else
+ specs.map do |s|
+ {
+ name: s.name,
+ version: s.version.to_s,
+ git_version: s.git_version&.strip,
+ }.tap do |h|
+ h[:path] = s.full_gem_path if @options["paths"]
+ end
+ end
+ end
+ Bundler.ui.info({ gems: gems }.to_json)
+ end
+
+ def print_human(specs:)
return Bundler.ui.info "No gems in the Gemfile" if specs.empty?
return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"]
@@ -37,8 +70,6 @@ module Bundler
Bundler.ui.info "Use `bundle info` to print more detailed information about a gem"
end
- private
-
def verify_group_exists(groups)
(@without_group + @only_group).each do |group|
raise InvalidOption, "`#{group}` group could not be found." unless groups.include?(group)
diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb
index b60c82e3a1..2f78868936 100644
--- a/lib/bundler/cli/lock.rb
+++ b/lib/bundler/cli/lock.rb
@@ -35,11 +35,8 @@ module Bundler
update = { bundler: bundler }
end
- file = options[:lockfile]
- file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile
-
Bundler.settings.temporary(frozen: false) do
- definition = Bundler.definition(update, file)
+ definition = Bundler.definition(update, Bundler.default_lockfile)
definition.add_checksums if options["add-checksums"]
Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update]
@@ -71,8 +68,11 @@ module Bundler
if print
puts definition.to_lock
else
+ file = options[:lockfile]
+ file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile
+
puts "Writing lockfile to #{file}"
- definition.lock
+ definition.write_lock(file, false)
end
end
diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb
index 1be44ff4b4..0c8ba3ebf7 100644
--- a/lib/bundler/cli/outdated.rb
+++ b/lib/bundler/cli/outdated.rb
@@ -155,7 +155,7 @@ module Bundler
return active_spec if strict
- active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
+ active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version)
if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
end
diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb
index fd61ef0d95..32fa660fe0 100644
--- a/lib/bundler/cli/plugin.rb
+++ b/lib/bundler/cli/plugin.rb
@@ -10,11 +10,15 @@ module Bundler
method_option "source", type: :string, default: nil, banner: "URL of the RubyGems source to fetch the plugin from"
method_option "version", type: :string, default: nil, banner: "The version of the plugin to fetch"
method_option "git", type: :string, default: nil, banner: "URL of the git repo to fetch from"
- method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)"
+ method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (removed)"
method_option "branch", type: :string, default: nil, banner: "The git branch to checkout"
method_option "ref", type: :string, default: nil, banner: "The git revision to check out"
method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use"
def install(*plugins)
+ if options.key?(:local_git)
+ raise InvalidOption, "--local_git has been removed, use --git"
+ end
+
Bundler::Plugin.install(plugins, options)
end
diff --git a/lib/bundler/cli/pristine.rb b/lib/bundler/cli/pristine.rb
index cfd4a995a3..b8545fe4c9 100644
--- a/lib/bundler/cli/pristine.rb
+++ b/lib/bundler/cli/pristine.rb
@@ -11,6 +11,7 @@ module Bundler
definition = Bundler.definition
definition.validate_runtime!
installer = Bundler::Installer.new(Bundler.root, definition)
+ git_sources = []
ProcessLock.lock do
installed_specs = definition.specs.reject do |spec|
@@ -41,6 +42,9 @@ module Bundler
end
FileUtils.rm_rf spec.extension_dir
FileUtils.rm_rf spec.full_gem_path
+
+ next if git_sources.include?(source)
+ git_sources << source
else
Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.")
next
diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb
index 13bb8b774b..67fdcc797e 100644
--- a/lib/bundler/cli/show.rb
+++ b/lib/bundler/cli/show.rb
@@ -6,7 +6,7 @@ module Bundler
def initialize(options, gem_name)
@options = options
@gem_name = gem_name
- @verbose = options[:verbose] || options[:outdated]
+ @verbose = options[:verbose]
@latest_specs = fetch_latest_specs if @verbose
end
@@ -57,12 +57,8 @@ module Bundler
def fetch_latest_specs
definition = Bundler.definition(true)
- if options[:outdated]
- Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
- Bundler.ui.silence { definition.remotely! }
- else
- definition.with_cache!
- end
+ Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
+ Bundler.ui.silence { definition.remotely! }
Bundler.reset!
definition.specs
end
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
index 985e8db051..9cc90acc58 100644
--- a/lib/bundler/cli/update.rb
+++ b/lib/bundler/cli/update.rb
@@ -15,7 +15,7 @@ module Bundler
Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler
- Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
sources = Array(options[:source])
groups = Array(options[:group]).map(&:to_sym)
@@ -23,10 +23,10 @@ module Bundler
full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !update_bundler
if full_update && !options[:all]
- if Bundler.feature_flag.update_requires_all_flag?
+ if Bundler.settings[:update_requires_all_flag]
raise InvalidOption, "To update everything, pass the `--all` flag."
end
- SharedHelpers.major_deprecation 3, "Pass --all to `bundle update` to update everything"
+ SharedHelpers.feature_deprecated! "Pass --all to `bundle update` to update everything"
elsif !full_update && options[:all]
raise InvalidOption, "Cannot specify --all along with specific options."
end
@@ -63,7 +63,7 @@ module Bundler
opts = options.dup
opts["update"] = true
opts["local"] = options[:local]
- opts["force"] = options[:redownload]
+ opts["force"] = options[:redownload] if options[:redownload]
Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
@@ -92,7 +92,7 @@ module Bundler
locked_spec = locked_info[:spec]
new_spec = Bundler.definition.specs[name].first
unless new_spec
- unless locked_spec.match_platform(Bundler.local_platform)
+ unless locked_spec.installable_on_platform?(Bundler.local_platform)
Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one"
end
diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb
deleted file mode 100644
index 5c09e00995..0000000000
--- a/lib/bundler/cli/viz.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Bundler
- class CLI::Viz
- attr_reader :options, :gem_name
- def initialize(options)
- @options = options
- end
-
- def run
- # make sure we get the right `graphviz`. There is also a `graphviz`
- # gem we're not built to support
- gem "ruby-graphviz"
- require "graphviz"
-
- options[:without] = options[:without].join(":").tr(" ", ":").split(":")
- output_file = File.expand_path(options[:file])
-
- graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without])
- graph.viz
- rescue LoadError => e
- Bundler.ui.error e.inspect
- Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:"
- Bundler.ui.warn "`gem install ruby-graphviz`"
- rescue StandardError => e
- raise unless e.message.to_s.include?("GraphViz not installed or dot not in PATH")
- Bundler.ui.error e.message
- Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`."
- end
- end
-end
diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb
index 37e2ccced8..6865e30dbc 100644
--- a/lib/bundler/compact_index_client.rb
+++ b/lib/bundler/compact_index_client.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require "pathname"
require "set"
module Bundler
diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb
index e7c872031f..17c7655adb 100644
--- a/lib/bundler/current_ruby.rb
+++ b/lib/bundler/current_ruby.rb
@@ -11,7 +11,7 @@ module Bundler
end
class CurrentRuby
- ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..35).to_a).freeze
+ ALL_RUBY_VERSIONS = [*18..27, *30..34, *40..41].freeze
KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze
KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze
PLATFORM_MAP = {
@@ -32,7 +32,7 @@ module Bundler
end.freeze
def ruby?
- return true if Bundler::GemHelpers.generic_local_platform_is_ruby?
+ return true if Bundler::MatchPlatform.generic_local_platform_is_ruby?
!windows? && (RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby")
end
@@ -50,19 +50,10 @@ module Bundler
end
def maglev?
- message =
- "`CurrentRuby#maglev?` is deprecated with no replacement. Please use the " \
- "built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on."
removed_message =
"`CurrentRuby#maglev?` was removed with no replacement. Please use the " \
"built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on."
- internally_exempted = caller_locations(1, 1).first.path == __FILE__
-
- unless internally_exempted
- SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true)
- end
-
- RUBY_ENGINE == "maglev"
+ SharedHelpers.feature_removed!(removed_message)
end
def truffleruby?
@@ -90,14 +81,11 @@ module Bundler
end
define_method(:"maglev_#{trimmed_version}?") do
- message =
- "`CurrentRuby##{__method__}` is deprecated with no replacement. Please use the " \
- "built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check."
removed_message =
"`CurrentRuby##{__method__}` was removed with no replacement. Please use the " \
"built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check."
- SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true)
+ SharedHelpers.feature_removed!(removed_message)
send(:"maglev?") && send(:"on_#{trimmed_version}?")
end
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index e9b67005a9..5ab577f504 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -1,16 +1,17 @@
# frozen_string_literal: true
require_relative "lockfile_parser"
+require_relative "worker"
module Bundler
class Definition
- include GemHelpers
-
class << self
# Do not create or modify a lockfile (Makes #lock a noop)
attr_accessor :no_lock
end
+ attr_writer :lockfile
+
attr_reader(
:dependencies,
:locked_checksums,
@@ -62,6 +63,7 @@ module Bundler
if unlock == true
@unlocking_all = true
+ strict = false
@unlocking_bundler = false
@unlocking = unlock
@sources_to_unlock = []
@@ -70,6 +72,7 @@ module Bundler
conservative = false
else
@unlocking_all = false
+ strict = unlock.delete(:strict)
@unlocking_bundler = unlock.delete(:bundler)
@unlocking = unlock.any? {|_k, v| !Array(v).empty? }
@sources_to_unlock = unlock.delete(:sources) || []
@@ -99,7 +102,7 @@ module Bundler
if lockfile_exists?
@lockfile_contents = Bundler.read_file(lockfile)
- @locked_gems = LockfileParser.new(@lockfile_contents)
+ @locked_gems = LockfileParser.new(@lockfile_contents, strict: strict)
@locked_platforms = @locked_gems.platforms
@most_specific_locked_platform = @locked_gems.most_specific_locked_platform
@platforms = @locked_platforms.dup
@@ -107,6 +110,7 @@ module Bundler
@locked_ruby_version = @locked_gems.ruby_version
@locked_deps = @locked_gems.dependencies
@originally_locked_specs = SpecSet.new(@locked_gems.specs)
+ @originally_locked_sources = @locked_gems.sources
@locked_checksums = @locked_gems.checksums
if @unlocking_all
@@ -114,7 +118,16 @@ module Bundler
@locked_sources = []
else
@locked_specs = @originally_locked_specs
- @locked_sources = @locked_gems.sources
+ @locked_sources = @originally_locked_sources
+ end
+
+ locked_gem_sources = @originally_locked_sources.select {|s| s.is_a?(Source::Rubygems) }
+ multisource_lockfile = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes?
+
+ if multisource_lockfile
+ msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure."
+
+ Bundler::SharedHelpers.feature_removed! msg
end
else
@locked_gems = nil
@@ -123,22 +136,10 @@ module Bundler
@platforms = []
@locked_deps = {}
@locked_specs = SpecSet.new([])
- @originally_locked_specs = @locked_specs
@locked_sources = []
- @locked_checksums = Bundler.feature_flag.lockfile_checksums?
- end
-
- locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
- @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle?
-
- if @multisource_allowed
- unless sources.aggregate_global_source?
- msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure."
-
- Bundler::SharedHelpers.major_deprecation 2, msg
- end
-
- @sources.merged_gem_lockfile_sections!(locked_gem_sources.first)
+ @originally_locked_specs = @locked_specs
+ @originally_locked_sources = @locked_sources
+ @locked_checksums = Bundler.settings[:lockfile_checksums]
end
@unlocking_ruby ||= if @ruby_version && locked_ruby_version_object
@@ -189,12 +190,14 @@ module Bundler
def setup_domain!(options = {})
prefer_local! if options[:"prefer-local"]
+ sources.cached!
+
if options[:add_checksums] || (!options[:local] && install_needed?)
- remotely!
+ sources.remote!
true
else
Bundler.settings.set_command_option(:jobs, 1) unless install_needed? # to avoid the overhead of Bundler::Worker
- with_cache!
+ sources.local!
false
end
end
@@ -282,12 +285,17 @@ module Bundler
end
def filter_relevant(dependencies)
- platforms_array = [generic_local_platform].freeze
dependencies.select do |d|
- d.should_include? && !d.gem_platforms(platforms_array).empty?
+ relevant_deps?(d)
end
end
+ def relevant_deps?(dep)
+ platforms_array = [Bundler.generic_local_platform].freeze
+
+ dep.should_include? && !dep.gem_platforms(platforms_array).empty?
+ end
+
def locked_dependencies
@locked_deps.values
end
@@ -367,12 +375,50 @@ module Bundler
msg = "`Definition#lock` was passed a target file argument. #{suggestion}"
- Bundler::SharedHelpers.major_deprecation 2, msg
+ Bundler::SharedHelpers.feature_removed! msg
end
write_lock(target_lockfile, preserve_unknown_sections)
end
+ def write_lock(file, preserve_unknown_sections)
+ return if Definition.no_lock || !lockfile || file.nil?
+
+ contents = to_lock
+
+ # Convert to \r\n if the existing lock has them
+ # i.e., Windows with `git config core.autocrlf=true`
+ contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n")
+
+ if @locked_bundler_version
+ locked_major = @locked_bundler_version.segments.first
+ current_major = bundler_version_to_lock.segments.first
+
+ updating_major = locked_major < current_major
+ end
+
+ preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler))
+
+ if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)
+ return if Bundler.frozen_bundle?
+ SharedHelpers.filesystem_access(file) { FileUtils.touch(file) }
+ return
+ end
+
+ if Bundler.frozen_bundle?
+ Bundler.ui.error "Cannot write a changed lockfile while frozen."
+ return
+ end
+
+ begin
+ SharedHelpers.filesystem_access(file) do |p|
+ File.open(p, "wb") {|f| f.puts(contents) }
+ end
+ rescue ReadOnlyFileSystemError
+ raise ProductionError, lockfile_changes_summary("file system is read-only")
+ end
+ end
+
def locked_ruby_version
return unless ruby_version
if @unlocking_ruby || !@locked_ruby_version
@@ -456,8 +502,8 @@ module Bundler
return if current_platform_locked? || @platforms.include?(Gem::Platform::RUBY)
raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
- "but your local platform is #{local_platform}. " \
- "Add the current platform to the lockfile with\n`bundle lock --add-platform #{local_platform}` and try again."
+ "but your local platform is #{Bundler.local_platform}. " \
+ "Add the current platform to the lockfile with\n`bundle lock --add-platform #{Bundler.local_platform}` and try again."
end
def normalize_platforms
@@ -492,14 +538,25 @@ module Bundler
@unlocking
end
- attr_writer :source_requirements
-
def add_checksums
+ require "rubygems/package"
+
@locked_checksums = true
setup_domain!(add_checksums: true)
- specs # force materialization to real specifications, so that checksums are fetched
+ # force materialization to real specifications, so that checksums are fetched
+ specs.each do |spec|
+ next unless spec.source.is_a?(Bundler::Source::Rubygems)
+ # Checksum was fetched from the compact index API.
+ next if !spec.source.checksum_store.missing?(spec) && !spec.source.checksum_store.empty?(spec)
+ # The gem isn't installed, can't compute the checksum.
+ next unless spec.loaded_from
+
+ package = Gem::Package.new(spec.source.cached_built_in_gem(spec))
+ checksum = Checksum.from_gem_package(package)
+ spec.source.checksum_store.register(spec, checksum)
+ end
end
private
@@ -533,7 +590,7 @@ module Bundler
return unless added.any? || deleted.any? || changed.any? || resolve_needed?
- msg = String.new("#{change_reason.capitalize.strip}, but ")
+ msg = String.new("#{change_reason[0].upcase}#{change_reason[1..-1].strip}, but ")
msg << "the lockfile " unless msg.start_with?("Your lockfile")
msg << "can't be updated because #{update_refused_reason}"
msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
@@ -559,6 +616,7 @@ module Bundler
@missing_lockfile_dep ||
@unlocking_bundler ||
@locked_spec_with_missing_checksums ||
+ @locked_spec_with_empty_checksums ||
@locked_spec_with_missing_deps ||
@locked_spec_with_invalid_deps
end
@@ -568,53 +626,15 @@ module Bundler
end
def should_add_extra_platforms?
- !lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
+ !lockfile_exists? && Bundler::MatchPlatform.generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
end
def lockfile_exists?
lockfile && File.exist?(lockfile)
end
- def write_lock(file, preserve_unknown_sections)
- return if Definition.no_lock || file.nil?
-
- contents = to_lock
-
- # Convert to \r\n if the existing lock has them
- # i.e., Windows with `git config core.autocrlf=true`
- contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n")
-
- if @locked_bundler_version
- locked_major = @locked_bundler_version.segments.first
- current_major = bundler_version_to_lock.segments.first
-
- updating_major = locked_major < current_major
- end
-
- preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler))
-
- if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections)
- return if Bundler.frozen_bundle?
- SharedHelpers.filesystem_access(file) { FileUtils.touch(file) }
- return
- end
-
- if Bundler.frozen_bundle?
- Bundler.ui.error "Cannot write a changed lockfile while frozen."
- return
- end
-
- begin
- SharedHelpers.filesystem_access(file) do |p|
- File.open(p, "wb") {|f| f.puts(contents) }
- end
- rescue ReadOnlyFileSystemError
- raise ProductionError, lockfile_changes_summary("file system is read-only")
- end
- end
-
def resolver
- @resolver ||= Resolver.new(resolution_base, gem_version_promoter, @most_specific_locked_platform)
+ @resolver ||= new_resolver(resolution_base)
end
def expanded_dependencies
@@ -632,8 +652,7 @@ module Bundler
@resolution_base ||= begin
last_resolve = converge_locked_specs
remove_invalid_platforms!
- new_resolution_platforms = @current_platform_missing ? @new_platforms + [local_platform] : @new_platforms
- base = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @unlocking_all || @gems_to_unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms)
+ base = new_resolution_base(last_resolve: last_resolve, unlock: @unlocking_all || @gems_to_unlock)
base = additional_base_requirements_to_prevent_downgrades(base)
base = additional_base_requirements_to_force_updates(base)
base
@@ -645,20 +664,12 @@ module Bundler
end
def materialize(dependencies)
- # Tracks potential endless loops trying to re-resolve.
- # TODO: Remove as dead code if not reports are received in a while
- incorrect_spec = nil
-
specs = begin
resolve.materialize(dependencies)
rescue IncorrectLockfileDependencies => e
raise if Bundler.frozen_bundle?
- spec = e.spec
- raise "Infinite loop while fixing lockfile dependencies" if incorrect_spec == spec
-
- incorrect_spec = spec
- reresolve_without([spec])
+ reresolve_without([e.spec])
retry
end
@@ -738,9 +749,8 @@ module Bundler
end
def start_resolution
- local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(local_platform)
- @platforms << local_platform if local_platform_needed_for_resolvability
- add_platform(Gem::Platform::RUBY) if RUBY_ENGINE == "truffleruby"
+ local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(Bundler.local_platform)
+ @platforms << Bundler.local_platform if local_platform_needed_for_resolvability
result = SpecSet.new(resolver.start)
@@ -758,7 +768,7 @@ module Bundler
if result.incomplete_for_platform?(current_dependencies, @most_specific_non_local_locked_platform)
@platforms.delete(@most_specific_non_local_locked_platform)
elsif local_platform_needed_for_resolvability
- @platforms.delete(local_platform)
+ @platforms.delete(Bundler.local_platform)
end
end
@@ -772,22 +782,22 @@ module Bundler
end
def precompute_source_requirements_for_indirect_dependencies?
- sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source?
+ sources.non_global_rubygems_sources.all?(&:dependency_api_available?)
end
def current_platform_locked?
@platforms.any? do |bundle_platform|
- generic_local_platform == bundle_platform || local_platform === bundle_platform
+ Bundler.generic_local_platform == bundle_platform || Bundler.local_platform === bundle_platform
end
end
def add_current_platform
- return if @platforms.include?(local_platform)
+ return if @platforms.include?(Bundler.local_platform)
@most_specific_non_local_locked_platform = find_most_specific_locked_platform
return if @most_specific_non_local_locked_platform
- @platforms << local_platform
+ @platforms << Bundler.local_platform
true
end
@@ -848,6 +858,7 @@ module Bundler
[@missing_lockfile_dep, "your lockfile is missing \"#{@missing_lockfile_dep}\""],
[@unlocking_bundler, "an update to the version of Bundler itself was requested"],
[@locked_spec_with_missing_checksums, "your lockfile is missing a CHECKSUMS entry for \"#{@locked_spec_with_missing_checksums}\""],
+ [@locked_spec_with_empty_checksums, "your lockfile has an empty CHECKSUMS entry for \"#{@locked_spec_with_empty_checksums}\""],
[@locked_spec_with_missing_deps, "your lockfile includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"],
[@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""],
].select(&:first).map(&:last).join(", ")
@@ -907,13 +918,23 @@ module Bundler
@locked_spec_with_invalid_deps = nil
@locked_spec_with_missing_deps = nil
@locked_spec_with_missing_checksums = nil
+ @locked_spec_with_empty_checksums = nil
missing_deps = []
missing_checksums = []
+ empty_checksums = []
invalid = []
@locked_specs.each do |s|
- missing_checksums << s if @locked_checksums && s.source.checksum_store.missing?(s)
+ if @locked_checksums
+ checksum_store = s.source.checksum_store
+
+ if checksum_store.missing?(s)
+ missing_checksums << s
+ elsif checksum_store.empty?(s)
+ empty_checksums << s
+ end
+ end
validation = @locked_specs.validate_deps(s)
@@ -922,6 +943,7 @@ module Bundler
end
@locked_spec_with_missing_checksums = missing_checksums.first.name if missing_checksums.any?
+ @locked_spec_with_empty_checksums = empty_checksums.first.name if empty_checksums.any?
if missing_deps.any?
@locked_specs.delete(missing_deps)
@@ -951,7 +973,7 @@ module Bundler
sources.all_sources.each do |source|
# has to be done separately, because we want to keep the locked checksum
# store for a source, even when doing a full update
- if @locked_checksums && @locked_gems && locked_source = @locked_gems.sources.find {|s| s == source && !s.equal?(source) }
+ if @locked_checksums && @locked_gems && locked_source = @originally_locked_sources.find {|s| s == source && !s.equal?(source) }
source.checksum_store.merge!(locked_source.checksum_store)
end
# If the source is unlockable and the current command allows an unlock of
@@ -972,10 +994,11 @@ module Bundler
@missing_lockfile_dep = nil
@changed_dependencies = []
- current_dependencies.each do |dep|
+ @dependencies.each do |dep|
if dep.source
dep.source = sources.get(dep.source)
end
+ next unless relevant_deps?(dep)
name = dep.name
@@ -1033,26 +1056,43 @@ module Bundler
specs.each do |s|
name = s.name
+ next if @gems_to_unlock.include?(name)
+
dep = @dependencies.find {|d| s.satisfies?(d) }
lockfile_source = s.source
if dep
- gemfile_source = dep.source || default_source
+ replacement_source = dep.source
- deps << dep if !dep.source || lockfile_source.include?(dep.source) || new_deps.include?(dep)
-
- # Replace the locked dependency's source with the equivalent source from the Gemfile
- s.source = gemfile_source
+ deps << dep if !replacement_source || lockfile_source.include?(replacement_source) || new_deps.include?(dep)
else
- # Replace the locked dependency's source with the default source, if the locked source is no longer in the Gemfile
- s.source = default_source unless sources.get(lockfile_source)
+ parent_dep = @dependencies.find do |d|
+ next unless d.source && d.source != lockfile_source
+ next if d.source.is_a?(Source::Gemspec)
+
+ parent_locked_specs = @originally_locked_specs[d.name]
+
+ parent_locked_specs.any? do |parent_spec|
+ parent_spec.runtime_dependencies.any? {|rd| rd.name == s.name }
+ end
+ end
+
+ if parent_dep
+ replacement_source = parent_dep.source
+ else
+ replacement_source = sources.get(lockfile_source)
+ end
end
+ # Replace the locked dependency's source with the equivalent source from the Gemfile
+ s.source = replacement_source || default_source
+ next if s.source_changed?
+
source = s.source
next if @sources_to_unlock.include?(source.name)
# Path sources have special logic
- if source.instance_of?(Source::Path) || source.instance_of?(Source::Gemspec) || (source.instance_of?(Source::Git) && !@gems_to_unlock.include?(name) && deps.include?(dep))
+ if source.is_a?(Source::Path)
new_spec = source.specs[s].first
if new_spec
s.runtime_dependencies.replace(new_spec.runtime_dependencies)
@@ -1080,7 +1120,23 @@ module Bundler
@source_requirements ||= find_source_requirements
end
+ def preload_git_source_worker
+ @preload_git_source_worker ||= Bundler::Worker.new(5, "Git source preloading", ->(source, _) { source.specs })
+ end
+
+ def preload_git_sources
+ sources.git_sources.each {|source| preload_git_source_worker.enq(source) }
+ ensure
+ preload_git_source_worker.stop
+ end
+
def find_source_requirements
+ if Gem.ruby_version >= Gem::Version.new("3.3")
+ # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to
+ # fetch git repositories one by one.
+ preload_git_sources
+ end
+
# Record the specs available in each gem's source, so that those
# specs will be available later when the resolver knows where to
# look for that gemspec (or its dependencies)
@@ -1132,9 +1188,9 @@ module Bundler
end
def additional_base_requirements_to_prevent_downgrades(resolution_base)
- return resolution_base unless @locked_gems && !sources.expired_sources?(@locked_gems.sources)
+ return resolution_base unless @locked_gems
@originally_locked_specs.each do |locked_spec|
- next if locked_spec.source.is_a?(Source::Path)
+ next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed?
name = locked_spec.name
next if @changed_dependencies.include?(name)
@@ -1146,7 +1202,7 @@ module Bundler
def additional_base_requirements_to_force_updates(resolution_base)
return resolution_base if @explicit_unlocks.empty?
- full_update = dup_for_full_unlock.resolve
+ full_update = SpecSet.new(new_resolver_for_full_update.start)
@explicit_unlocks.each do |name|
version = full_update.version_for(name)
resolution_base.base_requirements[name] = Gem::Requirement.new("= #{version}") if version
@@ -1154,21 +1210,10 @@ module Bundler
resolution_base
end
- def dup_for_full_unlock
- unlocked_definition = self.class.new(@lockfile, @dependencies, @sources, true, @ruby_version, @optional_groups, @gemfiles)
- unlocked_definition.source_requirements = source_requirements
- unlocked_definition.gem_version_promoter.tap do |gvp|
- gvp.level = gem_version_promoter.level
- gvp.strict = gem_version_promoter.strict
- gvp.pre = gem_version_promoter.pre
- end
- unlocked_definition
- end
-
def remove_invalid_platforms!
return if Bundler.frozen_bundle?
- skips = (@new_platforms + [local_platform]).uniq
+ skips = (@new_platforms + [Bundler.local_platform]).uniq
# We should probably avoid removing non-ruby platforms, since that means
# lockfile will no longer install on those platforms, so a error to give
@@ -1183,5 +1228,22 @@ module Bundler
def source_map
@source_map ||= SourceMap.new(sources, dependencies, @locked_specs)
end
+
+ def new_resolver_for_full_update
+ new_resolver(unlocked_resolution_base)
+ end
+
+ def unlocked_resolution_base
+ new_resolution_base(last_resolve: SpecSet.new([]), unlock: true)
+ end
+
+ def new_resolution_base(last_resolve:, unlock:)
+ new_resolution_platforms = @current_platform_missing ? @new_platforms + [Bundler.local_platform] : @new_platforms
+ Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms)
+ end
+
+ def new_resolver(base)
+ Resolver.new(base, gem_version_promoter, @most_specific_locked_platform)
+ end
end
end
diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb
index e81696ff42..cb9c7a76ea 100644
--- a/lib/bundler/dependency.rb
+++ b/lib/bundler/dependency.rb
@@ -99,7 +99,7 @@ module Bundler
return RUBY_PLATFORM_ARRAY if force_ruby_platform
return valid_platforms if platforms.empty?
- valid_platforms.select {|p| expanded_platforms.include?(GemHelpers.generic(p)) }
+ valid_platforms.select {|p| expanded_platforms.include?(Gem::Platform.generic(p)) }
end
def expanded_platforms
diff --git a/lib/bundler/deployment.rb b/lib/bundler/deployment.rb
index b432ae6ae1..3344449e82 100644
--- a/lib/bundler/deployment.rb
+++ b/lib/bundler/deployment.rb
@@ -1,69 +1,6 @@
# frozen_string_literal: true
require_relative "shared_helpers"
-Bundler::SharedHelpers.major_deprecation 2, "Bundler no longer integrates with " \
+Bundler::SharedHelpers.feature_removed! "Bundler no longer integrates with " \
"Capistrano, but Capistrano provides its own integration with " \
"Bundler via the capistrano-bundler gem. Use it instead."
-
-module Bundler
- class Deployment
- def self.define_task(context, task_method = :task, opts = {})
- if defined?(Capistrano) && context.is_a?(Capistrano::Configuration)
- context_name = "capistrano"
- role_default = "{:except => {:no_release => true}}"
- error_type = ::Capistrano::CommandError
- else
- context_name = "vlad"
- role_default = "[:app]"
- error_type = ::Rake::CommandFailedError
- end
-
- roles = context.fetch(:bundle_roles, false)
- opts[:roles] = roles if roles
-
- context.send :namespace, :bundle do
- send :desc, <<-DESC
- Install the current Bundler environment. By default, gems will be \
- installed to the shared/bundle path. Gems in the development and \
- test group will not be installed. The install command is executed \
- with the --deployment and --quiet flags. If the bundle cmd cannot \
- be found then you can override the bundle_cmd variable to specify \
- which one it should use. The base path to the app is fetched from \
- the :latest_release variable. Set it for custom deploy layouts.
-
- You can override any of these defaults by setting the variables shown below.
-
- N.B. bundle_roles must be defined before you require 'bundler/#{context_name}' \
- in your deploy.rb file.
-
- set :bundle_gemfile, "Gemfile"
- set :bundle_dir, File.join(fetch(:shared_path), 'bundle')
- set :bundle_flags, "--deployment --quiet"
- set :bundle_without, [:development, :test]
- set :bundle_with, [:mysql]
- set :bundle_cmd, "bundle" # e.g. "/opt/ruby/bin/bundle"
- set :bundle_roles, #{role_default} # e.g. [:app, :batch]
- DESC
- send task_method, :install, opts do
- bundle_cmd = context.fetch(:bundle_cmd, "bundle")
- bundle_flags = context.fetch(:bundle_flags, "--deployment --quiet")
- bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), "bundle"))
- bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile")
- bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact
- bundle_with = [*context.fetch(:bundle_with, [])].compact
- app_path = context.fetch(:latest_release)
- if app_path.to_s.empty?
- raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.")
- end
- args = ["--gemfile #{File.join(app_path, bundle_gemfile)}"]
- args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty?
- args << bundle_flags.to_s
- args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty?
- args << "--with #{bundle_with.join(" ")}" unless bundle_with.empty?
-
- run "cd #{app_path} && #{bundle_cmd} install #{args.join(" ")}"
- end
- end
- end
- end
-end
diff --git a/lib/bundler/digest.rb b/lib/bundler/digest.rb
index 2c6d971f1b..158803033d 100644
--- a/lib/bundler/digest.rb
+++ b/lib/bundler/digest.rb
@@ -26,7 +26,7 @@ module Bundler
end
a, b, c, d, e = *words
(16..79).each do |i|
- w[i] = SHA1_MASK & rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1)
+ w[i] = SHA1_MASK & rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1)
end
0.upto(79) do |i|
case i
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index 32f45d97ec..6f06c4e918 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -9,8 +9,9 @@ module Bundler
def self.evaluate(gemfile, lockfile, unlock)
builder = new
+ builder.lockfile(lockfile)
builder.eval_gemfile(gemfile)
- builder.to_definition(lockfile, unlock)
+ builder.to_definition(builder.lockfile_path, unlock)
end
VALID_PLATFORMS = Bundler::CurrentRuby::PLATFORM_MAP.keys.freeze
@@ -38,6 +39,7 @@ module Bundler
@gemspecs = []
@gemfile = nil
@gemfiles = []
+ @lockfile = nil
add_git_sources
end
@@ -73,7 +75,7 @@ module Bundler
case specs_by_name_and_version.size
when 1
specs = specs_by_name_and_version.values.first
- spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first
+ spec = specs.find {|s| s.installable_on_platform?(Bundler.local_platform) } || specs.first
@gemspecs << spec
@@ -101,6 +103,15 @@ module Bundler
add_dependency(name, version, options)
end
+ # For usage in Dsl.evaluate, since lockfile is used as part of the Gemfile.
+ def lockfile_path
+ @lockfile
+ end
+
+ def lockfile(file)
+ @lockfile = file
+ end
+
def source(source, *args, &blk)
options = args.last.is_a?(Hash) ? args.pop.dup : {}
options = normalize_hash(options)
@@ -175,6 +186,7 @@ module Bundler
def to_definition(lockfile, unlock)
check_primary_source_safety
+ lockfile = @lockfile unless @lockfile.nil?
Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles)
end
@@ -290,7 +302,7 @@ module Bundler
@dependencies.delete(current)
elsif dep.gemspec_dev_dep?
return
- elsif current.source != dep.source
+ elsif current.source.to_s != dep.source.to_s
raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
"You specified that #{name} (#{dep.requirement}) should come from " \
"#{current.source || "an unspecified source"} and #{dep.source}\n"
@@ -411,7 +423,13 @@ module Bundler
next if VALID_PLATFORMS.include?(p)
raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
end
- deprecate_legacy_windows_platforms(platforms)
+
+ windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) }
+ if windows_platforms.any?
+ windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ")
+ deprecated_message = "Platform #{windows_platforms} will be removed in the future. Please use platform :windows instead."
+ Bundler::SharedHelpers.feature_deprecated! deprecated_message
+ end
# Save sources passed in a key
if opts.key?("source")
@@ -477,14 +495,10 @@ module Bundler
def normalize_source(source)
case source
when :gemcutter, :rubygems, :rubyforge
- message =
- "The source :#{source} is deprecated because HTTP requests are insecure.\n" \
- "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not."
removed_message =
"The source :#{source} is disallowed because HTTP requests are insecure.\n" \
"Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not."
- Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message
- "http://rubygems.org"
+ Bundler::SharedHelpers.feature_removed! removed_message
when String
source
else
@@ -492,16 +506,6 @@ module Bundler
end
end
- def deprecate_legacy_windows_platforms(platforms)
- windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) }
- return if windows_platforms.empty?
-
- windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ")
- message = "Platform #{windows_platforms} is deprecated. Please use platform :windows instead."
- removed_message = "Platform #{windows_platforms} has been removed. Please use platform :windows instead."
- Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message
- end
-
def check_path_source_safety
return if @sources.global_path_source.nil?
@@ -513,7 +517,7 @@ module Bundler
" gem 'rails'\n" \
" end\n\n"
- SharedHelpers.major_deprecation(2, msg.strip)
+ SharedHelpers.feature_removed! msg.strip
end
def check_rubygems_source_safety
@@ -521,24 +525,10 @@ module Bundler
end
def multiple_global_source_warning
- if Bundler.feature_flag.bundler_3_mode?
- msg = "This Gemfile contains multiple global sources. " \
- "Each source after the first must include a block to indicate which gems " \
- "should come from that source"
- raise GemfileEvalError, msg
- else
- message =
- "Your Gemfile contains multiple global sources. " \
- "Using `source` more than once without a block is a security risk, and " \
- "may result in installing unexpected gems. To resolve this warning, use " \
- "a block to indicate which gems should come from the secondary source."
- removed_message =
- "Your Gemfile contains multiple global sources. " \
- "Using `source` more than once without a block is a security risk, and " \
- "may result in installing unexpected gems. To resolve this error, use " \
- "a block to indicate which gems should come from the secondary source."
- Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message
- end
+ msg = "This Gemfile contains multiple global sources. " \
+ "Each source after the first must include a block to indicate which gems " \
+ "should come from that source"
+ raise GemfileEvalError, msg
end
class DSLError < GemfileError
diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb
index 444ab6fd37..bf9478a299 100644
--- a/lib/bundler/environment_preserver.rb
+++ b/lib/bundler/environment_preserver.rb
@@ -6,6 +6,7 @@ module Bundler
BUNDLER_KEYS = %w[
BUNDLE_BIN_PATH
BUNDLE_GEMFILE
+ BUNDLE_LOCKFILE
BUNDLER_VERSION
BUNDLER_SETUP
GEM_HOME
diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb
index 4d1bface51..d8df4d6ec5 100644
--- a/lib/bundler/errors.rb
+++ b/lib/bundler/errors.rb
@@ -25,6 +25,7 @@ module Bundler
class GemNotFound < BundlerError; status_code(7); end
class InstallHookError < BundlerError; status_code(8); end
+ class RemovedError < BundlerError; status_code(9); end
class GemfileNotFound < BundlerError; status_code(10); end
class GitError < BundlerError; status_code(11); end
class DeprecatedError < BundlerError; status_code(12); end
@@ -76,11 +77,6 @@ module Bundler
def mismatch_resolution_instructions
removable, remote = [@existing, @checksum].partition(&:removable?)
case removable.size
- when 0
- msg = +"Mismatched checksums each have an authoritative source:\n"
- msg << " 1. #{@existing.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n"
- msg << " 2. #{@checksum.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n"
- msg << "You may need to alter your Gemfile sources to resolve this issue.\n"
when 1
msg = +"If you trust #{remote.first.sources.first}, to resolve this issue you can:\n"
msg << removable.first.removal_instructions
@@ -135,7 +131,8 @@ module Bundler
attr_reader :orig_exception
def initialize(orig_exception, msg)
- full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\
+ full_message = msg + "\nGem Load Error is:
+ #{orig_exception.full_message(highlight: false)}\n"\
"Backtrace for gem load error is:\n"\
"#{orig_exception.backtrace.join("\n")}\n"\
"Bundler Error Backtrace:\n"
@@ -225,7 +222,9 @@ module Bundler
class DirectoryRemovalError < BundlerError
def initialize(orig_exception, msg)
full_message = "#{msg}.\n" \
- "The underlying error was #{orig_exception.class}: #{orig_exception.message}, with backtrace:\n" \
+ "The underlying error was #{orig_exception.class}:
+ #{orig_exception.full_message(highlight: false)},
+ with backtrace:\n" \
" #{orig_exception.backtrace.join("\n ")}\n\n" \
"Bundler Error Backtrace:"
super(full_message)
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
index b19cf42cc3..dea8abedba 100644
--- a/lib/bundler/feature_flag.rb
+++ b/lib/bundler/feature_flag.rb
@@ -2,45 +2,15 @@
module Bundler
class FeatureFlag
- def self.settings_flag(flag, &default)
- unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s)
- raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key"
- end
-
- settings_method("#{flag}?", flag, &default)
- end
- private_class_method :settings_flag
+ (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } }
- def self.settings_option(key, &default)
- settings_method(key, key, &default)
+ def removed_major?(target_major_version)
+ @major_version > target_major_version
end
- private_class_method :settings_option
- def self.settings_method(name, key, &default)
- define_method(name) do
- value = Bundler.settings[key]
- value = instance_eval(&default) if value.nil?
- value
- end
+ def deprecated_major?(target_major_version)
+ @major_version >= target_major_version
end
- private_class_method :settings_method
-
- (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } }
-
- settings_flag(:allow_offline_install) { bundler_3_mode? }
- settings_flag(:auto_clean_without_path) { bundler_3_mode? }
- settings_flag(:cache_all) { bundler_3_mode? }
- settings_flag(:default_install_uses_path) { bundler_3_mode? }
- settings_flag(:forget_cli_options) { bundler_3_mode? }
- settings_flag(:global_gem_cache) { bundler_3_mode? }
- settings_flag(:lockfile_checksums) { bundler_3_mode? }
- settings_flag(:path_relative_to_cwd) { bundler_3_mode? }
- settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
- settings_flag(:print_only_version_number) { bundler_3_mode? }
- settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? }
- settings_flag(:update_requires_all_flag) { bundler_4_mode? }
-
- settings_option(:default_cli_command) { bundler_3_mode? ? :cli_help : :install }
def initialize(bundler_version)
@bundler_version = Gem::Version.create(bundler_version)
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
index 9992b20c47..0b6ced6f39 100644
--- a/lib/bundler/fetcher.rb
+++ b/lib/bundler/fetcher.rb
@@ -72,19 +72,57 @@ module Bundler
end
end
+ HTTP_ERRORS = (Downloader::HTTP_RETRYABLE_ERRORS + Downloader::HTTP_NON_RETRYABLE_ERRORS).freeze
+ deprecate_constant :HTTP_ERRORS
+
+ NET_ERRORS = [
+ :HTTPBadGateway,
+ :HTTPBadRequest,
+ :HTTPFailedDependency,
+ :HTTPForbidden,
+ :HTTPInsufficientStorage,
+ :HTTPMethodNotAllowed,
+ :HTTPMovedPermanently,
+ :HTTPNoContent,
+ :HTTPNotFound,
+ :HTTPNotImplemented,
+ :HTTPPreconditionFailed,
+ :HTTPRequestEntityTooLarge,
+ :HTTPRequestURITooLong,
+ :HTTPUnauthorized,
+ :HTTPUnprocessableEntity,
+ :HTTPUnsupportedMediaType,
+ :HTTPVersionNotSupported,
+ ].freeze
+ deprecate_constant :NET_ERRORS
+
# Exceptions classes that should bypass retry attempts. If your password didn't work the
# first time, it's not going to the third time.
- NET_ERRORS = [:HTTPBadGateway, :HTTPBadRequest, :HTTPFailedDependency,
- :HTTPForbidden, :HTTPInsufficientStorage, :HTTPMethodNotAllowed,
- :HTTPMovedPermanently, :HTTPNoContent, :HTTPNotFound,
- :HTTPNotImplemented, :HTTPPreconditionFailed, :HTTPRequestEntityTooLarge,
- :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity,
- :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze
- FAIL_ERRORS = begin
- fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError, SecurityError]
- fail_errors << Gem::Requirement::BadRequirementError
- fail_errors.concat(NET_ERRORS.map {|e| Gem::Net.const_get(e) })
- end.freeze
+ FAIL_ERRORS = [
+ AuthenticationRequiredError,
+ BadAuthenticationError,
+ AuthenticationForbiddenError,
+ FallbackError,
+ SecurityError,
+ Gem::Requirement::BadRequirementError,
+ Gem::Net::HTTPBadGateway,
+ Gem::Net::HTTPBadRequest,
+ Gem::Net::HTTPFailedDependency,
+ Gem::Net::HTTPForbidden,
+ Gem::Net::HTTPInsufficientStorage,
+ Gem::Net::HTTPMethodNotAllowed,
+ Gem::Net::HTTPMovedPermanently,
+ Gem::Net::HTTPNoContent,
+ Gem::Net::HTTPNotFound,
+ Gem::Net::HTTPNotImplemented,
+ Gem::Net::HTTPPreconditionFailed,
+ Gem::Net::HTTPRequestEntityTooLarge,
+ Gem::Net::HTTPRequestURITooLong,
+ Gem::Net::HTTPUnauthorized,
+ Gem::Net::HTTPUnprocessableEntity,
+ Gem::Net::HTTPUnsupportedMediaType,
+ Gem::Net::HTTPVersionNotSupported,
+ ].freeze
class << self
attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
@@ -293,13 +331,6 @@ module Bundler
paths.find {|path| File.file? path }
end
- HTTP_ERRORS = [
- Gem::Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH,
- Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN,
- Gem::Net::HTTPBadResponse, Gem::Net::HTTPHeaderSyntaxError, Gem::Net::ProtocolError,
- Gem::Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH
- ].freeze
-
def bundler_cert_store
store = OpenSSL::X509::Store.new
ssl_ca_cert = Bundler.settings[:ssl_ca_cert] ||
diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb
index 6c82d57011..52168111fe 100644
--- a/lib/bundler/fetcher/compact_index.rb
+++ b/lib/bundler/fetcher/compact_index.rb
@@ -110,7 +110,7 @@ module Bundler
def call(path, headers)
fetcher.downloader.fetch(fetcher.fetch_uri + path, headers)
rescue NetworkDownError => e
- raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"]
+ raise unless headers["If-None-Match"]
ui.warn "Using the cached data for the new index because of a network error: #{e}"
Gem::Net::HTTPNotModified.new(nil, nil, nil)
end
diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb
index e12269925d..994b415e9c 100644
--- a/lib/bundler/fetcher/dependency.rb
+++ b/lib/bundler/fetcher/dependency.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
require_relative "base"
-require "cgi/util"
+require "cgi/escape"
+require "cgi/util" unless defined?(CGI::EscapeExt)
module Bundler
class Fetcher
diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb
index 868b39b959..2eac6e7975 100644
--- a/lib/bundler/fetcher/downloader.rb
+++ b/lib/bundler/fetcher/downloader.rb
@@ -3,6 +3,28 @@
module Bundler
class Fetcher
class Downloader
+ HTTP_NON_RETRYABLE_ERRORS = [
+ SocketError,
+ Errno::EADDRNOTAVAIL,
+ Errno::ENETDOWN,
+ Errno::ENETUNREACH,
+ Gem::Net::HTTP::Persistent::Error,
+ Errno::EHOSTUNREACH,
+ ].freeze
+
+ HTTP_RETRYABLE_ERRORS = [
+ Gem::Timeout::Error,
+ EOFError,
+ Errno::EINVAL,
+ Errno::ECONNRESET,
+ Errno::ETIMEDOUT,
+ Errno::EAGAIN,
+ Gem::Net::HTTPBadResponse,
+ Gem::Net::HTTPHeaderSyntaxError,
+ Gem::Net::ProtocolError,
+ Zlib::BufError,
+ ].freeze
+
attr_reader :connection
attr_reader :redirect_limit
@@ -67,15 +89,19 @@ module Bundler
connection.request(uri, req)
rescue OpenSSL::SSL::SSLError
raise CertificateFailureError.new(uri)
- rescue *HTTP_ERRORS => e
+ rescue *HTTP_NON_RETRYABLE_ERRORS => e
Bundler.ui.trace e
- if e.is_a?(SocketError) || e.message.to_s.include?("host down:")
- raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \
- "connection and try again."
- else
- raise HTTPError, "Network error while fetching #{filtered_uri}" \
+
+ host = uri.host
+ host_port = "#{host}:#{uri.port}"
+ host = host_port if filtered_uri.to_s.include?(host_port)
+ raise NetworkDownError, "Could not reach host #{host}. Check your network " \
+ "connection and try again."
+ rescue *HTTP_RETRYABLE_ERRORS => e
+ Bundler.ui.trace e
+
+ raise HTTPError, "Network error while fetching #{filtered_uri}" \
" (#{e})"
- end
end
private
diff --git a/lib/bundler/fetcher/gem_remote_fetcher.rb b/lib/bundler/fetcher/gem_remote_fetcher.rb
index 3fc7b68263..3c3c1826a1 100644
--- a/lib/bundler/fetcher/gem_remote_fetcher.rb
+++ b/lib/bundler/fetcher/gem_remote_fetcher.rb
@@ -5,6 +5,12 @@ require "rubygems/remote_fetcher"
module Bundler
class Fetcher
class GemRemoteFetcher < Gem::RemoteFetcher
+ def initialize(*)
+ super
+
+ @pool_size = 5
+ end
+
def request(*args)
super do |req|
req.delete("User-Agent") if headers["User-Agent"]
diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb
index 8903abf4a9..5e8eaee6bb 100644
--- a/lib/bundler/friendly_errors.rb
+++ b/lib/bundler/friendly_errors.rb
@@ -102,13 +102,14 @@ module Bundler
def issues_url(exception)
message = exception.message.lines.first.tr(":", " ").chomp
message = message.split("-").first if exception.is_a?(Errno)
- require "cgi/util"
- "https://github.com/rubygems/rubygems/search?q=" \
+ require "cgi/escape"
+ require "cgi/util" unless defined?(CGI::EscapeExt)
+ "https://github.com/ruby/rubygems/search?q=" \
"#{CGI.escape(message)}&type=Issues"
end
def new_issue_url
- "https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md"
+ "https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md"
end
end
diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb
deleted file mode 100644
index ad12bf89a4..0000000000
--- a/lib/bundler/gem_helpers.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-# frozen_string_literal: true
-
-module Bundler
- module GemHelpers
- GENERIC_CACHE = { Gem::Platform::RUBY => Gem::Platform::RUBY } # rubocop:disable Style/MutableConstant
- GENERICS = [
- Gem::Platform::JAVA,
- *Gem::Platform::WINDOWS,
- ].freeze
-
- def generic(p)
- GENERIC_CACHE[p] ||= begin
- found = GENERICS.find do |match|
- p === match
- end
- found || Gem::Platform::RUBY
- end
- end
- module_function :generic
-
- def generic_local_platform
- generic(local_platform)
- end
- module_function :generic_local_platform
-
- def local_platform
- Bundler.local_platform
- end
- module_function :local_platform
-
- def generic_local_platform_is_ruby?
- generic_local_platform == Gem::Platform::RUBY
- end
- module_function :generic_local_platform_is_ruby?
-
- def platform_specificity_match(spec_platform, user_platform)
- spec_platform = Gem::Platform.new(spec_platform)
-
- PlatformMatch.specificity_score(spec_platform, user_platform)
- end
- module_function :platform_specificity_match
-
- def select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
- matching = if force_ruby
- specs.select {|spec| spec.match_platform(Gem::Platform::RUBY) && spec.force_ruby_platform! }
- else
- specs.select {|spec| spec.match_platform(platform) }
- end
-
- if prefer_locked
- locked_originally = matching.select {|spec| spec.is_a?(LazySpecification) }
- return locked_originally if locked_originally.any?
- end
-
- matching
- end
- module_function :select_all_platform_match
-
- def select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
- matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked)
-
- sort_and_filter_best_platform_match(matching, platform)
- end
- module_function :select_best_platform_match
-
- def select_best_local_platform_match(specs, force_ruby: false)
- matching = select_all_platform_match(specs, local_platform, force_ruby: force_ruby).filter_map(&:materialized_for_installation)
-
- sort_best_platform_match(matching, local_platform)
- end
- module_function :select_best_local_platform_match
-
- def sort_and_filter_best_platform_match(matching, platform)
- return matching if matching.one?
-
- exact = matching.select {|spec| spec.platform == platform }
- return exact if exact.any?
-
- sorted_matching = sort_best_platform_match(matching, platform)
- exemplary_spec = sorted_matching.first
-
- sorted_matching.take_while {|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) }
- end
- module_function :sort_and_filter_best_platform_match
-
- def sort_best_platform_match(matching, platform)
- matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) }
- end
- module_function :sort_best_platform_match
-
- class PlatformMatch
- def self.specificity_score(spec_platform, user_platform)
- return -1 if spec_platform == user_platform
- return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
-
- os_match(spec_platform, user_platform) +
- cpu_match(spec_platform, user_platform) * 10 +
- platform_version_match(spec_platform, user_platform) * 100
- end
-
- def self.os_match(spec_platform, user_platform)
- if spec_platform.os == user_platform.os
- 0
- else
- 1
- end
- end
-
- def self.cpu_match(spec_platform, user_platform)
- if spec_platform.cpu == user_platform.cpu
- 0
- elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
- 0
- elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
- 1
- else
- 2
- end
- end
-
- def self.platform_version_match(spec_platform, user_platform)
- if spec_platform.version == user_platform.version
- 0
- elsif spec_platform.version.nil?
- 1
- else
- 2
- end
- end
- end
-
- def same_specificity(platform, spec, exemplary_spec)
- platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform)
- end
- module_function :same_specificity
-
- def same_deps(spec, exemplary_spec)
- same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort
- same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version
- same_runtime_deps && same_metadata_deps
- end
- module_function :same_deps
- end
-end
diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb
deleted file mode 100644
index b22b17a453..0000000000
--- a/lib/bundler/graph.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-# frozen_string_literal: true
-
-require "set"
-module Bundler
- class Graph
- GRAPH_NAME = :Gemfile
-
- def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = [])
- @env = env
- @output_file = output_file
- @show_version = show_version
- @show_requirements = show_requirements
- @output_format = output_format
- @without_groups = without.map(&:to_sym)
-
- @groups = []
- @relations = Hash.new {|h, k| h[k] = Set.new }
- @node_options = {}
- @edge_options = {}
-
- _populate_relations
- end
-
- attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format
-
- def viz
- GraphVizClient.new(self).run
- end
-
- private
-
- def _populate_relations
- parent_dependencies = _groups.values.to_set.flatten
- loop do
- break if parent_dependencies.empty?
-
- tmp = Set.new
- parent_dependencies.each do |dependency|
- child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set
- @relations[dependency.name] += child_dependencies.map(&:name).to_set
- tmp += child_dependencies
-
- @node_options[dependency.name] = _make_label(dependency, :node)
- child_dependencies.each do |c_dependency|
- @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge)
- end
- end
- parent_dependencies = tmp
- end
- end
-
- def _groups
- relations = Hash.new {|h, k| h[k] = Set.new }
- @env.current_dependencies.each do |dependency|
- dependency.groups.each do |group|
- next if @without_groups.include?(group)
-
- relations[group.to_s].add(dependency)
- @relations[group.to_s].add(dependency.name)
-
- @node_options[group.to_s] ||= _make_label(group, :node)
- @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge)
- end
- end
- @groups = relations.keys
- relations
- end
-
- def _make_label(symbol_or_string_or_dependency, element_type)
- case element_type.to_sym
- when :node
- if symbol_or_string_or_dependency.is_a?(Gem::Dependency)
- label = symbol_or_string_or_dependency.name.dup
- label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version
- else
- label = symbol_or_string_or_dependency.to_s
- end
- when :edge
- label = nil
- if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements
- tmp = symbol_or_string_or_dependency.requirements_list.join(", ")
- label = tmp if tmp != ">= 0"
- end
- else
- raise ArgumentError, "2nd argument is invalid"
- end
- label.nil? ? {} : { label: label }
- end
-
- def spec_for_dependency(dependency)
- @env.requested_specs.find {|s| s.name == dependency.name }
- end
-
- class GraphVizClient
- def initialize(graph_instance)
- @graph_name = graph_instance.class::GRAPH_NAME
- @groups = graph_instance.groups
- @relations = graph_instance.relations
- @node_options = graph_instance.node_options
- @edge_options = graph_instance.edge_options
- @output_file = graph_instance.output_file
- @output_format = graph_instance.output_format
- end
-
- def g
- @g ||= ::GraphViz.digraph(@graph_name, concentrate: true, normalize: true, nodesep: 0.55) do |g|
- g.edge[:weight] = 2
- g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif"
- g.edge[:fontsize] = 12
- end
- end
-
- def run
- @groups.each do |group|
- g.add_nodes(
- group, {
- style: "filled",
- fillcolor: "#B9B9D5",
- shape: "box3d",
- fontsize: 16,
- }.merge(@node_options[group])
- )
- end
-
- @relations.each do |parent, children|
- children.each do |child|
- if @groups.include?(parent)
- g.add_nodes(child, { style: "filled", fillcolor: "#B9B9D5" }.merge(@node_options[child]))
- g.add_edges(parent, child, { constraint: false }.merge(@edge_options["#{parent}_#{child}"]))
- else
- g.add_nodes(child, @node_options[child])
- g.add_edges(parent, child, @edge_options["#{parent}_#{child}"])
- end
- end
- end
-
- if @output_format.to_s == "debug"
- $stdout.puts g.output none: String
- Bundler.ui.info "debugging bundle viz..."
- else
- begin
- g.output @output_format.to_sym => "#{@output_file}.#{@output_format}"
- Bundler.ui.info "#{@output_file}.#{@output_format}"
- rescue ArgumentError => e
- warn "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb"
- raise e
- end
- end
- end
- end
- end
-end
diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb
index df46facc88..9aef2dfa12 100644
--- a/lib/bundler/index.rb
+++ b/lib/bundler/index.rb
@@ -46,13 +46,6 @@ module Bundler
true
end
- def search_all(name, &blk)
- return enum_for(:search_all, name) unless blk
- specs_by_name(name).each(&blk)
- @duplicates[name]&.each(&blk)
- @sources.each {|source| source.search_all(name, &blk) }
- end
-
# Search this index's specs, and any source indexes that this index knows
# about, returning all of the results.
def search(query)
@@ -131,6 +124,11 @@ module Bundler
return unless other
other.each do |spec|
if existing = find_by_spec(spec)
+ unless dependencies_eql?(existing, spec)
+ Bundler.ui.warn "Local specification for #{spec.full_name} has different dependencies than the remote gem, ignoring it"
+ next
+ end
+
add_duplicate(existing)
end
add spec
@@ -153,8 +151,8 @@ module Bundler
end
def dependencies_eql?(spec, other_spec)
- deps = spec.dependencies.select {|d| d.type != :development }
- other_deps = other_spec.dependencies.select {|d| d.type != :development }
+ deps = spec.runtime_dependencies
+ other_deps = other_spec.runtime_dependencies
deps.sort == other_deps.sort
end
diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb
index f2f5b22cd3..c861bee149 100644
--- a/lib/bundler/inline.rb
+++ b/lib/bundler/inline.rb
@@ -44,14 +44,16 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty?
old_gemfile = ENV["BUNDLE_GEMFILE"]
+ old_lockfile = ENV["BUNDLE_LOCKFILE"]
Bundler.unbundle_env!
begin
Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir))
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile"
+ Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock"
- Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
+ Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins]
builder = Bundler::Dsl.new
builder.instance_eval(&gemfile)
@@ -94,5 +96,11 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile)
else
ENV["BUNDLE_GEMFILE"] = ""
end
+
+ if old_lockfile
+ ENV["BUNDLE_LOCKFILE"] = old_lockfile
+ else
+ ENV["BUNDLE_LOCKFILE"] = ""
+ end
end
end
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb
index d41740a411..c5fd75431f 100644
--- a/lib/bundler/installer.rb
+++ b/lib/bundler/installer.rb
@@ -7,12 +7,6 @@ require_relative "installer/gem_installer"
module Bundler
class Installer
- class << self
- attr_accessor :ambiguous_gems
-
- Installer.ambiguous_gems = []
- end
-
attr_reader :post_install_messages, :definition
# Begins the installation process for Bundler.
diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb
index 1da91857bd..5c4fa78253 100644
--- a/lib/bundler/installer/gem_installer.rb
+++ b/lib/bundler/installer/gem_installer.rb
@@ -16,7 +16,6 @@ module Bundler
def install_from_spec
post_install_message = install
Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
- generate_executable_stubs
[true, post_install_message]
rescue Bundler::InstallHookError, Bundler::SecurityError, Bundler::APIResponseMismatchError, Bundler::InsecureInstallPathError
raise
@@ -71,15 +70,5 @@ module Bundler
def out_of_space_message
"#{install_error_message}\nYour disk is out of space. Free some space to be able to install your bundle."
end
-
- def generate_executable_stubs
- return if Bundler.feature_flag.forget_cli_options?
- return if Bundler.settings[:inline]
- if Bundler.settings[:bin] && standalone
- installer.generate_standalone_bundler_executable_stubs(spec)
- elsif Bundler.settings[:bin]
- installer.generate_bundler_executable_stubs(spec, force: true)
- end
- end
end
end
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index 061e4bb91e..786dbcae65 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -33,7 +33,7 @@ module Bundler
lazy_spec
end
- def initialize(name, version, platform, source = nil)
+ def initialize(name, version, platform, source = nil, **materialization_options)
@name = name
@version = version
@dependencies = []
@@ -43,6 +43,7 @@ module Bundler
@original_source = source
@source = source
+ @materialization_options = materialization_options
@force_ruby_platform = default_force_ruby_platform
@most_specific_locked_platform = nil
@@ -137,24 +138,16 @@ module Bundler
source.local!
if use_exact_resolved_specifications?
- materialize(self) do |matching_specs|
- choose_compatible(matching_specs)
- end
- else
- materialize([name, version]) do |matching_specs|
- target_platform = source.is_a?(Source::Path) ? platform : local_platform
-
- installable_candidates = GemHelpers.select_best_platform_match(matching_specs, target_platform)
-
- specification = choose_compatible(installable_candidates, fallback_to_non_installable: false)
- return specification unless specification.nil?
+ spec = materialize(self) {|specs| choose_compatible(specs, fallback_to_non_installable: false) }
+ return spec if spec
- if target_platform != platform
- installable_candidates = GemHelpers.select_best_platform_match(matching_specs, platform)
- end
-
- choose_compatible(installable_candidates)
+ # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant
+ # In non-frozen mode, return nil to trigger re-resolution and lockfile update
+ if Bundler.frozen_bundle?
+ materialize([name, version]) {|specs| resolve_best_platform(specs) }
end
+ else
+ materialize([name, version]) {|specs| resolve_best_platform(specs) }
end
end
@@ -189,8 +182,41 @@ module Bundler
!source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform?
end
+ # Try platforms in order of preference until finding a compatible spec.
+ # Used for legacy lockfiles and as a fallback when the exact locked spec
+ # is incompatible. Falls back to frozen bundle behavior if none match.
+ def resolve_best_platform(specs)
+ find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs)
+ end
+
+ def find_compatible_platform_spec(specs)
+ candidate_platforms.each do |plat|
+ candidates = MatchPlatform.select_best_platform_match(specs, plat)
+ spec = choose_compatible(candidates, fallback_to_non_installable: false)
+ return spec if spec
+ end
+ nil
+ end
+
+ # Platforms to try in order of preference. Ruby platform is last since it
+ # requires compilation, but works when precompiled gems are incompatible.
+ def candidate_platforms
+ target = source.is_a?(Source::Path) ? platform : Bundler.local_platform
+ [target, platform, Gem::Platform::RUBY].uniq
+ end
+
+ # In frozen mode, accept any candidate. Will error at install time.
+ # When target differs from locked platform, prefer locked platform's candidates
+ # to preserve lockfile integrity.
+ def frozen_bundle_fallback(specs)
+ target = source.is_a?(Source::Path) ? platform : Bundler.local_platform
+ fallback_platform = target == platform ? target : platform
+ candidates = MatchPlatform.select_best_platform_match(specs, fallback_platform)
+ choose_compatible(candidates)
+ end
+
def ruby_platform_materializes_to_ruby_platform?
- generic_platform = generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY
+ generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY
(most_specific_locked_platform != generic_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform]
end
@@ -226,12 +252,13 @@ module Bundler
# Validate dependencies of this locked spec are consistent with dependencies
# of the actual spec that was materialized.
#
- # Note that we don't validate dependencies of locally installed gems but
+ # Note that unless we are in strict mode (which we set during installation)
+ # we don't validate dependencies of locally installed gems but
# accept what's in the lockfile instead for performance, since loading
# dependencies of locally installed gems would mean evaluating all gemspecs,
# which would affect `bundler/setup` performance.
def validate_dependencies(spec)
- if spec.is_a?(StubSpecification)
+ if !@materialization_options[:strict] && spec.is_a?(StubSpecification)
spec.dependencies = dependencies
else
if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort
diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb
index 904552fa0c..6b6cf9d9ea 100644
--- a/lib/bundler/lockfile_generator.rb
+++ b/lib/bundler/lockfile_generator.rb
@@ -95,7 +95,7 @@ module Bundler
out << " #{key}: #{val}\n"
end
when String
- out << " #{value}\n"
+ out << " #{value}\n"
else
raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile"
end
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
index 7d57ec724d..ac0ce1ef3d 100644
--- a/lib/bundler/lockfile_parser.rb
+++ b/lib/bundler/lockfile_parser.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
+require_relative "shared_helpers"
+
module Bundler
class LockfileParser
- include GemHelpers
-
class Position
attr_reader :line, :column
def initialize(line, column)
@@ -94,7 +94,7 @@ module Bundler
lockfile_contents.split(BUNDLED).last.strip
end
- def initialize(lockfile)
+ def initialize(lockfile, strict: false)
@platforms = []
@sources = []
@dependencies = {}
@@ -106,6 +106,7 @@ module Bundler
"Gemfile.lock"
end
@pos = Position.new(1, 1)
+ @strict = strict
if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \
@@ -139,8 +140,13 @@ module Bundler
end
@pos.advance!(line)
end
+
+ if @platforms.include?(Gem::Platform::X64_MINGW_LEGACY)
+ SharedHelpers.feature_deprecated!("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.")
+ end
+
@most_specific_locked_platform = @platforms.min_by do |bundle_platform|
- platform_specificity_match(bundle_platform, local_platform)
+ Gem::Platform.platform_specificity_match(bundle_platform, Bundler.local_platform)
end
@specs = @specs.values.sort_by!(&:full_name).each do |spec|
spec.most_specific_locked_platform = @most_specific_locked_platform
@@ -271,7 +277,7 @@ module Bundler
version = Gem::Version.new(version)
platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
- @current_spec = LazySpecification.new(name, version, platform, @current_source)
+ @current_spec = LazySpecification.new(name, version, platform, @current_source, strict: @strict)
@current_source.add_dependency_names(name)
@specs[@current_spec.full_name] = @current_spec
diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1
index 176e8b117e..4474969db6 100644
--- a/lib/bundler/man/bundle-add.1
+++ b/lib/bundler/man/bundle-add.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-ADD" "1" "March 2025" ""
+.TH "BUNDLE\-ADD" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1
index 146c1c021e..b8c153696b 100644
--- a/lib/bundler/man/bundle-binstubs.1
+++ b/lib/bundler/man/bundle-binstubs.1
@@ -1,24 +1,21 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-BINSTUBS" "1" "March 2025" ""
+.TH "BUNDLE\-BINSTUBS" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
.SH "SYNOPSIS"
-\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-path PATH] [\-\-standalone] [\-\-all\-platforms]
+\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-standalone] [\-\-all\-platforms]
.SH "DESCRIPTION"
Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into \fBbin/\fR\. Binstubs are a shortcut\-or alternative\- to always using \fBbundle exec\fR\. This gives you a file that can be run directly, and one that will always run the correct gem version used by the application\.
.P
For example, if you run \fBbundle binstubs rspec\-core\fR, Bundler will create the file \fBbin/rspec\fR\. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec\.
.P
-This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the \fB\-\-path\fR directory if one has been set\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\.
+This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the directory specified by \fBbin\fR setting if it has been configured\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\.
.SH "OPTIONS"
.TP
\fB\-\-force\fR
Overwrite existing binstubs if they exist\.
.TP
-\fB\-\-path[=PATH]\fR
-The location to install the specified binstubs to\. This defaults to \fBbin\fR\.
-.TP
\fB\-\-standalone\fR
Makes binstubs that can work without depending on Rubygems or Bundler at runtime\.
.TP
diff --git a/lib/bundler/man/bundle-binstubs.1.ronn b/lib/bundler/man/bundle-binstubs.1.ronn
index ec2cfd80db..cbe5983f4d 100644
--- a/lib/bundler/man/bundle-binstubs.1.ronn
+++ b/lib/bundler/man/bundle-binstubs.1.ronn
@@ -3,7 +3,7 @@ bundle-binstubs(1) -- Install the binstubs of the listed gems
## SYNOPSIS
-`bundle binstubs` <GEM_NAME> [--force] [--path PATH] [--standalone] [--all-platforms]
+`bundle binstubs` <GEM_NAME> [--force] [--standalone] [--all-platforms]
## DESCRIPTION
@@ -19,17 +19,15 @@ the file `bin/rspec`. That file will contain enough code to load Bundler,
tell it to load the bundled gems, and then run rspec.
This command generates binstubs for executables in `GEM_NAME`.
-Binstubs are put into `bin`, or the `--path` directory if one has been set.
-Calling binstubs with [GEM [GEM]] will create binstubs for all given gems.
+Binstubs are put into `bin`, or the directory specified by `bin` setting if it
+has been configured. Calling binstubs with [GEM [GEM]] will create binstubs for
+all given gems.
## OPTIONS
* `--force`:
Overwrite existing binstubs if they exist.
-* `--path[=PATH]`:
- The location to install the specified binstubs to. This defaults to `bin`.
-
* `--standalone`:
Makes binstubs that can work without depending on Rubygems or Bundler at
runtime.
diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1
index 64e806029b..c1dafbf070 100644
--- a/lib/bundler/man/bundle-cache.1
+++ b/lib/bundler/man/bundle-cache.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CACHE" "1" "March 2025" ""
+.TH "BUNDLE\-CACHE" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
.SH "SYNOPSIS"
@@ -11,9 +11,6 @@ alias: \fBpackage\fR, \fBpack\fR
Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR, use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\.
.SH "OPTIONS"
.TP
-\fB\-\-all\fR
-Include all sources (including path and git)\.
-.TP
\fB\-\-all\-platforms\fR
Include gems for all platforms present in the lockfile, not only the current one\.
.TP
@@ -26,19 +23,10 @@ Use the specified gemfile instead of Gemfile\.
\fB\-\-no\-install\fR
Don't install the gems, only update the cache\.
.TP
-\fB\-\-no\-prune\fR
-Don't remove stale gems from the cache\.
-.TP
-\fB\-\-path=PATH\fR
-Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME)\.
-.TP
\fB\-\-quiet\fR
Only output warnings and errors\.
-.TP
-\fB\-\-frozen\fR
-Do not allow the Gemfile\.lock to be updated after this bundle cache operation's install\.
.SH "GIT AND PATH GEMS"
-The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This needs to be explicitly enabled via the \fB\-\-all\fR option\. Once used, the \fB\-\-all\fR option will be remembered\.
+The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This can be disabled setting \fBcache_all\fR to false\.
.SH "SUPPORT FOR MULTIPLE PLATFORMS"
When using gems that have different packages for different platforms, Bundler supports caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\.
.SH "REMOTE FETCHING"
diff --git a/lib/bundler/man/bundle-cache.1.ronn b/lib/bundler/man/bundle-cache.1.ronn
index ffcc07c7b1..51846c96b4 100644
--- a/lib/bundler/man/bundle-cache.1.ronn
+++ b/lib/bundler/man/bundle-cache.1.ronn
@@ -15,9 +15,6 @@ use the gems in the cache in preference to the ones on `rubygems.org`.
## OPTIONS
-* `--all`:
- Include all sources (including path and git).
-
* `--all-platforms`:
Include gems for all platforms present in the lockfile, not only the current one.
@@ -30,23 +27,13 @@ use the gems in the cache in preference to the ones on `rubygems.org`.
* `--no-install`:
Don't install the gems, only update the cache.
-* `--no-prune`:
- Don't remove stale gems from the cache.
-
-* `--path=PATH`:
- Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).
-
* `--quiet`:
Only output warnings and errors.
-* `--frozen`:
- Do not allow the Gemfile.lock to be updated after this bundle cache operation's install.
-
## GIT AND PATH GEMS
The `bundle cache` command can also package `:git` and `:path` dependencies
-besides .gem files. This needs to be explicitly enabled via the `--all` option.
-Once used, the `--all` option will be remembered.
+besides .gem files. This can be disabled setting `cache_all` to false.
## SUPPORT FOR MULTIPLE PLATFORMS
diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1
index bf16a22461..f83af1eb55 100644
--- a/lib/bundler/man/bundle-check.1
+++ b/lib/bundler/man/bundle-check.1
@@ -1,10 +1,10 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CHECK" "1" "March 2025" ""
+.TH "BUNDLE\-CHECK" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
.SH "SYNOPSIS"
-\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] [\-\-path=PATH]
+\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE]
.SH "DESCRIPTION"
\fBcheck\fR searches the local machine for each of the gems requested in the Gemfile\. If all gems are found, Bundler prints a success message and exits with a status of 0\.
.P
@@ -18,7 +18,4 @@ Locks the [\fBGemfile(5)\fR][Gemfile(5)] before running the command\.
.TP
\fB\-\-gemfile=GEMFILE\fR
Use the specified gemfile instead of the [\fBGemfile(5)\fR][Gemfile(5)]\.
-.TP
-\fB\-\-path=PATH\fR
-Specify a different path than the system default (\fB$BUNDLE_PATH\fR or \fB$GEM_HOME\fR)\. Bundler will remember this value for future installs on this machine\.
diff --git a/lib/bundler/man/bundle-check.1.ronn b/lib/bundler/man/bundle-check.1.ronn
index 23bb6f3eb8..92589159c9 100644
--- a/lib/bundler/man/bundle-check.1.ronn
+++ b/lib/bundler/man/bundle-check.1.ronn
@@ -5,7 +5,6 @@ bundle-check(1) -- Verifies if dependencies are satisfied by installed gems
`bundle check` [--dry-run]
[--gemfile=FILE]
- [--path=PATH]
## DESCRIPTION
@@ -25,7 +24,3 @@ installed on the local machine, if they satisfy the requirements.
* `--gemfile=GEMFILE`:
Use the specified gemfile instead of the [`Gemfile(5)`][Gemfile(5)].
-
-* `--path=PATH`:
- Specify a different path than the system default (`$BUNDLE_PATH` or `$GEM_HOME`).
- Bundler will remember this value for future installs on this machine.
diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1
index 83f7661482..c4d148c5df 100644
--- a/lib/bundler/man/bundle-clean.1
+++ b/lib/bundler/man/bundle-clean.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CLEAN" "1" "March 2025" ""
+.TH "BUNDLE\-CLEAN" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1
index 190177eb37..05c13e2d0f 100644
--- a/lib/bundler/man/bundle-config.1
+++ b/lib/bundler/man/bundle-config.1
@@ -1,16 +1,16 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CONFIG" "1" "March 2025" ""
+.TH "BUNDLE\-CONFIG" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-config\fR \- Set bundler configuration options
.SH "SYNOPSIS"
-\fBbundle config\fR list
+\fBbundle config\fR [list]
.br
-\fBbundle config\fR [get] NAME
+\fBbundle config\fR [get [\-\-local|\-\-global]] NAME
.br
-\fBbundle config\fR [set] NAME VALUE
+\fBbundle config\fR [set [\-\-local|\-\-global]] NAME VALUE
.br
-\fBbundle config\fR unset NAME
+\fBbundle config\fR unset [\-\-local|\-\-global] NAME
.SH "DESCRIPTION"
This command allows you to interact with Bundler's configuration system\.
.P
@@ -25,47 +25,203 @@ Global config (\fB~/\.bundle/config\fR)
Bundler default config
.IP "" 0
.P
+Executing \fBbundle\fR with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
+.SH "SUB\-COMMANDS"
+.SS "list (default command)"
Executing \fBbundle config list\fR will print a list of all bundler configuration for the current bundle, and where that configuration was set\.
+.SS "get"
+Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and all locations where it was set\.
.P
-Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and where it was set\.
-.P
-Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\. See \fB\-\-local\fR and \fB\-\-global\fR options below\.
+\fBOPTIONS\fR
+.TP
+\fB\-\-local\fR
+Get configuration from configuration file for the local application, namely, \fB<project_root>/\.bundle/config\fR, or \fB$BUNDLE_APP_CONFIG/config\fR if \fBBUNDLE_APP_CONFIG\fR is set\.
+.TP
+\fB\-\-global\fR
+Get configuration from configuration file global to all bundles executed as the current user, namely, from \fB~/\.bundle/config\fR\.
+.SS "set"
+Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\.
.P
+\fBOPTIONS\fR
+.TP
+\fB\-\-local\fR
Executing \fBbundle config set \-\-local <name> <value>\fR will set that configuration in the directory for the local application\. The configuration will be stored in \fB<project_root>/\.bundle/config\fR\. If \fBBUNDLE_APP_CONFIG\fR is set, the configuration will be stored in \fB$BUNDLE_APP_CONFIG/config\fR\.
-.P
+.TP
+\fB\-\-global\fR
Executing \fBbundle config set \-\-global <name> <value>\fR will set that configuration to the value specified for all bundles executed as the current user\. The configuration will be stored in \fB~/\.bundle/config\fR\. If \fIname\fR already is set, \fIname\fR will be overridden and user will be warned\.
-.P
+.SS "unset"
Executing \fBbundle config unset <name>\fR will delete the configuration in both local and global sources\.
.P
-Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\.
-.P
+\fBOPTIONS\fR
+.TP
+\fB\-\-local\fR
Executing \fBbundle config unset \-\-local <name>\fR will delete the configuration only from the local application\.
+.TP
+\fB\-\-global\fR
+Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\.
+.SH "CONFIGURATION KEYS"
+Configuration keys in bundler have two forms: the canonical form and the environment variable form\.
.P
-Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
-.SH "REMEMBERING OPTIONS"
-Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application's configuration (normally, \fB\./\.bundle/config\fR)\.
+For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\.
.P
-However, this will be changed in bundler 3, so it's better not to rely on this behavior\. If these options must be remembered, it's better to set them using \fBbundle config\fR (e\.g\., \fBbundle config set \-\-local path foo\fR)\.
+The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\.
.P
-The options that can be configured are:
+Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\.
+.SH "LIST OF AVAILABLE KEYS"
+The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\.
+.TP
+\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR)
+Automatically run \fBbundle install\fR when gems are missing\.
+.TP
+\fBbin\fR (\fBBUNDLE_BIN\fR)
+If configured, \fBbundle binstubs\fR will install executables from gems in the bundle to the specified directory\. Otherwise it will create them in a \fBbin\fR directory relative to the Gemfile directory\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, \fBbundle binstubs\fR will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
+.TP
+\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR)
+Cache all gems, including path and git gems\. This needs to be explicitly before bundler 4, but will be the default on bundler 4\.
+.TP
+\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR)
+Cache gems for all platforms\.
+.TP
+\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR)
+The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
+.TP
+\fBclean\fR (\fBBUNDLE_CLEAN\fR)
+Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 4, as long as \fBpath\fR is not explicitly configured\.
+.TP
+\fBconsole\fR (\fBBUNDLE_CONSOLE\fR)
+The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
+.TP
+\fBdefault_cli_command\fR (\fBBUNDLE_DEFAULT_CLI_COMMAND\fR)
+The command that running \fBbundle\fR without arguments should run\. Defaults to \fBcli_help\fR since Bundler 4, but can also be \fBinstall\fR which was the previous default\.
+.TP
+\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR)
+Equivalent to setting \fBfrozen\fR to \fBtrue\fR and \fBpath\fR to \fBvendor/bundle\fR\.
+.TP
+\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR)
+Allow installing gems even if they do not match the checksum provided by RubyGems\.
+.TP
+\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR)
+Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
+.TP
+\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR)
+Allow Bundler to use a local git override without a branch specified in the Gemfile\.
+.TP
+\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR)
+Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
+.TP
+\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR)
+Stop Bundler from accessing gems installed to RubyGems' normal location\.
+.TP
+\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR)
+Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
+.TP
+\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR)
+Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
+.TP
+\fBfrozen\fR (\fBBUNDLE_FROZEN\fR)
+Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\.
+.TP
+\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR)
+Sets a GitHub username or organization to be used in the \fBREADME\fR and \fB\.gemspec\fR files when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\.
+.TP
+\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR)
+Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
+.TP
+\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR)
+The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
+.TP
+\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR)
+Whether Bundler should cache all gems and compiled extensions globally, rather than locally to the configured installation path\.
+.TP
+\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR)
+When set, no funding requests will be printed\.
+.TP
+\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR)
+When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
+.TP
+\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR)
+Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
+.TP
+\fBjobs\fR (\fBBUNDLE_JOBS\fR)
+The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
+.TP
+\fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR)
+The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\.
+.TP
+\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR)
+Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\.
+.TP
+\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR)
+Whether \fBbundle package\fR should skip installing gems\.
+.TP
+\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR)
+Whether Bundler should leave outdated gems unpruned when caching\.
+.TP
+\fBonly\fR (\fBBUNDLE_ONLY\fR)
+A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, because they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\.
+.TP
+\fBpath\fR (\fBBUNDLE_PATH\fR)
+The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 4, and to the default system path (\fBGem\.dir\fR) before Bundler 4\. That means that before Bundler 4, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 4\.
+.TP
+\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR)
+Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
+.TP
+\fBplugins\fR (\fBBUNDLE_PLUGINS\fR)
+Enable Bundler's experimental plugin system\.
+.TP
+\fBprefer_patch\fR (BUNDLE_PREFER_PATCH)
+Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
+.TP
+\fBredirect\fR (\fBBUNDLE_REDIRECT\fR)
+The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
+.TP
+\fBretry\fR (\fBBUNDLE_RETRY\fR)
+The number of times to retry failed network requests\. Defaults to \fB3\fR\.
+.TP
+\fBshebang\fR (\fBBUNDLE_SHEBANG\fR)
+The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
+.TP
+\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR)
+Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\.
+.TP
+\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR)
+Silence the warning Bundler prints when installing gems as root\.
+.TP
+\fBsimulate_version\fR (\fBBUNDLE_SIMULATE_VERSION\fR)
+The virtual version Bundler should use for activating feature flags\. Can be used to simulate all the new functionality that will be enabled in a future major version\.
+.TP
+\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR)
+Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
+.TP
+\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR)
+Path to a designated file containing a X\.509 client certificate and key in PEM format\.
+.TP
+\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR)
+The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
+.TP
+\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR)
+The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
+.TP
+\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR)
+The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
.TP
-\fBbin\fR
-Creates a directory (defaults to \fB~/bin\fR) and place any executables from the gem there\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
+\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR)
+Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
.TP
-\fBdeployment\fR
-In deployment mode, Bundler will 'roll\-out' the bundle for \fBproduction\fR use\. Please check carefully if you want to have this option enabled in \fBdevelopment\fR or \fBtest\fR environments\.
+\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR)
+The custom user agent fragment Bundler includes in API requests\.
.TP
-\fBonly\fR
-A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, cause they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\.
+\fBverbose\fR (\fBBUNDLE_VERBOSE\fR)
+Whether Bundler should print verbose output\. Defaults to \fBfalse\fR, unless the \fB\-\-verbose\fR CLI flag is used\.
.TP
-\fBpath\fR
-The location to install the specified gems to\. This defaults to Rubygems' setting\. Bundler shares this location with Rubygems, \fBgem install \|\.\|\.\|\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \|\.\|\.\|\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\.
+\fBversion\fR (\fBBUNDLE_VERSION\fR)
+The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\.
.TP
-\fBwithout\fR
-A space\-separated or \fB:\fR\-separated list of groups referencing gems to skip during installation\.
+\fBwith\fR (\fBBUNDLE_WITH\fR)
+A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\.
.TP
-\fBwith\fR
-A space\-separated or \fB:\fR\-separated list of \fBoptional\fR groups referencing gems to include during installation\.
+\fBwithout\fR (\fBBUNDLE_WITHOUT\fR)
+A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\.
.SH "BUILD OPTIONS"
You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\.
.P
@@ -84,123 +240,6 @@ bundle config set \-\-global build\.mysql \-\-with\-mysql\-config=/usr/local/mys
.IP "" 0
.P
After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\.
-.SH "CONFIGURATION KEYS"
-Configuration keys in bundler have two forms: the canonical form and the environment variable form\.
-.P
-For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\.
-.P
-The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\.
-.P
-Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\.
-.SH "LIST OF AVAILABLE KEYS"
-The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\.
-.IP "\(bu" 4
-\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\.
-.IP "\(bu" 4
-\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\.
-.IP "\(bu" 4
-\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\.
-.IP "\(bu" 4
-\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\.
-.IP "\(bu" 4
-\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\.
-.IP "\(bu" 4
-\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\.
-.IP "\(bu" 4
-\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
-.IP "\(bu" 4
-\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\.
-.IP "\(bu" 4
-\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
-.IP "\(bu" 4
-\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\.
-.IP "\(bu" 4
-\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\.
-.IP "\(bu" 4
-\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\.
-.IP "\(bu" 4
-\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
-.IP "\(bu" 4
-\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\.
-.IP "\(bu" 4
-\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
-.IP "\(bu" 4
-\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems' normal location\.
-.IP "\(bu" 4
-\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
-.IP "\(bu" 4
-\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
-.IP "\(bu" 4
-\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\.
-.IP "\(bu" 4
-\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\.
-.IP "\(bu" 4
-\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
-.IP "\(bu" 4
-\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
-.IP "\(bu" 4
-\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\.
-.IP "\(bu" 4
-\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\.
-.IP "\(bu" 4
-\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
-.IP "\(bu" 4
-\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
-.IP "\(bu" 4
-\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
-.IP "\(bu" 4
-\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR): Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\.
-.IP "\(bu" 4
-\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.
-.IP "\(bu" 4
-\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\.
-.IP "\(bu" 4
-\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\.
-.IP "\(bu" 4
-\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\.
-.IP "\(bu" 4
-\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
-.IP "\(bu" 4
-\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\.
-.IP "\(bu" 4
-\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\.
-.IP "\(bu" 4
-\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
-.IP "\(bu" 4
-\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\.
-.IP "\(bu" 4
-\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
-.IP "\(bu" 4
-\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\.
-.IP "\(bu" 4
-\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\.
-.IP "\(bu" 4
-\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
-.IP "\(bu" 4
-\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\.
-.IP "\(bu" 4
-\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\.
-.IP "\(bu" 4
-\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
-.IP "\(bu" 4
-\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\.
-.IP "\(bu" 4
-\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
-.IP "\(bu" 4
-\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
-.IP "\(bu" 4
-\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
-.IP "\(bu" 4
-\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
-.IP "\(bu" 4
-\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\.
-.IP "\(bu" 4
-\fBversion\fR (\fBBUNDLE_VERSION\fR): The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\.
-.IP "\(bu" 4
-\fBwith\fR (\fBBUNDLE_WITH\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\.
-.IP "\(bu" 4
-\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\.
-.IP "" 0
.SH "LOCAL GIT REPOS"
Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override:
.IP "" 4
@@ -276,7 +315,7 @@ export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
For gems with a git source with HTTP(S) URL you can specify credentials like so:
.IP "" 4
.nf
-bundle config set \-\-global https://github\.com/rubygems/rubygems\.git username:password
+bundle config set \-\-global https://github\.com/ruby/rubygems\.git username:password
.fi
.IP "" 0
.P
diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn
index 44c31cd10d..7c34f1d1af 100644
--- a/lib/bundler/man/bundle-config.1.ronn
+++ b/lib/bundler/man/bundle-config.1.ronn
@@ -3,10 +3,10 @@ bundle-config(1) -- Set bundler configuration options
## SYNOPSIS
-`bundle config` list<br>
-`bundle config` [get] NAME<br>
-`bundle config` [set] NAME VALUE<br>
-`bundle config` unset NAME
+`bundle config` [list]<br>
+`bundle config` [get [--local|--global]] NAME<br>
+`bundle config` [set [--local|--global]] NAME VALUE<br>
+`bundle config` unset [--local|--global] NAME
## DESCRIPTION
@@ -19,103 +19,67 @@ Bundler loads configuration settings in this order:
3. Global config (`~/.bundle/config`)
4. Bundler default config
-Executing `bundle config list` will print a list of all bundler
-configuration for the current bundle, and where that configuration
-was set.
-
-Executing `bundle config get <name>` will print the value of that configuration
-setting, and where it was set.
-
-Executing `bundle config set <name> <value>` defaults to setting `local`
-configuration if executing from within a local application, otherwise it will
-set `global` configuration. See `--local` and `--global` options below.
-
-Executing `bundle config set --local <name> <value>` will set that configuration
-in the directory for the local application. The configuration will be stored in
-`<project_root>/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration
-will be stored in `$BUNDLE_APP_CONFIG/config`.
-
-Executing `bundle config set --global <name> <value>` will set that
-configuration to the value specified for all bundles executed as the current
-user. The configuration will be stored in `~/.bundle/config`. If <name> already
-is set, <name> will be overridden and user will be warned.
-
-Executing `bundle config unset <name>` will delete the configuration in both
-local and global sources.
-
-Executing `bundle config unset --global <name>` will delete the configuration
-only from the user configuration.
-
-Executing `bundle config unset --local <name>` will delete the configuration
-only from the local application.
-
-Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will
+Executing `bundle` with the `BUNDLE_IGNORE_CONFIG` environment variable set will
cause it to ignore all configuration.
-## REMEMBERING OPTIONS
+## SUB-COMMANDS
-Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or
-`--without production`, are remembered between commands and saved to your local
-application's configuration (normally, `./.bundle/config`).
+### list (default command)
-However, this will be changed in bundler 3, so it's better not to rely on this
-behavior. If these options must be remembered, it's better to set them using
-`bundle config` (e.g., `bundle config set --local path foo`).
+Executing `bundle config list` will print a list of all bundler
+configuration for the current bundle, and where that configuration
+was set.
-The options that can be configured are:
+### get
-* `bin`:
- Creates a directory (defaults to `~/bin`) and place any executables from the
- gem there. These executables run in Bundler's context. If used, you might add
- this directory to your environment's `PATH` variable. For instance, if the
- `rails` gem comes with a `rails` executable, this flag will create a
- `bin/rails` executable that ensures that all referred dependencies will be
- resolved using the bundled gems.
+Executing `bundle config get <name>` will print the value of that configuration
+setting, and all locations where it was set.
-* `deployment`:
- In deployment mode, Bundler will 'roll-out' the bundle for
- `production` use. Please check carefully if you want to have this option
- enabled in `development` or `test` environments.
+**OPTIONS**
-* `only`:
- A space-separated list of groups to install only gems of the specified groups.
- Please check carefully if you want to install also gems without a group, cause
- they get put inside `default` group. For example `only test:default` will install
- all gems specified in test group and without one.
+* `--local`:
+ Get configuration from configuration file for the local application, namely,
+ `<project_root>/.bundle/config`, or `$BUNDLE_APP_CONFIG/config` if
+ `BUNDLE_APP_CONFIG` is set.
-* `path`:
- The location to install the specified gems to. This defaults to Rubygems'
- setting. Bundler shares this location with Rubygems, `gem install ...` will
- have gem installed there, too. Therefore, gems installed without a
- `--path ...` setting will show up by calling `gem list`. Accordingly, gems
- installed to other locations will not get listed.
+* `--global`:
+ Get configuration from configuration file global to all bundles executed as
+ the current user, namely, from `~/.bundle/config`.
-* `without`:
- A space-separated or `:`-separated list of groups referencing gems to skip during
- installation.
+### set
-* `with`:
- A space-separated or `:`-separated list of **optional** groups referencing gems to
- include during installation.
+Executing `bundle config set <name> <value>` defaults to setting `local`
+configuration if executing from within a local application, otherwise it will
+set `global` configuration.
-## BUILD OPTIONS
+**OPTIONS**
-You can use `bundle config` to give Bundler the flags to pass to the gem
-installer every time bundler tries to install a particular gem.
+* `--local`:
+ Executing `bundle config set --local <name> <value>` will set that configuration
+ in the directory for the local application. The configuration will be stored in
+ `<project_root>/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration
+ will be stored in `$BUNDLE_APP_CONFIG/config`.
-A very common example, the `mysql` gem, requires Snow Leopard users to
-pass configuration flags to `gem install` to specify where to find the
-`mysql_config` executable.
+* `--global`:
+ Executing `bundle config set --global <name> <value>` will set that
+ configuration to the value specified for all bundles executed as the current
+ user. The configuration will be stored in `~/.bundle/config`. If <name> already
+ is set, <name> will be overridden and user will be warned.
- gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
+### unset
-Since the specific location of that executable can change from machine
-to machine, you can specify these flags on a per-machine basis.
+Executing `bundle config unset <name>` will delete the configuration in both
+local and global sources.
- bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config
+**OPTIONS**
-After running this command, every time bundler needs to install the
-`mysql` gem, it will pass along the flags you specified.
+* `--local`:
+ Executing `bundle config unset --local <name>` will delete the configuration
+ only from the local application.
+
+* `--global`:
+ Executing `bundle config unset --global <name>` will delete the configuration
+ only from the user configuration.
## CONFIGURATION KEYS
@@ -142,19 +106,20 @@ the environment variable `BUNDLE_LOCAL__RACK`.
The following is a list of all configuration keys and their purpose. You can
learn more about their operation in [bundle install(1)](bundle-install.1.html).
-* `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`):
- Allow Bundler to use cached data when installing without network access.
-* `auto_clean_without_path` (`BUNDLE_AUTO_CLEAN_WITHOUT_PATH`):
- Automatically run `bundle clean` after installing when an explicit `path`
- has not been set and Bundler is not installing into the system gems.
* `auto_install` (`BUNDLE_AUTO_INSTALL`):
Automatically run `bundle install` when gems are missing.
* `bin` (`BUNDLE_BIN`):
- Install executables from gems in the bundle to the specified directory.
- Defaults to `false`.
+ If configured, `bundle binstubs` will install executables from gems in the
+ bundle to the specified directory. Otherwise it will create them in a `bin`
+ directory relative to the Gemfile directory. These executables run in
+ Bundler's context. If used, you might add this directory to your
+ environment's `PATH` variable. For instance, if the `rails` gem comes with a
+ `rails` executable, `bundle binstubs` will create a `bin/rails` executable
+ that ensures that all referred dependencies will be resolved using the
+ bundled gems.
* `cache_all` (`BUNDLE_CACHE_ALL`):
Cache all gems, including path and git gems. This needs to be explicitly
- configured on bundler 1 and bundler 2, but will be the default on bundler 3.
+ before bundler 4, but will be the default on bundler 4.
* `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`):
Cache gems for all platforms.
* `cache_path` (`BUNDLE_CACHE_PATH`):
@@ -163,15 +128,16 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
Defaults to `vendor/cache`.
* `clean` (`BUNDLE_CLEAN`):
Whether Bundler should run `bundle clean` automatically after
- `bundle install`.
+ `bundle install`. Defaults to `true` in Bundler 4, as long as `path` is not
+ explicitly configured.
* `console` (`BUNDLE_CONSOLE`):
The console that `bundle console` starts. Defaults to `irb`.
-* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`):
- Whether a `bundle install` without an explicit `--path` argument defaults
- to installing gems in `.bundle`.
+* `default_cli_command` (`BUNDLE_DEFAULT_CLI_COMMAND`):
+ The command that running `bundle` without arguments should run. Defaults to
+ `cli_help` since Bundler 4, but can also be `install` which was the previous
+ default.
* `deployment` (`BUNDLE_DEPLOYMENT`):
- Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the
- lockfile has not been updated, running Bundler commands will be blocked.
+ Equivalent to setting `frozen` to `true` and `path` to `vendor/bundle`.
* `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`):
Allow installing gems even if they do not match the checksum provided by
RubyGems.
@@ -193,12 +159,13 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
Ignore the current machine's platform and install only `ruby` platform gems.
As a result, gems with native extensions will be compiled from source.
* `frozen` (`BUNDLE_FROZEN`):
- Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the
- lockfile has not been updated, running Bundler commands will be blocked.
- Defaults to `true` when `--deployment` is used.
+ Disallow any automatic changes to `Gemfile.lock`. Bundler commands will
+ be blocked unless the lockfile can be installed exactly as written.
+ Usually this will happen when changing the `Gemfile` manually and forgetting
+ to update the lockfile through `bundle lock` or `bundle install`.
* `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`):
- Sets a GitHub username or organization to be used in `README` file when you
- create a new gem via `bundle gem` command. It can be overridden by passing an
+ Sets a GitHub username or organization to be used in the `README` and `.gemspec` files
+ when you create a new gem via `bundle gem` command. It can be overridden by passing an
explicit `--github-username` flag to `bundle gem`.
* `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`):
Sets the `--key` parameter for `gem push` when using the `rake release`
@@ -210,8 +177,8 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
will search up from the current working directory until it finds a
`Gemfile`.
* `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`):
- Whether Bundler should cache all gems globally, rather than locally to the
- installing Ruby installation.
+ Whether Bundler should cache all gems and compiled extensions globally,
+ rather than locally to the configured installation path.
* `ignore_funding_requests` (`BUNDLE_IGNORE_FUNDING_REQUESTS`):
When set, no funding requests will be printed.
* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`):
@@ -222,36 +189,41 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
* `jobs` (`BUNDLE_JOBS`):
The number of gems Bundler can install in parallel. Defaults to the number of
available processors.
+* `lockfile` (`BUNDLE_LOCKFILE`):
+ The path to the lockfile that bundler should use. By default, Bundler adds
+ `.lock` to the end of the `gemfile` entry. Can be set to `false` in the
+ Gemfile to disable lockfile creation entirely (see gemfile(5)).
* `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`):
- Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources.
+ Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true.
* `no_install` (`BUNDLE_NO_INSTALL`):
Whether `bundle package` should skip installing gems.
* `no_prune` (`BUNDLE_NO_PRUNE`):
Whether Bundler should leave outdated gems unpruned when caching.
* `only` (`BUNDLE_ONLY`):
A space-separated list of groups to install only gems of the specified groups.
+ Please check carefully if you want to install also gems without a group, because
+ they get put inside `default` group. For example `only test:default` will install
+ all gems specified in test group and without one.
* `path` (`BUNDLE_PATH`):
The location on disk where all gems in your bundle will be located regardless
of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location
- will be installed by `bundle install`. Defaults to `Gem.dir`. When --deployment
- is used, defaults to vendor/bundle.
+ will be installed by `bundle install`. When not set, Bundler install by
+ default to a `.bundle` directory relative to repository root in Bundler 4,
+ and to the default system path (`Gem.dir`) before Bundler 4. That means that
+ before Bundler 4, Bundler shares this location with Rubygems, and `gem
+ install ...` will have gems installed in the same location and therefore,
+ gems installed without `path` set will show up by calling `gem list`. This
+ will not be the case in Bundler 4.
* `path.system` (`BUNDLE_PATH__SYSTEM`):
Whether Bundler will install gems into the default system path (`Gem.dir`).
-* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`)
- Makes `--path` relative to the CWD instead of the `Gemfile`.
* `plugins` (`BUNDLE_PLUGINS`):
Enable Bundler's experimental plugin system.
* `prefer_patch` (BUNDLE_PREFER_PATCH):
Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`.
-* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`):
- Print only version number from `bundler --version`.
* `redirect` (`BUNDLE_REDIRECT`):
The number of redirects allowed for network requests. Defaults to `5`.
* `retry` (`BUNDLE_RETRY`):
The number of times to retry failed network requests. Defaults to `3`.
-* `setup_makes_kernel_gem_public` (`BUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC`):
- Have `Bundler.setup` make the `Kernel#gem` method public, even though
- RubyGems declares it as private.
* `shebang` (`BUNDLE_SHEBANG`):
The program name that should be invoked for generated binstubs. Defaults to
the ruby install name used to generate the binstub.
@@ -260,6 +232,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
be changed in the next major version.
* `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`):
Silence the warning Bundler prints when installing gems as root.
+* `simulate_version` (`BUNDLE_SIMULATE_VERSION`):
+ The virtual version Bundler should use for activating feature flags. Can be
+ used to simulate all the new functionality that will be enabled in a future
+ major version.
* `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`):
Path to a designated CA certificate file or folder containing multiple
certificates for trusted CAs in PEM format.
@@ -278,6 +254,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
and disallow passing no options to `bundle update`.
* `user_agent` (`BUNDLE_USER_AGENT`):
The custom user agent fragment Bundler includes in API requests.
+* `verbose` (`BUNDLE_VERBOSE`):
+ Whether Bundler should print verbose output. Defaults to `false`, unless the
+ `--verbose` CLI flag is used.
* `version` (`BUNDLE_VERSION`):
The version of Bundler to use when running under Bundler environment.
Defaults to `lockfile`. You can also specify `system` or `x.y.z`.
@@ -289,6 +268,25 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
* `without` (`BUNDLE_WITHOUT`):
A space-separated or `:`-separated list of groups whose gems bundler should not install.
+## BUILD OPTIONS
+
+You can use `bundle config` to give Bundler the flags to pass to the gem
+installer every time bundler tries to install a particular gem.
+
+A very common example, the `mysql` gem, requires Snow Leopard users to
+pass configuration flags to `gem install` to specify where to find the
+`mysql_config` executable.
+
+ gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+Since the specific location of that executable can change from machine
+to machine, you can specify these flags on a per-machine basis.
+
+ bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config
+
+After running this command, every time bundler needs to install the
+`mysql` gem, it will pass along the flags you specified.
+
## LOCAL GIT REPOS
Bundler also allows you to work against a git repository locally
@@ -366,7 +364,7 @@ Or you can set the credentials as an environment variable like this:
For gems with a git source with HTTP(S) URL you can specify credentials like so:
- bundle config set --global https://github.com/rubygems/rubygems.git username:password
+ bundle config set --global https://github.com/ruby/rubygems.git username:password
Or you can set the credentials as an environment variable like so:
diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1
index de9eeac907..5ab15668be 100644
--- a/lib/bundler/man/bundle-console.1
+++ b/lib/bundler/man/bundle-console.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CONSOLE" "1" "March 2025" ""
+.TH "BUNDLE\-CONSOLE" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1
index cd831f2fee..a0329dfc48 100644
--- a/lib/bundler/man/bundle-doctor.1
+++ b/lib/bundler/man/bundle-doctor.1
@@ -1,14 +1,21 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-DOCTOR" "1" "March 2025" ""
+.TH "BUNDLE\-DOCTOR" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-doctor\fR \- Checks the bundle for common problems
.SH "SYNOPSIS"
-\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE]
+\fBbundle doctor [diagnose]\fR [\-\-quiet] [\-\-gemfile=GEMFILE] [\-\-ssl]
+.br
+\fBbundle doctor ssl\fR [\-\-host=HOST] [\-\-tls\-version=TLS\-VERSION] [\-\-verify\-mode=VERIFY\-MODE]
+.br
+\fBbundle doctor\fR help [COMMAND]
.SH "DESCRIPTION"
+You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue\.
+.SH "SUB\-COMMANDS"
+.SS "diagnose (default command)"
Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\.
.P
-Examples of common problems caught by bundle\-doctor include:
+Examples of common problems caught include:
.IP "\(bu" 4
Invalid Bundler settings
.IP "\(bu" 4
@@ -20,11 +27,43 @@ Uninstalled gems
.IP "\(bu" 4
Missing dependencies
.IP "" 0
-.SH "OPTIONS"
+.P
+\fBOPTIONS\fR
.TP
\fB\-\-quiet\fR
Only output warnings and errors\.
.TP
\fB\-\-gemfile=GEMFILE\fR
The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
+.TP
+\fB\-\-ssl\fR
+Diagnose common SSL problems when connecting to https://rubygems\.org\.
+.IP
+This flag runs the \fBbundle doctor ssl\fR subcommand with default values underneath\.
+.SS "ssl"
+If you've experienced issues related to SSL certificates and/or TLS versions while connecting to https://rubygems\.org, this command can help troubleshoot common problems\. The diagnostic will perform a few checks such as:
+.IP "\(bu" 4
+Verify the Ruby OpenSSL version installed on your system\.
+.IP "\(bu" 4
+Check the OpenSSL library version used for compilation\.
+.IP "\(bu" 4
+Ensure CA certificates are correctly setup on your machine\.
+.IP "\(bu" 4
+Open a TLS connection and verify the outcome\.
+.IP "" 0
+.P
+\fBOPTIONS\fR
+.TP
+\fB\-\-host=HOST\fR
+Perform the diagnostic on HOST\. Defaults to \fBrubygems\.org\fR\.
+.TP
+\fB\-\-tls\-version=TLS\-VERSION\fR
+Specify the TLS version when opening the connection to HOST\.
+.IP
+Accepted values are: \fB1\.1\fR or \fB1\.2\fR\.
+.TP
+\fB\-\-verify\-mode=VERIFY\-MODE\fR
+Specify the TLS verify mode when opening the connection to HOST\. Defaults to \fBSSL_VERIFY_PEER\fR\.
+.IP
+Accepted values are: \fBCLIENT_ONCE\fR, \fBFAIL_IF_NO_PEER_CERT\fR, \fBNONE\fR, \fBPEER\fR\.
diff --git a/lib/bundler/man/bundle-doctor.1.ronn b/lib/bundler/man/bundle-doctor.1.ronn
index 5970f6188b..7495099ff5 100644
--- a/lib/bundler/man/bundle-doctor.1.ronn
+++ b/lib/bundler/man/bundle-doctor.1.ronn
@@ -3,16 +3,27 @@ bundle-doctor(1) -- Checks the bundle for common problems
## SYNOPSIS
-`bundle doctor` [--quiet]
- [--gemfile=GEMFILE]
+`bundle doctor [diagnose]` [--quiet]
+ [--gemfile=GEMFILE]
+ [--ssl]<br>
+`bundle doctor ssl` [--host=HOST]
+ [--tls-version=TLS-VERSION]
+ [--verify-mode=VERIFY-MODE]<br>
+`bundle doctor` help [COMMAND]
## DESCRIPTION
+You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue.
+
+## SUB-COMMANDS
+
+### diagnose (default command)
+
Checks your Gemfile and gem environment for common problems. If issues
are detected, Bundler prints them and exits status 1. Otherwise,
Bundler prints a success message and exits status 0.
-Examples of common problems caught by bundle-doctor include:
+Examples of common problems caught include:
* Invalid Bundler settings
* Mismatched Ruby versions
@@ -20,7 +31,7 @@ Examples of common problems caught by bundle-doctor include:
* Uninstalled gems
* Missing dependencies
-## OPTIONS
+**OPTIONS**
* `--quiet`:
Only output warnings and errors.
@@ -31,3 +42,36 @@ Examples of common problems caught by bundle-doctor include:
will assume that the location of the Gemfile(5) is also the project's
root and will try to find `Gemfile.lock` and `vendor/cache` relative
to this location.
+
+* `--ssl`:
+ Diagnose common SSL problems when connecting to https://rubygems.org.
+
+ This flag runs the `bundle doctor ssl` subcommand with default values
+ underneath.
+
+### ssl
+
+If you've experienced issues related to SSL certificates and/or TLS versions while connecting
+to https://rubygems.org, this command can help troubleshoot common problems.
+The diagnostic will perform a few checks such as:
+
+* Verify the Ruby OpenSSL version installed on your system.
+* Check the OpenSSL library version used for compilation.
+* Ensure CA certificates are correctly setup on your machine.
+* Open a TLS connection and verify the outcome.
+
+**OPTIONS**
+
+* `--host=HOST`:
+ Perform the diagnostic on HOST. Defaults to `rubygems.org`.
+
+* `--tls-version=TLS-VERSION`:
+ Specify the TLS version when opening the connection to HOST.
+
+ Accepted values are: `1.1` or `1.2`.
+
+* `--verify-mode=VERIFY-MODE`:
+ Specify the TLS verify mode when opening the connection to HOST.
+ Defaults to `SSL_VERIFY_PEER`.
+
+ Accepted values are: `CLIENT_ONCE`, `FAIL_IF_NO_PEER_CERT`, `NONE`, `PEER`.
diff --git a/lib/bundler/man/bundle-env.1 b/lib/bundler/man/bundle-env.1
index c936294827..eee3ca05d0 100644
--- a/lib/bundler/man/bundle-env.1
+++ b/lib/bundler/man/bundle-env.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-ENV" "1" "March 2025" ""
+.TH "BUNDLE\-ENV" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-env\fR \- Print information about the environment Bundler is running under
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1
index 0ebbb4c198..24c84889b5 100644
--- a/lib/bundler/man/bundle-exec.1
+++ b/lib/bundler/man/bundle-exec.1
@@ -1,10 +1,10 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-EXEC" "1" "March 2025" ""
+.TH "BUNDLE\-EXEC" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-exec\fR \- Execute a command in the context of the bundle
.SH "SYNOPSIS"
-\fBbundle exec\fR [\-\-keep\-file\-descriptors] [\-\-gemfile=GEMFILE] \fIcommand\fR
+\fBbundle exec\fR [\-\-gemfile=GEMFILE] \fIcommand\fR
.SH "DESCRIPTION"
This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\.
.P
@@ -13,9 +13,6 @@ Essentially, if you would normally have run something like \fBrspec spec/my_spec
Note that \fBbundle exec\fR does not require that an executable is available on your shell's \fB$PATH\fR\.
.SH "OPTIONS"
.TP
-\fB\-\-keep\-file\-descriptors\fR
-Passes all file descriptors to the new processes\. Default is true from bundler version 2\.2\.26\. Setting it to false is now deprecated\.
-.TP
\fB\-\-gemfile=GEMFILE\fR
Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\.
.SH "BUNDLE INSTALL \-\-BINSTUBS"
diff --git a/lib/bundler/man/bundle-exec.1.ronn b/lib/bundler/man/bundle-exec.1.ronn
index 3d3f0eed7b..e51a66a084 100644
--- a/lib/bundler/man/bundle-exec.1.ronn
+++ b/lib/bundler/man/bundle-exec.1.ronn
@@ -3,7 +3,7 @@ bundle-exec(1) -- Execute a command in the context of the bundle
## SYNOPSIS
-`bundle exec` [--keep-file-descriptors] [--gemfile=GEMFILE] <command>
+`bundle exec` [--gemfile=GEMFILE] <command>
## DESCRIPTION
@@ -20,10 +20,6 @@ available on your shell's `$PATH`.
## OPTIONS
-* `--keep-file-descriptors`:
- Passes all file descriptors to the new processes. Default is true from
- bundler version 2.2.26. Setting it to false is now deprecated.
-
* `--gemfile=GEMFILE`:
Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)].
diff --git a/lib/bundler/man/bundle-fund.1 b/lib/bundler/man/bundle-fund.1
index 641d8cf864..fe24a25ca1 100644
--- a/lib/bundler/man/bundle-fund.1
+++ b/lib/bundler/man/bundle-fund.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-FUND" "1" "March 2025" ""
+.TH "BUNDLE\-FUND" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-fund\fR \- Lists information about gems seeking funding assistance
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1
index 65882afa4f..85c0f57674 100644
--- a/lib/bundler/man/bundle-gem.1
+++ b/lib/bundler/man/bundle-gem.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-GEM" "1" "March 2025" ""
+.TH "BUNDLE\-GEM" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem
.SH "SYNOPSIS"
@@ -19,67 +19,87 @@ The generated project skeleton can be customized with OPTIONS, as explained belo
\fBgem\.test\fR
.IP "" 0
.SH "OPTIONS"
-.IP "\(bu" 4
-\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR: Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\.
-.IP "\(bu" 4
-\fB\-\-no\-exe\fR: Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-coc\fR: Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-coc\fR: Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-changelog\fR Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-changelog\fR: Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR: Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
-.IP "\(bu" 4
-\fB\-\-no\-ext\fR: Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-git\fR: Initialize a git repo inside your library\.
-.IP "\(bu" 4
-\fB\-\-github\-username=GITHUB_USERNAME\fR: Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\.
-.IP "\(bu" 4
-\fB\-\-mit\fR: Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-mit\fR: Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR: Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified:
+.TP
+\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR
+Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\.
+.TP
+\fB\-\-no\-exe\fR
+Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\.
+.TP
+\fB\-\-coc\fR
+Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
+.TP
+\fB\-\-no\-coc\fR
+Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\.
+.TP
+\fB\-\-changelog\fR
+Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. Update the default with \fBbundle config set \-\-global gem\.changelog <true|false>\fR\.
+.TP
+\fB\-\-no\-changelog\fR
+Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\.
+.TP
+\fB\-\-ext=c\fR, \fB\-\-ext=go\fR, \fB\-\-ext=rust\fR
+Add boilerplate for C, Go (currently go\-gem\-wrapper \fIhttps://github\.com/ruby\-go\-gem/go\-gem\-wrapper\fR based) or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
+.TP
+\fB\-\-no\-ext\fR
+Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\.
+.TP
+\fB\-\-git\fR
+Initialize a git repo inside your library\.
+.TP
+\fB\-\-github\-username=GITHUB_USERNAME\fR
+Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\.
+.TP
+\fB\-\-mit\fR
+Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
+.TP
+\fB\-\-no\-mit\fR
+Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\.
+.TP
+\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR
+Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified:
.IP
When Bundler is configured to generate tests, this defaults to Bundler's global config setting \fBgem\.test\fR\.
.IP
When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-test\fR: Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-changelog\fR: Generate changelog file\. Set a default with \fBbundle config set \-\-global gem\.changelog true\fR\.
-.IP "\(bu" 4
-\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR: Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
+.TP
+\fB\-\-no\-test\fR
+Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\.
+.TP
+\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR
+Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.IP
When Bundler is configured to generate CI files, this defaults to Bundler's global config setting \fBgem\.ci\fR\.
.IP
When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-ci\fR: Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR: Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
+.TP
+\fB\-\-no\-ci\fR
+Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\.
+.TP
+\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR
+Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.IP
When Bundler is configured to add a linter, this defaults to Bundler's global config setting \fBgem\.linter\fR\.
.IP
When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-linter\fR: Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-rubocop\fR: Add rubocop to the generated Rakefile and gemspec\. Set a default with \fBbundle config set \-\-global gem\.rubocop true\fR\.
-.IP "\(bu" 4
-\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR: Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
-.IP "" 0
+.TP
+\fB\-\-no\-linter\fR
+Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\.
+.TP
+\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR
+Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
+.TP
+\fB\-\-bundle\fR
+Run \fBbundle install\fR after creating the gem\.
+.TP
+\fB\-\-no\-bundle\fR
+Do not run \fBbundle install\fR after creating the gem\.
.SH "SEE ALSO"
.IP "\(bu" 4
bundle config(1) \fIbundle\-config\.1\.html\fR
diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn
index 13dc55c310..488c8113e4 100644
--- a/lib/bundler/man/bundle-gem.1.ronn
+++ b/lib/bundler/man/bundle-gem.1.ronn
@@ -41,17 +41,18 @@ configuration file using the following names:
Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the
global config).
-* `--changelog`
+* `--changelog`:
Add a `CHANGELOG.md` file to the root of the generated project. If
this option is unspecified, an interactive prompt will be displayed and the
answer will be saved in Bundler's global config for future `bundle gem` use.
+ Update the default with `bundle config set --global gem.changelog <true|false>`.
* `--no-changelog`:
Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the
global config).
-* `--ext=c`, `--ext=rust`:
- Add boilerplate for C or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior
+* `--ext=c`, `--ext=go`, `--ext=rust`:
+ Add boilerplate for C, Go (currently [go-gem-wrapper](https://github.com/ruby-go-gem/go-gem-wrapper) based) or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior
is disabled by default.
* `--no-ext`:
@@ -95,9 +96,6 @@ configuration file using the following names:
Do not use a test framework (overrides `--test` specified in the global
config).
-* `--changelog`:
- Generate changelog file. Set a default with `bundle config set --global gem.changelog true`.
-
* `--ci`, `--ci=circle`, `--ci=github`, `--ci=gitlab`:
Specify the continuous integration service that Bundler should use when
generating the project. Acceptable values are `github`, `gitlab`
@@ -137,13 +135,16 @@ configuration file using the following names:
* `--no-linter`:
Do not add a linter (overrides `--linter` specified in the global config).
-* `--rubocop`:
- Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`.
-
* `--edit=EDIT`, `-e=EDIT`:
Open the resulting GEM_NAME.gemspec in EDIT, or the default editor if not
specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`.
+* `--bundle`:
+ Run `bundle install` after creating the gem.
+
+* `--no-bundle`:
+ Do not run `bundle install` after creating the gem.
+
## SEE ALSO
* [bundle config(1)](bundle-config.1.html)
diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1
index d8dd4660dc..05fd5a7c48 100644
--- a/lib/bundler/man/bundle-help.1
+++ b/lib/bundler/man/bundle-help.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-HELP" "1" "March 2025" ""
+.TH "BUNDLE\-HELP" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-help\fR \- Displays detailed help for each subcommand
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1
index 8124836519..96c7d876f6 100644
--- a/lib/bundler/man/bundle-info.1
+++ b/lib/bundler/man/bundle-info.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INFO" "1" "March 2025" ""
+.TH "BUNDLE\-INFO" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-info\fR \- Show information for the given gem in your bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1
index 2e4b99b28a..83dad5c050 100644
--- a/lib/bundler/man/bundle-init.1
+++ b/lib/bundler/man/bundle-init.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INIT" "1" "March 2025" ""
+.TH "BUNDLE\-INIT" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-init\fR \- Generates a Gemfile into the current working directory
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1
deleted file mode 100644
index 54cacaa56d..0000000000
--- a/lib/bundler/man/bundle-inject.1
+++ /dev/null
@@ -1,31 +0,0 @@
-.\" generated with Ronn-NG/v0.10.1
-.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INJECT" "1" "March 2025" ""
-.SH "NAME"
-\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile
-.SH "SYNOPSIS"
-\fBbundle inject\fR [GEM] [VERSION] [\-\-source=SOURCE] [\-\-group=GROUP]
-.SH "DESCRIPTION"
-Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\.
-.P
-This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn't listed yet\.
-.P
-Example:
-.IP "" 4
-.nf
-bundle install
-bundle inject 'rack' '> 0'
-.fi
-.IP "" 0
-.P
-This will inject the 'rack' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock\.
-.P
-The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 3\.0\.
-.SH "OPTIONS"
-.TP
-\fB\-\-source=SOURCE\fR
-Install gem from the given source\.
-.TP
-\fB\-\-group=GROUP\fR
-Install gem into a bundler group\.
-
diff --git a/lib/bundler/man/bundle-inject.1.ronn b/lib/bundler/man/bundle-inject.1.ronn
deleted file mode 100644
index e2a911b6d8..0000000000
--- a/lib/bundler/man/bundle-inject.1.ronn
+++ /dev/null
@@ -1,32 +0,0 @@
-bundle-inject(1) -- Add named gem(s) with version requirements to Gemfile
-=========================================================================
-
-## SYNOPSIS
-
-`bundle inject` [GEM] [VERSION] [--source=SOURCE] [--group=GROUP]
-
-## DESCRIPTION
-
-Adds the named gem(s) with their version requirements to the resolved
-[`Gemfile(5)`][Gemfile(5)].
-
-This command will add the gem to both your [`Gemfile(5)`][Gemfile(5)] and Gemfile.lock if it
-isn't listed yet.
-
-Example:
-
- bundle install
- bundle inject 'rack' '> 0'
-
-This will inject the 'rack' gem with a version greater than 0 in your
-[`Gemfile(5)`][Gemfile(5)] and Gemfile.lock.
-
-The `bundle inject` command was deprecated in Bundler 2.1 and will be removed in Bundler 3.0.
-
-## OPTIONS
-
-* `--source=SOURCE`:
- Install gem from the given source.
-
-* `--group=GROUP`:
- Install gem into a bundler group.
diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1
index 272f4187ed..68530f3ebb 100644
--- a/lib/bundler/man/bundle-install.1
+++ b/lib/bundler/man/bundle-install.1
@@ -1,10 +1,10 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INSTALL" "1" "March 2025" ""
+.TH "BUNDLE\-INSTALL" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
.SH "SYNOPSIS"
-\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang=SHEBANG] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]]
+\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-lockfile=LOCKFILE] [\-\-no\-cache] [\-\-no\-lock] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG]
.SH "DESCRIPTION"
Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\.
.P
@@ -12,30 +12,9 @@ If a \fBGemfile\.lock\fR does exist, and you have not updated your Gemfile(5), B
.P
If a \fBGemfile\.lock\fR does exist, and you have updated your Gemfile(5), Bundler will use the dependencies in the \fBGemfile\.lock\fR for all gems that you did not update, but will re\-resolve the dependencies of gems that you did update\. You can find more information about this update process below under \fICONSERVATIVE UPDATING\fR\.
.SH "OPTIONS"
-The \fB\-\-clean\fR, \fB\-\-deployment\fR, \fB\-\-frozen\fR, \fB\-\-no\-prune\fR, \fB\-\-path\fR, \fB\-\-shebang\fR, \fB\-\-system\fR, \fB\-\-without\fR and \fB\-\-with\fR options are deprecated because they only make sense if they are applied to every subsequent \fBbundle install\fR run automatically and that requires \fBbundler\fR to silently remember them\. Since \fBbundler\fR will no longer remember CLI flags in future versions, \fBbundle config\fR (see bundle\-config(1)) should be used to apply them permanently\.
.TP
-\fB\-\-binstubs[=BINSTUBS]\fR
-Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it in \fBbin/\fR\. This lets you link the binstub inside of an application to the exact gem version the application needs\.
-.IP
-Creates a directory (defaults to \fB~/bin\fR when the option is used without a value, or to the given \fB<BINSTUBS>\fR directory otherwise) and places any executables from the gem there\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
-.TP
-\fB\-\-clean\fR
-On finishing the installation Bundler is going to remove any gems not present in the current Gemfile(5)\. Don't worry, gems currently in use will not be removed\.
-.IP
-This option is deprecated in favor of the \fBclean\fR setting\.
-.TP
-\fB\-\-deployment\fR
-In \fIdeployment mode\fR, Bundler will 'roll\-out' the bundle for production or CI use\. Please check carefully if you want to have this option enabled in your development environment\.
-.IP
-This option is deprecated in favor of the \fBdeployment\fR setting\.
-.TP
-\fB\-\-redownload\fR, \fB\-\-force\fR
-Force download every gem, even if the required versions are already available locally\.
-.TP
-\fB\-\-frozen\fR
-Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\.
-.IP
-This option is deprecated in favor of the \fBfrozen\fR setting\.
+\fB\-\-force\fR, \fB\-\-redownload\fR
+Force reinstalling every gem, even if already installed\.
.TP
\fB\-\-full\-index\fR
Bundler will not call Rubygems' API endpoint (default) but download and cache a (currently big) index file of all gems\. Performance can be improved for large bundles that seldom change by enabling this option\.
@@ -49,21 +28,19 @@ The maximum number of parallel download and install jobs\. The default is the nu
\fB\-\-local\fR
Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems' cache or in \fBvendor/cache\fR\. Note that if an appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\.
.TP
+\fB\-\-lockfile=LOCKFILE\fR
+The location of the lockfile which Bundler should use\. This defaults to the Gemfile location with \fB\.lock\fR appended\.
+.TP
\fB\-\-prefer\-local\fR
Force using locally installed gems, or gems already present in Rubygems' cache or in \fBvendor/cache\fR, when resolving, even if newer versions are available remotely\. Only attempt to connect to \fBrubygems\.org\fR for gems that are not present locally\.
.TP
\fB\-\-no\-cache\fR
Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\.
.TP
-\fB\-\-no\-prune\fR
-Don't remove stale gems from the cache when the installation finishes\.
+\fB\-\-no\-lock\fR
+Do not create a lockfile\. Useful if you want to install dependencies but not lock versions of gems\. Recommended for library development, and other situations where the code is expected to work with a range of dependency versions\.
.IP
-This option is deprecated in favor of the \fBno_prune\fR setting\.
-.TP
-\fB\-\-path=PATH\fR
-The location to install the specified gems to\. This defaults to Rubygems' setting\. Bundler shares this location with Rubygems, \fBgem install \|\.\|\.\|\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \|\.\|\.\|\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\.
-.IP
-This option is deprecated in favor of the \fBpath\fR setting\.
+This has the same effect as using \fBlockfile false\fR in the Gemfile\. See gemfile(5) for more information\.
.TP
\fB\-\-quiet\fR
Do not print progress information to the standard output\.
@@ -71,36 +48,16 @@ Do not print progress information to the standard output\.
\fB\-\-retry=[<number>]\fR
Retry failed network or git requests for \fInumber\fR times\.
.TP
-\fB\-\-shebang=SHEBANG\fR
-Uses the specified ruby executable (usually \fBruby\fR) to execute the scripts created with \fB\-\-binstubs\fR\. In addition, if you use \fB\-\-binstubs\fR together with \fB\-\-shebang jruby\fR these executables will be changed to execute \fBjruby\fR instead\.
-.IP
-This option is deprecated in favor of the \fBshebang\fR setting\.
-.TP
\fB\-\-standalone[=<list>]\fR
-Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install can be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler's own setup in the manner required\. Using this option implicitly sets \fBpath\fR, which is a [remembered option][REMEMBERED OPTIONS]\.
-.TP
-\fB\-\-system\fR
-Installs the gems specified in the bundle to the system's Rubygems location\. This overrides any previous configuration of \fB\-\-path\fR\.
-.IP
-This option is deprecated in favor of the \fBsystem\fR setting\.
+Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install can be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler's own setup in the manner required\.
.TP
\fB\-\-trust\-policy=TRUST\-POLICY\fR
Apply the Rubygems security policy \fIpolicy\fR, where policy is one of \fBHighSecurity\fR, \fBMediumSecurity\fR, \fBLowSecurity\fR, \fBAlmostNoSecurity\fR, or \fBNoSecurity\fR\. For more details, please see the Rubygems signing documentation linked below in \fISEE ALSO\fR\.
.TP
\fB\-\-target\-rbconfig=TARGET\-RBCONFIG\fR
Path to rbconfig\.rb for the deployment target platform\.
-.TP
-\fB\-\-with=<list>\fR
-A space\-separated list of groups referencing gems to install\. If an optional group is given it is installed\. If a group is given that is in the remembered list of groups given to \-\-without, it is removed from that list\.
-.IP
-This option is deprecated in favor of the \fBwith\fR setting\.
-.TP
-\fB\-\-without=<list>\fR
-A space\-separated list of groups referencing gems to skip during installation\. If a group is given that is in the remembered list of groups given to \-\-with, it is removed from that list\.
-.IP
-This option is deprecated in favor of the \fBwithout\fR setting\.
.SH "DEPLOYMENT MODE"
-Bundler's defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fB\-\-deployment\fR flag\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\.
+Bundler's defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fBdeployment\fR setting\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\.
.IP "1." 4
A \fBGemfile\.lock\fR is required\.
.IP
@@ -120,14 +77,14 @@ In development, it's convenient to share the gems used in your application with
.IP
In deployment, isolation is a more important default\. In addition, the user deploying the application may not have permission to install gems to the system, or the web server may not have permission to read them\.
.IP
-As a result, \fBbundle install \-\-deployment\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fB\-\-path\fR option\.
+As a result, when \fBdeployment\fR is configured, \fBbundle install\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fBpath\fR setting\.
.IP "" 0
.SH "INSTALLING GROUPS"
By default, \fBbundle install\fR will install all gems in all groups in your Gemfile(5), except those declared for a different platform\.
.P
-However, you can explicitly tell Bundler to skip installing certain groups with the \fB\-\-without\fR option\. This option takes a space\-separated list of groups\.
+However, you can explicitly tell Bundler to skip installing certain groups with the \fBwithout\fR setting\. This setting takes a space\-separated list of groups\.
.P
-While the \fB\-\-without\fR option will skip \fIinstalling\fR the gems in the specified groups, it will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\.
+While the \fBwithout\fR setting will skip \fIinstalling\fR the gems in the specified groups, \fBbundle install\fR will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\.
.P
This is so that installing a different set of groups on another machine (such as a production server) will not change the gems and versions that you have already developed and tested against\.
.P
@@ -148,7 +105,7 @@ end
.P
In this case, \fBsinatra\fR depends on any version of Rack (\fB>= 1\.0\fR), while \fBrack\-perftools\-profiler\fR depends on 1\.x (\fB~> 1\.0\fR)\.
.P
-When you run \fBbundle install \-\-without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\.
+When you configure \fBbundle config without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\.
.P
This should not cause any problems in practice, because we do not attempt to \fBinstall\fR the gems in the excluded groups, and only evaluate as part of the dependency resolution process\.
.P
diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn
index a3606826df..c7d88bfb73 100644
--- a/lib/bundler/man/bundle-install.1.ronn
+++ b/lib/bundler/man/bundle-install.1.ronn
@@ -3,28 +3,20 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile
## SYNOPSIS
-`bundle install` [--binstubs[=DIRECTORY]]
- [--clean]
- [--deployment]
- [--frozen]
+`bundle install` [--force]
[--full-index]
[--gemfile=GEMFILE]
[--jobs=NUMBER]
[--local]
+ [--lockfile=LOCKFILE]
[--no-cache]
- [--no-prune]
- [--path PATH]
+ [--no-lock]
[--prefer-local]
[--quiet]
- [--redownload]
[--retry=NUMBER]
- [--shebang=SHEBANG]
[--standalone[=GROUP[ GROUP...]]]
- [--system]
[--trust-policy=TRUST-POLICY]
[--target-rbconfig=TARGET-RBCONFIG]
- [--with=GROUP[ GROUP...]]
- [--without=GROUP[ GROUP...]]
## DESCRIPTION
@@ -45,50 +37,8 @@ update process below under [CONSERVATIVE UPDATING][].
## OPTIONS
-The `--clean`, `--deployment`, `--frozen`, `--no-prune`, `--path`, `--shebang`,
-`--system`, `--without` and `--with` options are deprecated because they only
-make sense if they are applied to every subsequent `bundle install` run
-automatically and that requires `bundler` to silently remember them. Since
-`bundler` will no longer remember CLI flags in future versions, `bundle config`
-(see bundle-config(1)) should be used to apply them permanently.
-
-* `--binstubs[=BINSTUBS]`:
- Binstubs are scripts that wrap around executables. Bundler creates a small Ruby
- file (a binstub) that loads Bundler, runs the command, and puts it in `bin/`.
- This lets you link the binstub inside of an application to the exact gem
- version the application needs.
-
- Creates a directory (defaults to `~/bin` when the option is used without a
- value, or to the given `<BINSTUBS>` directory otherwise) and places any
- executables from the gem there. These executables run in Bundler's context. If
- used, you might add this directory to your environment's `PATH` variable. For
- instance, if the `rails` gem comes with a `rails` executable, this flag will
- create a `bin/rails` executable that ensures that all referred dependencies
- will be resolved using the bundled gems.
-
-* `--clean`:
- On finishing the installation Bundler is going to remove any gems not present
- in the current Gemfile(5). Don't worry, gems currently in use will not be
- removed.
-
- This option is deprecated in favor of the `clean` setting.
-
-* `--deployment`:
- In [deployment mode][DEPLOYMENT MODE], Bundler will 'roll-out' the bundle for
- production or CI use. Please check carefully if you want to have this option
- enabled in your development environment.
-
- This option is deprecated in favor of the `deployment` setting.
-
-* `--redownload`, `--force`:
- Force download every gem, even if the required versions are already available
- locally.
-
-* `--frozen`:
- Do not allow the Gemfile.lock to be updated after this install. Exits
- non-zero if there are going to be changes to the Gemfile.lock.
-
- This option is deprecated in favor of the `frozen` setting.
+* `--force`, `--redownload`:
+ Force reinstalling every gem, even if already installed.
* `--full-index`:
Bundler will not call Rubygems' API endpoint (default) but download and cache
@@ -112,6 +62,10 @@ automatically and that requires `bundler` to silently remember them. Since
appropriate platform-specific gem exists on `rubygems.org` it will not be
found.
+* `--lockfile=LOCKFILE`:
+ The location of the lockfile which Bundler should use. This defaults
+ to the Gemfile location with `.lock` appended.
+
* `--prefer-local`:
Force using locally installed gems, or gems already present in Rubygems' cache
or in `vendor/cache`, when resolving, even if newer versions are available
@@ -123,19 +77,14 @@ automatically and that requires `bundler` to silently remember them. Since
does not remove any gems in the cache but keeps the newly bundled gems from
being cached during the install.
-* `--no-prune`:
- Don't remove stale gems from the cache when the installation finishes.
+* `--no-lock`:
+ Do not create a lockfile. Useful if you want to install dependencies but not
+ lock versions of gems. Recommended for library development, and other
+ situations where the code is expected to work with a range of dependency
+ versions.
- This option is deprecated in favor of the `no_prune` setting.
-
-* `--path=PATH`:
- The location to install the specified gems to. This defaults to Rubygems'
- setting. Bundler shares this location with Rubygems, `gem install ...` will
- have gem installed there, too. Therefore, gems installed without a
- `--path ...` setting will show up by calling `gem list`. Accordingly, gems
- installed to other locations will not get listed.
-
- This option is deprecated in favor of the `path` setting.
+ This has the same effect as using `lockfile false` in the Gemfile.
+ See gemfile(5) for more information.
* `--quiet`:
Do not print progress information to the standard output.
@@ -143,27 +92,12 @@ automatically and that requires `bundler` to silently remember them. Since
* `--retry=[<number>]`:
Retry failed network or git requests for <number> times.
-* `--shebang=SHEBANG`:
- Uses the specified ruby executable (usually `ruby`) to execute the scripts
- created with `--binstubs`. In addition, if you use `--binstubs` together with
- `--shebang jruby` these executables will be changed to execute `jruby`
- instead.
-
- This option is deprecated in favor of the `shebang` setting.
-
* `--standalone[=<list>]`:
Makes a bundle that can work without depending on Rubygems or Bundler at
runtime. A space separated list of groups to install can be specified.
Bundler creates a directory named `bundle` and installs the bundle there. It
also generates a `bundle/bundler/setup.rb` file to replace Bundler's own setup
- in the manner required. Using this option implicitly sets `path`, which is a
- [remembered option][REMEMBERED OPTIONS].
-
-* `--system`:
- Installs the gems specified in the bundle to the system's Rubygems location.
- This overrides any previous configuration of `--path`.
-
- This option is deprecated in favor of the `system` setting.
+ in the manner required.
* `--trust-policy=TRUST-POLICY`:
Apply the Rubygems security policy <policy>, where policy is one of
@@ -174,26 +108,11 @@ automatically and that requires `bundler` to silently remember them. Since
* `--target-rbconfig=TARGET-RBCONFIG`:
Path to rbconfig.rb for the deployment target platform.
-* `--with=<list>`:
- A space-separated list of groups referencing gems to install. If an
- optional group is given it is installed. If a group is given that is
- in the remembered list of groups given to --without, it is removed
- from that list.
-
- This option is deprecated in favor of the `with` setting.
-
-* `--without=<list>`:
- A space-separated list of groups referencing gems to skip during installation.
- If a group is given that is in the remembered list of groups given
- to --with, it is removed from that list.
-
- This option is deprecated in favor of the `without` setting.
-
## DEPLOYMENT MODE
Bundler's defaults are optimized for development. To switch to
-defaults optimized for deployment and for CI, use the `--deployment`
-flag. Do not activate deployment mode on development machines, as it
+defaults optimized for deployment and for CI, use the `deployment`
+setting. Do not activate deployment mode on development machines, as it
will cause an error when the Gemfile(5) is modified.
1. A `Gemfile.lock` is required.
@@ -225,9 +144,9 @@ will cause an error when the Gemfile(5) is modified.
gems to the system, or the web server may not have permission to
read them.
- As a result, `bundle install --deployment` installs gems to
- the `vendor/bundle` directory in the application. This may be
- overridden using the `--path` option.
+ As a result, when `deployment` is configured, `bundle install` installs gems
+ to the `vendor/bundle` directory in the application. This may be
+ overridden using the `path` setting.
## INSTALLING GROUPS
@@ -235,12 +154,12 @@ By default, `bundle install` will install all gems in all groups
in your Gemfile(5), except those declared for a different platform.
However, you can explicitly tell Bundler to skip installing
-certain groups with the `--without` option. This option takes
+certain groups with the `without` setting. This setting takes
a space-separated list of groups.
-While the `--without` option will skip _installing_ the gems in the
-specified groups, it will still _download_ those gems and use them to
-resolve the dependencies of every gem in your Gemfile(5).
+While the `without` setting will skip _installing_ the gems in the
+specified groups, `bundle install` will still _download_ those gems and use them
+to resolve the dependencies of every gem in your Gemfile(5).
This is so that installing a different set of groups on another
machine (such as a production server) will not change the
@@ -266,7 +185,7 @@ For a simple illustration, consider the following Gemfile(5):
In this case, `sinatra` depends on any version of Rack (`>= 1.0`), while
`rack-perftools-profiler` depends on 1.x (`~> 1.0`).
-When you run `bundle install --without production` in development, we
+When you configure `bundle config without production` in development, we
look at the dependencies of `rack-perftools-profiler` as well. That way,
you do not spend all your time developing against Rack 2.0, using new
APIs unavailable in Rack 1.x, only to have Bundler switch to Rack 1.2
diff --git a/lib/bundler/man/bundle-issue.1 b/lib/bundler/man/bundle-issue.1
index 02d28c91ba..394d6c5469 100644
--- a/lib/bundler/man/bundle-issue.1
+++ b/lib/bundler/man/bundle-issue.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-ISSUE" "1" "March 2025" ""
+.TH "BUNDLE\-ISSUE" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-issue\fR \- Get help reporting Bundler issues
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-licenses.1 b/lib/bundler/man/bundle-licenses.1
index d0dbf3913c..2931e42dd7 100644
--- a/lib/bundler/man/bundle-licenses.1
+++ b/lib/bundler/man/bundle-licenses.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-LICENSES" "1" "March 2025" ""
+.TH "BUNDLE\-LICENSES" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-licenses\fR \- Print the license of all gems in the bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1
index cd09ccab31..d8bcf20585 100644
--- a/lib/bundler/man/bundle-list.1
+++ b/lib/bundler/man/bundle-list.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-LIST" "1" "March 2025" ""
+.TH "BUNDLE\-LIST" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-list\fR \- List all the gems in the bundle
.SH "SYNOPSIS"
@@ -19,6 +19,8 @@ bundle list \-\-without\-group test
bundle list \-\-only\-group dev
.P
bundle list \-\-only\-group dev test \-\-paths
+.P
+bundle list \-\-format json
.SH "OPTIONS"
.TP
\fB\-\-name\-only\fR
@@ -32,4 +34,7 @@ A space\-separated list of groups of gems to skip during printing\.
.TP
\fB\-\-only\-group=<list>\fR
A space\-separated list of groups of gems to print\.
+.TP
+\fB\-\-format=FORMAT\fR
+Format output ('json' is the only supported format)
diff --git a/lib/bundler/man/bundle-list.1.ronn b/lib/bundler/man/bundle-list.1.ronn
index 81bee0ac33..9ec2b13282 100644
--- a/lib/bundler/man/bundle-list.1.ronn
+++ b/lib/bundler/man/bundle-list.1.ronn
@@ -21,6 +21,8 @@ bundle list --only-group dev
bundle list --only-group dev test --paths
+bundle list --format json
+
## OPTIONS
* `--name-only`:
@@ -34,3 +36,6 @@ bundle list --only-group dev test --paths
* `--only-group=<list>`:
A space-separated list of groups of gems to print.
+
+* `--format=FORMAT`:
+ Format output ('json' is the only supported format)
diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1
index 8c9b94e8e2..478d173535 100644
--- a/lib/bundler/man/bundle-lock.1
+++ b/lib/bundler/man/bundle-lock.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-LOCK" "1" "March 2025" ""
+.TH "BUNDLE\-LOCK" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-lock\fR \- Creates / Updates a lockfile without installing
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1
index eb4ac2e859..2f13b1329f 100644
--- a/lib/bundler/man/bundle-open.1
+++ b/lib/bundler/man/bundle-open.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-OPEN" "1" "March 2025" ""
+.TH "BUNDLE\-OPEN" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-open\fR \- Opens the source directory for a gem in your bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1
index 4f8a2cc56f..7e10d202be 100644
--- a/lib/bundler/man/bundle-outdated.1
+++ b/lib/bundler/man/bundle-outdated.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-OUTDATED" "1" "March 2025" ""
+.TH "BUNDLE\-OUTDATED" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-outdated\fR \- List installed gems with newer versions available
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1
index bdac52f937..6a3a08c3a9 100644
--- a/lib/bundler/man/bundle-platform.1
+++ b/lib/bundler/man/bundle-platform.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-PLATFORM" "1" "March 2025" ""
+.TH "BUNDLE\-PLATFORM" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-platform\fR \- Displays platform compatibility information
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1
index ded328dbd8..25da562475 100644
--- a/lib/bundler/man/bundle-plugin.1
+++ b/lib/bundler/man/bundle-plugin.1
@@ -1,12 +1,12 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-PLUGIN" "1" "March 2025" ""
+.TH "BUNDLE\-PLUGIN" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-plugin\fR \- Manage Bundler plugins
.SH "SYNOPSIS"
-\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] [\-\-path=\fIpath\fR]
+\fBbundle plugin\fR install PLUGINS [\-\-source=SOURCE] [\-\-version=VERSION] [\-\-git=GIT] [\-\-branch=BRANCH|\-\-ref=REF] [\-\-path=PATH]
.br
-\fBbundle plugin\fR uninstall PLUGINS
+\fBbundle plugin\fR uninstall PLUGINS [\-\-all]
.br
\fBbundle plugin\fR list
.br
@@ -16,18 +16,23 @@ You can install, uninstall, and list plugin(s) with this command to extend funct
.SH "SUB\-COMMANDS"
.SS "install"
Install the given plugin(s)\.
+.P
+For example, \fBbundle plugin install bundler\-graph\fR will install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. Note that the global source specified in Gemfile is ignored\.
+.P
+\fBOPTIONS\fR
.TP
-\fBbundle plugin install bundler\-graph\fR
-Install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. The global source, specified in source in Gemfile is ignored\.
-.TP
-\fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR
-Install bundler\-graph gem from example\.com\. The global source, specified in source in Gemfile is not considered\.
+\fB\-\-source=SOURCE\fR
+Install the plugin gem from a specific source, rather than from globally configured sources\.
+.IP
+Example: \fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR
.TP
-\fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR
-You can specify the version of the gem via \fB\-\-version\fR\.
+\fB\-\-version=VERSION\fR
+Specify a version of the plugin gem to install via \fB\-\-version\fR\.
+.IP
+Example: \fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR
.TP
-\fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR
-Install bundler\-graph gem from Git repository\. You can use standard Git URLs like:
+\fB\-\-git=GIT\fR
+Install the plugin gem from a Git repository\. You can use standard Git URLs like:
.IP
\fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR
.br
@@ -37,12 +42,25 @@ Install bundler\-graph gem from Git repository\. You can use standard Git URLs l
.br
\fBfile:///path/to/repo\fR
.IP
-When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\.
+Example: \fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR
.TP
-\fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR
-Install bundler\-graph gem from a local path\.
+\fB\-\-branch=BRANCH\fR
+When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR to use\.
+.TP
+\fB\-\-ref=REF\fR
+When you specify \fB\-\-git\fR, you can use \fB\-\-ref\fR to specify any tag, or commit hash (revision) to use\.
+.TP
+\fB\-\-path=PATH\fR
+Install the plugin gem from a local path\.
+.IP
+Example: \fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR
.SS "uninstall"
Uninstall the plugin(s) specified in PLUGINS\.
+.P
+\fBOPTIONS\fR
+.TP
+\fB\-\-all\fR
+Uninstall all the installed plugins\. If no plugin is installed, then it does nothing\.
.SS "list"
List the installed plugins and available commands\.
.P
diff --git a/lib/bundler/man/bundle-plugin.1.ronn b/lib/bundler/man/bundle-plugin.1.ronn
index b0a34660ea..b54e0c08b4 100644
--- a/lib/bundler/man/bundle-plugin.1.ronn
+++ b/lib/bundler/man/bundle-plugin.1.ronn
@@ -3,10 +3,10 @@ bundle-plugin(1) -- Manage Bundler plugins
## SYNOPSIS
-`bundle plugin` install PLUGINS [--source=<SOURCE>] [--version=<version>]
- [--git=<git-url>] [--branch=<branch>|--ref=<rev>]
- [--path=<path>]<br>
-`bundle plugin` uninstall PLUGINS<br>
+`bundle plugin` install PLUGINS [--source=SOURCE] [--version=VERSION]
+ [--git=GIT] [--branch=BRANCH|--ref=REF]
+ [--path=PATH]<br>
+`bundle plugin` uninstall PLUGINS [--all]<br>
`bundle plugin` list<br>
`bundle plugin` help [COMMAND]
@@ -20,32 +20,53 @@ You can install, uninstall, and list plugin(s) with this command to extend funct
Install the given plugin(s).
-* `bundle plugin install bundler-graph`:
- Install bundler-graph gem from globally configured sources (defaults to RubyGems.org). The global source, specified in source in Gemfile is ignored.
+For example, `bundle plugin install bundler-graph` will install bundler-graph
+gem from globally configured sources (defaults to RubyGems.org). Note that the
+global source specified in Gemfile is ignored.
-* `bundle plugin install bundler-graph --source https://example.com`:
- Install bundler-graph gem from example.com. The global source, specified in source in Gemfile is not considered.
+**OPTIONS**
-* `bundle plugin install bundler-graph --version 0.2.1`:
- You can specify the version of the gem via `--version`.
+* `--source=SOURCE`:
+ Install the plugin gem from a specific source, rather than from globally configured sources.
-* `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph`:
- Install bundler-graph gem from Git repository. You can use standard Git URLs like:
+ Example: `bundle plugin install bundler-graph --source https://example.com`
+
+* `--version=VERSION`:
+ Specify a version of the plugin gem to install via `--version`.
+
+ Example: `bundle plugin install bundler-graph --version 0.2.1`
+
+* `--git=GIT`:
+ Install the plugin gem from a Git repository. You can use standard Git URLs like:
`ssh://[user@]host.xz[:port]/path/to/repo.git`<br>
`http[s]://host.xz[:port]/path/to/repo.git`<br>
`/path/to/repo`<br>
`file:///path/to/repo`
- When you specify `--git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use.
+ Example: `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph`
+
+* `--branch=BRANCH`:
+ When you specify `--git`, you can use `--branch` to use.
-* `bundle plugin install bundler-graph --path ../bundler-graph`:
- Install bundler-graph gem from a local path.
+* `--ref=REF`:
+ When you specify `--git`, you can use `--ref` to specify any tag, or commit
+ hash (revision) to use.
+
+* `--path=PATH`:
+ Install the plugin gem from a local path.
+
+ Example: `bundle plugin install bundler-graph --path ../bundler-graph`
### uninstall
Uninstall the plugin(s) specified in PLUGINS.
+**OPTIONS**
+
+* `--all`:
+ Uninstall all the installed plugins. If no plugin is installed, then it does nothing.
+
### list
List the installed plugins and available commands.
diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1
index 294ef179a7..a8316b5cca 100644
--- a/lib/bundler/man/bundle-pristine.1
+++ b/lib/bundler/man/bundle-pristine.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-PRISTINE" "1" "March 2025" ""
+.TH "BUNDLE\-PRISTINE" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-pristine\fR \- Restores installed gems to their pristine condition
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1
index 2e42a12de3..4dc7a03b9f 100644
--- a/lib/bundler/man/bundle-remove.1
+++ b/lib/bundler/man/bundle-remove.1
@@ -1,21 +1,15 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-REMOVE" "1" "March 2025" ""
+.TH "BUNDLE\-REMOVE" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-remove\fR \- Removes gems from the Gemfile
.SH "SYNOPSIS"
-\fBbundle remove [GEM [GEM \|\.\|\.\|\.]] [\-\-install]\fR
+`bundle remove [GEM [GEM \|\.\|\.\|\.]]
.SH "DESCRIPTION"
Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid\. If a gem cannot be removed, a warning is printed\. If a gem is already absent from the Gemfile, and error is raised\.
-.SH "OPTIONS"
-.TP
-\fB\-\-install\fR
-Runs \fBbundle install\fR after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s)\.
.P
Example:
.P
bundle remove rails
.P
bundle remove rails rack
-.P
-bundle remove rails rack \-\-install
diff --git a/lib/bundler/man/bundle-remove.1.ronn b/lib/bundler/man/bundle-remove.1.ronn
index ceb1a980be..49cb4dc1fd 100644
--- a/lib/bundler/man/bundle-remove.1.ronn
+++ b/lib/bundler/man/bundle-remove.1.ronn
@@ -3,21 +3,14 @@ bundle-remove(1) -- Removes gems from the Gemfile
## SYNOPSIS
-`bundle remove [GEM [GEM ...]] [--install]`
+`bundle remove [GEM [GEM ...]]
## DESCRIPTION
Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If a gem cannot be removed, a warning is printed. If a gem is already absent from the Gemfile, and error is raised.
-## OPTIONS
-
-* `--install`:
- Runs `bundle install` after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s).
-
Example:
bundle remove rails
bundle remove rails rack
-
-bundle remove rails rack --install
diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1
index d460e7a256..901460962c 100644
--- a/lib/bundler/man/bundle-show.1
+++ b/lib/bundler/man/bundle-show.1
@@ -1,10 +1,10 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-SHOW" "1" "March 2025" ""
+.TH "BUNDLE\-SHOW" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
.SH "SYNOPSIS"
-\fBbundle show\fR [GEM] [\-\-paths] [\-\-outdated]
+\fBbundle show\fR [GEM] [\-\-paths]
.SH "DESCRIPTION"
Without the [GEM] option, \fBshow\fR will print a list of the names and versions of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by name\.
.P
@@ -13,7 +13,4 @@ Calling show with [GEM] will list the exact location of that gem on your machine
.TP
\fB\-\-paths\fR
List the paths of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by gem name\.
-.TP
-\fB\-\-outdated\fR
-Show verbose output including whether gems are outdated\.
diff --git a/lib/bundler/man/bundle-show.1.ronn b/lib/bundler/man/bundle-show.1.ronn
index 6e80b04696..a6a59a1445 100644
--- a/lib/bundler/man/bundle-show.1.ronn
+++ b/lib/bundler/man/bundle-show.1.ronn
@@ -5,7 +5,6 @@ bundle-show(1) -- Shows all the gems in your bundle, or the path to a gem
`bundle show` [GEM]
[--paths]
- [--outdated]
## DESCRIPTION
@@ -20,6 +19,3 @@ machine.
* `--paths`:
List the paths of all gems that are required by your [`Gemfile(5)`][Gemfile(5)],
sorted by gem name.
-
-* `--outdated`:
- Show verbose output including whether gems are outdated.
diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1
index 855a5049aa..2f9932dc1e 100644
--- a/lib/bundler/man/bundle-update.1
+++ b/lib/bundler/man/bundle-update.1
@@ -1,10 +1,10 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-UPDATE" "1" "March 2025" ""
+.TH "BUNDLE\-UPDATE" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-update\fR \- Update your gems to the latest available versions
.SH "SYNOPSIS"
-\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-redownload] [\-\-strict] [\-\-conservative]
+\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative]
.SH "DESCRIPTION"
Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\.
.P
@@ -29,6 +29,9 @@ Update the locked version of Ruby to the current version of Ruby\.
\fB\-\-bundler[=BUNDLER]\fR
Update the locked version of bundler to the invoked bundler version\.
.TP
+\fB\-\-force\fR, \fB\-\-redownload\fR
+Force reinstalling every gem, even if already installed\.
+.TP
\fB\-\-full\-index\fR
Fall back to using the single\-file index of all gems\.
.TP
@@ -44,9 +47,6 @@ Retry failed network or git requests for \fInumber\fR times\.
\fB\-\-quiet\fR
Only output warnings and errors\.
.TP
-\fB\-\-redownload\fR, \fB\-\-force\fR
-Force downloading every gem\.
-.TP
\fB\-\-patch\fR
Prefer updating only to next patch version\.
.TP
diff --git a/lib/bundler/man/bundle-update.1.ronn b/lib/bundler/man/bundle-update.1.ronn
index 1b8b31951d..bfe381677c 100644
--- a/lib/bundler/man/bundle-update.1.ronn
+++ b/lib/bundler/man/bundle-update.1.ronn
@@ -9,13 +9,13 @@ bundle-update(1) -- Update your gems to the latest available versions
[--local]
[--ruby]
[--bundler[=VERSION]]
+ [--force]
[--full-index]
[--gemfile=GEMFILE]
[--jobs=NUMBER]
[--quiet]
[--patch|--minor|--major]
[--pre]
- [--redownload]
[--strict]
[--conservative]
@@ -54,6 +54,9 @@ gem.
* `--bundler[=BUNDLER]`:
Update the locked version of bundler to the invoked bundler version.
+* `--force`, `--redownload`:
+ Force reinstalling every gem, even if already installed.
+
* `--full-index`:
Fall back to using the single-file index of all gems.
@@ -70,9 +73,6 @@ gem.
* `--quiet`:
Only output warnings and errors.
-* `--redownload`, `--force`:
- Force downloading every gem.
-
* `--patch`:
Prefer updating only to next patch version.
diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1
index 17add566d8..6462ec7958 100644
--- a/lib/bundler/man/bundle-version.1
+++ b/lib/bundler/man/bundle-version.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-VERSION" "1" "March 2025" ""
+.TH "BUNDLE\-VERSION" "1" "September 2025" ""
.SH "NAME"
\fBbundle\-version\fR \- Prints Bundler version information
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1
deleted file mode 100644
index 17e6f90cca..0000000000
--- a/lib/bundler/man/bundle-viz.1
+++ /dev/null
@@ -1,30 +0,0 @@
-.\" generated with Ronn-NG/v0.10.1
-.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-VIZ" "1" "March 2025" ""
-.SH "NAME"
-\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
-.SH "SYNOPSIS"
-\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP]
-.SH "DESCRIPTION"
-\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\.
-.P
-The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\.
-.P
-\fBviz\fR command was deprecated in Bundler 2\.2\. Use bundler\-graph plugin \fIhttps://github\.com/rubygems/bundler\-graph\fR instead\.
-.SH "OPTIONS"
-.TP
-\fB\-\-file=FILE\fR, \fB\-f=FILE\fR
-The name to use for the generated file\. See \fB\-\-format\fR option
-.TP
-\fB\-\-format=FORMAT\fR, \fB\-F=FORMAT\fR
-This is output format option\. Supported format is png, jpg, svg, dot \|\.\|\.\|\.
-.TP
-\fB\-\-requirements\fR, \fB\-R\fR
-Set to show the version of each required dependency\.
-.TP
-\fB\-\-version\fR, \fB\-v\fR
-Set to show each gem version\.
-.TP
-\fB\-\-without=<list>\fR, \fB\-W=<list>\fR
-Exclude gems that are part of the specified named group\.
-
diff --git a/lib/bundler/man/bundle-viz.1.ronn b/lib/bundler/man/bundle-viz.1.ronn
deleted file mode 100644
index 730b0eed22..0000000000
--- a/lib/bundler/man/bundle-viz.1.ronn
+++ /dev/null
@@ -1,36 +0,0 @@
-bundle-viz(1) -- Generates a visual dependency graph for your Gemfile
-=====================================================================
-
-## SYNOPSIS
-
-`bundle viz` [--file=FILE]
- [--format=FORMAT]
- [--requirements]
- [--version]
- [--without=GROUP GROUP]
-
-## DESCRIPTION
-
-`viz` generates a PNG file of the current `Gemfile(5)` as a dependency graph.
-`viz` requires the ruby-graphviz gem (and its dependencies).
-
-The associated gems must also be installed via [`bundle install(1)`](bundle-install.1.html).
-
-`viz` command was deprecated in Bundler 2.2. Use [bundler-graph plugin](https://github.com/rubygems/bundler-graph) instead.
-
-## OPTIONS
-
-* `--file=FILE`, `-f=FILE`:
- The name to use for the generated file. See `--format` option
-
-* `--format=FORMAT`, `-F=FORMAT`:
- This is output format option. Supported format is png, jpg, svg, dot ...
-
-* `--requirements`, `-R`:
- Set to show the version of each required dependency.
-
-* `--version`, `-v`:
- Set to show each gem version.
-
-* `--without=<list>`, `-W=<list>`:
- Exclude gems that are part of the specified named group.
diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1
index 3b40f58210..9f7feb4133 100644
--- a/lib/bundler/man/bundle.1
+++ b/lib/bundler/man/bundle.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE" "1" "March 2025" ""
+.TH "BUNDLE" "1" "September 2025" ""
.SH "NAME"
\fBbundle\fR \- Ruby Dependency Management
.SH "SYNOPSIS"
@@ -66,9 +66,6 @@ Open an installed gem in the editor
\fBbundle lock(1)\fR \fIbundle\-lock\.1\.html\fR
Generate a lockfile for your dependencies
.TP
-\fBbundle viz(1)\fR \fIbundle\-viz\.1\.html\fR (deprecated)
-Generate a visual representation of your dependencies
-.TP
\fBbundle init(1)\fR \fIbundle\-init\.1\.html\fR
Generate a simple \fBGemfile\fR, placed in the current directory
.TP
@@ -94,9 +91,3 @@ Manage Bundler plugins
Prints Bundler version information
.SH "PLUGINS"
When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named \fBbundler\-<command>\fR and execute it, passing down any extra arguments to it\.
-.SH "OBSOLETE"
-These commands are obsolete and should no longer be used:
-.IP "\(bu" 4
-\fBbundle inject(1)\fR
-.IP "" 0
-
diff --git a/lib/bundler/man/bundle.1.ronn b/lib/bundler/man/bundle.1.ronn
index 8245effabd..1c2b3df7af 100644
--- a/lib/bundler/man/bundle.1.ronn
+++ b/lib/bundler/man/bundle.1.ronn
@@ -76,9 +76,6 @@ We divide `bundle` subcommands into primary commands and utilities:
* [`bundle lock(1)`](bundle-lock.1.html):
Generate a lockfile for your dependencies
-* [`bundle viz(1)`](bundle-viz.1.html) (deprecated):
- Generate a visual representation of your dependencies
-
* [`bundle init(1)`](bundle-init.1.html):
Generate a simple `Gemfile`, placed in the current directory
@@ -108,9 +105,3 @@ We divide `bundle` subcommands into primary commands and utilities:
When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES,
Bundler will try to find an executable on your path named `bundler-<command>`
and execute it, passing down any extra arguments to it.
-
-## OBSOLETE
-
-These commands are obsolete and should no longer be used:
-
-* `bundle inject(1)`
diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5
index f52864a2bf..a8c055a0c1 100644
--- a/lib/bundler/man/gemfile.5
+++ b/lib/bundler/man/gemfile.5
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "GEMFILE" "5" "March 2025" ""
+.TH "GEMFILE" "5" "September 2025" ""
.SH "NAME"
\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs
.SH "SYNOPSIS"
@@ -469,4 +469,35 @@ For implicit gems (dependencies of explicit gems), any source, git, or path repo
.IP "3." 4
If neither of the above conditions are met, the global source will be used\. If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1\.13, so Bundler prints a warning and will abort with an error in the future\.
.IP "" 0
+.SH "LOCKFILE"
+By default, Bundler will create a lockfile by adding \fB\.lock\fR to the end of the Gemfile name\. To change this, use the \fBlockfile\fR method:
+.IP "" 4
+.nf
+lockfile "/path/to/lockfile\.lock"
+.fi
+.IP "" 0
+.P
+This is useful when you want to use different lockfiles per ruby version or platform\.
+.P
+To avoid writing a lock file, use \fBfalse\fR as the argument:
+.IP "" 4
+.nf
+lockfile false
+.fi
+.IP "" 0
+.P
+This is useful for library development and other situations where the code is expected to work with a range of dependency versions\.
+.SS "LOCKFILE PRECEDENCE"
+When determining path to the lockfile or whether to create a lockfile, the following precedence is used:
+.IP "1." 4
+The \fBbundle install\fR \fB\-\-no\-lock\fR option (which disables lockfile creation)\.
+.IP "2." 4
+The \fBbundle install\fR \fB\-\-lockfile\fR option\.
+.IP "3." 4
+The \fBBUNDLE_LOCKFILE\fR environment variable\.
+.IP "4." 4
+The \fBlockfile\fR method in the Gemfile\.
+.IP "5." 4
+The default behavior of adding \fB\.lock\fR to the end of the Gemfile name\.
+.IP "" 0
diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn
index 802549737e..18d7bb826e 100644
--- a/lib/bundler/man/gemfile.5.ronn
+++ b/lib/bundler/man/gemfile.5.ronn
@@ -556,3 +556,31 @@ bundler uses the following priority order:
If multiple global sources are specified, they will be prioritized from
last to first, but this is deprecated since Bundler 1.13, so Bundler prints
a warning and will abort with an error in the future.
+
+## LOCKFILE
+
+By default, Bundler will create a lockfile by adding `.lock` to the end of the
+Gemfile name. To change this, use the `lockfile` method:
+
+ lockfile "/path/to/lockfile.lock"
+
+This is useful when you want to use different lockfiles per ruby version or
+platform.
+
+To avoid writing a lock file, use `false` as the argument:
+
+ lockfile false
+
+This is useful for library development and other situations where the code is
+expected to work with a range of dependency versions.
+
+### LOCKFILE PRECEDENCE
+
+When determining path to the lockfile or whether to create a lockfile, the
+following precedence is used:
+
+1. The `bundle install` `--no-lock` option (which disables lockfile creation).
+1. The `bundle install` `--lockfile` option.
+1. The `BUNDLE_LOCKFILE` environment variable.
+1. The `lockfile` method in the Gemfile.
+1. The default behavior of adding `.lock` to the end of the Gemfile name.
diff --git a/lib/bundler/man/index.txt b/lib/bundler/man/index.txt
index 3ea3495f1b..f610ba852a 100644
--- a/lib/bundler/man/index.txt
+++ b/lib/bundler/man/index.txt
@@ -15,7 +15,6 @@ bundle-gem(1) bundle-gem.1
bundle-help(1) bundle-help.1
bundle-info(1) bundle-info.1
bundle-init(1) bundle-init.1
-bundle-inject(1) bundle-inject.1
bundle-install(1) bundle-install.1
bundle-issue(1) bundle-issue.1
bundle-licenses(1) bundle-licenses.1
@@ -30,4 +29,3 @@ bundle-remove(1) bundle-remove.1
bundle-show(1) bundle-show.1
bundle-update(1) bundle-update.1
bundle-version(1) bundle-version.1
-bundle-viz(1) bundle-viz.1
diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb
index ece9fb8679..479818e5ec 100644
--- a/lib/bundler/match_platform.rb
+++ b/lib/bundler/match_platform.rb
@@ -1,23 +1,42 @@
# frozen_string_literal: true
-require_relative "gem_helpers"
-
module Bundler
module MatchPlatform
- include GemHelpers
+ def installable_on_platform?(target_platform) # :nodoc:
+ return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform)
+ return true if Gem::Platform.new(platform) === target_platform
- def match_platform(p)
- MatchPlatform.platforms_match?(platform, p)
+ false
end
- def self.platforms_match?(gemspec_platform, local_platform)
- return true if gemspec_platform.nil?
- return true if gemspec_platform == Gem::Platform::RUBY
- return true if local_platform == gemspec_platform
- gemspec_platform = Gem::Platform.new(gemspec_platform)
- return true if gemspec_platform === local_platform
+ def self.select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
+ matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked)
- false
+ Gem::Platform.sort_and_filter_best_platform_match(matching, platform)
+ end
+
+ def self.select_best_local_platform_match(specs, force_ruby: false)
+ local = Bundler.local_platform
+ matching = select_all_platform_match(specs, local, force_ruby: force_ruby).filter_map(&:materialized_for_installation)
+
+ Gem::Platform.sort_best_platform_match(matching, local)
+ end
+
+ def self.select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false)
+ matching = specs.select {|spec| spec.installable_on_platform?(force_ruby ? Gem::Platform::RUBY : platform) }
+
+ specs.each(&:force_ruby_platform!) if force_ruby
+
+ if prefer_locked
+ locked_originally = matching.select {|spec| spec.is_a?(::Bundler::LazySpecification) }
+ return locked_originally if locked_originally.any?
+ end
+
+ matching
+ end
+
+ def self.generic_local_platform_is_ruby?
+ Bundler.generic_local_platform == Gem::Platform::RUBY
end
end
end
diff --git a/lib/bundler/materialization.rb b/lib/bundler/materialization.rb
index 6542c07649..82e48464a7 100644
--- a/lib/bundler/materialization.rb
+++ b/lib/bundler/materialization.rb
@@ -22,14 +22,14 @@ module Bundler
@specs ||= if @candidates.nil?
[]
elsif platform
- GemHelpers.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform)
+ MatchPlatform.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform)
else
- GemHelpers.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform)
+ MatchPlatform.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform)
end
end
def dependencies
- specs.first.runtime_dependencies.map {|d| [d, platform] }
+ (materialized_spec || specs.first).runtime_dependencies.map {|d| [d, platform] }
end
def materialized_spec
diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb
index 44129cc0ff..fd6da6cf6d 100644
--- a/lib/bundler/plugin.rb
+++ b/lib/bundler/plugin.rb
@@ -220,7 +220,7 @@ module Bundler
#
# @param [String] event
def hook(event, *args, &arg_blk)
- return unless Bundler.feature_flag.plugins?
+ return unless Bundler.settings[:plugins]
unless Events.defined_event?(event)
raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events"
end
diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb
index ac3c3ea7f3..853ad9edca 100644
--- a/lib/bundler/plugin/installer.rb
+++ b/lib/bundler/plugin/installer.rb
@@ -43,16 +43,6 @@ module Bundler
private
def check_sources_consistency!(options)
- if options.key?(:git) && options.key?(:local_git)
- raise InvalidOption, "Remote and local plugin git sources can't be both specified"
- end
-
- # back-compat; local_git is an alias for git
- if options.key?(:local_git)
- Bundler::SharedHelpers.major_deprecation(2, "--local_git is deprecated, use --git")
- options[:git] = options.delete(:local_git)
- end
-
if (options.keys & [:source, :git, :path]).length > 1
raise InvalidOption, "Only one of --source, --git, or --path may be specified"
end
diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb
index 746996de55..d929ade29e 100644
--- a/lib/bundler/plugin/source_list.rb
+++ b/lib/bundler/plugin/source_list.rb
@@ -23,7 +23,7 @@ module Bundler
private
- def rubygems_aggregate_class
+ def source_class
Plugin::Installer::Rubygems
end
end
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index f5d1c57a11..1dbf565d46 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -14,8 +14,6 @@ module Bundler
require_relative "resolver/root"
require_relative "resolver/strategy"
- include GemHelpers
-
def initialize(base, gem_version_promoter, most_specific_locked_platform = nil)
@source_requirements = base.source_requirements
@base = base
@@ -167,7 +165,7 @@ module Bundler
PubGrub::VersionConstraint.new(package, range: range)
end
- def versions_for(package, range=VersionRange.any)
+ def versions_for(package, range = VersionRange.any)
range.select_versions(@sorted_versions[package])
end
@@ -273,7 +271,7 @@ module Bundler
next groups if platform_specs.all?(&:empty?)
end
- ruby_specs = select_best_platform_match(specs, Gem::Platform::RUBY)
+ ruby_specs = MatchPlatform.select_best_platform_match(specs, Gem::Platform::RUBY)
ruby_group = Resolver::SpecGroup.new(ruby_specs)
unless ruby_group.empty?
diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb
index 0e86a4f84d..3906be3f57 100644
--- a/lib/bundler/resolver/package.rb
+++ b/lib/bundler/resolver/package.rb
@@ -21,6 +21,7 @@ module Bundler
@locked_version = locked_specs.version_for(name)
@unlock = unlock
@dependency = dependency || Dependency.new(name, @locked_version)
+ @platforms |= [Gem::Platform::RUBY] if @dependency.default_force_ruby_platform
@top_level = !dependency.nil?
@prerelease = @dependency.prerelease? || @locked_version&.prerelease? || prerelease ? :consider_first : :ignore
@prefer_local = prefer_local
@@ -30,7 +31,7 @@ module Bundler
def platform_specs(specs)
platforms.map do |platform|
prefer_locked = @new_platforms.include?(platform) ? false : !unlock?
- GemHelpers.select_best_platform_match(specs, platform, prefer_locked: prefer_locked)
+ MatchPlatform.select_best_platform_match(specs, platform, prefer_locked: prefer_locked)
end
end
diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb
index cd88253f46..5e52f38c8f 100644
--- a/lib/bundler/ruby_dsl.rb
+++ b/lib/bundler/ruby_dsl.rb
@@ -42,21 +42,26 @@ module Bundler
# Loads the file relative to the dirname of the Gemfile itself.
def normalize_ruby_file(filename)
file_content = Bundler.read_file(gemfile.dirname.join(filename))
- # match "ruby-3.2.2", ruby = "3.2.2" or "ruby 3.2.2" capturing version string up to the first space or comment
- if /^ # Start of line
- ruby # Literal "ruby"
- [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format)
- (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format)
- "? # Optional opening quote
- ( # Start capturing group
- [^\s#"]+ # One or more chars that aren't spaces, #, or quotes
- ) # End capturing group
- "? # Optional closing quote
- /x.match(file_content)
- $1
+ # match "ruby-3.2.2", ruby = "3.2.2", ruby = '3.2.2' or "ruby 3.2.2" capturing version string up to the first space or comment
+ version_match = /^ # Start of line
+ ruby # Literal "ruby"
+ [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format)
+ (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format)
+ (?:
+ "([^"]+)" # Double quoted version
+ |
+ '([^']+)' # Single quoted version
+ |
+ ([^\s#"']+) # Unquoted version
+ )
+ /x.match(file_content)
+ if version_match
+ version_match[1] || version_match[2] || version_match[3]
else
file_content.strip
end
+ rescue Errno::ENOENT
+ raise GemfileError, "Could not find version file #{filename}"
end
end
end
diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb
index 0ed5cbc6ca..7f60dde476 100644
--- a/lib/bundler/ruby_version.rb
+++ b/lib/bundler/ruby_version.rb
@@ -43,7 +43,6 @@ module Bundler
def to_s(versions = self.versions)
output = String.new("ruby #{versions_string(versions)}")
- output << "p#{patchlevel}" if patchlevel && patchlevel != "-1"
output << " (#{engine} #{versions_string(engine_versions)})" unless engine == "ruby"
output
@@ -72,8 +71,7 @@ module Bundler
def ==(other)
versions == other.versions &&
engine == other.engine &&
- engine_versions == other.engine_versions &&
- patchlevel == other.patchlevel
+ engine_versions == other.engine_versions
end
def host
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index 1f3fb0fdde..fedf44b0e6 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -13,15 +13,6 @@ require "rubygems" unless defined?(Gem)
# `Gem::Source` from the redefined `Gem::Specification#source`.
require "rubygems/source"
-# Cherry-pick fixes to `Gem.ruby_version` to be useful for modern Bundler
-# versions and ignore patchlevels
-# (https://github.com/rubygems/rubygems/pull/5472,
-# https://github.com/rubygems/rubygems/pull/5486). May be removed once RubyGems
-# 3.3.12 support is dropped.
-unless Gem.ruby_version.to_s == RUBY_VERSION || RUBY_PATCHLEVEL == -1
- Gem.instance_variable_set(:@ruby_version, Gem::Version.new(RUBY_VERSION))
-end
-
module Gem
# Can be removed once RubyGems 3.5.11 support is dropped
unless Gem.respond_to?(:freebsd_platform?)
@@ -61,81 +52,122 @@ module Gem
require "rubygems/platform"
class Platform
- JAVA = Gem::Platform.new("java")
- MSWIN = Gem::Platform.new("mswin32")
- MSWIN64 = Gem::Platform.new("mswin64")
- MINGW = Gem::Platform.new("x86-mingw32")
- X64_MINGW = [Gem::Platform.new("x64-mingw32"),
- Gem::Platform.new("x64-mingw-ucrt")].freeze
- UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw")
- WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].flatten.freeze
- X64_LINUX = Gem::Platform.new("x86_64-linux")
- X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl")
-
- if X64_LINUX === X64_LINUX_MUSL
- remove_method :===
-
- def ===(other)
- return nil unless Gem::Platform === other
-
- # universal-mingw32 matches x64-mingw-ucrt
- return true if (@cpu == "universal" || other.cpu == "universal") &&
- @os.start_with?("mingw") && other.os.start_with?("mingw")
-
- # cpu
- ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu ||
- (@cpu == "arm" && other.cpu.start_with?("armv"))) &&
-
- # os
- @os == other.os &&
-
- # version
- (
- (@os != "linux" && (@version.nil? || other.version.nil?)) ||
- (@os == "linux" && (normalized_linux_version_ext == other.normalized_linux_version_ext || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) ||
- @version == other.version
- )
- end
+ # Can be removed once RubyGems 3.6.9 support is dropped
+ unless respond_to?(:generic)
+ JAVA = Gem::Platform.new("java") # :nodoc:
+ MSWIN = Gem::Platform.new("mswin32") # :nodoc:
+ MSWIN64 = Gem::Platform.new("mswin64") # :nodoc:
+ MINGW = Gem::Platform.new("x86-mingw32") # :nodoc:
+ X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc:
+ X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc:
+ UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc:
+ WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc:
+ X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc:
+ X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc:
+
+ GENERICS = [JAVA, *WINDOWS].freeze # :nodoc:
+ private_constant :GENERICS
+
+ GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc:
+ private_constant :GENERIC_CACHE
- # This is a copy of RubyGems 3.3.23 or higher `normalized_linux_method`.
- # Once only 3.3.23 is supported, we can use the method in RubyGems.
- def normalized_linux_version_ext
- return nil unless @version
+ class << self
+ ##
+ # Returns the generic platform for the given platform.
- without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "")
- return nil if without_gnu_nor_abi_modifiers.empty?
+ def generic(platform)
+ return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY
- without_gnu_nor_abi_modifiers
- end
- end
- end
+ GENERIC_CACHE[platform] ||= begin
+ found = GENERICS.find do |match|
+ platform === match
+ end
+ found || Gem::Platform::RUBY
+ end
+ end
- Platform.singleton_class.module_eval do
- unless Platform.singleton_methods.include?(:match_spec?)
- def match_spec?(spec)
- match_gem?(spec.platform, spec.name)
- end
+ ##
+ # Returns the platform specificity match for the given spec platform and user platform.
- def match_gem?(platform, gem_name)
- match_platforms?(platform, Gem.platforms)
- end
- end
+ def platform_specificity_match(spec_platform, user_platform)
+ return -1 if spec_platform == user_platform
+ return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
+
+ os_match(spec_platform, user_platform) +
+ cpu_match(spec_platform, user_platform) * 10 +
+ version_match(spec_platform, user_platform) * 100
+ end
+
+ ##
+ # Sorts and filters the best platform match for the given matching specs and platform.
- match_platforms_defined = Gem::Platform.respond_to?(:match_platforms?, true)
+ def sort_and_filter_best_platform_match(matching, platform)
+ return matching if matching.one?
- if !match_platforms_defined || Gem::Platform.send(:match_platforms?, Gem::Platform::X64_LINUX_MUSL, [Gem::Platform::X64_LINUX])
+ exact = matching.select {|spec| spec.platform == platform }
+ return exact if exact.any?
- private
+ sorted_matching = sort_best_platform_match(matching, platform)
+ exemplary_spec = sorted_matching.first
- remove_method :match_platforms? if match_platforms_defined
+ sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) }
+ end
+
+ ##
+ # Sorts the best platform match for the given matching specs and platform.
+
+ def sort_best_platform_match(matching, platform)
+ matching.sort_by.with_index do |spec, i|
+ [
+ platform_specificity_match(spec.platform, platform),
+ i, # for stable sort
+ ]
+ end
+ end
+
+ private
+
+ def same_specificity?(platform, spec, exemplary_spec)
+ platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform)
+ end
- def match_platforms?(platform, platforms)
- platforms.any? do |local_platform|
- platform.nil? ||
- local_platform == platform ||
- (local_platform != Gem::Platform::RUBY && platform =~ local_platform)
+ def same_deps?(spec, exemplary_spec)
+ spec.required_ruby_version == exemplary_spec.required_ruby_version &&
+ spec.required_rubygems_version == exemplary_spec.required_rubygems_version &&
+ spec.dependencies.sort == exemplary_spec.dependencies.sort
+ end
+
+ def os_match(spec_platform, user_platform)
+ if spec_platform.os == user_platform.os
+ 0
+ else
+ 1
+ end
+ end
+
+ def cpu_match(spec_platform, user_platform)
+ if spec_platform.cpu == user_platform.cpu
+ 0
+ elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
+ 0
+ elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
+ 1
+ else
+ 2
+ end
+ end
+
+ def version_match(spec_platform, user_platform)
+ if spec_platform.version == user_platform.version
+ 0
+ elsif spec_platform.version.nil?
+ 1
+ else
+ 2
+ end
end
end
+
end
end
@@ -144,9 +176,6 @@ module Gem
# Can be removed once RubyGems 3.5.14 support is dropped
VALIDATES_FOR_RESOLUTION = Specification.new.respond_to?(:validate_for_resolution).freeze
- # Can be removed once RubyGems 3.3.15 support is dropped
- FLATTENS_REQUIRED_PATHS = Specification.new.respond_to?(:flatten_require_paths).freeze
-
class Specification
# Can be removed once RubyGems 3.5.15 support is dropped
correct_array_attributes = @@default_value.select {|_k,v| v.is_a?(Array) }.keys
@@ -158,7 +187,6 @@ module Gem
require_relative "match_platform"
include ::Bundler::MatchMetadata
- include ::Bundler::MatchPlatform
attr_accessor :remote, :relative_loaded_from
@@ -214,23 +242,6 @@ module Gem
full_gem_path
end
- unless const_defined?(:LATEST_RUBY_WITHOUT_PATCH_VERSIONS)
- LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1")
-
- alias_method :rg_required_ruby_version=, :required_ruby_version=
- def required_ruby_version=(req)
- self.rg_required_ruby_version = req
-
- @required_ruby_version.requirements.map! do |op, v|
- if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4
- [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))]
- else
- [op, v]
- end
- end
- end
- end
-
def insecurely_materialized?
false
end
@@ -272,25 +283,16 @@ module Gem
end
end
- unless FLATTENS_REQUIRED_PATHS
- def flatten_require_paths
- return unless raw_require_paths.first.is_a?(Array)
-
- warn "#{name} #{version} includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this"
- raw_require_paths.flatten!
- end
+ if Gem.rubygems_version < Gem::Version.new("3.5.22")
+ module FixPathSourceMissingExtensions
+ def missing_extensions?
+ return false if %w[Bundler::Source::Path Bundler::Source::Gemspec].include?(source.class.name)
- class << self
- module RequirePathFlattener
- def from_yaml(input)
- spec = super(input)
- spec.flatten_require_paths
- spec
- end
+ super
end
-
- prepend RequirePathFlattener
end
+
+ prepend FixPathSourceMissingExtensions
end
private
@@ -401,6 +403,11 @@ module Gem
@ignored = missing_extensions?
end
end
+
+ # Can be removed once RubyGems 3.6.9 support is dropped
+ unless new.respond_to?(:installable_on_platform?)
+ include(::Bundler::MatchPlatform)
+ end
end
require "rubygems/name_tuple"
@@ -410,7 +417,7 @@ module Gem
unless Gem::NameTuple.new("a", Gem::Version.new("1"), Gem::Platform.new("x86_64-linux")).platform.is_a?(String)
alias_method :initialize_with_platform, :initialize
- def initialize(name, version, platform=Gem::Platform::RUBY)
+ def initialize(name, version, platform = Gem::Platform::RUBY)
if Gem::Platform === platform
initialize_with_platform(name, version, platform.to_s)
else
@@ -471,15 +478,4 @@ module Gem
Package::TarReader::Entry.prepend(FixFullNameEncoding)
end
-
- require "rubygems/uri"
-
- # Can be removed once RubyGems 3.3.15 support is dropped
- unless Gem::Uri.respond_to?(:redact)
- class Uri
- def self.redact(uri)
- new(uri).redacted
- end
- end
- end
end
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
index 1af1b85ff0..64ce6193d3 100644
--- a/lib/bundler/rubygems_gem_installer.rb
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -69,7 +69,7 @@ module Bundler
end
def generate_plugins
- return unless Gem::Installer.instance_methods(false).include?(:generate_plugins)
+ return unless Gem::Installer.method_defined?(:generate_plugins, false)
latest = Gem::Specification.stubs_for(spec.name).first
return if latest && latest.version > spec.version
@@ -103,6 +103,10 @@ module Bundler
end
end
+ def build_jobs
+ Bundler.settings[:jobs] || super
+ end
+
def build_extensions
extension_cache_path = options[:bundler_extension_cache_path]
extension_dir = spec.extension_dir
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index eddf36278c..e04ef23259 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -214,16 +214,11 @@ module Bundler
e.requirement = dep.requirement
raise e
end
-
- # backwards compatibility shim, see https://github.com/rubygems/bundler/issues/5102
- kernel_class.send(:public, :gem) if Bundler.feature_flag.setup_makes_kernel_gem_public?
end
end
# Used to give better error messages when activating specs outside of the current bundle
def replace_bin_path(specs_by_name)
- gem_class = (class << Gem; self; end)
-
redefine_method(gem_class, :find_spec_for_exe) do |gem_name, *args|
exec_name = args.first
raise ArgumentError, "you must supply exec_name" unless exec_name
@@ -345,9 +340,13 @@ module Bundler
Gem::Specification.all = specs
end
- redefine_method((class << Gem; self; end), :finish_resolve) do |*|
+ redefine_method(gem_class, :finish_resolve) do |*|
[]
end
+
+ redefine_method(gem_class, :load_plugins) do |*|
+ load_plugin_files specs.flat_map(&:plugins)
+ end
end
def plain_specs
@@ -417,11 +416,7 @@ module Bundler
end
def all_specs
- SharedHelpers.major_deprecation 2, "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs"
-
- Gem::Specification.stubs.map do |stub|
- StubSpecification.from_stub(stub)
- end
+ SharedHelpers.feature_removed! "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs"
end
def installed_specs
@@ -437,7 +432,7 @@ module Bundler
end
def find_bundler(version)
- find_name("bundler").find {|s| s.version.to_s == version }
+ find_name("bundler").find {|s| s.version.to_s == version.to_s }
end
def find_name(name)
@@ -447,6 +442,12 @@ module Bundler
def default_stubs
Gem::Specification.default_stubs("*.gemspec")
end
+
+ private
+
+ def gem_class
+ class << Gem; self; end
+ end
end
def self.rubygems
diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb
index 444b085bed..5280e72aa2 100644
--- a/lib/bundler/runtime.rb
+++ b/lib/bundler/runtime.rb
@@ -71,7 +71,7 @@ module Bundler
raise Bundler::GemRequireError.new e,
"There was an error while trying to load the gem '#{file}'."
end
- rescue RuntimeError => e
+ rescue StandardError => e
raise Bundler::GemRequireError.new e,
"There was an error while trying to load the gem '#{file}'."
end
@@ -174,7 +174,14 @@ module Bundler
spec_cache_paths = []
spec_gemspec_paths = []
spec_extension_paths = []
- Bundler.rubygems.add_default_gems_to(specs).values.each do |spec|
+ specs_to_keep = Bundler.rubygems.add_default_gems_to(specs).values
+
+ current_bundler = Bundler.rubygems.find_bundler(Bundler.gem_version)
+ if current_bundler
+ specs_to_keep << current_bundler
+ end
+
+ specs_to_keep.each do |spec|
spec_gem_paths << spec.full_gem_path
# need to check here in case gems are nested like for the rails git repo
md = %r{(.+bundler/gems/.+-[a-f0-9]{7,12})}.match(spec.full_gem_path)
@@ -240,7 +247,11 @@ module Bundler
cached.each do |path|
Bundler.ui.info " * #{File.basename(path)}"
- File.delete(path)
+
+ begin
+ File.delete(path)
+ rescue Errno::ENOENT
+ end
end
end
end
diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb
index 2aeac6be52..1db77fd46b 100644
--- a/lib/bundler/self_manager.rb
+++ b/lib/bundler/self_manager.rb
@@ -7,13 +7,15 @@ module Bundler
#
class SelfManager
def restart_with_locked_bundler_if_needed
- return unless needs_switching? && installed?
+ restart_version = find_restart_version
+ return unless restart_version && installed?(restart_version)
restart_with(restart_version)
end
def install_locked_bundler_and_restart_with_it_if_needed
- return unless needs_switching?
+ restart_version = find_restart_version
+ return unless restart_version
if restart_version == lockfile_version
Bundler.ui.info \
@@ -29,8 +31,6 @@ module Bundler
end
def update_bundler_and_restart_with_it_if_needed(target)
- return unless autoswitching_applies?
-
spec = resolve_update_version_from(target)
return unless spec
@@ -38,7 +38,7 @@ module Bundler
Bundler.ui.info "Updating bundler to #{version}."
- install(spec)
+ install(spec) unless installed?(version)
restart_with(version)
end
@@ -68,47 +68,37 @@ module Bundler
def restart_with(version)
configured_gem_home = ENV["GEM_HOME"]
+ configured_orig_gem_home = ENV["BUNDLER_ORIG_GEM_HOME"]
configured_gem_path = ENV["GEM_PATH"]
+ configured_orig_gem_path = ENV["BUNDLER_ORIG_GEM_PATH"]
- # Bundler specs need some stuff to be required before Bundler starts
- # running, for example, for faking the compact index API. However, these
- # flags are lost when we reexec to a different version of Bundler. In the
- # future, we may be able to properly reconstruct the original Ruby
- # invocation (see https://bugs.ruby-lang.org/issues/6648), but for now
- # there's no way to do it, so we need to be explicit about how to re-exec.
- # This may be a feature end users request at some point, but maybe by that
- # time, we have builtin tools to do. So for now, we use an undocumented
- # ENV variable only for our specs.
- bundler_spec_original_cmd = ENV["BUNDLER_SPEC_ORIGINAL_CMD"]
- if bundler_spec_original_cmd
- require "shellwords"
- cmd = [*Shellwords.shellsplit(bundler_spec_original_cmd), *ARGV]
- else
- argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0
- cmd = [argv0, *ARGV]
- cmd.unshift(Gem.ruby) unless File.executable?(argv0)
- end
+ argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0
+ cmd = [argv0, *ARGV]
+ cmd.unshift(Gem.ruby) unless File.executable?(argv0)
Bundler.with_original_env do
Kernel.exec(
- { "GEM_HOME" => configured_gem_home, "GEM_PATH" => configured_gem_path, "BUNDLER_VERSION" => version.to_s },
+ {
+ "GEM_HOME" => configured_gem_home,
+ "BUNDLER_ORIG_GEM_HOME" => configured_orig_gem_home,
+ "GEM_PATH" => configured_gem_path,
+ "BUNDLER_ORIG_GEM_PATH" => configured_orig_gem_path,
+ "BUNDLER_VERSION" => version.to_s,
+ },
*cmd
)
end
end
- def needs_switching?
+ def needs_switching?(restart_version)
autoswitching_applies? &&
- Bundler.settings[:version] != "system" &&
released?(restart_version) &&
- !running?(restart_version) &&
- !updating?
+ !running?(restart_version)
end
def autoswitching_applies?
- ENV["BUNDLER_VERSION"].nil? &&
+ (ENV["BUNDLER_VERSION"].nil? || ENV["BUNDLER_VERSION"].empty?) &&
ruby_can_restart_with_same_arguments? &&
- SharedHelpers.in_bundle? &&
lockfile_version
end
@@ -142,6 +132,7 @@ module Bundler
end
def find_latest_matching_spec(requirement)
+ Bundler.configure
local_result = find_latest_matching_spec_from_collection(local_specs, requirement)
return local_result if local_result && requirement.specific?
@@ -171,18 +162,14 @@ module Bundler
$PROGRAM_NAME != "-e"
end
- def updating?
- "update".start_with?(ARGV.first || " ") && ARGV[1..-1].any? {|a| a.start_with?("--bundler") }
- end
-
- def installed?
+ def installed?(restart_version)
Bundler.configure
Bundler.rubygems.find_bundler(restart_version.to_s)
end
def current_version
- @current_version ||= Gem::Version.new(Bundler::VERSION)
+ @current_version ||= Bundler.gem_version
end
def lockfile_version
@@ -194,13 +181,16 @@ module Bundler
@lockfile_version = nil
end
- def restart_version
- return @restart_version if defined?(@restart_version)
- # BUNDLE_VERSION=x.y.z
- @restart_version = Gem::Version.new(Bundler.settings[:version])
- rescue ArgumentError
- # BUNDLE_VERSION=lockfile
- @restart_version = lockfile_version
+ def find_restart_version
+ return unless SharedHelpers.in_bundle?
+
+ configured_version = Bundler.settings[:version]
+ return if configured_version == "system"
+
+ restart_version = configured_version == "lockfile" ? lockfile_version : Gem::Version.new(configured_version)
+ return unless needs_switching?(restart_version)
+
+ restart_version
end
end
end
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index cde01e0181..d00a4bb916 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -7,13 +7,10 @@ module Bundler
autoload :Validator, File.expand_path("settings/validator", __dir__)
BOOL_KEYS = %w[
- allow_offline_install
- auto_clean_without_path
auto_install
cache_all
cache_all_platforms
clean
- default_install_uses_path
deployment
disable_checksum_validation
disable_exec_load
@@ -22,11 +19,11 @@ module Bundler
disable_shared_gems
disable_version_check
force_ruby_platform
- forget_cli_options
frozen
gem.changelog
gem.coc
gem.mit
+ gem.bundle
git.allow_insecure
global_gem_cache
ignore_messages
@@ -35,29 +32,13 @@ module Bundler
lockfile_checksums
no_install
no_prune
- path_relative_to_cwd
path.system
plugins
prefer_patch
- print_only_version_number
- setup_makes_kernel_gem_public
silence_deprecations
silence_root_warning
update_requires_all_flag
- ].freeze
-
- REMEMBERED_KEYS = %w[
- bin
- cache_all
- clean
- deployment
- frozen
- no_prune
- path
- shebang
- path.system
- without
- with
+ verbose
].freeze
NUMBER_KEYS = %w[
@@ -84,8 +65,10 @@ module Bundler
gem.rubocop
gem.test
gemfile
+ lockfile
path
shebang
+ simulate_version
system_bindir
trust-policy
version
@@ -99,6 +82,11 @@ module Bundler
"BUNDLE_RETRY" => 3,
"BUNDLE_TIMEOUT" => 10,
"BUNDLE_VERSION" => "lockfile",
+ "BUNDLE_LOCKFILE_CHECKSUMS" => true,
+ "BUNDLE_CACHE_ALL" => true,
+ "BUNDLE_PLUGINS" => true,
+ "BUNDLE_GLOBAL_GEM_CACHE" => false,
+ "BUNDLE_UPDATE_REQUIRES_ALL_FLAG" => false,
}.freeze
def initialize(root = nil)
@@ -130,12 +118,8 @@ module Bundler
end
def set_command_option(key, value)
- if !is_remembered(key) || Bundler.feature_flag.forget_cli_options?
- temporary(key => value)
- value
- else
- set_local(key, value)
- end
+ temporary(key => value)
+ value
end
def set_command_option_if_given(key, value)
@@ -274,7 +258,7 @@ module Bundler
def use_system_gems?
return true if system_path
return false if explicit_path
- !Bundler.feature_flag.default_install_uses_path?
+ !Bundler.feature_flag.bundler_5_mode?
end
def base_path
@@ -389,10 +373,6 @@ module Bundler
ARRAY_KEYS.include?(self.class.key_to_s(key))
end
- def is_remembered(key)
- REMEMBERED_KEYS.include?(self.class.key_to_s(key))
- end
-
def is_credential(key)
key == "gem.push_key"
end
diff --git a/lib/bundler/settings/validator.rb b/lib/bundler/settings/validator.rb
index 0a57ea7f03..9aa1627fb2 100644
--- a/lib/bundler/settings/validator.rb
+++ b/lib/bundler/settings/validator.rb
@@ -74,29 +74,6 @@ module Bundler
fail!(key, value, "`#{other_key}` is current set to #{other_setting.inspect}", "the `#{conflicting.join("`, `")}` groups conflict")
end
end
-
- rule %w[path], "relative paths are expanded relative to the current working directory" do |key, value, settings|
- next if value.nil?
-
- path = Pathname.new(value)
- next if !path.relative? || !Bundler.feature_flag.path_relative_to_cwd?
-
- path = path.expand_path
-
- root = begin
- Bundler.root
- rescue GemfileNotFound
- Pathname.pwd.expand_path
- end
-
- path = begin
- path.relative_path_from(root)
- rescue ArgumentError
- path
- end
-
- set(settings, key, path.to_s)
- end
end
end
end
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index 1ef9d61361..2aa8abe0a0 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -4,8 +4,6 @@ require_relative "version"
require_relative "rubygems_integration"
require_relative "current_ruby"
-autoload :Pathname, "pathname"
-
module Bundler
autoload :WINDOWS, File.expand_path("constants", __dir__)
autoload :FREEBSD, File.expand_path("constants", __dir__)
@@ -25,6 +23,9 @@ module Bundler
end
def default_lockfile
+ given = ENV["BUNDLE_LOCKFILE"]
+ return Pathname.new(given) if given && !given.empty?
+
gemfile = default_gemfile
case gemfile.basename.to_s
@@ -57,7 +58,7 @@ module Bundler
def pwd
Bundler.rubygems.ext_lock.synchronize do
- Pathname.pwd
+ Dir.pwd
end
end
@@ -104,7 +105,8 @@ module Bundler
def filesystem_access(path, action = :write, &block)
yield(path.dup)
rescue Errno::EACCES => e
- raise unless e.message.include?(path.to_s) || action == :create
+ path_basename = File.basename(path.to_s)
+ raise unless e.message.include?(path_basename) || action == :create
raise PermissionError.new(path, action)
rescue Errno::EAGAIN
@@ -125,24 +127,17 @@ module Bundler
raise GenericSystemCallError.new(e, "There was an error #{[:create, :write].include?(action) ? "creating" : "accessing"} `#{path}`.")
end
- def major_deprecation(major_version, message, removed_message: nil, print_caller_location: false)
- if print_caller_location
- caller_location = caller_locations(2, 2).first
- suffix = " (called at #{caller_location.path}:#{caller_location.lineno})"
- message += suffix
- removed_message += suffix if removed_message
- end
+ def feature_deprecated!(message)
+ return unless prints_major_deprecations?
- bundler_major_version = Bundler.bundler_major_version
- if bundler_major_version > major_version
- require_relative "errors"
- raise DeprecatedError, "[REMOVED] #{removed_message || message}"
- end
-
- return unless bundler_major_version >= major_version && prints_major_deprecations?
Bundler.ui.warn("[DEPRECATED] #{message}")
end
+ def feature_removed!(message)
+ require_relative "errors"
+ raise RemovedError, "[REMOVED] #{message}"
+ end
+
def print_major_deprecations!
multiple_gemfiles = search_up(".") do |dir|
gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) }
@@ -306,6 +301,7 @@ module Bundler
def set_bundle_variables
Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", bundle_bin_path
Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s
+ Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", default_lockfile.to_s
Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION
Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__)
end
@@ -386,7 +382,6 @@ module Bundler
end
def prints_major_deprecations?
- require_relative "../bundler"
return false if Bundler.settings[:silence_deprecations]
require_relative "deprecate"
return false if Bundler::Deprecate.skip
diff --git a/lib/bundler/similarity_detector.rb b/lib/bundler/similarity_detector.rb
deleted file mode 100644
index 50e66b9cab..0000000000
--- a/lib/bundler/similarity_detector.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-module Bundler
- class SimilarityDetector
- SimilarityScore = Struct.new(:string, :distance)
-
- # initialize with an array of words to be matched against
- def initialize(corpus)
- @corpus = corpus
- end
-
- # return an array of words similar to 'word' from the corpus
- def similar_words(word, limit = 3)
- words_by_similarity = @corpus.map {|w| SimilarityScore.new(w, levenshtein_distance(word, w)) }
- words_by_similarity.select {|s| s.distance <= limit }.sort_by(&:distance).map(&:string)
- end
-
- # return the result of 'similar_words', concatenated into a list
- # (eg "a, b, or c")
- def similar_word_list(word, limit = 3)
- words = similar_words(word, limit)
- if words.length == 1
- words[0]
- elsif words.length > 1
- [words[0..-2].join(", "), words[-1]].join(" or ")
- end
- end
-
- protected
-
- # https://www.informit.com/articles/article.aspx?p=683059&seqNum=36
- def levenshtein_distance(this, that, ins = 2, del = 2, sub = 1)
- # ins, del, sub are weighted costs
- return nil if this.nil?
- return nil if that.nil?
- dm = [] # distance matrix
-
- # Initialize first row values
- dm[0] = (0..this.length).collect {|i| i * ins }
- fill = [0] * (this.length - 1)
-
- # Initialize first column values
- (1..that.length).each do |i|
- dm[i] = [i * del, fill.flatten]
- end
-
- # populate matrix
- (1..that.length).each do |i|
- (1..this.length).each do |j|
- # critical comparison
- dm[i][j] = [
- dm[i - 1][j - 1] + (this[j - 1] == that[i - 1] ? 0 : sub),
- dm[i][j - 1] + ins,
- dm[i - 1][j] + del,
- ].min
- end
- end
-
- # The last value in matrix is the Levenshtein distance between the strings
- dm[that.length][this.length]
- end
- end
-end
diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb
index 232873503b..2b90a0eff1 100644
--- a/lib/bundler/source.rb
+++ b/lib/bundler/source.rb
@@ -79,7 +79,7 @@ module Bundler
end
def extension_cache_path(spec)
- return unless Bundler.feature_flag.global_gem_cache?
+ return unless Bundler.settings[:global_gem_cache]
return unless source_slug = extension_cache_slug(spec)
Bundler.user_cache.join(
"extensions", Gem::Platform.local.to_s, Bundler.ruby_scope,
diff --git a/lib/bundler/source/gemspec.rb b/lib/bundler/source/gemspec.rb
index b59dce1d09..ed766dbe74 100644
--- a/lib/bundler/source/gemspec.rb
+++ b/lib/bundler/source/gemspec.rb
@@ -10,6 +10,10 @@ module Bundler
super
@gemspec = options["gemspec"]
end
+
+ def to_s
+ "gemspec at `#{@path}`"
+ end
end
end
end
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index d57944ee12..bb669ebba3 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -238,7 +238,7 @@ module Bundler
# across different projects, this cache will be shared.
# When using local git repos, this is set to the local repo.
def cache_path
- @cache_path ||= if Bundler.feature_flag.global_gem_cache?
+ @cache_path ||= if Bundler.settings[:global_gem_cache]
Bundler.user_cache
else
Bundler.bundle_path.join("cache", "bundler")
@@ -268,7 +268,7 @@ module Bundler
private
def cache_to(custom_path, try_migrate: false)
- return unless Bundler.feature_flag.cache_all?
+ return unless Bundler.settings[:cache_all]
app_cache_path = app_cache_path(custom_path)
@@ -416,7 +416,6 @@ module Bundler
def fetch
git_proxy.checkout
rescue GitError => e
- raise unless Bundler.feature_flag.allow_offline_install?
Bundler.ui.warn "Using cached git data because of network errors:\n#{e}"
end
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index 8230584260..cd352c22a7 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -16,7 +16,7 @@ module Bundler
def initialize(command)
msg = String.new
msg << "Bundler is trying to run `#{command}` at runtime. You probably need to run `bundle install`. However, "
- msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md "
+ msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md "
msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
super msg
end
@@ -121,7 +121,7 @@ module Bundler
FileUtils.rm_rf(p)
end
git "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s
- File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination)
+ File.chmod((File.stat(destination).mode | 0o777) & ~File.umask, destination)
rescue Errno::EEXIST => e
file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1]
raise GitError, "Bundler could not install a gem because it needs to " \
@@ -305,8 +305,8 @@ module Bundler
end
def has_revision_cached?
- return unless @revision && path.exist?
- git("cat-file", "-e", @revision, dir: path)
+ return unless commit && path.exist?
+ git("cat-file", "-e", commit, dir: path)
true
rescue GitError
false
@@ -408,11 +408,7 @@ module Bundler
def capture3_args_for(cmd, dir)
return ["git", *cmd] unless dir
- if Bundler.feature_flag.bundler_3_mode? || supports_minus_c?
- ["git", "-C", dir.to_s, *cmd]
- else
- ["git", *cmd, { chdir: dir.to_s }]
- end
+ ["git", "-C", dir.to_s, *cmd]
end
def extra_clone_args
@@ -451,10 +447,6 @@ module Bundler
depth.nil?
end
- def supports_minus_c?
- @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5")
- end
-
def needs_allow_any_sha1_in_want?
@needs_allow_any_sha1_in_want ||= Gem::Version.new(version) <= Gem::Version.new("2.13.7")
end
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index 885dd96d85..82e782ba25 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -24,7 +24,7 @@ module Bundler
@path = Pathname.new(options["path"])
expanded_path = expand(@path)
@path = if @path.relative?
- expanded_path.relative_path_from(root_path.expand_path)
+ expanded_path.relative_path_from(File.expand_path(root_path))
else
expanded_path
end
@@ -53,6 +53,8 @@ module Bundler
"source at `#{@path}`"
end
+ alias_method :identifier, :to_s
+
alias_method :to_gemfile, :path
def hash
@@ -81,7 +83,7 @@ module Bundler
def cache(spec, custom_path = nil)
app_cache_path = app_cache_path(custom_path)
- return unless Bundler.feature_flag.cache_all?
+ return unless Bundler.settings[:cache_all]
return if expand(@original_path).to_s.index(root_path.to_s + "/") == 0
unless @original_path.exist?
@@ -124,11 +126,7 @@ module Bundler
end
def expand(somepath)
- if Bundler.current_ruby.jruby? # TODO: Unify when https://github.com/rubygems/bundler/issues/7598 fixed upstream and all supported jrubies include the fix
- somepath.expand_path(root_path).expand_path
- else
- somepath.expand_path(root_path)
- end
+ somepath.expand_path(root_path)
rescue ArgumentError => e
Bundler.ui.debug(e)
raise PathError, "There was an error while trying to use the path " \
@@ -167,6 +165,13 @@ module Bundler
next unless spec = load_gemspec(file)
spec.source = self
+ # The ignore attribute is for ignoring installed gems that don't
+ # have extensions correctly compiled for activation. In the case of
+ # path sources, there's a single version of each gem in the path
+ # source available to Bundler, so we always certainly want to
+ # consider that for activation and never makes sense to ignore it.
+ spec.ignored = false
+
# Validation causes extension_dir to be calculated, which depends
# on #source, so we validate here instead of load_gemspec
validate_spec(spec)
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index 19800e9c58..e1e030ffc8 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -8,7 +8,7 @@ module Bundler
autoload :Remote, File.expand_path("rubygems/remote", __dir__)
# Ask for X gems per API request
- API_REQUEST_SIZE = 50
+ API_REQUEST_SIZE = 100
attr_accessor :remotes
@@ -168,12 +168,6 @@ module Bundler
return nil # no post-install message
end
- if spec.remote
- # Check for this spec from other sources
- uris = [spec.remote, *remotes_for_spec(spec)].map(&:anonymized_uri).uniq
- Installer.ambiguous_gems << [spec.name, *uris] if uris.length > 1
- end
-
path = fetch_gem_if_possible(spec, options[:previous_spec])
raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path
@@ -217,7 +211,11 @@ module Bundler
message += " with native extensions" if spec.extensions.any?
Bundler.ui.confirm message
- installed_spec = installer.install
+ installed_spec = nil
+
+ Gem.time("Installed #{spec.name} in", 0, true) do
+ installed_spec = installer.install
+ end
spec.full_gem_path = installed_spec.full_gem_path
spec.loaded_from = installed_spec.loaded_from
@@ -332,13 +330,6 @@ module Bundler
remotes.map(&method(:remove_auth))
end
- def remotes_for_spec(spec)
- specs.search_all(spec.name).inject([]) do |uris, s|
- uris << s.remote if s.remote
- uris
- end
- end
-
def cached_gem(spec)
global_cache_path = download_cache_path(spec)
caches << global_cache_path if global_cache_path
@@ -491,7 +482,10 @@ module Bundler
uri = spec.remote.uri
Bundler.ui.confirm("Fetching #{version_message(spec, previous_spec)}")
gem_remote_fetcher = remote_fetchers.fetch(spec.remote).gem_remote_fetcher
- Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher)
+
+ Gem.time("Downloaded #{spec.name} in", 0, true) do
+ Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher)
+ end
end
# Returns the global cache path of the calling Rubygems::Source object.
@@ -506,7 +500,7 @@ module Bundler
# @return [Pathname] The global cache path.
#
def download_cache_path(spec)
- return unless Bundler.feature_flag.global_gem_cache?
+ return unless Bundler.settings[:global_gem_cache]
return unless remote = spec.remote
return unless cache_slug = remote.cache_slug
diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb
index d1308b1dfb..38fa0972e6 100644
--- a/lib/bundler/source_list.rb
+++ b/lib/bundler/source_list.rb
@@ -9,7 +9,7 @@ module Bundler
:metadata_source
def global_rubygems_source
- @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true)
+ @global_rubygems_source ||= source_class.new("allow_local" => true)
end
def initialize
@@ -21,19 +21,9 @@ module Bundler
@rubygems_sources = []
@metadata_source = Source::Metadata.new
- @merged_gem_lockfile_sections = false
@local_mode = true
end
- def merged_gem_lockfile_sections?
- @merged_gem_lockfile_sections
- end
-
- def merged_gem_lockfile_sections!(replacement_source)
- @merged_gem_lockfile_sections = true
- @global_rubygems_source = replacement_source
- end
-
def aggregate_global_source?
global_rubygems_source.multiple_remotes?
end
@@ -90,10 +80,6 @@ module Bundler
@rubygems_sources
end
- def rubygems_remotes
- rubygems_sources.flat_map(&:remotes).uniq
- end
-
def all_sources
path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source]
end
@@ -103,7 +89,7 @@ module Bundler
end
def get(source)
- source_list_for(source).find {|s| equivalent_source?(source, s) }
+ source_list_for(source).find {|s| s.include?(source) }
end
def lock_sources
@@ -115,11 +101,7 @@ module Bundler
end
def lock_rubygems_sources
- if merged_gem_lockfile_sections?
- [combine_rubygems_sources]
- else
- rubygems_sources.sort_by(&:identifier)
- end
+ rubygems_sources.sort_by(&:identifier)
end
# Returns true if there are changes
@@ -129,16 +111,7 @@ module Bundler
@rubygems_sources, @path_sources, @git_sources, @plugin_sources = map_sources(replacement_sources)
@global_rubygems_source = global_replacement_source(replacement_sources)
- different_sources?(lock_sources, replacement_sources)
- end
-
- # Returns true if there are changes
- def expired_sources?(replacement_sources)
- return false if replacement_sources.empty?
-
- lock_sources = dup_with_replaced_sources(replacement_sources).lock_sources
-
- different_sources?(lock_sources, replacement_sources)
+ !equivalent_sources?(lock_sources, replacement_sources)
end
def prefer_local!
@@ -165,12 +138,6 @@ module Bundler
private
- def dup_with_replaced_sources(replacement_sources)
- new_source_list = dup
- new_source_list.replace_sources!(replacement_sources)
- new_source_list
- end
-
def map_sources(replacement_sources)
rubygems = @rubygems_sources.map do |source|
replace_rubygems_source(replacement_sources, source)
@@ -224,11 +191,7 @@ module Bundler
end
end
- def different_sources?(lock_sources, replacement_sources)
- !equivalent_sources?(lock_sources, replacement_sources)
- end
-
- def rubygems_aggregate_class
+ def source_class
Source::Rubygems
end
@@ -247,10 +210,6 @@ module Bundler
end
end
- def combine_rubygems_sources
- Source::Rubygems.new("remotes" => rubygems_remotes)
- end
-
def warn_on_git_protocol(source)
return if Bundler.settings["git.allow_insecure"]
@@ -265,9 +224,5 @@ module Bundler
def equivalent_sources?(lock_sources, replacement_sources)
lock_sources.sort_by(&:identifier) == replacement_sources.sort_by(&:identifier)
end
-
- def equivalent_source?(source, other_source)
- source == other_source
- end
end
end
diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb
index ca73e01f9d..cb88caf1bd 100644
--- a/lib/bundler/source_map.rb
+++ b/lib/bundler/source_map.rb
@@ -23,15 +23,12 @@ module Bundler
if previous_source.nil?
requirements[indirect_dependency_name] = source
else
- no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode?
-
msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."]
msg.concat [previous_source, source].map {|s| " * #{s}" }.sort
- msg << "You #{no_ambiguous_sources ? :must : :should} add this gem to the source block for the source you wish it to be installed from."
+ msg << "You must add this gem to the source block for the source you wish it to be installed from."
msg = msg.join("\n")
- raise SecurityError, msg if no_ambiguous_sources
- Bundler.ui.warn "Warning: #{msg}"
+ raise SecurityError, msg
end
end
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
index 5fa179b978..f9179e7a06 100644
--- a/lib/bundler/spec_set.rb
+++ b/lib/bundler/spec_set.rb
@@ -11,16 +11,11 @@ module Bundler
@specs = specs
end
- def for(dependencies, platforms_or_legacy_check = [nil], legacy_platforms = [nil], skips: [])
- platforms = if [true, false].include?(platforms_or_legacy_check)
- Bundler::SharedHelpers.major_deprecation 2,
+ def for(dependencies, platforms = [nil], legacy_platforms = [nil], skips: [])
+ if [true, false].include?(platforms)
+ Bundler::SharedHelpers.feature_removed! \
"SpecSet#for received a `check` parameter, but that's no longer used and deprecated. " \
- "SpecSet#for always implicitly performs validation. Please remove this parameter",
- print_caller_location: true
-
- legacy_platforms
- else
- platforms_or_legacy_check
+ "SpecSet#for always implicitly performs validation. Please remove this parameter"
end
materialize_dependencies(dependencies, platforms, skips: skips)
@@ -76,7 +71,7 @@ module Bundler
new_platforms = all_platforms.select do |platform|
next if platforms.include?(platform)
- next unless GemHelpers.generic(platform) == Gem::Platform::RUBY
+ next unless Gem::Platform.generic(platform) == Gem::Platform::RUBY
complete_platform(platform)
end
@@ -179,11 +174,11 @@ module Bundler
end
def -(other)
- SpecSet.new(to_a - other.to_a)
+ SharedHelpers.feature_removed! "SpecSet#- has been removed with no replacement"
end
def find_by_name_and_platform(name, platform)
- @specs.detect {|spec| spec.name == name && spec.match_platform(platform) }
+ @specs.detect {|spec| spec.name == name && spec.installable_on_platform?(platform) }
end
def specs_with_additional_variants_from(other)
@@ -210,7 +205,7 @@ module Bundler
end
def <<(spec)
- @specs << spec
+ SharedHelpers.feature_removed! "SpecSet#<< has been removed with no replacement"
end
def length
@@ -280,7 +275,7 @@ module Bundler
valid_platform = lookup.all? do |_, specs|
spec = specs.first
matching_specs = spec.source.specs.search([spec.name, spec.version])
- platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s|
+ platform_spec = MatchPlatform.select_best_platform_match(matching_specs, platform).find do |s|
valid?(s)
end
diff --git a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt
index 67fe8cee79..633baebdd5 100644
--- a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt
+++ b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt
@@ -1,132 +1,10 @@
-# Contributor Covenant Code of Conduct
+# Code of Conduct
-## Our Pledge
+<%= config[:name].inspect %> follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, caste, color, religion, or sexual
-identity and orientation.
+* Participants will be tolerant of opposing views.
+* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
+* When interpreting the words and actions of others, participants should always assume good intentions.
+* Behaviour which can be reasonably considered harassment will not be tolerated.
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
-
-## Our Standards
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
- and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the overall
- community
-
-Examples of unacceptable behavior include:
-
-* The use of sexualized language or imagery, and sexual attention or advances of
- any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email address,
- without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Enforcement Responsibilities
-
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
-
-## Scope
-
-This Code of Conduct applies within all community spaces, and also applies when
-an individual is officially representing the community in public spaces.
-Examples of representing our community include using an official email address,
-posting via an official social media account, or acting as an appointed
-representative at an online or offline event.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-[INSERT CONTACT METHOD].
-All complaints will be reviewed and investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
-
-## Enforcement Guidelines
-
-Community leaders will follow these Community Impact Guidelines in determining
-the consequences for any action they deem in violation of this Code of Conduct:
-
-### 1. Correction
-
-**Community Impact**: Use of inappropriate language or other behavior deemed
-unprofessional or unwelcome in the community.
-
-**Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
-behavior was inappropriate. A public apology may be requested.
-
-### 2. Warning
-
-**Community Impact**: A violation through a single incident or series of
-actions.
-
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction with
-those enforcing the Code of Conduct, for a specified period of time. This
-includes avoiding interactions in community spaces as well as external channels
-like social media. Violating these terms may lead to a temporary or permanent
-ban.
-
-### 3. Temporary Ban
-
-**Community Impact**: A serious violation of community standards, including
-sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No public or
-private interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, is allowed during this period.
-Violating these terms may lead to a permanent ban.
-
-### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
-
-**Consequence**: A permanent ban from any sort of public interaction within the
-community.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.1, available at
-[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
-
-Community Impact Guidelines were inspired by
-[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
-
-For answers to common questions about this code of conduct, see the FAQ at
-[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
-[https://www.contributor-covenant.org/translations][translations].
-
-[homepage]: https://www.contributor-covenant.org
-[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
-[Mozilla CoC]: https://github.com/mozilla/diversity
-[FAQ]: https://www.contributor-covenant.org/faq
-[translations]: https://www.contributor-covenant.org/translations
+If you have any concerns about behaviour within this project, please contact us at [<%= config[:email].inspect %>](mailto:<%= config[:email].inspect %>).
diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt
index 172183d4b4..83f10009c7 100644
--- a/lib/bundler/templates/newgem/Rakefile.tt
+++ b/lib/bundler/templates/newgem/Rakefile.tt
@@ -59,6 +59,11 @@ Rake::ExtensionTask.new("<%= config[:underscored_name] %>", GEMSPEC) do |ext|
end
<% end -%>
+<% if config[:ext] == "go" -%>
+require "go_gem/rake_task"
+
+GoGem::RakeTask.new("<%= config[:underscored_name] %>")
+<% end -%>
<% end -%>
<% if default_task_names.size == 1 -%>
task default: <%= default_task_names.first.inspect %>
diff --git a/lib/bundler/templates/newgem/circleci/config.yml.tt b/lib/bundler/templates/newgem/circleci/config.yml.tt
index f40f029bf1..c4dd9d0647 100644
--- a/lib/bundler/templates/newgem/circleci/config.yml.tt
+++ b/lib/bundler/templates/newgem/circleci/config.yml.tt
@@ -7,6 +7,10 @@ jobs:
environment:
RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true'
<%- end -%>
+<%- if config[:ext] == 'go' -%>
+ environment:
+ GO_VERSION: '1.23.0'
+<%- end -%>
steps:
- checkout
<%- if config[:ext] == 'rust' -%>
@@ -17,6 +21,14 @@ jobs:
name: Install a RubyGems version that can compile rust extensions
command: gem update --system '<%= ::Gem.rubygems_version %>'
<%- end -%>
+<%- if config[:ext] == 'go' -%>
+ - run:
+ name: Install Go
+ command: |
+ wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
+ tar -C /usr/local -xzf /tmp/go.tar.gz
+ echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV"
+<%- end -%>
- run:
name: Run the default task
command: |
diff --git a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt
index 0ebce0e4a0..c0dc63fbfa 100644
--- a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt
+++ b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt
@@ -12,4 +12,4 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
-magnus = { version = "0.6.2" }
+magnus = { version = "0.8.2" }
diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt
new file mode 100644
index 0000000000..a689e21ebe
--- /dev/null
+++ b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require "mkmf"
+require "go_gem/mkmf"
+
+# Makes all symbols private by default to avoid unintended conflict
+# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED
+# selectively, or entirely remove this flag.
+append_cflags("-fvisibility=hidden")
+
+create_go_makefile(<%= config[:makefile_path].inspect %>)
diff --git a/lib/bundler/templates/newgem/ext/newgem/go.mod.tt b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt
new file mode 100644
index 0000000000..3f4819d004
--- /dev/null
+++ b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt
@@ -0,0 +1,5 @@
+module github.com/<%= config[:go_module_username] %>/<%= config[:underscored_name] %>
+
+go 1.23
+
+require github.com/ruby-go-gem/go-gem-wrapper latest
diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt
new file mode 100644
index 0000000000..119c0c96ea
--- /dev/null
+++ b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt
@@ -0,0 +1,2 @@
+#include "<%= config[:underscored_name] %>.h"
+#include "_cgo_export.h"
diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt
new file mode 100644
index 0000000000..f19b750e58
--- /dev/null
+++ b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt
@@ -0,0 +1,31 @@
+package main
+
+/*
+#include "<%= config[:underscored_name] %>.h"
+
+VALUE rb_<%= config[:underscored_name] %>_sum(VALUE self, VALUE a, VALUE b);
+*/
+import "C"
+
+import (
+ "github.com/ruby-go-gem/go-gem-wrapper/ruby"
+)
+
+//export rb_<%= config[:underscored_name] %>_sum
+func rb_<%= config[:underscored_name] %>_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE {
+ longA := ruby.NUM2LONG(ruby.VALUE(a))
+ longB := ruby.NUM2LONG(ruby.VALUE(b))
+
+ sum := longA + longB
+
+ return C.VALUE(ruby.LONG2NUM(sum))
+}
+
+//export Init_<%= config[:underscored_name] %>
+func Init_<%= config[:underscored_name] %>() {
+ rb_m<%= config[:constant_array].join %> := ruby.RbDefineModule(<%= config[:constant_name].inspect %>)
+ ruby.RbDefineSingletonMethod(rb_m<%= config[:constant_array].join %>, "sum", C.rb_<%= config[:underscored_name] %>_sum, 2)
+}
+
+func main() {
+}
diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt
index d1b5ae0534..7f3e3a5b66 100644
--- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt
+++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt
@@ -18,6 +18,8 @@ jobs:
steps:
- uses: actions/checkout@v4
+ with:
+ persist-credentials: false
<%- if config[:ext] == 'rust' -%>
- name: Set up Ruby & Rust
uses: oxidize-rb/actions/setup-ruby-and-rust@v1
@@ -33,5 +35,11 @@ jobs:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
<%- end -%>
+<%- if config[:ext] == 'go' -%>
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: ext/<%= config[:underscored_name] %>/go.mod
+<%- end -%>
- name: Run the default task
run: bundle exec rake
diff --git a/lib/bundler/templates/newgem/gitlab-ci.yml.tt b/lib/bundler/templates/newgem/gitlab-ci.yml.tt
index d2e1f33736..adbd70cbc0 100644
--- a/lib/bundler/templates/newgem/gitlab-ci.yml.tt
+++ b/lib/bundler/templates/newgem/gitlab-ci.yml.tt
@@ -6,6 +6,11 @@ default:
- apt-get update && apt-get install -y clang
- gem update --system '<%= ::Gem.rubygems_version %>'
<%- end -%>
+<%- if config[:ext] == 'go' -%>
+ - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz
+ - tar -C /usr/local -xzf /tmp/go.tar.gz
+ - export PATH=/usr/local/go/bin:$PATH
+<%- end -%>
- gem install bundler -v <%= Bundler::VERSION %>
- bundle install
@@ -14,5 +19,9 @@ example_job:
variables:
RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true'
<%- end -%>
+<%- if config[:ext] == 'go' -%>
+ variables:
+ GO_VERSION: '1.23.0'
+<%- end -%>
script:
- bundle exec rake
diff --git a/lib/bundler/templates/newgem/lib/newgem.rb.tt b/lib/bundler/templates/newgem/lib/newgem.rb.tt
index caf6e32f4a..3aedee0d25 100644
--- a/lib/bundler/templates/newgem/lib/newgem.rb.tt
+++ b/lib/bundler/templates/newgem/lib/newgem.rb.tt
@@ -2,7 +2,7 @@
require_relative "<%= File.basename(config[:namespaced_path]) %>/version"
<%- if config[:ext] -%>
-require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>"
+require "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>"
<%- end -%>
<%- config[:constant_array].each_with_index do |c, i| -%>
diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt
index ced300f379..513875fd63 100644
--- a/lib/bundler/templates/newgem/newgem.gemspec.tt
+++ b/lib/bundler/templates/newgem/newgem.gemspec.tt
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
spec.summary = "TODO: Write a short summary, because RubyGems requires one."
spec.description = "TODO: Write a longer description or delete this line."
- spec.homepage = "TODO: Put your gem's website or public repo URL here."
+ spec.homepage = "<%= config[:homepage_uri] %>"
<%- if config[:mit] -%>
spec.license = "MIT"
<%- end -%>
@@ -20,10 +20,11 @@ Gem::Specification.new do |spec|
<%- end -%>
spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
-
spec.metadata["homepage_uri"] = spec.homepage
- spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
- spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
+ spec.metadata["source_code_uri"] = "<%= config[:source_code_uri] %>"
+<%- if config[:changelog] -%>
+ spec.metadata["changelog_uri"] = "<%= config[:changelog_uri] %>"
+<%- end -%>
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -31,13 +32,13 @@ Gem::Specification.new do |spec|
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
ls.readlines("\x0", chomp: true).reject do |f|
(f == gemspec) ||
- f.start_with?(*%w[bin/ test/ spec/ features/ .git <%= config[:ci_config_path] %>appveyor Gemfile])
+ f.start_with?(*%w[<%= config[:ignore_paths].join(" ") %>])
end
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
-<%- if config[:ext] == 'c' || config[:ext] == 'rust' -%>
+<%- if %w(c rust go).include?(config[:ext]) -%>
spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"]
<%- end -%>
@@ -46,6 +47,9 @@ Gem::Specification.new do |spec|
<%- if config[:ext] == 'rust' -%>
spec.add_dependency "rb_sys", "~> 0.9.91"
<%- end -%>
+<%- if config[:ext] == 'go' -%>
+ spec.add_dependency "go_gem", "~> 0.2"
+<%- end -%>
# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb
index 6df1512a5b..b836208da8 100644
--- a/lib/bundler/ui/shell.rb
+++ b/lib/bundler/ui/shell.rb
@@ -17,6 +17,7 @@ module Bundler
@level = ENV["DEBUG"] ? "debug" : "info"
@warning_history = []
@output_stream = :stdout
+ @thread_safe_logger_key = "logger_level_#{object_id}"
end
def add_color(string, *color)
@@ -80,11 +81,11 @@ module Bundler
end
def ask(msg)
- @shell.ask(msg)
+ @shell.ask(msg, :green)
end
def yes?(msg)
- @shell.yes?(msg)
+ @shell.yes?(msg, :green)
end
def no?(msg)
@@ -97,11 +98,13 @@ module Bundler
end
def level(name = nil)
- return @level unless name
+ current_level = Thread.current.thread_variable_get(@thread_safe_logger_key) || @level
+ return current_level unless name
+
unless index = LEVELS.index(name)
raise "#{name.inspect} is not a valid level"
end
- index <= LEVELS.index(@level)
+ index <= LEVELS.index(current_level)
end
def output_stream=(symbol)
@@ -167,12 +170,13 @@ module Bundler
end * "\n"
end
- def with_level(level)
- original = @level
- @level = level
+ def with_level(desired_level)
+ old_level = level
+ Thread.current.thread_variable_set(@thread_safe_logger_key, desired_level)
+
yield
ensure
- @level = original
+ Thread.current.thread_variable_set(@thread_safe_logger_key, old_level)
end
def with_output_stream(symbol)
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb
index cb4485e463..e8aaf70016 100644
--- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb
+++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb
@@ -39,7 +39,7 @@ end
# - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true
#
class Bundler::ConnectionPool
- DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}
+ DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}.freeze
def self.wrap(options, &block)
Wrapper.new(options, &block)
@@ -99,10 +99,14 @@ class Bundler::ConnectionPool
@available = TimedStack.new(@size, &block)
@key = :"pool-#{@available.object_id}"
@key_count = :"pool-#{@available.object_id}-count"
- INSTANCES[self] = self if INSTANCES
+ @discard_key = :"pool-#{@available.object_id}-discard"
+ INSTANCES[self] = self if @auto_reload_after_fork && INSTANCES
end
def with(options = {})
+ # We need to manage exception handling manually here in order
+ # to work correctly with `Gem::Timeout.timeout` and `Thread#raise`.
+ # Otherwise an interrupted Thread can leak connections.
Thread.handle_interrupt(Exception => :never) do
conn = checkout(options)
begin
@@ -116,20 +120,65 @@ class Bundler::ConnectionPool
end
alias_method :then, :with
+ ##
+ # Marks the current thread's checked-out connection for discard.
+ #
+ # When a connection is marked for discard, it will not be returned to the pool
+ # when checked in. Instead, the connection will be discarded.
+ # This is useful when a connection has become invalid or corrupted
+ # and should not be reused.
+ #
+ # Takes an optional block that will be called with the connection to be discarded.
+ # The block should perform any necessary clean-up on the connection.
+ #
+ # @yield [conn]
+ # @yieldparam conn [Object] The connection to be discarded.
+ # @yieldreturn [void]
+ #
+ #
+ # Note: This only affects the connection currently checked out by the calling thread.
+ # The connection will be discarded when +checkin+ is called.
+ #
+ # @return [void]
+ #
+ # @example
+ # pool.with do |conn|
+ # begin
+ # conn.execute("SELECT 1")
+ # rescue SomeConnectionError
+ # pool.discard_current_connection # Mark connection as bad
+ # raise
+ # end
+ # end
+ def discard_current_connection(&block)
+ ::Thread.current[@discard_key] = block || proc { |conn| conn }
+ end
+
def checkout(options = {})
if ::Thread.current[@key]
::Thread.current[@key_count] += 1
::Thread.current[@key]
else
::Thread.current[@key_count] = 1
- ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
+ ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout, options)
end
end
def checkin(force: false)
if ::Thread.current[@key]
if ::Thread.current[@key_count] == 1 || force
- @available.push(::Thread.current[@key])
+ if ::Thread.current[@discard_key]
+ begin
+ @available.decrement_created
+ ::Thread.current[@discard_key].call(::Thread.current[@key])
+ rescue
+ nil
+ ensure
+ ::Thread.current[@discard_key] = nil
+ end
+ else
+ @available.push(::Thread.current[@key])
+ end
::Thread.current[@key] = nil
::Thread.current[@key_count] = nil
else
@@ -146,7 +195,6 @@ class Bundler::ConnectionPool
# Shuts down the Bundler::ConnectionPool by passing each connection to +block+ and
# then removing it from the pool. Attempting to checkout a connection after
# shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+.
-
def shutdown(&block)
@available.shutdown(&block)
end
@@ -155,7 +203,6 @@ class Bundler::ConnectionPool
# Reloads the Bundler::ConnectionPool by passing each connection to +block+ and then
# removing it the pool. Subsequent checkouts will create new connections as
# needed.
-
def reload(&block)
@available.shutdown(reload: true, &block)
end
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb
index 02e4485eb2..026d2c5be2 100644
--- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb
+++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb
@@ -1,8 +1,8 @@
##
# The TimedStack manages a pool of homogeneous connections (or any resource
-# you wish to manage). Connections are created lazily up to a given maximum
+# you wish to manage). Connections are created lazily up to a given maximum
# number.
-
+#
# Examples:
#
# ts = TimedStack.new(1) { MyConnection.new }
@@ -16,14 +16,12 @@
# conn = ts.pop
# ts.pop timeout: 5
# #=> raises Bundler::ConnectionPool::TimeoutError after 5 seconds
-
class Bundler::ConnectionPool::TimedStack
attr_reader :max
##
# Creates a new pool with +size+ connections that are created from the given
# +block+.
-
def initialize(size = 0, &block)
@create_block = block
@created = 0
@@ -35,9 +33,8 @@ class Bundler::ConnectionPool::TimedStack
end
##
- # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be
+ # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be
# used by subclasses that extend TimedStack.
-
def push(obj, options = {})
@mutex.synchronize do
if @shutdown_block
@@ -53,14 +50,16 @@ class Bundler::ConnectionPool::TimedStack
alias_method :<<, :push
##
- # Retrieves a connection from the stack. If a connection is available it is
- # immediately returned. If no connection is available within the given
+ # Retrieves a connection from the stack. If a connection is available it is
+ # immediately returned. If no connection is available within the given
# timeout a Bundler::ConnectionPool::TimeoutError is raised.
#
- # +:timeout+ is the only checked entry in +options+ and is preferred over
- # the +timeout+ argument (which will be removed in a future release). Other
- # options may be used by subclasses that extend TimedStack.
-
+ # @option options [Float] :timeout (0.5) Wait this many seconds for an available entry
+ # @option options [Class] :exception (Bundler::ConnectionPool::TimeoutError) Exception class to raise
+ # if an entry was not available within the timeout period. Use `exception: false` to return nil.
+ #
+ # The +timeout+ argument will be removed in 3.0.
+ # Other options may be used by subclasses that extend TimedStack.
def pop(timeout = 0.5, options = {})
options, timeout = timeout, 0.5 if Hash === timeout
timeout = options.fetch :timeout, timeout
@@ -69,13 +68,22 @@ class Bundler::ConnectionPool::TimedStack
@mutex.synchronize do
loop do
raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block
- return fetch_connection(options) if connection_stored?(options)
+ if (conn = try_fetch_connection(options))
+ return conn
+ end
connection = try_create(options)
return connection if connection
to_wait = deadline - current_time
- raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0
+ if to_wait <= 0
+ exc = options.fetch(:exception, Bundler::ConnectionPool::TimeoutError)
+ if exc
+ raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available"
+ else
+ return nil
+ end
+ end
@resource.wait(@mutex, to_wait)
end
end
@@ -86,7 +94,6 @@ class Bundler::ConnectionPool::TimedStack
# removing it from the pool. Attempting to checkout a connection after
# shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+ unless
# +:reload+ is +true+.
-
def shutdown(reload: false, &block)
raise ArgumentError, "shutdown must receive a block" unless block
@@ -121,14 +128,12 @@ class Bundler::ConnectionPool::TimedStack
##
# Returns +true+ if there are no available connections.
-
def empty?
(@created - @que.length) >= @max
end
##
# The number of connections available on the stack.
-
def length
@max - @created + @que.length
end
@@ -139,6 +144,12 @@ class Bundler::ConnectionPool::TimedStack
@que.length
end
+ ##
+ # Reduce the created count
+ def decrement_created
+ @created -= 1 unless @created == 0
+ end
+
private
def current_time
@@ -148,8 +159,17 @@ class Bundler::ConnectionPool::TimedStack
##
# This is an extension point for TimedStack and is called with a mutex.
#
- # This method must returns true if a connection is available on the stack.
+ # This method must returns a connection from the stack if one exists. Allows
+ # subclasses with expensive match/search algorithms to avoid double-handling
+ # their stack.
+ def try_fetch_connection(options = nil)
+ connection_stored?(options) && fetch_connection(options)
+ end
+ ##
+ # This is an extension point for TimedStack and is called with a mutex.
+ #
+ # This method must returns true if a connection is available on the stack.
def connection_stored?(options = nil)
!@que.empty?
end
@@ -158,7 +178,6 @@ class Bundler::ConnectionPool::TimedStack
# This is an extension point for TimedStack and is called with a mutex.
#
# This method must return a connection from the stack.
-
def fetch_connection(options = nil)
@que.pop&.first
end
@@ -167,10 +186,8 @@ class Bundler::ConnectionPool::TimedStack
# This is an extension point for TimedStack and is called with a mutex.
#
# This method must shut down all connections on the stack.
-
def shutdown_connections(options = nil)
- while connection_stored?(options)
- conn = fetch_connection(options)
+ while (conn = try_fetch_connection(options))
@created -= 1 unless @created == 0
@shutdown_block.call(conn)
end
@@ -181,7 +198,6 @@ class Bundler::ConnectionPool::TimedStack
#
# This method returns the oldest idle connection if it has been idle for more than idle_seconds.
# This requires that the stack is kept in order of checked in time (oldest first).
-
def reserve_idle_connection(idle_seconds)
return unless idle_connections?(idle_seconds)
@@ -194,7 +210,6 @@ class Bundler::ConnectionPool::TimedStack
# This is an extension point for TimedStack and is called with a mutex.
#
# Returns true if the first connection in the stack has been idle for more than idle_seconds
-
def idle_connections?(idle_seconds)
connection_stored? && (current_time - @que.first.last > idle_seconds)
end
@@ -203,7 +218,6 @@ class Bundler::ConnectionPool::TimedStack
# This is an extension point for TimedStack and is called with a mutex.
#
# This method must return +obj+ to the stack.
-
def store_connection(obj, options = nil)
@que.push [obj, current_time]
end
@@ -213,7 +227,6 @@ class Bundler::ConnectionPool::TimedStack
#
# This method must create a connection if and only if the total number of
# connections allowed has not been met.
-
def try_create(options = nil)
unless @created == @max
object = @create_block.call
diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb
index 88a3714fb8..2e9eebdbb6 100644
--- a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb
+++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb
@@ -1,3 +1,3 @@
class Bundler::ConnectionPool
- VERSION = "2.5.0"
+ VERSION = "2.5.5"
end
diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb
index 4d7619ddcf..a11fdc7176 100644
--- a/lib/bundler/vendor/fileutils/lib/fileutils.rb
+++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb
@@ -181,7 +181,7 @@ end
#
module Bundler::FileUtils
# The version number.
- VERSION = "1.7.3"
+ VERSION = "1.8.0"
def self.private_module_function(name) #:nodoc:
module_function name
@@ -706,11 +706,12 @@ module Bundler::FileUtils
#
def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil)
if relative
- return ln_sr(src, dest, force: force, noop: noop, verbose: verbose)
+ return ln_sr(src, dest, force: force, target_directory: target_directory, noop: noop, verbose: verbose)
end
- fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ fu_output_message "ln -s#{force ? 'f' : ''}#{
+ target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose
return if noop
- fu_each_src_dest0(src, dest) do |s,d|
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
remove_file d, true if force
File.symlink s, d
end
@@ -730,42 +731,37 @@ module Bundler::FileUtils
# Like Bundler::FileUtils.ln_s, but create links relative to +dest+.
#
def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)
- options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}"
- dest = File.path(dest)
- srcs = Array(src)
- link = proc do |s, target_dir_p = true|
- s = File.path(s)
- if target_dir_p
- d = File.join(destdirs = dest, File.basename(s))
+ cmd = "ln -s#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" if verbose
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
+ if target_directory
+ parent = File.dirname(d)
+ destdirs = fu_split_path(parent)
+ real_ddirs = fu_split_path(File.realpath(parent))
else
- destdirs = File.dirname(d = dest)
+ destdirs ||= fu_split_path(dest)
+ real_ddirs ||= fu_split_path(File.realdirpath(dest))
end
- destdirs = fu_split_path(File.realpath(destdirs))
- if fu_starting_path?(s)
- srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s)))
- base = fu_relative_components_from(srcdirs, destdirs)
- s = File.join(*base)
+ srcdirs = fu_split_path(s)
+ i = fu_common_components(srcdirs, destdirs)
+ n = destdirs.size - i
+ n -= 1 unless target_directory
+ link1 = fu_clean_components(*Array.new([n, 0].max, '..'), *srcdirs[i..-1])
+ begin
+ real_sdirs = fu_split_path(File.realdirpath(s)) rescue nil
+ rescue
else
- srcdirs = fu_clean_components(*fu_split_path(s))
- base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs)
- while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last)
- srcdirs.shift
- base.pop
- end
- s = File.join(*base, *srcdirs)
+ i = fu_common_components(real_sdirs, real_ddirs)
+ n = real_ddirs.size - i
+ n -= 1 unless target_directory
+ link2 = fu_clean_components(*Array.new([n, 0].max, '..'), *real_sdirs[i..-1])
+ link1 = link2 if link1.size > link2.size
end
- fu_output_message "ln -s#{options} #{s} #{d}" if verbose
+ s = File.join(link1)
+ fu_output_message [cmd, s, d].flatten.join(' ') if verbose
next if noop
remove_file d, true if force
File.symlink s, d
end
- case srcs.size
- when 0
- when 1
- link[srcs[0], target_directory && File.directory?(dest)]
- else
- srcs.each(&link)
- end
end
module_function :ln_sr
@@ -800,13 +796,13 @@ module Bundler::FileUtils
# File.file?('dest1/dir1/t2.txt') # => true
# File.file?('dest1/dir1/t3.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link.
- # - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
+ # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before creating links (+false+ by default).
#
# Raises an exception if +dest+ is the path to an existing file or directory
- # and keyword argument <tt>remove_destination: true</tt> is not given.
+ # and optional argument +remove_destination+ is not given.
#
# Related: Bundler::FileUtils.ln (has different options).
#
@@ -1029,12 +1025,12 @@ module Bundler::FileUtils
# directories, and symbolic links;
# other file types (FIFO streams, device files, etc.) are not supported.
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link,
- # follows the link.
- # - <tt>preserve: true</tt> - preserves file times.
- # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
+ # - +dereference_root+ - if +src+ is a symbolic link,
+ # follows the link (+false+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
@@ -1065,12 +1061,12 @@ module Bundler::FileUtils
# Bundler::FileUtils.copy_file('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference: false</tt> - if +src+ is a symbolic link,
- # does not follow the link.
- # - <tt>preserve: true</tt> - preserves file times.
- # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
+ # - +dereference+ - if +src+ is a symbolic link,
+ # follows the link (+true+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
@@ -1491,7 +1487,8 @@ module Bundler::FileUtils
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def remove_dir(path, force = false)
- remove_entry path, force # FIXME?? check if it is a directory
+ raise Errno::ENOTDIR, path unless force or File.directory?(path)
+ remove_entry path, force
end
module_function :remove_dir
@@ -2475,6 +2472,10 @@ module Bundler::FileUtils
def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
if tmp = Array.try_convert(src)
+ unless target_directory or tmp.size <= 1
+ tmp = tmp.map {|f| File.path(f)} # A workaround for RBS
+ raise ArgumentError, "extra target #{tmp}"
+ end
tmp.each do |s|
s = File.path(s)
yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)
@@ -2509,7 +2510,11 @@ module Bundler::FileUtils
path = File.path(path)
list = []
until (parent, base = File.split(path); parent == path or parent == ".")
- list << base
+ if base != '..' and list.last == '..' and !(fu_have_symlink? && File.symlink?(path))
+ list.pop
+ else
+ list << base
+ end
path = parent
end
list << path
@@ -2517,14 +2522,14 @@ module Bundler::FileUtils
end
private_module_function :fu_split_path
- def fu_relative_components_from(target, base) #:nodoc:
+ def fu_common_components(target, base) #:nodoc:
i = 0
while target[i]&.== base[i]
i += 1
end
- Array.new(base.size-i, '..').concat(target[i..-1])
+ i
end
- private_module_function :fu_relative_components_from
+ private_module_function :fu_common_components
def fu_clean_components(*comp) #:nodoc:
comp.shift while comp.first == "."
@@ -2534,7 +2539,7 @@ module Bundler::FileUtils
while c = comp.shift
if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path))
clean.pop
- path.chomp!(%r((?<=\A|/)[^/]+/\z), "")
+ path.sub!(%r((?<=\A|/)[^/]+/\z), "")
else
clean << c
path << c << "/"
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
index cfc0f48197..93e403a5bb 100644
--- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
@@ -1,6 +1,10 @@
require_relative '../../../../../vendored_net_http'
require_relative '../../../../../vendored_uri'
-require 'cgi' # for escaping
+begin
+ require 'cgi/escape'
+rescue LoadError
+ require 'cgi/util' # for escaping
+end
require_relative '../../../../connection_pool/lib/connection_pool'
autoload :OpenSSL, 'openssl'
@@ -42,9 +46,8 @@ autoload :OpenSSL, 'openssl'
# # perform the POST, the Gem::URI is always required
# response http.request post_uri, post
#
-# Note that for GET, HEAD and other requests that do not have a body you want
-# to use Gem::URI#request_uri not Gem::URI#path. The request_uri contains the query
-# params which are sent in the body for other requests.
+# âš  Note that for GET, HEAD and other requests that do not have a body,
+# it uses Gem::URI#request_uri as default to send query params
#
# == TLS/SSL
#
@@ -60,6 +63,7 @@ autoload :OpenSSL, 'openssl'
# #ca_path :: Directory with certificate-authorities
# #cert_store :: An SSL certificate store
# #ciphers :: List of SSl ciphers allowed
+# #extra_chain_cert :: Extra certificates to be added to the certificate chain
# #private_key :: The client's SSL private key
# #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new
# connection
@@ -176,7 +180,7 @@ class Gem::Net::HTTP::Persistent
##
# The version of Gem::Net::HTTP::Persistent you are using
- VERSION = '4.0.4'
+ VERSION = '4.0.6'
##
# Error class for errors raised by Gem::Net::HTTP::Persistent. Various
@@ -268,6 +272,11 @@ class Gem::Net::HTTP::Persistent
attr_reader :ciphers
##
+ # Extra certificates to be added to the certificate chain
+
+ attr_reader :extra_chain_cert
+
+ ##
# Sends debug_output to this IO via Gem::Net::HTTP#set_debug_output.
#
# Never use this method in production code, it causes a serious security
@@ -587,6 +596,21 @@ class Gem::Net::HTTP::Persistent
reconnect_ssl
end
+ if Gem::Net::HTTP.method_defined?(:extra_chain_cert=)
+ ##
+ # Extra certificates to be added to the certificate chain.
+ # It is only supported starting from Gem::Net::HTTP version 0.1.1
+ def extra_chain_cert= extra_chain_cert
+ @extra_chain_cert = extra_chain_cert
+
+ reconnect_ssl
+ end
+ else
+ def extra_chain_cert= _extra_chain_cert
+ raise "extra_chain_cert= is not supported by this version of Gem::Net::HTTP"
+ end
+ end
+
##
# Creates a new connection for +uri+
@@ -605,47 +629,49 @@ class Gem::Net::HTTP::Persistent
connection = @pool.checkout net_http_args
- http = connection.http
+ begin
+ http = connection.http
- connection.ressl @ssl_generation if
- connection.ssl_generation != @ssl_generation
+ connection.ressl @ssl_generation if
+ connection.ssl_generation != @ssl_generation
- if not http.started? then
- ssl http if use_ssl
- start http
- elsif expired? connection then
- reset connection
- end
+ if not http.started? then
+ ssl http if use_ssl
+ start http
+ elsif expired? connection then
+ reset connection
+ end
- http.keep_alive_timeout = @idle_timeout if @idle_timeout
- http.max_retries = @max_retries if http.respond_to?(:max_retries=)
- http.read_timeout = @read_timeout if @read_timeout
- http.write_timeout = @write_timeout if
- @write_timeout && http.respond_to?(:write_timeout=)
+ http.keep_alive_timeout = @idle_timeout if @idle_timeout
+ http.max_retries = @max_retries if http.respond_to?(:max_retries=)
+ http.read_timeout = @read_timeout if @read_timeout
+ http.write_timeout = @write_timeout if
+ @write_timeout && http.respond_to?(:write_timeout=)
+
+ return yield connection
+ rescue Errno::ECONNREFUSED
+ if http.proxy?
+ address = http.proxy_address
+ port = http.proxy_port
+ else
+ address = http.address
+ port = http.port
+ end
- return yield connection
- rescue Errno::ECONNREFUSED
- if http.proxy?
- address = http.proxy_address
- port = http.proxy_port
- else
- address = http.address
- port = http.port
- end
+ raise Error, "connection refused: #{address}:#{port}"
+ rescue Errno::EHOSTDOWN
+ if http.proxy?
+ address = http.proxy_address
+ port = http.proxy_port
+ else
+ address = http.address
+ port = http.port
+ end
- raise Error, "connection refused: #{address}:#{port}"
- rescue Errno::EHOSTDOWN
- if http.proxy?
- address = http.proxy_address
- port = http.proxy_port
- else
- address = http.address
- port = http.port
+ raise Error, "host down: #{address}:#{port}"
+ ensure
+ @pool.checkin net_http_args
end
-
- raise Error, "host down: #{address}:#{port}"
- ensure
- @pool.checkin net_http_args
end
##
@@ -782,7 +808,7 @@ class Gem::Net::HTTP::Persistent
@proxy_connection_id = [nil, *@proxy_args].join ':'
if @proxy_uri.query then
- @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
+ @no_proxy = Gem::URI.decode_www_form(@proxy_uri.query).filter_map { |k, v| v if k == 'no_proxy' }.join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
end
end
@@ -953,7 +979,8 @@ class Gem::Net::HTTP::Persistent
end
##
- # Shuts down all connections
+ # Shuts down all connections. Attempting to checkout a connection after
+ # shutdown will raise an error.
#
# *NOTE*: Calling shutdown for can be dangerous!
#
@@ -965,6 +992,17 @@ class Gem::Net::HTTP::Persistent
end
##
+ # Discard all existing connections. Subsequent checkouts will create
+ # new connections as needed.
+ #
+ # If any thread is still using a connection it may cause an error! Call
+ # #reload when you are completely done making requests!
+
+ def reload
+ @pool.reload { |http| http.finish }
+ end
+
+ ##
# Enables SSL on +connection+
def ssl connection
@@ -1021,6 +1059,10 @@ application:
connection.key = @private_key
end
+ if defined?(@extra_chain_cert) and @extra_chain_cert
+ connection.extra_chain_cert = @extra_chain_cert
+ end
+
connection.cert_store = if @cert_store then
@cert_store
else
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb
index 214804fcd9..034fbe39b8 100644
--- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb
@@ -63,7 +63,8 @@ class Gem::Net::HTTP::Persistent::TimedStackMulti < Bundler::ConnectionPool::Tim
if @created >= @max && @enqueued >= 1
oldest, = @lru.first
@lru.delete oldest
- @ques[oldest].pop
+ connection = @ques[oldest].pop
+ connection.close if connection.respond_to?(:close)
@created -= 1
end
diff --git a/lib/bundler/vendor/thor/lib/thor.rb b/lib/bundler/vendor/thor/lib/thor.rb
index bfd9f5c914..945bdbd551 100644
--- a/lib/bundler/vendor/thor/lib/thor.rb
+++ b/lib/bundler/vendor/thor/lib/thor.rb
@@ -625,7 +625,7 @@ class Bundler::Thor
# alias name.
def find_command_possibilities(meth)
len = meth.to_s.length
- possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort
+ possibilities = all_commands.reject { |_k, c| c.hidden? }.merge(map).keys.select { |n| meth == n[0, len] }.sort
unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
if possibilities.include?(meth)
diff --git a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb
index bccfbb6b85..d8c9863054 100644
--- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb
+++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb
@@ -242,6 +242,35 @@ class Bundler::Thor
insert_into_file(path, *(args << config), &block)
end
+ # Run a regular expression replacement on a file, raising an error if the
+ # contents of the file are not changed.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # flag<Regexp|String>:: the regexp or string to be replaced
+ # replacement<String>:: the replacement, can be also given as a block
+ # config<Hash>:: give :verbose => false to not log the status, and
+ # :force => true, to force the replacement regardless of runner behavior.
+ #
+ # ==== Example
+ #
+ # gsub_file! 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
+ #
+ # gsub_file! 'README', /rake/, :green do |match|
+ # match << " no more. Use thor!"
+ # end
+ #
+ def gsub_file!(path, flag, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+
+ return unless behavior == :invoke || config.fetch(:force, false)
+
+ path = File.expand_path(path, destination_root)
+ say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
+
+ actually_gsub_file(path, flag, args, true, &block) unless options[:pretend]
+ end
+
# Run a regular expression replacement on a file.
#
# ==== Parameters
@@ -267,11 +296,7 @@ class Bundler::Thor
path = File.expand_path(path, destination_root)
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
- unless options[:pretend]
- content = File.binread(path)
- content.gsub!(flag, *args, &block)
- File.open(path, "wb") { |file| file.write(content) }
- end
+ actually_gsub_file(path, flag, args, false, &block) unless options[:pretend]
end
# Uncomment all lines matching a given regex. Preserves indentation before
@@ -348,7 +373,7 @@ class Bundler::Thor
end
def with_output_buffer(buf = "".dup) #:nodoc:
- raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen?
+ raise ArgumentError, "Buffer cannot be a frozen object" if buf.frozen?
old_buffer = output_buffer
self.output_buffer = buf
yield
@@ -357,6 +382,17 @@ class Bundler::Thor
self.output_buffer = old_buffer
end
+ def actually_gsub_file(path, flag, args, error_on_no_change, &block)
+ content = File.binread(path)
+ success = content.gsub!(flag, *args, &block)
+
+ if success.nil? && error_on_no_change
+ raise Bundler::Thor::Error, "The content of #{path} did not change"
+ end
+
+ File.open(path, "wb") { |file| file.write(content) }
+ end
+
# Bundler::Thor::Actions#capture depends on what kind of buffer is used in ERB.
# Thus CapturableERB fixes ERB to use String buffer.
class CapturableERB < ERB
diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
index 734f5fe7e3..fe22d989e5 100644
--- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb
+++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb
@@ -144,7 +144,7 @@ class Bundler::Thor
def check_exclusive!
opts = @assigns.keys
# When option A and B are exclusive, if A and B are given at the same time,
- # the diffrence of argument array size will decrease.
+ # the difference of argument array size will decrease.
found = @exclusives.find{ |ex| (ex - opts).size < ex.size - 1 }
if found
names = names_to_switch_names(found & opts).map{|n| "'#{n}'"}
diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb
index c7cc873131..f0ce6df96c 100644
--- a/lib/bundler/vendor/thor/lib/thor/runner.rb
+++ b/lib/bundler/vendor/thor/lib/thor/runner.rb
@@ -1,9 +1,8 @@
require_relative "../thor"
require_relative "group"
-require "yaml"
require "digest/sha2"
-require "pathname"
+require "pathname" unless defined?(Pathname)
class Bundler::Thor::Runner < Bundler::Thor #:nodoc:
map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
@@ -195,6 +194,7 @@ private
def thor_yaml
@thor_yaml ||= begin
yaml_file = File.join(thor_root, "thor.yml")
+ require "yaml"
yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file)
yaml || {}
end
diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
index b3e85733cb..da02b94227 100644
--- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
+++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb
@@ -314,7 +314,7 @@ class Bundler::Thor
diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
require "tempfile"
- Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
+ Tempfile.open(File.basename(destination), File.dirname(destination), binmode: true) do |temp|
temp.write content
temp.rewind
system %(#{diff_cmd} "#{destination}" "#{temp.path}")
@@ -372,16 +372,12 @@ class Bundler::Thor
Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
temp.write content
temp.rewind
- system %(#{merge_tool} "#{temp.path}" "#{destination}")
+ system(merge_tool, temp.path, destination)
end
end
def merge_tool #:nodoc:
- @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool
- end
-
- def git_merge_tool #:nodoc:
- `git config merge.tool`.rstrip rescue ""
+ @merge_tool ||= ENV["THOR_MERGE"] || "git difftool --no-index"
end
end
end
diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb
index cd7b4f060e..5474a2f71b 100644
--- a/lib/bundler/vendor/thor/lib/thor/version.rb
+++ b/lib/bundler/vendor/thor/lib/thor/version.rb
@@ -1,3 +1,3 @@
class Bundler::Thor
- VERSION = "1.3.2"
+ VERSION = "1.4.0"
end
diff --git a/lib/bundler/vendor/uri/lib/uri/common.rb b/lib/bundler/vendor/uri/lib/uri/common.rb
index 186da24fa8..38339119c5 100644
--- a/lib/bundler/vendor/uri/lib/uri/common.rb
+++ b/lib/bundler/vendor/uri/lib/uri/common.rb
@@ -30,6 +30,9 @@ module Bundler::URI
remove_const(:Parser) if defined?(::Bundler::URI::Parser)
const_set("Parser", parser.class)
+ remove_const(:PARSER) if defined?(::Bundler::URI::PARSER)
+ const_set("PARSER", parser)
+
remove_const(:REGEXP) if defined?(::Bundler::URI::REGEXP)
remove_const(:PATTERN) if defined?(::Bundler::URI::PATTERN)
if Parser == RFC2396_Parser
@@ -49,10 +52,10 @@ module Bundler::URI
warn "Bundler::URI::REGEXP is obsolete. Use Bundler::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE
Bundler::URI::RFC2396_REGEXP
elsif value = RFC2396_PARSER.regexp[const]
- warn "Bundler::URI::#{const} is obsolete. Use RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE
+ warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE
value
elsif value = RFC2396_Parser.const_get(const)
- warn "Bundler::URI::#{const} is obsolete. Use RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE
+ warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE
value
else
super
@@ -92,6 +95,40 @@ module Bundler::URI
end
module Schemes # :nodoc:
+ class << self
+ ReservedChars = ".+-"
+ EscapedChars = "\u01C0\u01C1\u01C2"
+ # Use Lo category chars as escaped chars for TruffleRuby, which
+ # does not allow Symbol categories as identifiers.
+
+ def escape(name)
+ unless name and name.ascii_only?
+ return nil
+ end
+ name.upcase.tr(ReservedChars, EscapedChars)
+ end
+
+ def unescape(name)
+ name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase
+ end
+
+ def find(name)
+ const_get(name, false) if name and const_defined?(name, false)
+ end
+
+ def register(name, klass)
+ unless scheme = escape(name)
+ raise ArgumentError, "invalid character as scheme - #{name}"
+ end
+ const_set(scheme, klass)
+ end
+
+ def list
+ constants.map { |name|
+ [unescape(name.to_s), const_get(name)]
+ }.to_h
+ end
+ end
end
private_constant :Schemes
@@ -104,7 +141,7 @@ module Bundler::URI
# Note that after calling String#upcase on +scheme+, it must be a valid
# constant name.
def self.register_scheme(scheme, klass)
- Schemes.const_set(scheme.to_s.upcase, klass)
+ Schemes.register(scheme, klass)
end
# Returns a hash of the defined schemes:
@@ -122,14 +159,14 @@ module Bundler::URI
#
# Related: Bundler::URI.register_scheme.
def self.scheme_list
- Schemes.constants.map { |name|
- [name.to_s.upcase, Schemes.const_get(name)]
- }.to_h
+ Schemes.list
end
+ # :stopdoc:
INITIAL_SCHEMES = scheme_list
private_constant :INITIAL_SCHEMES
Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
+ # :startdoc:
# Returns a new object constructed from the given +scheme+, +arguments+,
# and +default+:
@@ -148,12 +185,10 @@ module Bundler::URI
# # => #<Bundler::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
def self.for(scheme, *arguments, default: Generic)
- const_name = scheme.to_s.upcase
+ const_name = Schemes.escape(scheme)
uri_class = INITIAL_SCHEMES[const_name]
- uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
- Schemes.const_get(const_name, false)
- end
+ uri_class ||= Schemes.find(const_name)
uri_class ||= default
return uri_class.new(scheme, *arguments)
@@ -195,7 +230,7 @@ module Bundler::URI
# ["fragment", "top"]]
#
def self.split(uri)
- DEFAULT_PARSER.split(uri)
+ PARSER.split(uri)
end
# Returns a new \Bundler::URI object constructed from the given string +uri+:
@@ -205,11 +240,11 @@ module Bundler::URI
# Bundler::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# # => #<Bundler::URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
- # It's recommended to first ::escape string +uri+
+ # It's recommended to first Bundler::URI::RFC2396_PARSER.escape string +uri+
# if it may contain invalid Bundler::URI characters.
#
def self.parse(uri)
- DEFAULT_PARSER.parse(uri)
+ PARSER.parse(uri)
end
# Merges the given Bundler::URI strings +str+
@@ -265,7 +300,7 @@ module Bundler::URI
#
def self.extract(str, schemes = nil, &block) # :nodoc:
warn "Bundler::URI.extract is obsolete", uplevel: 1 if $VERBOSE
- DEFAULT_PARSER.extract(str, schemes, &block)
+ PARSER.extract(str, schemes, &block)
end
#
@@ -302,7 +337,7 @@ module Bundler::URI
#
def self.regexp(schemes = nil)# :nodoc:
warn "Bundler::URI.regexp is obsolete", uplevel: 1 if $VERBOSE
- DEFAULT_PARSER.make_regexp(schemes)
+ PARSER.make_regexp(schemes)
end
TBLENCWWWCOMP_ = {} # :nodoc:
@@ -407,6 +442,8 @@ module Bundler::URI
_decode_uri_component(/%\h\h/, str, enc)
end
+ # Returns a string derived from the given string +str+ with
+ # Bundler::URI-encoded characters matching +regexp+ according to +table+.
def self._encode_uri_component(regexp, table, str, enc)
str = str.to_s.dup
if str.encoding != Encoding::ASCII_8BIT
@@ -421,6 +458,8 @@ module Bundler::URI
end
private_class_method :_encode_uri_component
+ # Returns a string decoding characters matching +regexp+ from the
+ # given \URL-encoded string +str+.
def self._decode_uri_component(regexp, str, enc)
raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
@@ -859,6 +898,7 @@ module Bundler
# Returns a \Bundler::URI object derived from the given +uri+,
# which may be a \Bundler::URI string or an existing \Bundler::URI object:
#
+ # require 'bundler/vendor/uri/lib/uri'
# # Returns a new Bundler::URI.
# uri = Bundler::URI('http://github.com/ruby/ruby')
# # => #<Bundler::URI::HTTP http://github.com/ruby/ruby>
@@ -866,6 +906,8 @@ module Bundler
# Bundler::URI(uri)
# # => #<Bundler::URI::HTTP http://github.com/ruby/ruby>
#
+ # You must require 'bundler/vendor/uri/lib/uri' to use this method.
+ #
def URI(uri)
if uri.is_a?(Bundler::URI::Generic)
uri
diff --git a/lib/bundler/vendor/uri/lib/uri/file.rb b/lib/bundler/vendor/uri/lib/uri/file.rb
index de347af6eb..21dd9ee535 100644
--- a/lib/bundler/vendor/uri/lib/uri/file.rb
+++ b/lib/bundler/vendor/uri/lib/uri/file.rb
@@ -47,7 +47,7 @@ module Bundler::URI
# :path => '/ruby/src'})
# uri2.to_s # => "file://host.example.com/ruby/src"
#
- # uri3 = Bundler::URI::File.build({:path => Bundler::URI::escape('/path/my file.txt')})
+ # uri3 = Bundler::URI::File.build({:path => Bundler::URI::RFC2396_PARSER.escape('/path/my file.txt')})
# uri3.to_s # => "file:///path/my%20file.txt"
#
def self.build(args)
diff --git a/lib/bundler/vendor/uri/lib/uri/generic.rb b/lib/bundler/vendor/uri/lib/uri/generic.rb
index 6abb171d14..30dab60903 100644
--- a/lib/bundler/vendor/uri/lib/uri/generic.rb
+++ b/lib/bundler/vendor/uri/lib/uri/generic.rb
@@ -73,7 +73,7 @@ module Bundler::URI
#
# At first, tries to create a new Bundler::URI::Generic instance using
# Bundler::URI::Generic::build. But, if exception Bundler::URI::InvalidComponentError is raised,
- # then it does Bundler::URI::Escape.escape all Bundler::URI components and tries again.
+ # then it does Bundler::URI::RFC2396_PARSER.escape all Bundler::URI components and tries again.
#
def self.build2(args)
begin
@@ -126,9 +126,9 @@ module Bundler::URI
end
end
else
- component = self.class.component rescue ::Bundler::URI::Generic::COMPONENT
+ component = self.component rescue ::Bundler::URI::Generic::COMPONENT
raise ArgumentError,
- "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})"
+ "expected Array of or Hash of components of #{self} (#{component.join(', ')})"
end
tmp << nil
@@ -186,18 +186,18 @@ module Bundler::URI
if arg_check
self.scheme = scheme
- self.userinfo = userinfo
self.hostname = host
self.port = port
+ self.userinfo = userinfo
self.path = path
self.query = query
self.opaque = opaque
self.fragment = fragment
else
self.set_scheme(scheme)
- self.set_userinfo(userinfo)
self.set_host(host)
self.set_port(port)
+ self.set_userinfo(userinfo)
self.set_path(path)
self.query = query
self.set_opaque(opaque)
@@ -284,7 +284,7 @@ module Bundler::URI
# Returns the parser to be used.
#
- # Unless a Bundler::URI::Parser is defined, DEFAULT_PARSER is used.
+ # Unless the +parser+ is defined, DEFAULT_PARSER is used.
#
def parser
if !defined?(@parser) || !@parser
@@ -315,7 +315,7 @@ module Bundler::URI
end
#
- # Checks the scheme +v+ component against the Bundler::URI::Parser Regexp for :SCHEME.
+ # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME.
#
def check_scheme(v)
if v && parser.regexp[:SCHEME] !~ v
@@ -385,7 +385,7 @@ module Bundler::URI
#
# Checks the user +v+ component for RFC2396 compliance
- # and against the Bundler::URI::Parser Regexp for :USERINFO.
+ # and against the +parser+ Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
@@ -409,7 +409,7 @@ module Bundler::URI
#
# Checks the password +v+ component for RFC2396 compliance
- # and against the Bundler::URI::Parser Regexp for :USERINFO.
+ # and against the +parser+ Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
@@ -511,7 +511,7 @@ module Bundler::URI
user, password = split_userinfo(user)
end
@user = user
- @password = password if password
+ @password = password
[@user, @password]
end
@@ -522,7 +522,7 @@ module Bundler::URI
# See also Bundler::URI::Generic.user=.
#
def set_user(v)
- set_userinfo(v, @password)
+ set_userinfo(v, nil)
v
end
protected :set_user
@@ -574,6 +574,12 @@ module Bundler::URI
@password
end
+ # Returns the authority info (array of user, password, host and
+ # port), if any is set. Or returns +nil+.
+ def authority
+ return @user, @password, @host, @port if @user || @password || @host || @port
+ end
+
# Returns the user component after Bundler::URI decoding.
def decoded_user
Bundler::URI.decode_uri_component(@user) if @user
@@ -586,7 +592,7 @@ module Bundler::URI
#
# Checks the host +v+ component for RFC2396 compliance
- # and against the Bundler::URI::Parser Regexp for :HOST.
+ # and against the +parser+ Regexp for :HOST.
#
# Can not have a registry or opaque component defined,
# with a host component defined.
@@ -615,6 +621,13 @@ module Bundler::URI
end
protected :set_host
+ # Protected setter for the authority info (+user+, +password+, +host+
+ # and +port+). If +port+ is +nil+, +default_port+ will be set.
+ #
+ protected def set_authority(user, password, host, port = nil)
+ @user, @password, @host, @port = user, password, host, port || self.default_port
+ end
+
#
# == Args
#
@@ -639,6 +652,7 @@ module Bundler::URI
def host=(v)
check_host(v)
set_host(v)
+ set_userinfo(nil)
v
end
@@ -675,7 +689,7 @@ module Bundler::URI
#
# Checks the port +v+ component for RFC2396 compliance
- # and against the Bundler::URI::Parser Regexp for :PORT.
+ # and against the +parser+ Regexp for :PORT.
#
# Can not have a registry or opaque component defined,
# with a port component defined.
@@ -729,6 +743,7 @@ module Bundler::URI
def port=(v)
check_port(v)
set_port(v)
+ set_userinfo(nil)
port
end
@@ -748,7 +763,7 @@ module Bundler::URI
#
# Checks the path +v+ component for RFC2396 compliance
- # and against the Bundler::URI::Parser Regexp
+ # and against the +parser+ Regexp
# for :ABS_PATH and :REL_PATH.
#
# Can not have a opaque component defined,
@@ -853,7 +868,7 @@ module Bundler::URI
#
# Checks the opaque +v+ component for RFC2396 compliance and
- # against the Bundler::URI::Parser Regexp for :OPAQUE.
+ # against the +parser+ Regexp for :OPAQUE.
#
# Can not have a host, port, user, or path component defined,
# with an opaque component defined.
@@ -905,7 +920,7 @@ module Bundler::URI
end
#
- # Checks the fragment +v+ component against the Bundler::URI::Parser Regexp for :FRAGMENT.
+ # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT.
#
#
# == Args
@@ -1121,7 +1136,7 @@ module Bundler::URI
base = self.dup
- authority = rel.userinfo || rel.host || rel.port
+ authority = rel.authority
# RFC2396, Section 5.2, 2)
if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
@@ -1134,9 +1149,7 @@ module Bundler::URI
# RFC2396, Section 5.2, 4)
if authority
- base.set_userinfo(rel.userinfo)
- base.set_host(rel.host)
- base.set_port(rel.port || base.default_port)
+ base.set_authority(*authority)
base.set_path(rel.path)
elsif base.path && rel.path
base.set_path(merge_path(base.path, rel.path))
@@ -1527,7 +1540,7 @@ module Bundler::URI
else
unless proxy_uri = env[name]
if proxy_uri = env[name.upcase]
- warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1
+ warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1
end
end
end
diff --git a/lib/bundler/vendor/uri/lib/uri/http.rb b/lib/bundler/vendor/uri/lib/uri/http.rb
index 6c34a469b7..9b217ee266 100644
--- a/lib/bundler/vendor/uri/lib/uri/http.rb
+++ b/lib/bundler/vendor/uri/lib/uri/http.rb
@@ -61,6 +61,18 @@ module Bundler::URI
super(tmp)
end
+ # Do not allow empty host names, as they are not allowed by RFC 3986.
+ def check_host(v)
+ ret = super
+
+ if ret && v.empty?
+ raise InvalidComponentError,
+ "bad component(expected host component): #{v}"
+ end
+
+ ret
+ end
+
#
# == Description
#
diff --git a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb
index 229971f73c..522113fe67 100644
--- a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb
+++ b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb
@@ -67,7 +67,7 @@ module Bundler::URI
#
# == Synopsis
#
- # Bundler::URI::Parser.new([opts])
+ # Bundler::URI::RFC2396_Parser.new([opts])
#
# == Args
#
@@ -86,7 +86,7 @@ module Bundler::URI
#
# == Examples
#
- # p = Bundler::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
+ # p = Bundler::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
# u = p.parse("http://example.jp/%uABCD") #=> #<Bundler::URI::HTTP http://example.jp/%uABCD>
# Bundler::URI.parse(u.to_s) #=> raises Bundler::URI::InvalidURIError
#
@@ -108,12 +108,12 @@ module Bundler::URI
# The Hash of patterns.
#
- # See also Bundler::URI::Parser.initialize_pattern.
+ # See also #initialize_pattern.
attr_reader :pattern
# The Hash of Regexp.
#
- # See also Bundler::URI::Parser.initialize_regexp.
+ # See also #initialize_regexp.
attr_reader :regexp
# Returns a split Bundler::URI against +regexp[:ABS_URI]+.
@@ -202,8 +202,7 @@ module Bundler::URI
#
# == Usage
#
- # p = Bundler::URI::Parser.new
- # p.parse("ldap://ldap.example.com/dc=example?user=john")
+ # Bundler::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john")
# #=> #<Bundler::URI::LDAP ldap://ldap.example.com/dc=example?user=john>
#
def parse(uri)
@@ -244,7 +243,7 @@ module Bundler::URI
# If no +block+ given, then returns the result,
# else it calls +block+ for each element in result.
#
- # See also Bundler::URI::Parser.make_regexp.
+ # See also #make_regexp.
#
def extract(str, schemes = nil)
if block_given?
@@ -263,7 +262,7 @@ module Bundler::URI
unless schemes
@regexp[:ABS_URI_REF]
else
- /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x
+ /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x
end
end
@@ -524,6 +523,8 @@ module Bundler::URI
ret
end
+ # Returns +uri+ as-is if it is Bundler::URI, or convert it to Bundler::URI if it is
+ # a String.
def convert_to_uri(uri)
if uri.is_a?(Bundler::URI::Generic)
uri
diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb
index d4996a12e2..ad76308e81 100644
--- a/lib/bundler/vendor/uri/lib/uri/version.rb
+++ b/lib/bundler/vendor/uri/lib/uri/version.rb
@@ -1,6 +1,6 @@
module Bundler::URI
# :stopdoc:
- VERSION_CODE = '010003'.freeze
- VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
+ VERSION = '1.1.1'.freeze
+ VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze
# :startdoc:
end
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
index fa24b4966e..ca7bb0719a 100644
--- a/lib/bundler/version.rb
+++ b/lib/bundler/version.rb
@@ -1,13 +1,21 @@
# frozen_string_literal: false
module Bundler
- VERSION = "2.7.0.dev".freeze
+ VERSION = "4.1.0.dev".freeze
def self.bundler_major_version
- @bundler_major_version ||= VERSION.split(".").first.to_i
+ @bundler_major_version ||= gem_version.segments.first
end
def self.gem_version
@gem_version ||= Gem::Version.create(VERSION)
end
+
+ def self.verbose_version
+ @verbose_version ||= "#{VERSION}#{simulated_version ? " (simulating Bundler #{simulated_version})" : ""}"
+ end
+
+ def self.simulated_version
+ @simulated_version ||= Bundler.settings[:simulate_version]
+ end
end
diff --git a/lib/bundler/vlad.rb b/lib/bundler/vlad.rb
index 6179d0e4eb..c3a3d949a6 100644
--- a/lib/bundler/vlad.rb
+++ b/lib/bundler/vlad.rb
@@ -1,17 +1,4 @@
# frozen_string_literal: true
require_relative "shared_helpers"
-Bundler::SharedHelpers.major_deprecation 2,
- "The Bundler task for Vlad"
-
-# Vlad task for Bundler.
-#
-# Add "require 'bundler/vlad'" in your Vlad deploy.rb, and
-# include the vlad:bundle:install task in your vlad:deploy task.
-require_relative "deployment"
-
-include Rake::DSL if defined? Rake::DSL
-
-namespace :vlad do
- Bundler::Deployment.define_task(Rake::RemoteTask, :remote_task, roles: :app)
-end
+Bundler::SharedHelpers.feature_removed! "The Bundler task for Vlad"
diff --git a/lib/cgi.rb b/lib/cgi.rb
index 69c3c4fd24..bb306d2e06 100644
--- a/lib/cgi.rb
+++ b/lib/cgi.rb
@@ -1,297 +1,7 @@
# frozen_string_literal: true
-#
-# cgi.rb - cgi support library
-#
-# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
-#
-# Copyright (C) 2000 Information-technology Promotion Agency, Japan
-#
-# Author: Wakou Aoyama <wakou@ruby-lang.org>
-#
-# Documentation: Wakou Aoyama (RDoc'd and embellished by William Webber)
-#
-# == Overview
-#
-# The Common Gateway Interface (CGI) is a simple protocol for passing an HTTP
-# request from a web server to a standalone program, and returning the output
-# to the web browser. Basically, a CGI program is called with the parameters
-# of the request passed in either in the environment (GET) or via $stdin
-# (POST), and everything it prints to $stdout is returned to the client.
-#
-# This file holds the CGI class. This class provides functionality for
-# retrieving HTTP request parameters, managing cookies, and generating HTML
-# output.
-#
-# The file CGI::Session provides session management functionality; see that
-# class for more details.
-#
-# See http://www.w3.org/CGI/ for more information on the CGI protocol.
-#
-# == Introduction
-#
-# CGI is a large class, providing several categories of methods, many of which
-# are mixed in from other modules. Some of the documentation is in this class,
-# some in the modules CGI::QueryExtension and CGI::HtmlExtension. See
-# CGI::Cookie for specific information on handling cookies, and cgi/session.rb
-# (CGI::Session) for information on sessions.
-#
-# For queries, CGI provides methods to get at environmental variables,
-# parameters, cookies, and multipart request data. For responses, CGI provides
-# methods for writing output and generating HTML.
-#
-# Read on for more details. Examples are provided at the bottom.
-#
-# == Queries
-#
-# The CGI class dynamically mixes in parameter and cookie-parsing
-# functionality, environmental variable access, and support for
-# parsing multipart requests (including uploaded files) from the
-# CGI::QueryExtension module.
-#
-# === Environmental Variables
-#
-# The standard CGI environmental variables are available as read-only
-# attributes of a CGI object. The following is a list of these variables:
-#
-#
-# AUTH_TYPE HTTP_HOST REMOTE_IDENT
-# CONTENT_LENGTH HTTP_NEGOTIATE REMOTE_USER
-# CONTENT_TYPE HTTP_PRAGMA REQUEST_METHOD
-# GATEWAY_INTERFACE HTTP_REFERER SCRIPT_NAME
-# HTTP_ACCEPT HTTP_USER_AGENT SERVER_NAME
-# HTTP_ACCEPT_CHARSET PATH_INFO SERVER_PORT
-# HTTP_ACCEPT_ENCODING PATH_TRANSLATED SERVER_PROTOCOL
-# HTTP_ACCEPT_LANGUAGE QUERY_STRING SERVER_SOFTWARE
-# HTTP_CACHE_CONTROL REMOTE_ADDR
-# HTTP_FROM REMOTE_HOST
-#
-#
-# For each of these variables, there is a corresponding attribute with the
-# same name, except all lower case and without a preceding HTTP_.
-# +content_length+ and +server_port+ are integers; the rest are strings.
-#
-# === Parameters
-#
-# The method #params() returns a hash of all parameters in the request as
-# name/value-list pairs, where the value-list is an Array of one or more
-# values. The CGI object itself also behaves as a hash of parameter names
-# to values, but only returns a single value (as a String) for each
-# parameter name.
-#
-# For instance, suppose the request contains the parameter
-# "favourite_colours" with the multiple values "blue" and "green". The
-# following behavior would occur:
-#
-# cgi.params["favourite_colours"] # => ["blue", "green"]
-# cgi["favourite_colours"] # => "blue"
-#
-# If a parameter does not exist, the former method will return an empty
-# array, the latter an empty string. The simplest way to test for existence
-# of a parameter is by the #has_key? method.
-#
-# === Cookies
-#
-# HTTP Cookies are automatically parsed from the request. They are available
-# from the #cookies() accessor, which returns a hash from cookie name to
-# CGI::Cookie object.
-#
-# === Multipart requests
-#
-# If a request's method is POST and its content type is multipart/form-data,
-# then it may contain uploaded files. These are stored by the QueryExtension
-# module in the parameters of the request. The parameter name is the name
-# attribute of the file input field, as usual. However, the value is not
-# a string, but an IO object, either an IOString for small files, or a
-# Tempfile for larger ones. This object also has the additional singleton
-# methods:
-#
-# #local_path():: the path of the uploaded file on the local filesystem
-# #original_filename():: the name of the file on the client computer
-# #content_type():: the content type of the file
-#
-# == Responses
-#
-# The CGI class provides methods for sending header and content output to
-# the HTTP client, and mixes in methods for programmatic HTML generation
-# from CGI::HtmlExtension and CGI::TagMaker modules. The precise version of HTML
-# to use for HTML generation is specified at object creation time.
-#
-# === Writing output
-#
-# The simplest way to send output to the HTTP client is using the #out() method.
-# This takes the HTTP headers as a hash parameter, and the body content
-# via a block. The headers can be generated as a string using the #http_header()
-# method. The output stream can be written directly to using the #print()
-# method.
-#
-# === Generating HTML
-#
-# Each HTML element has a corresponding method for generating that
-# element as a String. The name of this method is the same as that
-# of the element, all lowercase. The attributes of the element are
-# passed in as a hash, and the body as a no-argument block that evaluates
-# to a String. The HTML generation module knows which elements are
-# always empty, and silently drops any passed-in body. It also knows
-# which elements require matching closing tags and which don't. However,
-# it does not know what attributes are legal for which elements.
-#
-# There are also some additional HTML generation methods mixed in from
-# the CGI::HtmlExtension module. These include individual methods for the
-# different types of form inputs, and methods for elements that commonly
-# take particular attributes where the attributes can be directly specified
-# as arguments, rather than via a hash.
-#
-# === Utility HTML escape and other methods like a function.
-#
-# There are some utility tool defined in cgi/util.rb .
-# And when include, you can use utility methods like a function.
-#
-# == Examples of use
-#
-# === Get form values
-#
-# require "cgi"
-# cgi = CGI.new
-# value = cgi['field_name'] # <== value string for 'field_name'
-# # if not 'field_name' included, then return "".
-# fields = cgi.keys # <== array of field names
-#
-# # returns true if form has 'field_name'
-# cgi.has_key?('field_name')
-# cgi.has_key?('field_name')
-# cgi.include?('field_name')
-#
-# CAUTION! <code>cgi['field_name']</code> returned an Array with the old
-# cgi.rb(included in Ruby 1.6)
-#
-# === Get form values as hash
-#
-# require "cgi"
-# cgi = CGI.new
-# params = cgi.params
-#
-# cgi.params is a hash.
-#
-# cgi.params['new_field_name'] = ["value"] # add new param
-# cgi.params['field_name'] = ["new_value"] # change value
-# cgi.params.delete('field_name') # delete param
-# cgi.params.clear # delete all params
-#
-#
-# === Save form values to file
-#
-# require "pstore"
-# db = PStore.new("query.db")
-# db.transaction do
-# db["params"] = cgi.params
-# end
-#
-#
-# === Restore form values from file
-#
-# require "pstore"
-# db = PStore.new("query.db")
-# db.transaction do
-# cgi.params = db["params"]
-# end
-#
-#
-# === Get multipart form values
-#
-# require "cgi"
-# cgi = CGI.new
-# value = cgi['field_name'] # <== value string for 'field_name'
-# value.read # <== body of value
-# value.local_path # <== path to local file of value
-# value.original_filename # <== original filename of value
-# value.content_type # <== content_type of value
-#
-# and value has StringIO or Tempfile class methods.
-#
-# === Get cookie values
-#
-# require "cgi"
-# cgi = CGI.new
-# values = cgi.cookies['name'] # <== array of 'name'
-# # if not 'name' included, then return [].
-# names = cgi.cookies.keys # <== array of cookie names
-#
-# and cgi.cookies is a hash.
-#
-# === Get cookie objects
-#
-# require "cgi"
-# cgi = CGI.new
-# for name, cookie in cgi.cookies
-# cookie.expires = Time.now + 30
-# end
-# cgi.out("cookie" => cgi.cookies) {"string"}
-#
-# cgi.cookies # { "name1" => cookie1, "name2" => cookie2, ... }
-#
-# require "cgi"
-# cgi = CGI.new
-# cgi.cookies['name'].expires = Time.now + 30
-# cgi.out("cookie" => cgi.cookies['name']) {"string"}
-#
-# === Print http header and html string to $DEFAULT_OUTPUT ($>)
-#
-# require "cgi"
-# cgi = CGI.new("html4") # add HTML generation methods
-# cgi.out do
-# cgi.html do
-# cgi.head do
-# cgi.title { "TITLE" }
-# end +
-# cgi.body do
-# cgi.form("ACTION" => "uri") do
-# cgi.p do
-# cgi.textarea("get_text") +
-# cgi.br +
-# cgi.submit
-# end
-# end +
-# cgi.pre do
-# CGI.escapeHTML(
-# "params: #{cgi.params.inspect}\n" +
-# "cookies: #{cgi.cookies.inspect}\n" +
-# ENV.collect do |key, value|
-# "#{key} --> #{value}\n"
-# end.join("")
-# )
-# end
-# end
-# end
-# end
-#
-# # add HTML generation methods
-# CGI.new("html3") # html3.2
-# CGI.new("html4") # html4.01 (Strict)
-# CGI.new("html4Tr") # html4.01 Transitional
-# CGI.new("html4Fr") # html4.01 Frameset
-# CGI.new("html5") # html5
-#
-# === Some utility methods
-#
-# require 'cgi/util'
-# CGI.escapeHTML('Usage: foo "bar" <baz>')
-#
-#
-# === Some utility methods like a function
-#
-# require 'cgi/util'
-# include CGI::Util
-# escapeHTML('Usage: foo "bar" <baz>')
-# h('Usage: foo "bar" <baz>') # alias
-#
-#
-
-class CGI
- VERSION = "0.4.2"
-end
-
-require 'cgi/core'
-require 'cgi/cookie'
-require 'cgi/util'
-CGI.autoload(:HtmlExtension, 'cgi/html')
+require "cgi/escape"
+warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE
+CGI library is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features.
+If you need to use the full features of CGI library, Please install cgi gem.
+WARNING
diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec
deleted file mode 100644
index 5ef00d591d..0000000000
--- a/lib/cgi/cgi.gemspec
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-name = File.basename(__FILE__, ".gemspec")
-version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
- end rescue nil
-end
-
-Gem::Specification.new do |spec|
- spec.name = name
- spec.version = version
- spec.authors = ["Yukihiro Matsumoto"]
- spec.email = ["matz@ruby-lang.org"]
-
- spec.summary = %q{Support for the Common Gateway Interface protocol.}
- spec.description = %q{Support for the Common Gateway Interface protocol.}
- spec.homepage = "https://github.com/ruby/cgi"
- spec.licenses = ["Ruby", "BSD-2-Clause"]
- spec.required_ruby_version = ">= 2.5.0"
-
- spec.metadata["homepage_uri"] = spec.homepage
- spec.metadata["source_code_uri"] = spec.homepage
-
- spec.executables = []
-
- spec.files = [
- "COPYING",
- "BSDL",
- "README.md",
- *Dir["lib{.rb,/**/*.rb}", "bin/*"] ]
-
- spec.require_paths = ["lib"]
-
- if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby'
- spec.platform = 'java'
- spec.require_paths << "ext/java/org/jruby/ext/cgi/escape/lib"
- spec.files += Dir["ext/java/**/*.{rb}", "lib/cgi/escape.jar"]
- else
- spec.files += Dir["ext/cgi/**/*.{rb,c,h,sh}", "ext/cgi/escape/depend", "lib/cgi/escape.so"]
- spec.extensions = ["ext/cgi/escape/extconf.rb"]
- end
-end
diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb
deleted file mode 100644
index 1c4ef6a600..0000000000
--- a/lib/cgi/cookie.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-# frozen_string_literal: true
-require_relative 'util'
-class CGI
- # Class representing an HTTP cookie.
- #
- # In addition to its specific fields and methods, a Cookie instance
- # is a delegator to the array of its values.
- #
- # See RFC 2965.
- #
- # == Examples of use
- # cookie1 = CGI::Cookie.new("name", "value1", "value2", ...)
- # cookie1 = CGI::Cookie.new("name" => "name", "value" => "value")
- # cookie1 = CGI::Cookie.new('name' => 'name',
- # 'value' => ['value1', 'value2', ...],
- # 'path' => 'path', # optional
- # 'domain' => 'domain', # optional
- # 'expires' => Time.now, # optional
- # 'secure' => true, # optional
- # 'httponly' => true # optional
- # )
- #
- # cgi.out("cookie" => [cookie1, cookie2]) { "string" }
- #
- # name = cookie1.name
- # values = cookie1.value
- # path = cookie1.path
- # domain = cookie1.domain
- # expires = cookie1.expires
- # secure = cookie1.secure
- # httponly = cookie1.httponly
- #
- # cookie1.name = 'name'
- # cookie1.value = ['value1', 'value2', ...]
- # cookie1.path = 'path'
- # cookie1.domain = 'domain'
- # cookie1.expires = Time.now + 30
- # cookie1.secure = true
- # cookie1.httponly = true
- class Cookie < Array
- @@accept_charset="UTF-8" unless defined?(@@accept_charset)
-
- TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z"
- PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z"
- DOMAIN_VALUE_RE = %r"\A\.?(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z"
-
- # Create a new CGI::Cookie object.
- #
- # :call-seq:
- # Cookie.new(name_string,*value)
- # Cookie.new(options_hash)
- #
- # +name_string+::
- # The name of the cookie; in this form, there is no #domain or
- # #expiration. The #path is gleaned from the +SCRIPT_NAME+ environment
- # variable, and #secure is false.
- # <tt>*value</tt>::
- # value or list of values of the cookie
- # +options_hash+::
- # A Hash of options to initialize this Cookie. Possible options are:
- #
- # name:: the name of the cookie. Required.
- # value:: the cookie's value or list of values.
- # path:: the path for which this cookie applies. Defaults to
- # the value of the +SCRIPT_NAME+ environment variable.
- # domain:: the domain for which this cookie applies.
- # expires:: the time at which this cookie expires, as a +Time+ object.
- # secure:: whether this cookie is a secure cookie or not (default to
- # false). Secure cookies are only transmitted to HTTPS
- # servers.
- # httponly:: whether this cookie is a HttpOnly cookie or not (default to
- # false). HttpOnly cookies are not available to javascript.
- #
- # These keywords correspond to attributes of the cookie object.
- def initialize(name = "", *value)
- @domain = nil
- @expires = nil
- if name.kind_of?(String)
- self.name = name
- self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
- @secure = false
- @httponly = false
- return super(value)
- end
-
- options = name
- unless options.has_key?("name")
- raise ArgumentError, "`name' required"
- end
-
- self.name = options["name"]
- value = Array(options["value"])
- # simple support for IE
- self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
- self.domain = options["domain"]
- @expires = options["expires"]
- @secure = options["secure"] == true
- @httponly = options["httponly"] == true
-
- super(value)
- end
-
- # Name of this cookie, as a +String+
- attr_reader :name
- # Set name of this cookie
- def name=(str)
- if str and !TOKEN_RE.match?(str)
- raise ArgumentError, "invalid name: #{str.dump}"
- end
- @name = str
- end
-
- # Path for which this cookie applies, as a +String+
- attr_reader :path
- # Set path for which this cookie applies
- def path=(str)
- if str and !PATH_VALUE_RE.match?(str)
- raise ArgumentError, "invalid path: #{str.dump}"
- end
- @path = str
- end
-
- # Domain for which this cookie applies, as a +String+
- attr_reader :domain
- # Set domain for which this cookie applies
- def domain=(str)
- if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str))
- raise ArgumentError, "invalid domain: #{str.dump}"
- end
- @domain = str
- end
-
- # Time at which this cookie expires, as a +Time+
- attr_accessor :expires
- # True if this cookie is secure; false otherwise
- attr_reader :secure
- # True if this cookie is httponly; false otherwise
- attr_reader :httponly
-
- # Returns the value or list of values for this cookie.
- def value
- self
- end
-
- # Replaces the value of this cookie with a new value or list of values.
- def value=(val)
- replace(Array(val))
- end
-
- # Set whether the Cookie is a secure cookie or not.
- #
- # +val+ must be a boolean.
- def secure=(val)
- @secure = val if val == true or val == false
- @secure
- end
-
- # Set whether the Cookie is a httponly cookie or not.
- #
- # +val+ must be a boolean.
- def httponly=(val)
- @httponly = !!val
- end
-
- # Convert the Cookie to its string representation.
- def to_s
- val = collect{|v| CGI.escape(v) }.join("&")
- buf = "#{@name}=#{val}".dup
- buf << "; domain=#{@domain}" if @domain
- buf << "; path=#{@path}" if @path
- buf << "; expires=#{CGI.rfc1123_date(@expires)}" if @expires
- buf << "; secure" if @secure
- buf << "; HttpOnly" if @httponly
- buf
- end
-
- # Parse a raw cookie string into a hash of cookie-name=>Cookie
- # pairs.
- #
- # cookies = CGI::Cookie.parse("raw_cookie_string")
- # # { "name1" => cookie1, "name2" => cookie2, ... }
- #
- def self.parse(raw_cookie)
- cookies = Hash.new([])
- return cookies unless raw_cookie
-
- raw_cookie.split(/;\s?/).each do |pairs|
- name, values = pairs.split('=',2)
- next unless name and values
- values ||= ""
- values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) }
- if cookies.has_key?(name)
- cookies[name].concat(values)
- else
- cookies[name] = Cookie.new(name, *values)
- end
- end
-
- cookies
- end
-
- # A summary of cookie string.
- def inspect
- "#<CGI::Cookie: #{self.to_s.inspect}>"
- end
-
- end # class Cookie
-end
-
-
diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb
deleted file mode 100644
index 62e606837a..0000000000
--- a/lib/cgi/core.rb
+++ /dev/null
@@ -1,900 +0,0 @@
-# frozen_string_literal: true
-#--
-# Methods for generating HTML, parsing CGI-related parameters, and
-# generating HTTP responses.
-#++
-class CGI
- unless const_defined?(:Util)
- module Util
- @@accept_charset = "UTF-8" # :nodoc:
- end
- include Util
- extend Util
- end
-
- $CGI_ENV = ENV # for FCGI support
-
- # String for carriage return
- CR = "\015"
-
- # String for linefeed
- LF = "\012"
-
- # Standard internet newline sequence
- EOL = CR + LF
-
- REVISION = '$Id$' #:nodoc:
-
- # Whether processing will be required in binary vs text
- NEEDS_BINMODE = File::BINARY != 0
-
- # Path separators in different environments.
- PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'}
-
- # HTTP status codes.
- HTTP_STATUS = {
- "OK" => "200 OK",
- "PARTIAL_CONTENT" => "206 Partial Content",
- "MULTIPLE_CHOICES" => "300 Multiple Choices",
- "MOVED" => "301 Moved Permanently",
- "REDIRECT" => "302 Found",
- "NOT_MODIFIED" => "304 Not Modified",
- "BAD_REQUEST" => "400 Bad Request",
- "AUTH_REQUIRED" => "401 Authorization Required",
- "FORBIDDEN" => "403 Forbidden",
- "NOT_FOUND" => "404 Not Found",
- "METHOD_NOT_ALLOWED" => "405 Method Not Allowed",
- "NOT_ACCEPTABLE" => "406 Not Acceptable",
- "LENGTH_REQUIRED" => "411 Length Required",
- "PRECONDITION_FAILED" => "412 Precondition Failed",
- "SERVER_ERROR" => "500 Internal Server Error",
- "NOT_IMPLEMENTED" => "501 Method Not Implemented",
- "BAD_GATEWAY" => "502 Bad Gateway",
- "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"
- }
-
- # :startdoc:
-
- # Synonym for ENV.
- def env_table
- ENV
- end
-
- # Synonym for $stdin.
- def stdinput
- $stdin
- end
-
- # Synonym for $stdout.
- def stdoutput
- $stdout
- end
-
- private :env_table, :stdinput, :stdoutput
-
- # Create an HTTP header block as a string.
- #
- # :call-seq:
- # http_header(content_type_string="text/html")
- # http_header(headers_hash)
- #
- # Includes the empty line that ends the header block.
- #
- # +content_type_string+::
- # If this form is used, this string is the <tt>Content-Type</tt>
- # +headers_hash+::
- # A Hash of header values. The following header keys are recognized:
- #
- # type:: The Content-Type header. Defaults to "text/html"
- # charset:: The charset of the body, appended to the Content-Type header.
- # nph:: A boolean value. If true, prepend protocol string and status
- # code, and date; and sets default values for "server" and
- # "connection" if not explicitly set.
- # status::
- # The HTTP status code as a String, returned as the Status header. The
- # values are:
- #
- # OK:: 200 OK
- # PARTIAL_CONTENT:: 206 Partial Content
- # MULTIPLE_CHOICES:: 300 Multiple Choices
- # MOVED:: 301 Moved Permanently
- # REDIRECT:: 302 Found
- # NOT_MODIFIED:: 304 Not Modified
- # BAD_REQUEST:: 400 Bad Request
- # AUTH_REQUIRED:: 401 Authorization Required
- # FORBIDDEN:: 403 Forbidden
- # NOT_FOUND:: 404 Not Found
- # METHOD_NOT_ALLOWED:: 405 Method Not Allowed
- # NOT_ACCEPTABLE:: 406 Not Acceptable
- # LENGTH_REQUIRED:: 411 Length Required
- # PRECONDITION_FAILED:: 412 Precondition Failed
- # SERVER_ERROR:: 500 Internal Server Error
- # NOT_IMPLEMENTED:: 501 Method Not Implemented
- # BAD_GATEWAY:: 502 Bad Gateway
- # VARIANT_ALSO_VARIES:: 506 Variant Also Negotiates
- #
- # server:: The server software, returned as the Server header.
- # connection:: The connection type, returned as the Connection header (for
- # instance, "close".
- # length:: The length of the content that will be sent, returned as the
- # Content-Length header.
- # language:: The language of the content, returned as the Content-Language
- # header.
- # expires:: The time on which the current content expires, as a +Time+
- # object, returned as the Expires header.
- # cookie::
- # A cookie or cookies, returned as one or more Set-Cookie headers. The
- # value can be the literal string of the cookie; a CGI::Cookie object;
- # an Array of literal cookie strings or Cookie objects; or a hash all of
- # whose values are literal cookie strings or Cookie objects.
- #
- # These cookies are in addition to the cookies held in the
- # @output_cookies field.
- #
- # Other headers can also be set; they are appended as key: value.
- #
- # Examples:
- #
- # http_header
- # # Content-Type: text/html
- #
- # http_header("text/plain")
- # # Content-Type: text/plain
- #
- # http_header("nph" => true,
- # "status" => "OK", # == "200 OK"
- # # "status" => "200 GOOD",
- # "server" => ENV['SERVER_SOFTWARE'],
- # "connection" => "close",
- # "type" => "text/html",
- # "charset" => "iso-2022-jp",
- # # Content-Type: text/html; charset=iso-2022-jp
- # "length" => 103,
- # "language" => "ja",
- # "expires" => Time.now + 30,
- # "cookie" => [cookie1, cookie2],
- # "my_header1" => "my_value",
- # "my_header2" => "my_value")
- #
- # This method does not perform charset conversion.
- def http_header(options='text/html')
- if options.is_a?(String)
- content_type = options
- buf = _header_for_string(content_type)
- elsif options.is_a?(Hash)
- if options.size == 1 && options.has_key?('type')
- content_type = options['type']
- buf = _header_for_string(content_type)
- else
- buf = _header_for_hash(options.dup)
- end
- else
- raise ArgumentError.new("expected String or Hash but got #{options.class}")
- end
- if defined?(MOD_RUBY)
- _header_for_modruby(buf)
- return ''
- else
- buf << EOL # empty line of separator
- return buf
- end
- end # http_header()
-
- # This method is an alias for #http_header, when HTML5 tag maker is inactive.
- #
- # NOTE: use #http_header to create HTTP header blocks, this alias is only
- # provided for backwards compatibility.
- #
- # Using #header with the HTML5 tag maker will create a <header> element.
- alias :header :http_header
-
- def _no_crlf_check(str)
- if str
- str = str.to_s
- raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/
- str
- else
- nil
- end
- end
- private :_no_crlf_check
-
- def _header_for_string(content_type) #:nodoc:
- buf = ''.dup
- if nph?()
- buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}"
- buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
- buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}"
- buf << "Connection: close#{EOL}"
- end
- buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}"
- if @output_cookies
- @output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" }
- end
- return buf
- end # _header_for_string
- private :_header_for_string
-
- def _header_for_hash(options) #:nodoc:
- buf = ''.dup
- ## add charset to option['type']
- options['type'] ||= 'text/html'
- charset = options.delete('charset')
- options['type'] += "; charset=#{charset}" if charset
- ## NPH
- options.delete('nph') if defined?(MOD_RUBY)
- if options.delete('nph') || nph?()
- protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'
- status = options.delete('status')
- status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK'
- buf << "#{protocol} #{status}#{EOL}"
- buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
- options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
- options['connection'] ||= 'close'
- end
- ## common headers
- status = options.delete('status')
- buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status
- server = options.delete('server')
- buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server
- connection = options.delete('connection')
- buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection
- type = options.delete('type')
- buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type
- length = options.delete('length')
- buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length
- language = options.delete('language')
- buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language
- expires = options.delete('expires')
- buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
- ## cookie
- if cookie = options.delete('cookie')
- case cookie
- when String, Cookie
- buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}"
- when Array
- arr = cookie
- arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
- when Hash
- hash = cookie
- hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
- end
- end
- if @output_cookies
- @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
- end
- ## other headers
- options.each do |key, value|
- buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}"
- end
- return buf
- end # _header_for_hash
- private :_header_for_hash
-
- def nph? #:nodoc:
- return /IIS\/(\d+)/ =~ $CGI_ENV['SERVER_SOFTWARE'] && $1.to_i < 5
- end
-
- def _header_for_modruby(buf) #:nodoc:
- request = Apache::request
- buf.scan(/([^:]+): (.+)#{EOL}/o) do |name, value|
- $stderr.printf("name:%s value:%s\n", name, value) if $DEBUG
- case name
- when 'Set-Cookie'
- request.headers_out.add(name, value)
- when /^status$/i
- request.status_line = value
- request.status = value.to_i
- when /^content-type$/i
- request.content_type = value
- when /^content-encoding$/i
- request.content_encoding = value
- when /^location$/i
- request.status = 302 if request.status == 200
- request.headers_out[name] = value
- else
- request.headers_out[name] = value
- end
- end
- request.send_http_header
- return ''
- end
- private :_header_for_modruby
-
- # Print an HTTP header and body to $DEFAULT_OUTPUT ($>)
- #
- # :call-seq:
- # cgi.out(content_type_string='text/html')
- # cgi.out(headers_hash)
- #
- # +content_type_string+::
- # If a string is passed, it is assumed to be the content type.
- # +headers_hash+::
- # This is a Hash of headers, similar to that used by #http_header.
- # +block+::
- # A block is required and should evaluate to the body of the response.
- #
- # <tt>Content-Length</tt> is automatically calculated from the size of
- # the String returned by the content block.
- #
- # If <tt>ENV['REQUEST_METHOD'] == "HEAD"</tt>, then only the header
- # is output (the content block is still required, but it is ignored).
- #
- # If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then the
- # content is converted to this charset, and the language is set to "ja".
- #
- # Example:
- #
- # cgi = CGI.new
- # cgi.out{ "string" }
- # # Content-Type: text/html
- # # Content-Length: 6
- # #
- # # string
- #
- # cgi.out("text/plain") { "string" }
- # # Content-Type: text/plain
- # # Content-Length: 6
- # #
- # # string
- #
- # cgi.out("nph" => true,
- # "status" => "OK", # == "200 OK"
- # "server" => ENV['SERVER_SOFTWARE'],
- # "connection" => "close",
- # "type" => "text/html",
- # "charset" => "iso-2022-jp",
- # # Content-Type: text/html; charset=iso-2022-jp
- # "language" => "ja",
- # "expires" => Time.now + (3600 * 24 * 30),
- # "cookie" => [cookie1, cookie2],
- # "my_header1" => "my_value",
- # "my_header2" => "my_value") { "string" }
- # # HTTP/1.1 200 OK
- # # Date: Sun, 15 May 2011 17:35:54 GMT
- # # Server: Apache 2.2.0
- # # Connection: close
- # # Content-Type: text/html; charset=iso-2022-jp
- # # Content-Length: 6
- # # Content-Language: ja
- # # Expires: Tue, 14 Jun 2011 17:35:54 GMT
- # # Set-Cookie: foo
- # # Set-Cookie: bar
- # # my_header1: my_value
- # # my_header2: my_value
- # #
- # # string
- def out(options = "text/html") # :yield:
-
- options = { "type" => options } if options.kind_of?(String)
- content = yield
- options["length"] = content.bytesize.to_s
- output = stdoutput
- output.binmode if defined? output.binmode
- output.print http_header(options)
- output.print content unless "HEAD" == env_table['REQUEST_METHOD']
- end
-
-
- # Print an argument or list of arguments to the default output stream
- #
- # cgi = CGI.new
- # cgi.print # default: cgi.print == $DEFAULT_OUTPUT.print
- def print(*options)
- stdoutput.print(*options)
- end
-
- # Parse an HTTP query string into a hash of key=>value pairs.
- #
- # params = CGI.parse("query_string")
- # # {"name1" => ["value1", "value2", ...],
- # # "name2" => ["value1", "value2", ...], ... }
- #
- def self.parse(query)
- params = {}
- query.split(/[&;]/).each do |pairs|
- key, value = pairs.split('=',2).collect{|v| CGI.unescape(v) }
-
- next unless key
-
- params[key] ||= []
- params[key].push(value) if value
- end
-
- params.default=[].freeze
- params
- end
-
- # Maximum content length of post data
- ##MAX_CONTENT_LENGTH = 2 * 1024 * 1024
-
- # Maximum number of request parameters when multipart
- MAX_MULTIPART_COUNT = 128
-
- # Mixin module that provides the following:
- #
- # 1. Access to the CGI environment variables as methods. See
- # documentation to the CGI class for a list of these variables. The
- # methods are exposed by removing the leading +HTTP_+ (if it exists) and
- # downcasing the name. For example, +auth_type+ will return the
- # environment variable +AUTH_TYPE+, and +accept+ will return the value
- # for +HTTP_ACCEPT+.
- #
- # 2. Access to cookies, including the cookies attribute.
- #
- # 3. Access to parameters, including the params attribute, and overloading
- # #[] to perform parameter value lookup by key.
- #
- # 4. The initialize_query method, for initializing the above
- # mechanisms, handling multipart forms, and allowing the
- # class to be used in "offline" mode.
- #
- module QueryExtension
-
- %w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
- define_method(env.delete_prefix('HTTP_').downcase) do
- (val = env_table[env]) && Integer(val)
- end
- end
-
- %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
- PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST
- REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME
- SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE
-
- HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
- HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
- HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
- define_method(env.delete_prefix('HTTP_').downcase) do
- env_table[env]
- end
- end
-
- # Get the raw cookies as a string.
- def raw_cookie
- env_table["HTTP_COOKIE"]
- end
-
- # Get the raw RFC2965 cookies as a string.
- def raw_cookie2
- env_table["HTTP_COOKIE2"]
- end
-
- # Get the cookies as a hash of cookie-name=>Cookie pairs.
- attr_accessor :cookies
-
- # Get the parameters as a hash of name=>values pairs, where
- # values is an Array.
- attr_reader :params
-
- # Get the uploaded files as a hash of name=>values pairs
- attr_reader :files
-
- # Set all the parameters.
- def params=(hash)
- @params.clear
- @params.update(hash)
- end
-
- ##
- # Parses multipart form elements according to
- # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
- #
- # Returns a hash of multipart form parameters with bodies of type StringIO or
- # Tempfile depending on whether the multipart form element exceeds 10 KB
- #
- # params[name => body]
- #
- def read_multipart(boundary, content_length)
- ## read first boundary
- stdin = stdinput
- first_line = "--#{boundary}#{EOL}"
- content_length -= first_line.bytesize
- status = stdin.read(first_line.bytesize)
- raise EOFError.new("no content body") unless status
- raise EOFError.new("bad content body") unless first_line == status
- ## parse and set params
- params = {}
- @files = {}
- boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/
- boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize
- buf = ''.dup
- bufsize = 10 * 1024
- max_count = MAX_MULTIPART_COUNT
- n = 0
- tempfiles = []
- while true
- (n += 1) < max_count or raise StandardError.new("too many parameters.")
- ## create body (StringIO or Tempfile)
- body = create_body(bufsize < content_length)
- tempfiles << body if defined?(Tempfile) && body.kind_of?(Tempfile)
- class << body
- if method_defined?(:path)
- alias local_path path
- else
- def local_path
- nil
- end
- end
- attr_reader :original_filename, :content_type
- end
- ## find head and boundary
- head = nil
- separator = EOL * 2
- until head && matched = boundary_rexp.match(buf)
- if !head && pos = buf.index(separator)
- len = pos + EOL.bytesize
- head = buf[0, len]
- buf = buf[(pos+separator.bytesize)..-1]
- else
- if head && buf.size > boundary_size
- len = buf.size - boundary_size
- body.print(buf[0, len])
- buf[0, len] = ''
- end
- c = stdin.read(bufsize < content_length ? bufsize : content_length)
- raise EOFError.new("bad content body") if c.nil? || c.empty?
- buf << c
- content_length -= c.bytesize
- end
- end
- ## read to end of boundary
- m = matched
- len = m.begin(0)
- s = buf[0, len]
- if s =~ /(\r?\n)\z/
- s = buf[0, len - $1.bytesize]
- end
- body.print(s)
- buf = buf[m.end(0)..-1]
- boundary_end = m[1]
- content_length = -1 if boundary_end == '--'
- ## reset file cursor position
- body.rewind
- ## original filename
- /Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
- filename = $1 || $2 || ''.dup
- filename = CGI.unescape(filename) if unescape_filename?()
- body.instance_variable_set(:@original_filename, filename)
- ## content type
- /Content-Type: (.*)/i.match(head)
- (content_type = $1 || ''.dup).chomp!
- body.instance_variable_set(:@content_type, content_type)
- ## query parameter name
- /Content-Disposition:.* name=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
- name = $1 || $2 || ''
- if body.original_filename.empty?
- value=body.read.dup.force_encoding(@accept_charset)
- body.close! if defined?(Tempfile) && body.kind_of?(Tempfile)
- (params[name] ||= []) << value
- unless value.valid_encoding?
- if @accept_charset_error_block
- @accept_charset_error_block.call(name,value)
- else
- raise InvalidEncoding,"Accept-Charset encoding error"
- end
- end
- class << params[name].last;self;end.class_eval do
- define_method(:read){self}
- define_method(:original_filename){""}
- define_method(:content_type){""}
- end
- else
- (params[name] ||= []) << body
- @files[name]=body
- end
- ## break loop
- break if content_length == -1
- end
- raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/
- params.default = []
- params
- rescue Exception
- if tempfiles
- tempfiles.each {|t|
- if t.path
- t.close!
- end
- }
- end
- raise
- end # read_multipart
- private :read_multipart
- def create_body(is_large) #:nodoc:
- if is_large
- require 'tempfile'
- body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT)
- else
- begin
- require 'stringio'
- body = StringIO.new("".b)
- rescue LoadError
- require 'tempfile'
- body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT)
- end
- end
- body.binmode if defined? body.binmode
- return body
- end
- def unescape_filename? #:nodoc:
- user_agent = $CGI_ENV['HTTP_USER_AGENT']
- return false unless user_agent
- return /Mac/i.match(user_agent) && /Mozilla/i.match(user_agent) && !/MSIE/i.match(user_agent)
- end
-
- # offline mode. read name=value pairs on standard input.
- def read_from_cmdline
- require "shellwords"
-
- string = unless ARGV.empty?
- ARGV.join(' ')
- else
- if STDIN.tty?
- STDERR.print(
- %|(offline mode: enter name=value pairs on standard input)\n|
- )
- end
- array = readlines rescue nil
- if not array.nil?
- array.join(' ').gsub(/\n/n, '')
- else
- ""
- end
- end.gsub(/\\=/n, '%3D').gsub(/\\&/n, '%26')
-
- words = Shellwords.shellwords(string)
-
- if words.find{|x| /=/n.match(x) }
- words.join('&')
- else
- words.join('+')
- end
- end
- private :read_from_cmdline
-
- # A wrapper class to use a StringIO object as the body and switch
- # to a TempFile when the passed threshold is passed.
- # Initialize the data from the query.
- #
- # Handles multipart forms (in particular, forms that involve file uploads).
- # Reads query parameters in the @params field, and cookies into @cookies.
- def initialize_query()
- if ("POST" == env_table['REQUEST_METHOD']) and
- %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?| =~ env_table['CONTENT_TYPE']
- current_max_multipart_length = @max_multipart_length.respond_to?(:call) ? @max_multipart_length.call : @max_multipart_length
- raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > current_max_multipart_length
- boundary = $1.dup
- @multipart = true
- @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
- else
- @multipart = false
- @params = CGI.parse(
- case env_table['REQUEST_METHOD']
- when "GET", "HEAD"
- if defined?(MOD_RUBY)
- Apache::request.args or ""
- else
- env_table['QUERY_STRING'] or ""
- end
- when "POST"
- stdinput.binmode if defined? stdinput.binmode
- stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
- else
- read_from_cmdline
- end.dup.force_encoding(@accept_charset)
- )
- unless Encoding.find(@accept_charset) == Encoding::ASCII_8BIT
- @params.each do |key,values|
- values.each do |value|
- unless value.valid_encoding?
- if @accept_charset_error_block
- @accept_charset_error_block.call(key,value)
- else
- raise InvalidEncoding,"Accept-Charset encoding error"
- end
- end
- end
- end
- end
- end
-
- @cookies = CGI::Cookie.parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
- end
- private :initialize_query
-
- # Returns whether the form contained multipart/form-data
- def multipart?
- @multipart
- end
-
- # Get the value for the parameter with a given key.
- #
- # If the parameter has multiple values, only the first will be
- # retrieved; use #params to get the array of values.
- def [](key)
- params = @params[key]
- return '' unless params
- value = params[0]
- if @multipart
- if value
- return value
- elsif defined? StringIO
- StringIO.new("".b)
- else
- Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT)
- end
- else
- str = if value then value.dup else "" end
- str
- end
- end
-
- # Return all query parameter names as an array of String.
- def keys(*args)
- @params.keys(*args)
- end
-
- # Returns true if a given query string parameter exists.
- def has_key?(*args)
- @params.has_key?(*args)
- end
- alias key? has_key?
- alias include? has_key?
-
- end # QueryExtension
-
- # Exception raised when there is an invalid encoding detected
- class InvalidEncoding < Exception; end
-
- # @@accept_charset is default accept character set.
- # This default value default is "UTF-8"
- # If you want to change the default accept character set
- # when create a new CGI instance, set this:
- #
- # CGI.accept_charset = "EUC-JP"
- #
- @@accept_charset="UTF-8" if false # needed for rdoc?
-
- # Return the accept character set for all new CGI instances.
- def self.accept_charset
- @@accept_charset
- end
-
- # Set the accept character set for all new CGI instances.
- def self.accept_charset=(accept_charset)
- @@accept_charset=accept_charset
- end
-
- # Return the accept character set for this CGI instance.
- attr_reader :accept_charset
-
- # @@max_multipart_length is the maximum length of multipart data.
- # The default value is 128 * 1024 * 1024 bytes
- #
- # The default can be set to something else in the CGI constructor,
- # via the :max_multipart_length key in the option hash.
- #
- # See CGI.new documentation.
- #
- @@max_multipart_length= 128 * 1024 * 1024
-
- # Create a new CGI instance.
- #
- # :call-seq:
- # CGI.new(tag_maker) { block }
- # CGI.new(options_hash = {}) { block }
- #
- #
- # <tt>tag_maker</tt>::
- # This is the same as using the +options_hash+ form with the value <tt>{
- # :tag_maker => tag_maker }</tt> Note that it is recommended to use the
- # +options_hash+ form, since it also allows you specify the charset you
- # will accept.
- # <tt>options_hash</tt>::
- # A Hash that recognizes three options:
- #
- # <tt>:accept_charset</tt>::
- # specifies encoding of received query string. If omitted,
- # <tt>@@accept_charset</tt> is used. If the encoding is not valid, a
- # CGI::InvalidEncoding will be raised.
- #
- # Example. Suppose <tt>@@accept_charset</tt> is "UTF-8"
- #
- # when not specified:
- #
- # cgi=CGI.new # @accept_charset # => "UTF-8"
- #
- # when specified as "EUC-JP":
- #
- # cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP"
- #
- # <tt>:tag_maker</tt>::
- # String that specifies which version of the HTML generation methods to
- # use. If not specified, no HTML generation methods will be loaded.
- #
- # The following values are supported:
- #
- # "html3":: HTML 3.x
- # "html4":: HTML 4.0
- # "html4Tr":: HTML 4.0 Transitional
- # "html4Fr":: HTML 4.0 with Framesets
- # "html5":: HTML 5
- #
- # <tt>:max_multipart_length</tt>::
- # Specifies maximum length of multipart data. Can be an Integer scalar or
- # a lambda, that will be evaluated when the request is parsed. This
- # allows more complex logic to be set when determining whether to accept
- # multipart data (e.g. consult a registered users upload allowance)
- #
- # Default is 128 * 1024 * 1024 bytes
- #
- # cgi=CGI.new(:max_multipart_length => 268435456) # simple scalar
- #
- # cgi=CGI.new(:max_multipart_length => -> {check_filesystem}) # lambda
- #
- # <tt>block</tt>::
- # If provided, the block is called when an invalid encoding is
- # encountered. For example:
- #
- # encoding_errors={}
- # cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value|
- # encoding_errors[name] = value
- # end
- #
- # Finally, if the CGI object is not created in a standard CGI call
- # environment (that is, it can't locate REQUEST_METHOD in its environment),
- # then it will run in "offline" mode. In this mode, it reads its parameters
- # from the command line or (failing that) from standard input. Otherwise,
- # cookies and other parameters are parsed automatically from the standard
- # CGI locations, which varies according to the REQUEST_METHOD.
- def initialize(options = {}, &block) # :yields: name, value
- @accept_charset_error_block = block_given? ? block : nil
- @options={
- :accept_charset=>@@accept_charset,
- :max_multipart_length=>@@max_multipart_length
- }
- case options
- when Hash
- @options.merge!(options)
- when String
- @options[:tag_maker]=options
- end
- @accept_charset=@options[:accept_charset]
- @max_multipart_length=@options[:max_multipart_length]
- if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
- Apache.request.setup_cgi_env
- end
-
- extend QueryExtension
- @multipart = false
-
- initialize_query() # set @params, @cookies
- @output_cookies = nil
- @output_hidden = nil
-
- case @options[:tag_maker]
- when "html3"
- require_relative 'html'
- extend Html3
- extend HtmlExtension
- when "html4"
- require_relative 'html'
- extend Html4
- extend HtmlExtension
- when "html4Tr"
- require_relative 'html'
- extend Html4Tr
- extend HtmlExtension
- when "html4Fr"
- require_relative 'html'
- extend Html4Tr
- extend Html4Fr
- extend HtmlExtension
- when "html5"
- require_relative 'html'
- extend Html5
- extend HtmlExtension
- end
- end
-
-end # class CGI
diff --git a/lib/cgi/escape.rb b/lib/cgi/escape.rb
new file mode 100644
index 0000000000..555d24a5da
--- /dev/null
+++ b/lib/cgi/escape.rb
@@ -0,0 +1,232 @@
+# frozen_string_literal: true
+
+# Since Ruby 4.0, \CGI is a small holder for various escaping methods, included from CGI::Escape
+#
+# require 'cgi/escape'
+#
+# CGI.escape("Ruby programming language")
+# #=> "Ruby+programming+language"
+# CGI.escapeURIComponent("Ruby programming language")
+# #=> "Ruby%20programming%20language"
+#
+# See CGI::Escape module for methods list and their description.
+class CGI
+ module Escape; end
+ include Escape
+ extend Escape
+ module EscapeExt; end # :nodoc:
+end
+
+# Web-related escape/unescape functionality.
+module CGI::Escape
+ @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset)
+
+ # URL-encode a string into application/x-www-form-urlencoded.
+ # Space characters (<tt>" "</tt>) are encoded with plus signs (<tt>"+"</tt>)
+ # url_encoded_string = CGI.escape("'Stop!' said Fred")
+ # # => "%27Stop%21%27+said+Fred"
+ def escape(string)
+ encoding = string.encoding
+ buffer = string.b
+ buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m|
+ '%' + m.unpack('H2' * m.bytesize).join('%').upcase
+ end
+ buffer.tr!(' ', '+')
+ buffer.force_encoding(encoding)
+ end
+
+ # URL-decode an application/x-www-form-urlencoded string with encoding(optional).
+ # string = CGI.unescape("%27Stop%21%27+said+Fred")
+ # # => "'Stop!' said Fred"
+ def unescape(string, encoding = @@accept_charset)
+ str = string.tr('+', ' ')
+ str = str.b
+ str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
+ [m.delete('%')].pack('H*')
+ end
+ str.force_encoding(encoding)
+ str.valid_encoding? ? str : str.force_encoding(string.encoding)
+ end
+
+ # URL-encode a string following RFC 3986
+ # Space characters (<tt>" "</tt>) are encoded with (<tt>"%20"</tt>)
+ # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred")
+ # # => "%27Stop%21%27%20said%20Fred"
+ def escapeURIComponent(string)
+ encoding = string.encoding
+ buffer = string.b
+ buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m|
+ '%' + m.unpack('H2' * m.bytesize).join('%').upcase
+ end
+ buffer.force_encoding(encoding)
+ end
+ alias escape_uri_component escapeURIComponent
+
+ # URL-decode a string following RFC 3986 with encoding(optional).
+ # string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred")
+ # # => "'Stop!'+said Fred"
+ def unescapeURIComponent(string, encoding = @@accept_charset)
+ str = string.b
+ str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
+ [m.delete('%')].pack('H*')
+ end
+ str.force_encoding(encoding)
+ str.valid_encoding? ? str : str.force_encoding(string.encoding)
+ end
+
+ alias unescape_uri_component unescapeURIComponent
+
+ # The set of special characters and their escaped values
+ TABLE_FOR_ESCAPE_HTML__ = { # :nodoc:
+ "'" => '&#39;',
+ '&' => '&amp;',
+ '"' => '&quot;',
+ '<' => '&lt;',
+ '>' => '&gt;',
+ }
+
+ # \Escape special characters in HTML, namely <tt>'&\"<></tt>
+ # CGI.escapeHTML('Usage: foo "bar" <baz>')
+ # # => "Usage: foo &quot;bar&quot; &lt;baz&gt;"
+ def escapeHTML(string)
+ enc = string.encoding
+ unless enc.ascii_compatible?
+ if enc.dummy?
+ origenc = enc
+ enc = Encoding::Converter.asciicompat_encoding(enc)
+ string = enc ? string.encode(enc) : string.b
+ end
+ table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}]
+ string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table)
+ string.encode!(origenc) if origenc
+ string
+ else
+ string = string.b
+ string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
+ string.force_encoding(enc)
+ end
+ end
+
+ # Unescape a string that has been HTML-escaped
+ # CGI.unescapeHTML("Usage: foo &quot;bar&quot; &lt;baz&gt;")
+ # # => "Usage: foo \"bar\" <baz>"
+ def unescapeHTML(string)
+ enc = string.encoding
+ unless enc.ascii_compatible?
+ if enc.dummy?
+ origenc = enc
+ enc = Encoding::Converter.asciicompat_encoding(enc)
+ string = enc ? string.encode(enc) : string.b
+ end
+ string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
+ case $1.encode(Encoding::US_ASCII)
+ when 'apos' then "'".encode(enc)
+ when 'amp' then '&'.encode(enc)
+ when 'quot' then '"'.encode(enc)
+ when 'gt' then '>'.encode(enc)
+ when 'lt' then '<'.encode(enc)
+ when /\A#0*(\d+)\z/ then $1.to_i.chr(enc)
+ when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc)
+ end
+ end
+ string.encode!(origenc) if origenc
+ return string
+ end
+ return string unless string.include? '&'
+ charlimit = case enc
+ when Encoding::UTF_8; 0x10ffff
+ when Encoding::ISO_8859_1; 256
+ else 128
+ end
+ string = string.b
+ string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do
+ match = $1.dup
+ case match
+ when 'apos' then "'"
+ when 'amp' then '&'
+ when 'quot' then '"'
+ when 'gt' then '>'
+ when 'lt' then '<'
+ when /\A#0*(\d+)\z/
+ n = $1.to_i
+ if n < charlimit
+ n.chr(enc)
+ else
+ "&##{$1};"
+ end
+ when /\A#x([0-9a-f]+)\z/i
+ n = $1.hex
+ if n < charlimit
+ n.chr(enc)
+ else
+ "&#x#{$1};"
+ end
+ else
+ "&#{match};"
+ end
+ end
+ string.force_encoding enc
+ end
+
+ alias escape_html escapeHTML
+ alias h escapeHTML
+
+ alias unescape_html unescapeHTML
+
+ # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there
+ unless RUBY_ENGINE == 'truffleruby'
+ begin
+ require 'cgi/escape.so'
+ rescue LoadError
+ end
+ end
+
+ # \Escape only the tags of certain HTML elements in +string+.
+ #
+ # Takes an element or elements or array of elements. Each element
+ # is specified by the name of the element, without angle brackets.
+ # This matches both the start and the end tag of that element.
+ # The attribute list of the open tag will also be escaped (for
+ # instance, the double-quotes surrounding attribute values).
+ #
+ # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
+ # # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
+ #
+ # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
+ # # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
+ def escapeElement(string, *elements)
+ elements = elements[0] if elements[0].kind_of?(Array)
+ unless elements.empty?
+ string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do
+ CGI.escapeHTML($&)
+ end
+ else
+ string
+ end
+ end
+
+ # Undo escaping such as that done by CGI.escapeElement
+ #
+ # print CGI.unescapeElement(
+ # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG")
+ # # "&lt;BR&gt;<A HREF="url"></A>"
+ #
+ # print CGI.unescapeElement(
+ # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"])
+ # # "&lt;BR&gt;<A HREF="url"></A>"
+ def unescapeElement(string, *elements)
+ elements = elements[0] if elements[0].kind_of?(Array)
+ unless elements.empty?
+ string.gsub(/&lt;\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:&gt;)?/im) do
+ unescapeHTML($&)
+ end
+ else
+ string
+ end
+ end
+
+ alias escape_element escapeElement
+
+ alias unescape_element unescapeElement
+
+end
diff --git a/lib/cgi/html.rb b/lib/cgi/html.rb
deleted file mode 100644
index 1543943320..0000000000
--- a/lib/cgi/html.rb
+++ /dev/null
@@ -1,1035 +0,0 @@
-# frozen_string_literal: true
-class CGI
- # Base module for HTML-generation mixins.
- #
- # Provides methods for code generation for tags following
- # the various DTD element types.
- module TagMaker # :nodoc:
-
- # Generate code for an element with required start and end tags.
- #
- # - -
- def nn_element(element, attributes = {})
- s = nOE_element(element, attributes)
- if block_given?
- s << yield.to_s
- end
- s << "</#{element.upcase}>"
- end
-
- def nn_element_def(attributes = {}, &block)
- nn_element(__callee__, attributes, &block)
- end
-
- # Generate code for an empty element.
- #
- # - O EMPTY
- def nOE_element(element, attributes = {})
- attributes={attributes=>nil} if attributes.kind_of?(String)
- s = "<#{element.upcase}".dup
- attributes.each do|name, value|
- next unless value
- s << " "
- s << CGI.escapeHTML(name.to_s)
- if value != true
- s << '="'
- s << CGI.escapeHTML(value.to_s)
- s << '"'
- end
- end
- s << ">"
- end
-
- def nOE_element_def(attributes = {}, &block)
- nOE_element(__callee__, attributes, &block)
- end
-
-
- # Generate code for an element for which the end (and possibly the
- # start) tag is optional.
- #
- # O O or - O
- def nO_element(element, attributes = {})
- s = nOE_element(element, attributes)
- if block_given?
- s << yield.to_s
- s << "</#{element.upcase}>"
- end
- s
- end
-
- def nO_element_def(attributes = {}, &block)
- nO_element(__callee__, attributes, &block)
- end
-
- end # TagMaker
-
-
- # Mixin module providing HTML generation methods.
- #
- # For example,
- # cgi.a("http://www.example.com") { "Example" }
- # # => "<A HREF=\"http://www.example.com\">Example</A>"
- #
- # Modules Html3, Html4, etc., contain more basic HTML-generation methods
- # (+#title+, +#h1+, etc.).
- #
- # See class CGI for a detailed example.
- #
- module HtmlExtension
-
-
- # Generate an Anchor element as a string.
- #
- # +href+ can either be a string, giving the URL
- # for the HREF attribute, or it can be a hash of
- # the element's attributes.
- #
- # The body of the element is the string returned by the no-argument
- # block passed in.
- #
- # a("http://www.example.com") { "Example" }
- # # => "<A HREF=\"http://www.example.com\">Example</A>"
- #
- # a("HREF" => "http://www.example.com", "TARGET" => "_top") { "Example" }
- # # => "<A HREF=\"http://www.example.com\" TARGET=\"_top\">Example</A>"
- #
- def a(href = "") # :yield:
- attributes = if href.kind_of?(String)
- { "HREF" => href }
- else
- href
- end
- super(attributes)
- end
-
- # Generate a Document Base URI element as a String.
- #
- # +href+ can either by a string, giving the base URL for the HREF
- # attribute, or it can be a has of the element's attributes.
- #
- # The passed-in no-argument block is ignored.
- #
- # base("http://www.example.com/cgi")
- # # => "<BASE HREF=\"http://www.example.com/cgi\">"
- def base(href = "") # :yield:
- attributes = if href.kind_of?(String)
- { "HREF" => href }
- else
- href
- end
- super(attributes)
- end
-
- # Generate a BlockQuote element as a string.
- #
- # +cite+ can either be a string, give the URI for the source of
- # the quoted text, or a hash, giving all attributes of the element,
- # or it can be omitted, in which case the element has no attributes.
- #
- # The body is provided by the passed-in no-argument block
- #
- # blockquote("http://www.example.com/quotes/foo.html") { "Foo!" }
- # #=> "<BLOCKQUOTE CITE=\"http://www.example.com/quotes/foo.html\">Foo!</BLOCKQUOTE>
- def blockquote(cite = {}) # :yield:
- attributes = if cite.kind_of?(String)
- { "CITE" => cite }
- else
- cite
- end
- super(attributes)
- end
-
-
- # Generate a Table Caption element as a string.
- #
- # +align+ can be a string, giving the alignment of the caption
- # (one of top, bottom, left, or right). It can be a hash of
- # all the attributes of the element. Or it can be omitted.
- #
- # The body of the element is provided by the passed-in no-argument block.
- #
- # caption("left") { "Capital Cities" }
- # # => <CAPTION ALIGN=\"left\">Capital Cities</CAPTION>
- def caption(align = {}) # :yield:
- attributes = if align.kind_of?(String)
- { "ALIGN" => align }
- else
- align
- end
- super(attributes)
- end
-
-
- # Generate a Checkbox Input element as a string.
- #
- # The attributes of the element can be specified as three arguments,
- # +name+, +value+, and +checked+. +checked+ is a boolean value;
- # if true, the CHECKED attribute will be included in the element.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # checkbox("name")
- # # = checkbox("NAME" => "name")
- #
- # checkbox("name", "value")
- # # = checkbox("NAME" => "name", "VALUE" => "value")
- #
- # checkbox("name", "value", true)
- # # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true)
- def checkbox(name = "", value = nil, checked = nil)
- attributes = if name.kind_of?(String)
- { "TYPE" => "checkbox", "NAME" => name,
- "VALUE" => value, "CHECKED" => checked }
- else
- name["TYPE"] = "checkbox"
- name
- end
- input(attributes)
- end
-
- # Generate a sequence of checkbox elements, as a String.
- #
- # The checkboxes will all have the same +name+ attribute.
- # Each checkbox is followed by a label.
- # There will be one checkbox for each value. Each value
- # can be specified as a String, which will be used both
- # as the value of the VALUE attribute and as the label
- # for that checkbox. A single-element array has the
- # same effect.
- #
- # Each value can also be specified as a three-element array.
- # The first element is the VALUE attribute; the second is the
- # label; and the third is a boolean specifying whether this
- # checkbox is CHECKED.
- #
- # Each value can also be specified as a two-element
- # array, by omitting either the value element (defaults
- # to the same as the label), or the boolean checked element
- # (defaults to false).
- #
- # checkbox_group("name", "foo", "bar", "baz")
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
- #
- # checkbox_group("name", ["foo"], ["bar", true], "baz")
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
- # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
- #
- # checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo
- # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz
- #
- # checkbox_group("NAME" => "name",
- # "VALUES" => ["foo", "bar", "baz"])
- #
- # checkbox_group("NAME" => "name",
- # "VALUES" => [["foo"], ["bar", true], "baz"])
- #
- # checkbox_group("NAME" => "name",
- # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
- def checkbox_group(name = "", *values)
- if name.kind_of?(Hash)
- values = name["VALUES"]
- name = name["NAME"]
- end
- values.collect{|value|
- if value.kind_of?(String)
- checkbox(name, value) + value
- else
- if value[-1] == true || value[-1] == false
- checkbox(name, value[0], value[-1]) +
- value[-2]
- else
- checkbox(name, value[0]) +
- value[-1]
- end
- end
- }.join
- end
-
-
- # Generate an File Upload Input element as a string.
- #
- # The attributes of the element can be specified as three arguments,
- # +name+, +size+, and +maxlength+. +maxlength+ is the maximum length
- # of the file's _name_, not of the file's _contents_.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # See #multipart_form() for forms that include file uploads.
- #
- # file_field("name")
- # # <INPUT TYPE="file" NAME="name" SIZE="20">
- #
- # file_field("name", 40)
- # # <INPUT TYPE="file" NAME="name" SIZE="40">
- #
- # file_field("name", 40, 100)
- # # <INPUT TYPE="file" NAME="name" SIZE="40" MAXLENGTH="100">
- #
- # file_field("NAME" => "name", "SIZE" => 40)
- # # <INPUT TYPE="file" NAME="name" SIZE="40">
- def file_field(name = "", size = 20, maxlength = nil)
- attributes = if name.kind_of?(String)
- { "TYPE" => "file", "NAME" => name,
- "SIZE" => size.to_s }
- else
- name["TYPE"] = "file"
- name
- end
- attributes["MAXLENGTH"] = maxlength.to_s if maxlength
- input(attributes)
- end
-
-
- # Generate a Form element as a string.
- #
- # +method+ should be either "get" or "post", and defaults to the latter.
- # +action+ defaults to the current CGI script name. +enctype+
- # defaults to "application/x-www-form-urlencoded".
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # See also #multipart_form() for forms that include file uploads.
- #
- # form{ "string" }
- # # <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
- #
- # form("get") { "string" }
- # # <FORM METHOD="get" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
- #
- # form("get", "url") { "string" }
- # # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
- #
- # form("METHOD" => "post", "ENCTYPE" => "enctype") { "string" }
- # # <FORM METHOD="post" ENCTYPE="enctype">string</FORM>
- def form(method = "post", action = script_name, enctype = "application/x-www-form-urlencoded")
- attributes = if method.kind_of?(String)
- { "METHOD" => method, "ACTION" => action,
- "ENCTYPE" => enctype }
- else
- unless method.has_key?("METHOD")
- method["METHOD"] = "post"
- end
- unless method.has_key?("ENCTYPE")
- method["ENCTYPE"] = enctype
- end
- method
- end
- if block_given?
- body = yield
- else
- body = ""
- end
- if @output_hidden
- body << @output_hidden.collect{|k,v|
- "<INPUT TYPE=\"HIDDEN\" NAME=\"#{k}\" VALUE=\"#{v}\">"
- }.join
- end
- super(attributes){body}
- end
-
- # Generate a Hidden Input element as a string.
- #
- # The attributes of the element can be specified as two arguments,
- # +name+ and +value+.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # hidden("name")
- # # <INPUT TYPE="hidden" NAME="name">
- #
- # hidden("name", "value")
- # # <INPUT TYPE="hidden" NAME="name" VALUE="value">
- #
- # hidden("NAME" => "name", "VALUE" => "reset", "ID" => "foo")
- # # <INPUT TYPE="hidden" NAME="name" VALUE="value" ID="foo">
- def hidden(name = "", value = nil)
- attributes = if name.kind_of?(String)
- { "TYPE" => "hidden", "NAME" => name, "VALUE" => value }
- else
- name["TYPE"] = "hidden"
- name
- end
- input(attributes)
- end
-
- # Generate a top-level HTML element as a string.
- #
- # The attributes of the element are specified as a hash. The
- # pseudo-attribute "PRETTY" can be used to specify that the generated
- # HTML string should be indented. "PRETTY" can also be specified as
- # a string as the sole argument to this method. The pseudo-attribute
- # "DOCTYPE", if given, is used as the leading DOCTYPE SGML tag; it
- # should include the entire text of this tag, including angle brackets.
- #
- # The body of the html element is supplied as a block.
- #
- # html{ "string" }
- # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>string</HTML>
- #
- # html("LANG" => "ja") { "string" }
- # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML LANG="ja">string</HTML>
- #
- # html("DOCTYPE" => false) { "string" }
- # # <HTML>string</HTML>
- #
- # html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">') { "string" }
- # # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML>string</HTML>
- #
- # html("PRETTY" => " ") { "<BODY></BODY>" }
- # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
- # # <HTML>
- # # <BODY>
- # # </BODY>
- # # </HTML>
- #
- # html("PRETTY" => "\t") { "<BODY></BODY>" }
- # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
- # # <HTML>
- # # <BODY>
- # # </BODY>
- # # </HTML>
- #
- # html("PRETTY") { "<BODY></BODY>" }
- # # = html("PRETTY" => " ") { "<BODY></BODY>" }
- #
- # html(if $VERBOSE then "PRETTY" end) { "HTML string" }
- #
- def html(attributes = {}) # :yield:
- if nil == attributes
- attributes = {}
- elsif "PRETTY" == attributes
- attributes = { "PRETTY" => true }
- end
- pretty = attributes.delete("PRETTY")
- pretty = " " if true == pretty
- buf = "".dup
-
- if attributes.has_key?("DOCTYPE")
- if attributes["DOCTYPE"]
- buf << attributes.delete("DOCTYPE")
- else
- attributes.delete("DOCTYPE")
- end
- else
- buf << doctype
- end
-
- buf << super(attributes)
-
- if pretty
- CGI.pretty(buf, pretty)
- else
- buf
- end
-
- end
-
- # Generate an Image Button Input element as a string.
- #
- # +src+ is the URL of the image to use for the button. +name+
- # is the input name. +alt+ is the alternative text for the image.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # image_button("url")
- # # <INPUT TYPE="image" SRC="url">
- #
- # image_button("url", "name", "string")
- # # <INPUT TYPE="image" SRC="url" NAME="name" ALT="string">
- #
- # image_button("SRC" => "url", "ALT" => "string")
- # # <INPUT TYPE="image" SRC="url" ALT="string">
- def image_button(src = "", name = nil, alt = nil)
- attributes = if src.kind_of?(String)
- { "TYPE" => "image", "SRC" => src, "NAME" => name,
- "ALT" => alt }
- else
- src["TYPE"] = "image"
- src["SRC"] ||= ""
- src
- end
- input(attributes)
- end
-
-
- # Generate an Image element as a string.
- #
- # +src+ is the URL of the image. +alt+ is the alternative text for
- # the image. +width+ is the width of the image, and +height+ is
- # its height.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # img("src", "alt", 100, 50)
- # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
- #
- # img("SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50)
- # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
- def img(src = "", alt = "", width = nil, height = nil)
- attributes = if src.kind_of?(String)
- { "SRC" => src, "ALT" => alt }
- else
- src
- end
- attributes["WIDTH"] = width.to_s if width
- attributes["HEIGHT"] = height.to_s if height
- super(attributes)
- end
-
-
- # Generate a Form element with multipart encoding as a String.
- #
- # Multipart encoding is used for forms that include file uploads.
- #
- # +action+ is the action to perform. +enctype+ is the encoding
- # type, which defaults to "multipart/form-data".
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # multipart_form{ "string" }
- # # <FORM METHOD="post" ENCTYPE="multipart/form-data">string</FORM>
- #
- # multipart_form("url") { "string" }
- # # <FORM METHOD="post" ACTION="url" ENCTYPE="multipart/form-data">string</FORM>
- def multipart_form(action = nil, enctype = "multipart/form-data")
- attributes = if action == nil
- { "METHOD" => "post", "ENCTYPE" => enctype }
- elsif action.kind_of?(String)
- { "METHOD" => "post", "ACTION" => action,
- "ENCTYPE" => enctype }
- else
- unless action.has_key?("METHOD")
- action["METHOD"] = "post"
- end
- unless action.has_key?("ENCTYPE")
- action["ENCTYPE"] = enctype
- end
- action
- end
- if block_given?
- form(attributes){ yield }
- else
- form(attributes)
- end
- end
-
-
- # Generate a Password Input element as a string.
- #
- # +name+ is the name of the input field. +value+ is its default
- # value. +size+ is the size of the input field display. +maxlength+
- # is the maximum length of the inputted password.
- #
- # Alternatively, attributes can be specified as a hash.
- #
- # password_field("name")
- # # <INPUT TYPE="password" NAME="name" SIZE="40">
- #
- # password_field("name", "value")
- # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="40">
- #
- # password_field("password", "value", 80, 200)
- # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
- #
- # password_field("NAME" => "name", "VALUE" => "value")
- # # <INPUT TYPE="password" NAME="name" VALUE="value">
- def password_field(name = "", value = nil, size = 40, maxlength = nil)
- attributes = if name.kind_of?(String)
- { "TYPE" => "password", "NAME" => name,
- "VALUE" => value, "SIZE" => size.to_s }
- else
- name["TYPE"] = "password"
- name
- end
- attributes["MAXLENGTH"] = maxlength.to_s if maxlength
- input(attributes)
- end
-
- # Generate a Select element as a string.
- #
- # +name+ is the name of the element. The +values+ are the options that
- # can be selected from the Select menu. Each value can be a String or
- # a one, two, or three-element Array. If a String or a one-element
- # Array, this is both the value of that option and the text displayed for
- # it. If a three-element Array, the elements are the option value, displayed
- # text, and a boolean value specifying whether this option starts as selected.
- # The two-element version omits either the option value (defaults to the same
- # as the display text) or the boolean selected specifier (defaults to false).
- #
- # The attributes and options can also be specified as a hash. In this
- # case, options are specified as an array of values as described above,
- # with the hash key of "VALUES".
- #
- # popup_menu("name", "foo", "bar", "baz")
- # # <SELECT NAME="name">
- # # <OPTION VALUE="foo">foo</OPTION>
- # # <OPTION VALUE="bar">bar</OPTION>
- # # <OPTION VALUE="baz">baz</OPTION>
- # # </SELECT>
- #
- # popup_menu("name", ["foo"], ["bar", true], "baz")
- # # <SELECT NAME="name">
- # # <OPTION VALUE="foo">foo</OPTION>
- # # <OPTION VALUE="bar" SELECTED>bar</OPTION>
- # # <OPTION VALUE="baz">baz</OPTION>
- # # </SELECT>
- #
- # popup_menu("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
- # # <SELECT NAME="name">
- # # <OPTION VALUE="1">Foo</OPTION>
- # # <OPTION SELECTED VALUE="2">Bar</OPTION>
- # # <OPTION VALUE="Baz">Baz</OPTION>
- # # </SELECT>
- #
- # popup_menu("NAME" => "name", "SIZE" => 2, "MULTIPLE" => true,
- # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
- # # <SELECT NAME="name" MULTIPLE SIZE="2">
- # # <OPTION VALUE="1">Foo</OPTION>
- # # <OPTION SELECTED VALUE="2">Bar</OPTION>
- # # <OPTION VALUE="Baz">Baz</OPTION>
- # # </SELECT>
- def popup_menu(name = "", *values)
-
- if name.kind_of?(Hash)
- values = name["VALUES"]
- size = name["SIZE"].to_s if name["SIZE"]
- multiple = name["MULTIPLE"]
- name = name["NAME"]
- else
- size = nil
- multiple = nil
- end
-
- select({ "NAME" => name, "SIZE" => size,
- "MULTIPLE" => multiple }){
- values.collect{|value|
- if value.kind_of?(String)
- option({ "VALUE" => value }){ value }
- else
- if value[value.size - 1] == true
- option({ "VALUE" => value[0], "SELECTED" => true }){
- value[value.size - 2]
- }
- else
- option({ "VALUE" => value[0] }){
- value[value.size - 1]
- }
- end
- end
- }.join
- }
-
- end
-
- # Generates a radio-button Input element.
- #
- # +name+ is the name of the input field. +value+ is the value of
- # the field if checked. +checked+ specifies whether the field
- # starts off checked.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # radio_button("name", "value")
- # # <INPUT TYPE="radio" NAME="name" VALUE="value">
- #
- # radio_button("name", "value", true)
- # # <INPUT TYPE="radio" NAME="name" VALUE="value" CHECKED>
- #
- # radio_button("NAME" => "name", "VALUE" => "value", "ID" => "foo")
- # # <INPUT TYPE="radio" NAME="name" VALUE="value" ID="foo">
- def radio_button(name = "", value = nil, checked = nil)
- attributes = if name.kind_of?(String)
- { "TYPE" => "radio", "NAME" => name,
- "VALUE" => value, "CHECKED" => checked }
- else
- name["TYPE"] = "radio"
- name
- end
- input(attributes)
- end
-
- # Generate a sequence of radio button Input elements, as a String.
- #
- # This works the same as #checkbox_group(). However, it is not valid
- # to have more than one radiobutton in a group checked.
- #
- # radio_group("name", "foo", "bar", "baz")
- # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
- # # <INPUT TYPE="radio" NAME="name" VALUE="bar">bar
- # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
- #
- # radio_group("name", ["foo"], ["bar", true], "baz")
- # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
- # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="bar">bar
- # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
- #
- # radio_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
- # # <INPUT TYPE="radio" NAME="name" VALUE="1">Foo
- # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="2">Bar
- # # <INPUT TYPE="radio" NAME="name" VALUE="Baz">Baz
- #
- # radio_group("NAME" => "name",
- # "VALUES" => ["foo", "bar", "baz"])
- #
- # radio_group("NAME" => "name",
- # "VALUES" => [["foo"], ["bar", true], "baz"])
- #
- # radio_group("NAME" => "name",
- # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
- def radio_group(name = "", *values)
- if name.kind_of?(Hash)
- values = name["VALUES"]
- name = name["NAME"]
- end
- values.collect{|value|
- if value.kind_of?(String)
- radio_button(name, value) + value
- else
- if value[-1] == true || value[-1] == false
- radio_button(name, value[0], value[-1]) +
- value[-2]
- else
- radio_button(name, value[0]) +
- value[-1]
- end
- end
- }.join
- end
-
- # Generate a reset button Input element, as a String.
- #
- # This resets the values on a form to their initial values. +value+
- # is the text displayed on the button. +name+ is the name of this button.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # reset
- # # <INPUT TYPE="reset">
- #
- # reset("reset")
- # # <INPUT TYPE="reset" VALUE="reset">
- #
- # reset("VALUE" => "reset", "ID" => "foo")
- # # <INPUT TYPE="reset" VALUE="reset" ID="foo">
- def reset(value = nil, name = nil)
- attributes = if (not value) or value.kind_of?(String)
- { "TYPE" => "reset", "VALUE" => value, "NAME" => name }
- else
- value["TYPE"] = "reset"
- value
- end
- input(attributes)
- end
-
- alias scrolling_list popup_menu
-
- # Generate a submit button Input element, as a String.
- #
- # +value+ is the text to display on the button. +name+ is the name
- # of the input.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # submit
- # # <INPUT TYPE="submit">
- #
- # submit("ok")
- # # <INPUT TYPE="submit" VALUE="ok">
- #
- # submit("ok", "button1")
- # # <INPUT TYPE="submit" VALUE="ok" NAME="button1">
- #
- # submit("VALUE" => "ok", "NAME" => "button1", "ID" => "foo")
- # # <INPUT TYPE="submit" VALUE="ok" NAME="button1" ID="foo">
- def submit(value = nil, name = nil)
- attributes = if (not value) or value.kind_of?(String)
- { "TYPE" => "submit", "VALUE" => value, "NAME" => name }
- else
- value["TYPE"] = "submit"
- value
- end
- input(attributes)
- end
-
- # Generate a text field Input element, as a String.
- #
- # +name+ is the name of the input field. +value+ is its initial
- # value. +size+ is the size of the input area. +maxlength+
- # is the maximum length of input accepted.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # text_field("name")
- # # <INPUT TYPE="text" NAME="name" SIZE="40">
- #
- # text_field("name", "value")
- # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="40">
- #
- # text_field("name", "value", 80)
- # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80">
- #
- # text_field("name", "value", 80, 200)
- # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
- #
- # text_field("NAME" => "name", "VALUE" => "value")
- # # <INPUT TYPE="text" NAME="name" VALUE="value">
- def text_field(name = "", value = nil, size = 40, maxlength = nil)
- attributes = if name.kind_of?(String)
- { "TYPE" => "text", "NAME" => name, "VALUE" => value,
- "SIZE" => size.to_s }
- else
- name["TYPE"] = "text"
- name
- end
- attributes["MAXLENGTH"] = maxlength.to_s if maxlength
- input(attributes)
- end
-
- # Generate a TextArea element, as a String.
- #
- # +name+ is the name of the textarea. +cols+ is the number of
- # columns and +rows+ is the number of rows in the display.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # The body is provided by the passed-in no-argument block
- #
- # textarea("name")
- # # = textarea("NAME" => "name", "COLS" => 70, "ROWS" => 10)
- #
- # textarea("name", 40, 5)
- # # = textarea("NAME" => "name", "COLS" => 40, "ROWS" => 5)
- def textarea(name = "", cols = 70, rows = 10) # :yield:
- attributes = if name.kind_of?(String)
- { "NAME" => name, "COLS" => cols.to_s,
- "ROWS" => rows.to_s }
- else
- name
- end
- super(attributes)
- end
-
- end # HtmlExtension
-
-
- # Mixin module for HTML version 3 generation methods.
- module Html3 # :nodoc:
- include TagMaker
-
- # The DOCTYPE declaration for this version of HTML
- def doctype
- %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">|
- end
-
- instance_method(:nn_element_def).tap do |m|
- # - -
- for element in %w[ A TT I B U STRIKE BIG SMALL SUB SUP EM STRONG
- DFN CODE SAMP KBD VAR CITE FONT ADDRESS DIV CENTER MAP
- APPLET PRE XMP LISTING DL OL UL DIR MENU SELECT TABLE TITLE
- STYLE SCRIPT H1 H2 H3 H4 H5 H6 TEXTAREA FORM BLOCKQUOTE
- CAPTION ]
- define_method(element.downcase, m)
- end
- end
-
- instance_method(:nOE_element_def).tap do |m|
- # - O EMPTY
- for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
- ISINDEX META ]
- define_method(element.downcase, m)
- end
- end
-
- instance_method(:nO_element_def).tap do |m|
- # O O or - O
- for element in %w[ HTML HEAD BODY P PLAINTEXT DT DD LI OPTION TR
- TH TD ]
- define_method(element.downcase, m)
- end
- end
-
- end # Html3
-
-
- # Mixin module for HTML version 4 generation methods.
- module Html4 # :nodoc:
- include TagMaker
-
- # The DOCTYPE declaration for this version of HTML
- def doctype
- %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|
- end
-
- # Initialize the HTML generation methods for this version.
- # - -
- instance_method(:nn_element_def).tap do |m|
- for element in %w[ TT I B BIG SMALL EM STRONG DFN CODE SAMP KBD
- VAR CITE ABBR ACRONYM SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT
- H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT OPTGROUP
- FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT
- TEXTAREA FORM A BLOCKQUOTE CAPTION ]
- define_method(element.downcase, m)
- end
- end
-
- # - O EMPTY
- instance_method(:nOE_element_def).tap do |m|
- for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ]
- define_method(element.downcase, m)
- end
- end
-
- # O O or - O
- instance_method(:nO_element_def).tap do |m|
- for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
- COLGROUP TR TH TD HEAD ]
- define_method(element.downcase, m)
- end
- end
-
- end # Html4
-
-
- # Mixin module for HTML version 4 transitional generation methods.
- module Html4Tr # :nodoc:
- include TagMaker
-
- # The DOCTYPE declaration for this version of HTML
- def doctype
- %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|
- end
-
- # Initialise the HTML generation methods for this version.
- # - -
- instance_method(:nn_element_def).tap do |m|
- for element in %w[ TT I B U S STRIKE BIG SMALL EM STRONG DFN
- CODE SAMP KBD VAR CITE ABBR ACRONYM FONT SUB SUP SPAN BDO
- ADDRESS DIV CENTER MAP OBJECT APPLET H1 H2 H3 H4 H5 H6 PRE Q
- INS DEL DL OL UL DIR MENU LABEL SELECT OPTGROUP FIELDSET
- LEGEND BUTTON TABLE IFRAME NOFRAMES TITLE STYLE SCRIPT
- NOSCRIPT TEXTAREA FORM A BLOCKQUOTE CAPTION ]
- define_method(element.downcase, m)
- end
- end
-
- # - O EMPTY
- instance_method(:nOE_element_def).tap do |m|
- for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
- COL ISINDEX META ]
- define_method(element.downcase, m)
- end
- end
-
- # O O or - O
- instance_method(:nO_element_def).tap do |m|
- for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
- COLGROUP TR TH TD HEAD ]
- define_method(element.downcase, m)
- end
- end
-
- end # Html4Tr
-
-
- # Mixin module for generating HTML version 4 with framesets.
- module Html4Fr # :nodoc:
- include TagMaker
-
- # The DOCTYPE declaration for this version of HTML
- def doctype
- %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|
- end
-
- # Initialise the HTML generation methods for this version.
- # - -
- instance_method(:nn_element_def).tap do |m|
- for element in %w[ FRAMESET ]
- define_method(element.downcase, m)
- end
- end
-
- # - O EMPTY
- instance_method(:nOE_element_def).tap do |m|
- for element in %w[ FRAME ]
- define_method(element.downcase, m)
- end
- end
-
- end # Html4Fr
-
-
- # Mixin module for HTML version 5 generation methods.
- module Html5 # :nodoc:
- include TagMaker
-
- # The DOCTYPE declaration for this version of HTML
- def doctype
- %|<!DOCTYPE HTML>|
- end
-
- # Initialise the HTML generation methods for this version.
- # - -
- instance_method(:nn_element_def).tap do |m|
- for element in %w[ SECTION NAV ARTICLE ASIDE HGROUP HEADER
- FOOTER FIGURE FIGCAPTION S TIME U MARK RUBY BDI IFRAME
- VIDEO AUDIO CANVAS DATALIST OUTPUT PROGRESS METER DETAILS
- SUMMARY MENU DIALOG I B SMALL EM STRONG DFN CODE SAMP KBD
- VAR CITE ABBR SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT
- H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT
- FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT
- TEXTAREA FORM A BLOCKQUOTE CAPTION ]
- define_method(element.downcase, m)
- end
- end
-
- # - O EMPTY
- instance_method(:nOE_element_def).tap do |m|
- for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META
- COMMAND EMBED KEYGEN SOURCE TRACK WBR ]
- define_method(element.downcase, m)
- end
- end
-
- # O O or - O
- instance_method(:nO_element_def).tap do |m|
- for element in %w[ HTML HEAD BODY P DT DD LI OPTION THEAD TFOOT TBODY
- OPTGROUP COLGROUP RT RP TR TH TD ]
- define_method(element.downcase, m)
- end
- end
-
- end # Html5
-
- class HTML3
- include Html3
- include HtmlExtension
- end
-
- class HTML4
- include Html4
- include HtmlExtension
- end
-
- class HTML4Tr
- include Html4Tr
- include HtmlExtension
- end
-
- class HTML4Fr
- include Html4Tr
- include Html4Fr
- include HtmlExtension
- end
-
- class HTML5
- include Html5
- include HtmlExtension
- end
-
-end
diff --git a/lib/cgi/session.rb b/lib/cgi/session.rb
deleted file mode 100644
index aab60869bb..0000000000
--- a/lib/cgi/session.rb
+++ /dev/null
@@ -1,562 +0,0 @@
-# frozen_string_literal: true
-#
-# cgi/session.rb - session support for cgi scripts
-#
-# Copyright (C) 2001 Yukihiro "Matz" Matsumoto
-# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
-# Copyright (C) 2000 Information-technology Promotion Agency, Japan
-#
-# Author: Yukihiro "Matz" Matsumoto
-#
-# Documentation: William Webber (william@williamwebber.com)
-
-require 'cgi'
-require 'tmpdir'
-
-class CGI
-
- # == Overview
- #
- # This file provides the CGI::Session class, which provides session
- # support for CGI scripts. A session is a sequence of HTTP requests
- # and responses linked together and associated with a single client.
- # Information associated with the session is stored
- # on the server between requests. A session id is passed between client
- # and server with every request and response, transparently
- # to the user. This adds state information to the otherwise stateless
- # HTTP request/response protocol.
- #
- # == Lifecycle
- #
- # A CGI::Session instance is created from a CGI object. By default,
- # this CGI::Session instance will start a new session if none currently
- # exists, or continue the current session for this client if one does
- # exist. The +new_session+ option can be used to either always or
- # never create a new session. See #new() for more details.
- #
- # #delete() deletes a session from session storage. It
- # does not however remove the session id from the client. If the client
- # makes another request with the same id, the effect will be to start
- # a new session with the old session's id.
- #
- # == Setting and retrieving session data.
- #
- # The Session class associates data with a session as key-value pairs.
- # This data can be set and retrieved by indexing the Session instance
- # using '[]', much the same as hashes (although other hash methods
- # are not supported).
- #
- # When session processing has been completed for a request, the
- # session should be closed using the close() method. This will
- # store the session's state to persistent storage. If you want
- # to store the session's state to persistent storage without
- # finishing session processing for this request, call the update()
- # method.
- #
- # == Storing session state
- #
- # The caller can specify what form of storage to use for the session's
- # data with the +database_manager+ option to CGI::Session::new. The
- # following storage classes are provided as part of the standard library:
- #
- # CGI::Session::FileStore:: stores data as plain text in a flat file. Only
- # works with String data. This is the default
- # storage type.
- # CGI::Session::MemoryStore:: stores data in an in-memory hash. The data
- # only persists for as long as the current Ruby
- # interpreter instance does.
- # CGI::Session::PStore:: stores data in Marshalled format. Provided by
- # cgi/session/pstore.rb. Supports data of any type,
- # and provides file-locking and transaction support.
- #
- # Custom storage types can also be created by defining a class with
- # the following methods:
- #
- # new(session, options)
- # restore # returns hash of session data.
- # update
- # close
- # delete
- #
- # Changing storage type mid-session does not work. Note in particular
- # that by default the FileStore and PStore session data files have the
- # same name. If your application switches from one to the other without
- # making sure that filenames will be different
- # and clients still have old sessions lying around in cookies, then
- # things will break nastily!
- #
- # == Maintaining the session id.
- #
- # Most session state is maintained on the server. However, a session
- # id must be passed backwards and forwards between client and server
- # to maintain a reference to this session state.
- #
- # The simplest way to do this is via cookies. The CGI::Session class
- # provides transparent support for session id communication via cookies
- # if the client has cookies enabled.
- #
- # If the client has cookies disabled, the session id must be included
- # as a parameter of all requests sent by the client to the server. The
- # CGI::Session class in conjunction with the CGI class will transparently
- # add the session id as a hidden input field to all forms generated
- # using the CGI#form() HTML generation method. No built-in support is
- # provided for other mechanisms, such as URL re-writing. The caller is
- # responsible for extracting the session id from the session_id
- # attribute and manually encoding it in URLs and adding it as a hidden
- # input to HTML forms created by other mechanisms. Also, session expiry
- # is not automatically handled.
- #
- # == Examples of use
- #
- # === Setting the user's name
- #
- # require 'cgi'
- # require 'cgi/session'
- # require 'cgi/session/pstore' # provides CGI::Session::PStore
- #
- # cgi = CGI.new("html4")
- #
- # session = CGI::Session.new(cgi,
- # 'database_manager' => CGI::Session::PStore, # use PStore
- # 'session_key' => '_rb_sess_id', # custom session key
- # 'session_expires' => Time.now + 30 * 60, # 30 minute timeout
- # 'prefix' => 'pstore_sid_') # PStore option
- # if cgi.has_key?('user_name') and cgi['user_name'] != ''
- # # coerce to String: cgi[] returns the
- # # string-like CGI::QueryExtension::Value
- # session['user_name'] = cgi['user_name'].to_s
- # elsif !session['user_name']
- # session['user_name'] = "guest"
- # end
- # session.close
- #
- # === Creating a new session safely
- #
- # require 'cgi'
- # require 'cgi/session'
- #
- # cgi = CGI.new("html4")
- #
- # # We make sure to delete an old session if one exists,
- # # not just to free resources, but to prevent the session
- # # from being maliciously hijacked later on.
- # begin
- # session = CGI::Session.new(cgi, 'new_session' => false)
- # session.delete
- # rescue ArgumentError # if no old session
- # end
- # session = CGI::Session.new(cgi, 'new_session' => true)
- # session.close
- #
- class Session
-
- class NoSession < RuntimeError #:nodoc:
- end
-
- # The id of this session.
- attr_reader :session_id, :new_session
-
- def Session::callback(dbman) #:nodoc:
- Proc.new{
- dbman[0].close unless dbman.empty?
- }
- end
-
- # Create a new session id.
- #
- # The session id is a secure random number by SecureRandom
- # if possible, otherwise an SHA512 hash based upon the time,
- # a random number, and a constant string. This routine is
- # used internally for automatically generated session ids.
- def create_new_id
- require 'securerandom'
- begin
- # by OpenSSL, or system provided entropy pool
- session_id = SecureRandom.hex(16)
- rescue NotImplementedError
- # never happens on modern systems
- require 'digest'
- d = Digest('SHA512').new
- now = Time::now
- d.update(now.to_s)
- d.update(String(now.usec))
- d.update(String(rand(0)))
- d.update(String($$))
- d.update('foobar')
- session_id = d.hexdigest[0, 32]
- end
- session_id
- end
- private :create_new_id
-
-
- # Create a new file to store the session data.
- #
- # This file will be created if it does not exist, or opened if it
- # does.
- #
- # This path is generated under _tmpdir_ from _prefix_, the
- # digested session id, and _suffix_.
- #
- # +option+ is a hash of options for the initializer. The
- # following options are recognised:
- #
- # tmpdir:: the directory to use for storing the FileStore
- # file. Defaults to Dir::tmpdir (generally "/tmp"
- # on Unix systems).
- # prefix:: the prefix to add to the session id when generating
- # the filename for this session's FileStore file.
- # Defaults to "cgi_sid_".
- # suffix:: the prefix to add to the session id when generating
- # the filename for this session's FileStore file.
- # Defaults to the empty string.
- def new_store_file(option={}) # :nodoc:
- dir = option['tmpdir'] || Dir::tmpdir
- prefix = option['prefix']
- suffix = option['suffix']
- require 'digest/md5'
- md5 = Digest::MD5.hexdigest(session_id)[0,16]
- path = dir+"/"
- path << prefix if prefix
- path << md5
- path << suffix if suffix
- if File::exist? path
- hash = nil
- elsif new_session
- hash = {}
- else
- raise NoSession, "uninitialized session"
- end
- return path, hash
- end
-
- # Create a new CGI::Session object for +request+.
- #
- # +request+ is an instance of the +CGI+ class (see cgi.rb).
- # +option+ is a hash of options for initialising this
- # CGI::Session instance. The following options are
- # recognised:
- #
- # session_key:: the parameter name used for the session id.
- # Defaults to '_session_id'.
- # session_id:: the session id to use. If not provided, then
- # it is retrieved from the +session_key+ parameter
- # of the request, or automatically generated for
- # a new session.
- # new_session:: if true, force creation of a new session. If not set,
- # a new session is only created if none currently
- # exists. If false, a new session is never created,
- # and if none currently exists and the +session_id+
- # option is not set, an ArgumentError is raised.
- # database_manager:: the name of the class providing storage facilities
- # for session state persistence. Built-in support
- # is provided for +FileStore+ (the default),
- # +MemoryStore+, and +PStore+ (from
- # cgi/session/pstore.rb). See the documentation for
- # these classes for more details.
- #
- # The following options are also recognised, but only apply if the
- # session id is stored in a cookie.
- #
- # session_expires:: the time the current session expires, as a
- # +Time+ object. If not set, the session will terminate
- # when the user's browser is closed.
- # session_domain:: the hostname domain for which this session is valid.
- # If not set, defaults to the hostname of the server.
- # session_secure:: if +true+, this session will only work over HTTPS.
- # session_path:: the path for which this session applies. Defaults
- # to the directory of the CGI script.
- #
- # +option+ is also passed on to the session storage class initializer; see
- # the documentation for each session storage class for the options
- # they support.
- #
- # The retrieved or created session is automatically added to +request+
- # as a cookie, and also to its +output_hidden+ table, which is used
- # to add hidden input elements to forms.
- #
- # *WARNING* the +output_hidden+
- # fields are surrounded by a <fieldset> tag in HTML 4 generation, which
- # is _not_ invisible on many browsers; you may wish to disable the
- # use of fieldsets with code similar to the following
- # (see https://blade.ruby-lang.org/ruby-list/37805)
- #
- # cgi = CGI.new("html4")
- # class << cgi
- # undef_method :fieldset
- # end
- #
- def initialize(request, option={})
- @new_session = false
- session_key = option['session_key'] || '_session_id'
- session_id = option['session_id']
- unless session_id
- if option['new_session']
- session_id = create_new_id
- @new_session = true
- end
- end
- unless session_id
- if request.key?(session_key)
- session_id = request[session_key]
- session_id = session_id.read if session_id.respond_to?(:read)
- end
- unless session_id
- session_id, = request.cookies[session_key]
- end
- unless session_id
- unless option.fetch('new_session', true)
- raise ArgumentError, "session_key `%s' should be supplied"%session_key
- end
- session_id = create_new_id
- @new_session = true
- end
- end
- @session_id = session_id
- dbman = option['database_manager'] || FileStore
- begin
- @dbman = dbman::new(self, option)
- rescue NoSession
- unless option.fetch('new_session', true)
- raise ArgumentError, "invalid session_id `%s'"%session_id
- end
- session_id = @session_id = create_new_id unless session_id
- @new_session=true
- retry
- end
- request.instance_eval do
- @output_hidden = {session_key => session_id} unless option['no_hidden']
- @output_cookies = [
- Cookie::new("name" => session_key,
- "value" => session_id,
- "expires" => option['session_expires'],
- "domain" => option['session_domain'],
- "secure" => option['session_secure'],
- "path" =>
- if option['session_path']
- option['session_path']
- elsif ENV["SCRIPT_NAME"]
- File::dirname(ENV["SCRIPT_NAME"])
- else
- ""
- end)
- ] unless option['no_cookies']
- end
- @dbprot = [@dbman]
- ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
- end
-
- # Retrieve the session data for key +key+.
- def [](key)
- @data ||= @dbman.restore
- @data[key]
- end
-
- # Set the session data for key +key+.
- def []=(key, val)
- @write_lock ||= true
- @data ||= @dbman.restore
- @data[key] = val
- end
-
- # Store session data on the server. For some session storage types,
- # this is a no-op.
- def update
- @dbman.update
- end
-
- # Store session data on the server and close the session storage.
- # For some session storage types, this is a no-op.
- def close
- @dbman.close
- @dbprot.clear
- end
-
- # Delete the session from storage. Also closes the storage.
- #
- # Note that the session's data is _not_ automatically deleted
- # upon the session expiring.
- def delete
- @dbman.delete
- @dbprot.clear
- end
-
- # File-based session storage class.
- #
- # Implements session storage as a flat file of 'key=value' values.
- # This storage type only works directly with String values; the
- # user is responsible for converting other types to Strings when
- # storing and from Strings when retrieving.
- class FileStore
- # Create a new FileStore instance.
- #
- # This constructor is used internally by CGI::Session. The
- # user does not generally need to call it directly.
- #
- # +session+ is the session for which this instance is being
- # created. The session id must only contain alphanumeric
- # characters; automatically generated session ids observe
- # this requirement.
- #
- # +option+ is a hash of options for the initializer. The
- # following options are recognised:
- #
- # tmpdir:: the directory to use for storing the FileStore
- # file. Defaults to Dir::tmpdir (generally "/tmp"
- # on Unix systems).
- # prefix:: the prefix to add to the session id when generating
- # the filename for this session's FileStore file.
- # Defaults to "cgi_sid_".
- # suffix:: the prefix to add to the session id when generating
- # the filename for this session's FileStore file.
- # Defaults to the empty string.
- #
- # This session's FileStore file will be created if it does
- # not exist, or opened if it does.
- def initialize(session, option={})
- option = {'prefix' => 'cgi_sid_'}.update(option)
- @path, @hash = session.new_store_file(option)
- end
-
- # Restore session state from the session's FileStore file.
- #
- # Returns the session state as a hash.
- def restore
- unless @hash
- @hash = {}
- begin
- lockf = File.open(@path+".lock", "r")
- lockf.flock File::LOCK_SH
- f = File.open(@path, 'r')
- for line in f
- line.chomp!
- k, v = line.split('=',2)
- @hash[CGI.unescape(k)] = Marshal.restore(CGI.unescape(v))
- end
- ensure
- f&.close
- lockf&.close
- end
- end
- @hash
- end
-
- # Save session state to the session's FileStore file.
- def update
- return unless @hash
- begin
- lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600)
- lockf.flock File::LOCK_EX
- f = File.open(@path+".new", File::CREAT|File::TRUNC|File::WRONLY, 0600)
- for k,v in @hash
- f.printf "%s=%s\n", CGI.escape(k), CGI.escape(String(Marshal.dump(v)))
- end
- f.close
- File.rename @path+".new", @path
- ensure
- f&.close
- lockf&.close
- end
- end
-
- # Update and close the session's FileStore file.
- def close
- update
- end
-
- # Close and delete the session's FileStore file.
- def delete
- File::unlink @path+".lock" rescue nil
- File::unlink @path+".new" rescue nil
- File::unlink @path rescue nil
- end
- end
-
- # In-memory session storage class.
- #
- # Implements session storage as a global in-memory hash. Session
- # data will only persist for as long as the Ruby interpreter
- # instance does.
- class MemoryStore
- GLOBAL_HASH_TABLE = {} #:nodoc:
-
- # Create a new MemoryStore instance.
- #
- # +session+ is the session this instance is associated with.
- # +option+ is a list of initialisation options. None are
- # currently recognized.
- def initialize(session, option=nil)
- @session_id = session.session_id
- unless GLOBAL_HASH_TABLE.key?(@session_id)
- unless session.new_session
- raise CGI::Session::NoSession, "uninitialized session"
- end
- GLOBAL_HASH_TABLE[@session_id] = {}
- end
- end
-
- # Restore session state.
- #
- # Returns session data as a hash.
- def restore
- GLOBAL_HASH_TABLE[@session_id]
- end
-
- # Update session state.
- #
- # A no-op.
- def update
- # don't need to update; hash is shared
- end
-
- # Close session storage.
- #
- # A no-op.
- def close
- # don't need to close
- end
-
- # Delete the session state.
- def delete
- GLOBAL_HASH_TABLE.delete(@session_id)
- end
- end
-
- # Dummy session storage class.
- #
- # Implements session storage place holder. No actual storage
- # will be done.
- class NullStore
- # Create a new NullStore instance.
- #
- # +session+ is the session this instance is associated with.
- # +option+ is a list of initialisation options. None are
- # currently recognised.
- def initialize(session, option=nil)
- end
-
- # Restore (empty) session state.
- def restore
- {}
- end
-
- # Update session state.
- #
- # A no-op.
- def update
- end
-
- # Close session storage.
- #
- # A no-op.
- def close
- end
-
- # Delete the session state.
- #
- # A no-op.
- def delete
- end
- end
- end
-end
diff --git a/lib/cgi/session/pstore.rb b/lib/cgi/session/pstore.rb
deleted file mode 100644
index 6e3d10f075..0000000000
--- a/lib/cgi/session/pstore.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-# frozen_string_literal: true
-#
-# cgi/session/pstore.rb - persistent storage of marshalled session data
-#
-# Documentation: William Webber (william@williamwebber.com)
-#
-# == Overview
-#
-# This file provides the CGI::Session::PStore class, which builds
-# persistent of session data on top of the pstore library. See
-# cgi/session.rb for more details on session storage managers.
-
-require_relative '../session'
-begin
- require 'pstore'
-rescue LoadError
-end
-
-class CGI
- class Session
- # PStore-based session storage class.
- #
- # This builds upon the top-level PStore class provided by the
- # library file pstore.rb. Session data is marshalled and stored
- # in a file. File locking and transaction services are provided.
- class PStore
- # Create a new CGI::Session::PStore instance
- #
- # This constructor is used internally by CGI::Session. The
- # user does not generally need to call it directly.
- #
- # +session+ is the session for which this instance is being
- # created. The session id must only contain alphanumeric
- # characters; automatically generated session ids observe
- # this requirement.
- #
- # +option+ is a hash of options for the initializer. The
- # following options are recognised:
- #
- # tmpdir:: the directory to use for storing the PStore
- # file. Defaults to Dir::tmpdir (generally "/tmp"
- # on Unix systems).
- # prefix:: the prefix to add to the session id when generating
- # the filename for this session's PStore file.
- # Defaults to the empty string.
- #
- # This session's PStore file will be created if it does
- # not exist, or opened if it does.
- def initialize(session, option={})
- option = {'suffix'=>''}.update(option)
- path, @hash = session.new_store_file(option)
- @p = ::PStore.new(path)
- @p.transaction do |p|
- File.chmod(0600, p.path)
- end
- end
-
- # Restore session state from the session's PStore file.
- #
- # Returns the session state as a hash.
- def restore
- unless @hash
- @p.transaction do
- @hash = @p['hash'] || {}
- end
- end
- @hash
- end
-
- # Save session state to the session's PStore file.
- def update
- @p.transaction do
- @p['hash'] = @hash
- end
- end
-
- # Update and close the session's PStore file.
- def close
- update
- end
-
- # Close and delete the session's PStore file.
- def delete
- path = @p.path
- File::unlink path
- end
-
- end if defined?(::PStore)
- end
-end
-# :enddoc:
diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb
index 5f12eae130..50a2e91665 100644
--- a/lib/cgi/util.rb
+++ b/lib/cgi/util.rb
@@ -1,258 +1,7 @@
# frozen_string_literal: true
-class CGI
- module Util; end
- include Util
- extend Util
-end
-module CGI::Util
- @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset)
- # URL-encode a string into application/x-www-form-urlencoded.
- # Space characters (+" "+) are encoded with plus signs (+"+"+)
- # url_encoded_string = CGI.escape("'Stop!' said Fred")
- # # => "%27Stop%21%27+said+Fred"
- def escape(string)
- encoding = string.encoding
- buffer = string.b
- buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m|
- '%' + m.unpack('H2' * m.bytesize).join('%').upcase
- end
- buffer.tr!(' ', '+')
- buffer.force_encoding(encoding)
- end
-
- # URL-decode an application/x-www-form-urlencoded string with encoding(optional).
- # string = CGI.unescape("%27Stop%21%27+said+Fred")
- # # => "'Stop!' said Fred"
- def unescape(string, encoding = @@accept_charset)
- str = string.tr('+', ' ')
- str = str.b
- str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
- [m.delete('%')].pack('H*')
- end
- str.force_encoding(encoding)
- str.valid_encoding? ? str : str.force_encoding(string.encoding)
- end
-
- # URL-encode a string following RFC 3986
- # Space characters (+" "+) are encoded with (+"%20"+)
- # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred")
- # # => "%27Stop%21%27%20said%20Fred"
- def escapeURIComponent(string)
- encoding = string.encoding
- buffer = string.b
- buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m|
- '%' + m.unpack('H2' * m.bytesize).join('%').upcase
- end
- buffer.force_encoding(encoding)
- end
- alias escape_uri_component escapeURIComponent
-
- # URL-decode a string following RFC 3986 with encoding(optional).
- # string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred")
- # # => "'Stop!'+said Fred"
- def unescapeURIComponent(string, encoding = @@accept_charset)
- str = string.b
- str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
- [m.delete('%')].pack('H*')
- end
- str.force_encoding(encoding)
- str.valid_encoding? ? str : str.force_encoding(string.encoding)
- end
-
- alias unescape_uri_component unescapeURIComponent
-
- # The set of special characters and their escaped values
- TABLE_FOR_ESCAPE_HTML__ = {
- "'" => '&#39;',
- '&' => '&amp;',
- '"' => '&quot;',
- '<' => '&lt;',
- '>' => '&gt;',
- }
-
- # Escape special characters in HTML, namely '&\"<>
- # CGI.escapeHTML('Usage: foo "bar" <baz>')
- # # => "Usage: foo &quot;bar&quot; &lt;baz&gt;"
- def escapeHTML(string)
- enc = string.encoding
- unless enc.ascii_compatible?
- if enc.dummy?
- origenc = enc
- enc = Encoding::Converter.asciicompat_encoding(enc)
- string = enc ? string.encode(enc) : string.b
- end
- table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}]
- string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table)
- string.encode!(origenc) if origenc
- string
- else
- string = string.b
- string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
- string.force_encoding(enc)
- end
- end
-
- # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there
- unless RUBY_ENGINE == 'truffleruby'
- begin
- require 'cgi/escape'
- rescue LoadError
- end
- end
-
- # Unescape a string that has been HTML-escaped
- # CGI.unescapeHTML("Usage: foo &quot;bar&quot; &lt;baz&gt;")
- # # => "Usage: foo \"bar\" <baz>"
- def unescapeHTML(string)
- enc = string.encoding
- unless enc.ascii_compatible?
- if enc.dummy?
- origenc = enc
- enc = Encoding::Converter.asciicompat_encoding(enc)
- string = enc ? string.encode(enc) : string.b
- end
- string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
- case $1.encode(Encoding::US_ASCII)
- when 'apos' then "'".encode(enc)
- when 'amp' then '&'.encode(enc)
- when 'quot' then '"'.encode(enc)
- when 'gt' then '>'.encode(enc)
- when 'lt' then '<'.encode(enc)
- when /\A#0*(\d+)\z/ then $1.to_i.chr(enc)
- when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc)
- end
- end
- string.encode!(origenc) if origenc
- return string
- end
- return string unless string.include? '&'
- charlimit = case enc
- when Encoding::UTF_8; 0x10ffff
- when Encoding::ISO_8859_1; 256
- else 128
- end
- string = string.b
- string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do
- match = $1.dup
- case match
- when 'apos' then "'"
- when 'amp' then '&'
- when 'quot' then '"'
- when 'gt' then '>'
- when 'lt' then '<'
- when /\A#0*(\d+)\z/
- n = $1.to_i
- if n < charlimit
- n.chr(enc)
- else
- "&##{$1};"
- end
- when /\A#x([0-9a-f]+)\z/i
- n = $1.hex
- if n < charlimit
- n.chr(enc)
- else
- "&#x#{$1};"
- end
- else
- "&#{match};"
- end
- end
- string.force_encoding enc
- end
-
- # Synonym for CGI.escapeHTML(str)
- alias escape_html escapeHTML
-
- # Synonym for CGI.unescapeHTML(str)
- alias unescape_html unescapeHTML
-
- # Escape only the tags of certain HTML elements in +string+.
- #
- # Takes an element or elements or array of elements. Each element
- # is specified by the name of the element, without angle brackets.
- # This matches both the start and the end tag of that element.
- # The attribute list of the open tag will also be escaped (for
- # instance, the double-quotes surrounding attribute values).
- #
- # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
- # # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
- #
- # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
- # # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
- def escapeElement(string, *elements)
- elements = elements[0] if elements[0].kind_of?(Array)
- unless elements.empty?
- string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do
- CGI.escapeHTML($&)
- end
- else
- string
- end
- end
-
- # Undo escaping such as that done by CGI.escapeElement()
- #
- # print CGI.unescapeElement(
- # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG")
- # # "&lt;BR&gt;<A HREF="url"></A>"
- #
- # print CGI.unescapeElement(
- # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"])
- # # "&lt;BR&gt;<A HREF="url"></A>"
- def unescapeElement(string, *elements)
- elements = elements[0] if elements[0].kind_of?(Array)
- unless elements.empty?
- string.gsub(/&lt;\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:&gt;)?/im) do
- unescapeHTML($&)
- end
- else
- string
- end
- end
-
- # Synonym for CGI.escapeElement(str)
- alias escape_element escapeElement
-
- # Synonym for CGI.unescapeElement(str)
- alias unescape_element unescapeElement
-
- # Format a +Time+ object as a String using the format specified by RFC 1123.
- #
- # CGI.rfc1123_date(Time.now)
- # # Sat, 01 Jan 2000 00:00:00 GMT
- def rfc1123_date(time)
- time.getgm.strftime("%a, %d %b %Y %T GMT")
- end
-
- # Prettify (indent) an HTML string.
- #
- # +string+ is the HTML string to indent. +shift+ is the indentation
- # unit to use; it defaults to two spaces.
- #
- # print CGI.pretty("<HTML><BODY></BODY></HTML>")
- # # <HTML>
- # # <BODY>
- # # </BODY>
- # # </HTML>
- #
- # print CGI.pretty("<HTML><BODY></BODY></HTML>", "\t")
- # # <HTML>
- # # <BODY>
- # # </BODY>
- # # </HTML>
- #
- def pretty(string, shift = " ")
- lines = string.gsub(/(?!\A)<.*?>/m, "\n\\0").gsub(/<.*?>(?!\n)/m, "\\0\n")
- end_pos = 0
- while end_pos = lines.index(/^<\/(\w+)/, end_pos)
- element = $1.dup
- start_pos = lines.rindex(/^\s*<#{element}/i, end_pos)
- lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__"
- end
- lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1')
- end
-
- alias h escapeHTML
-end
+require "cgi/escape"
+warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE
+CGI::Util is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features.
+If you are using CGI.parse, please install and use the cgi gem instead.
+WARNING
diff --git a/lib/delegate.rb b/lib/delegate.rb
index 824d02f28b..0cc3ddb1b0 100644
--- a/lib/delegate.rb
+++ b/lib/delegate.rb
@@ -39,7 +39,8 @@
# Be advised, RDoc will not detect delegated methods.
#
class Delegator < BasicObject
- VERSION = "0.4.0"
+ # The version string
+ VERSION = "0.6.1"
kernel = ::Kernel.dup
kernel.class_eval do
@@ -77,7 +78,7 @@ class Delegator < BasicObject
end
#
- # Handles the magic of delegation through \_\_getobj\_\_.
+ # Handles the magic of delegation through +__getobj__+.
#
ruby2_keywords def method_missing(m, *args, &block)
r = true
@@ -94,7 +95,7 @@ class Delegator < BasicObject
#
# Checks for a method provided by this the delegate object by forwarding the
- # call through \_\_getobj\_\_.
+ # call through +__getobj__+.
#
def respond_to_missing?(m, include_private)
r = true
@@ -107,7 +108,7 @@ class Delegator < BasicObject
r
end
- KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?)
+ KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) # :nodoc:
private_constant :KERNEL_RESPOND_TO
# Handle BasicObject instances
@@ -126,7 +127,7 @@ class Delegator < BasicObject
#
# Returns the methods available to this delegate object as the union
- # of this object's and \_\_getobj\_\_ methods.
+ # of this object's and +__getobj__+ methods.
#
def methods(all=true)
__getobj__.methods(all) | super
@@ -134,7 +135,7 @@ class Delegator < BasicObject
#
# Returns the methods available to this delegate object as the union
- # of this object's and \_\_getobj\_\_ public methods.
+ # of this object's and +__getobj__+ public methods.
#
def public_methods(all=true)
__getobj__.public_methods(all) | super
@@ -142,7 +143,7 @@ class Delegator < BasicObject
#
# Returns the methods available to this delegate object as the union
- # of this object's and \_\_getobj\_\_ protected methods.
+ # of this object's and +__getobj__+ protected methods.
#
def protected_methods(all=true)
__getobj__.protected_methods(all) | super
@@ -175,7 +176,7 @@ class Delegator < BasicObject
end
#
- # Delegates ! to the \_\_getobj\_\_
+ # Delegates ! to the +__getobj__+
#
def !
!__getobj__
@@ -198,7 +199,7 @@ class Delegator < BasicObject
end
#
- # Serialization support for the object returned by \_\_getobj\_\_.
+ # Serialization support for the object returned by +__getobj__+.
#
def marshal_dump
ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var}
@@ -232,7 +233,7 @@ class Delegator < BasicObject
##
# :method: freeze
- # Freeze both the object returned by \_\_getobj\_\_ and self.
+ # Freeze both the object returned by +__getobj__+ and self.
#
def freeze
__getobj__.freeze
@@ -398,6 +399,17 @@ def DelegateClass(superclass, &block)
protected_instance_methods -= ignores
public_instance_methods = superclass.public_instance_methods
public_instance_methods -= ignores
+
+ normal, special = public_instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) }
+
+ source = normal.map do |method|
+ "def #{method}(...); __getobj__.#{method}(...); end"
+ end
+
+ protected_instance_methods.each do |method|
+ source << "def #{method}(...); __getobj__.__send__(#{method.inspect}, ...); end"
+ end
+
klass.module_eval do
def __getobj__ # :nodoc:
unless defined?(@delegate_dc_obj)
@@ -406,18 +418,21 @@ def DelegateClass(superclass, &block)
end
@delegate_dc_obj
end
+
def __setobj__(obj) # :nodoc:
__raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
@delegate_dc_obj = obj
end
- protected_instance_methods.each do |method|
- define_method(method, Delegator.delegating_block(method))
- protected method
- end
- public_instance_methods.each do |method|
+
+ class_eval(source.join(";"), __FILE__, __LINE__)
+
+ special.each do |method|
define_method(method, Delegator.delegating_block(method))
end
+
+ protected(*protected_instance_methods)
end
+
klass.define_singleton_method :public_instance_methods do |all=true|
super(all) | superclass.public_instance_methods
end
diff --git a/lib/erb.rb b/lib/erb.rb
index bdff2deceb..445d4795b0 100644
--- a/lib/erb.rb
+++ b/lib/erb.rb
@@ -12,340 +12,824 @@
#
# You can redistribute it and/or modify it under the same terms as Ruby.
-require 'cgi/util'
+# A NOTE ABOUT TERMS:
+#
+# Formerly: The documentation in this file used the term _template_ to refer to an ERB object.
+#
+# Now: The documentation in this file uses the term _template_
+# to refer to the string input to ERB.new.
+#
+# The reason for the change: When documenting the ERB executable erb,
+# we need a term that refers to its string input;
+# _source_ is not a good idea, because ERB#src means something entirely different;
+# the two different sorts of sources would bring confusion.
+#
+# Therefore we use the term _template_ to refer to:
+#
+# - The string input to ERB.new
+# - The string input to executable erb.
+#
+
require 'erb/version'
require 'erb/compiler'
require 'erb/def_method'
require 'erb/util'
+# :markup: markdown
#
-# = ERB -- Ruby Templating
+# Class **ERB** (the name stands for **Embedded Ruby**)
+# is an easy-to-use, but also very powerful, [template processor][template processor].
#
-# == Introduction
+# ## Usage
#
-# ERB provides an easy to use but powerful templating system for Ruby. Using
-# ERB, actual Ruby code can be added to any plain text document for the
-# purposes of generating document information details and/or flow control.
+# Before you can use \ERB, you must first require it
+# (examples on this page assume that this has been done):
#
-# A very simple example is this:
+# ```
+# require 'erb'
+# ```
#
-# require 'erb'
+# ## In Brief
#
-# x = 42
-# template = ERB.new <<-EOF
-# The value of x is: <%= x %>
-# EOF
-# puts template.result(binding)
+# Here's how \ERB works:
#
-# <em>Prints:</em> The value of x is: 42
+# - You can create a *template*: a plain-text string that includes specially formatted *tags*..
+# - You can create an \ERB object to store the template.
+# - You can call instance method ERB#result to get the *result*.
#
-# More complex examples are given below.
+# \ERB supports tags of three kinds:
#
+# - [Expression tags][expression tags]:
+# each begins with `'<%='`, ends with `'%>'`; contains a Ruby expression;
+# in the result, the value of the expression replaces the entire tag:
#
-# == Recognized Tags
+# template = 'The magic word is <%= magic_word %>.'
+# erb = ERB.new(template)
+# magic_word = 'xyzzy'
+# erb.result(binding) # => "The magic word is xyzzy."
#
-# ERB recognizes certain tags in the provided template and converts them based
-# on the rules below:
+# The above call to #result passes argument `binding`,
+# which contains the binding of variable `magic_word` to its string value `'xyzzy'`.
#
-# <% Ruby code -- inline with output %>
-# <%= Ruby expression -- replace with result %>
-# <%# comment -- ignored -- useful in testing %> (`<% #` doesn't work. Don't use Ruby comments.)
-# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new)
-# %% replaced with % if first thing on a line and % processing is used
-# <%% or %%> -- replace with <% or %> respectively
+# The below call to #result need not pass a binding,
+# because its expression `Date::DAYNAMES` is globally defined.
#
-# All other text is passed through ERB filtering unchanged.
+# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result # => "Today is Monday."
#
+# - [Execution tags][execution tags]:
+# each begins with `'<%'`, ends with `'%>'`; contains Ruby code to be executed:
#
-# == Options
+# template = '<% File.write("t.txt", "Some stuff.") %>'
+# ERB.new(template).result
+# File.read('t.txt') # => "Some stuff."
#
-# There are several settings you can change when you use ERB:
-# * the nature of the tags that are recognized;
-# * the binding used to resolve local variables in the template.
+# - [Comment tags][comment tags]:
+# each begins with `'<%#'`, ends with `'%>'`; contains comment text;
+# in the result, the entire tag is omitted.
#
-# See the ERB.new and ERB#result methods for more detail.
+# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.'
+# ERB.new(template).result # => "Some stuff; more stuff."
#
-# == Character encodings
+# ## Some Simple Examples
#
-# ERB (or Ruby code generated by ERB) returns a string in the same
-# character encoding as the input string. When the input string has
-# a magic comment, however, it returns a string in the encoding specified
-# by the magic comment.
+# Here's a simple example of \ERB in action:
#
-# # -*- coding: utf-8 -*-
-# require 'erb'
+# ```
+# template = 'The time is <%= Time.now %>.'
+# erb = ERB.new(template)
+# erb.result
+# # => "The time is 2025-09-09 10:49:26 -0500."
+# ```
#
-# template = ERB.new <<EOF
-# <%#-*- coding: Big5 -*-%>
-# \_\_ENCODING\_\_ is <%= \_\_ENCODING\_\_ %>.
-# EOF
-# puts template.result
+# Details:
#
-# <em>Prints:</em> \_\_ENCODING\_\_ is Big5.
+# 1. A plain-text string is assigned to variable `template`.
+# Its embedded [expression tag][expression tags] `'<%= Time.now %>'` includes a Ruby expression, `Time.now`.
+# 2. The string is put into a new \ERB object, and stored in variable `erb`.
+# 4. Method call `erb.result` generates a string that contains the run-time value of `Time.now`,
+# as computed at the time of the call.
#
+# The
+# \ERB object may be re-used:
#
-# == Examples
+# ```
+# erb.result
+# # => "The time is 2025-09-09 10:49:33 -0500."
+# ```
#
-# === Plain Text
+# Another example:
#
-# ERB is useful for any generic templating situation. Note that in this example, we use the
-# convenient "% at start of line" tag, and we quote the template literally with
-# <tt>%q{...}</tt> to avoid trouble with the backslash.
+# ```
+# template = 'The magic word is <%= magic_word %>.'
+# erb = ERB.new(template)
+# magic_word = 'abracadabra'
+# erb.result(binding)
+# # => "The magic word is abracadabra."
+# ```
#
-# require "erb"
+# Details:
#
-# # Create template.
-# template = %q{
-# From: James Edward Gray II <james@grayproductions.net>
-# To: <%= to %>
-# Subject: Addressing Needs
+# 1. As before, a plain-text string is assigned to variable `template`.
+# Its embedded [expression tag][expression tags] `'<%= magic_word %>'` has a variable *name*, `magic_word`.
+# 2. The string is put into a new \ERB object, and stored in variable `erb`;
+# note that `magic_word` need not be defined before the \ERB object is created.
+# 3. `magic_word = 'abracadabra'` assigns a value to variable `magic_word`.
+# 4. Method call `erb.result(binding)` generates a string
+# that contains the *value* of `magic_word`.
#
-# <%= to[/\w+/] %>:
+# As before, the \ERB object may be re-used:
#
-# Just wanted to send a quick note assuring that your needs are being
-# addressed.
+# ```
+# magic_word = 'xyzzy'
+# erb.result(binding)
+# # => "The magic word is xyzzy."
+# ```
#
-# I want you to know that my team will keep working on the issues,
-# especially:
+# ## Bindings
#
-# <%# ignore numerous minor requests -- focus on priorities %>
-# % priorities.each do |priority|
-# * <%= priority %>
-# % end
+# A call to method #result, which produces the formatted result string,
+# requires a [Binding object][binding object] as its argument.
+#
+# The binding object provides the bindings for expressions in [expression tags][expression tags].
+#
+# There are three ways to provide the required binding:
+#
+# - [Default binding][default binding].
+# - [Local binding][local binding].
+# - [Augmented binding][augmented binding]
+#
+# ### Default Binding
+#
+# When you pass no `binding` argument to method #result,
+# the method uses its default binding: the one returned by method #new_toplevel.
+# This binding has the bindings defined by Ruby itself,
+# which are those for Ruby's constants and variables.
+#
+# That binding is sufficient for an expression tag that refers only to Ruby's constants and variables;
+# these expression tags refer only to Ruby's global constant `RUBY_COPYRIGHT` and global variable `$0`:
#
-# Thanks for your patience.
+# ```
+# template = <<TEMPLATE
+# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>.
+# The current process is <%= $0 %>.
+# TEMPLATE
+# puts ERB.new(template).result
+# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto".
+# The current process is irb.
+# ```
+#
+# (The current process is `irb` because that's where we're doing these examples!)
+#
+# ### Local Binding
+#
+# The default binding is *not* sufficient for an expression
+# that refers to a a constant or variable that is not defined there:
+#
+# ```
+# Foo = 1 # Defines local constant Foo.
+# foo = 2 # Defines local variable foo.
+# template = <<TEMPLATE
+# The current value of constant Foo is <%= Foo %>.
+# The current value of variable foo is <%= foo %>.
+# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>.
+# The current process is <%= $0 %>.
+# TEMPLATE
+# erb = ERB.new(template)
+# ```
+#
+# This call below raises `NameError` because although `Foo` and `foo` are defined locally,
+# they are not defined in the default binding:
+#
+# ```
+# erb.result # Raises NameError.
+# ```
+#
+# To make the locally-defined constants and variables available,
+# you can call #result with the local binding:
+#
+# ```
+# puts erb.result(binding)
+# The current value of constant Foo is 1.
+# The current value of variable foo is 2.
+# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto".
+# The current process is irb.
+# ```
+#
+# ### Augmented Binding
+#
+# Another way to make variable bindings (but not constant bindings) available
+# is to use method #result_with_hash(hash);
+# the passed hash has name/value pairs that are to be used to define and assign variables
+# in a copy of the default binding:
+#
+# ```
+# template = <<TEMPLATE
+# The current value of variable bar is <%= bar %>.
+# The current value of variable baz is <%= baz %>.
+# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>.
+# The current process is <%= $0 %>.
+# TEMPLATE
+# erb = ERB.new(template)
+# ```
+#
+# Both of these calls raise `NameError`, because `bar` and `baz`
+# are not defined in either the default binding or the local binding.
+#
+# ```
+# puts erb.result # Raises NameError.
+# puts erb.result(binding) # Raises NameError.
+# ```
+#
+# This call passes a hash that causes `bar` and `baz` to be defined
+# in a new binding (derived from #new_toplevel):
+#
+# ```
+# hash = {bar: 3, baz: 4}
+# puts erb.result_with_hash(hash)
+# The current value of variable bar is 3.
+# The current value of variable baz is 4.
+# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto".
+# The current process is irb.
+# ```
+#
+# ## Tags
+#
+# The examples above use expression tags.
+# These are the tags available in \ERB:
+#
+# - [Expression tag][expression tags]: the tag contains a Ruby expression;
+# in the result, the entire tag is to be replaced with the run-time value of the expression.
+# - [Execution tag][execution tags]: the tag contains Ruby code;
+# in the result, the entire tag is to be replaced with the run-time value of the code.
+# - [Comment tag][comment tags]: the tag contains comment code;
+# in the result, the entire tag is to be omitted.
+#
+# ### Expression Tags
+#
+# You can embed a Ruby expression in a template using an *expression tag*.
+#
+# Its syntax is `<%= _expression_ %>`,
+# where *expression* is any valid Ruby expression.
+#
+# When you call method #result,
+# the method evaluates the expression and replaces the entire expression tag with the expression's value:
+#
+# ```
+# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result
+# # => "Today is Monday."
+# ERB.new('Tomorrow will be <%= Date::DAYNAMES[Date.today.wday + 1] %>.').result
+# # => "Tomorrow will be Tuesday."
+# ERB.new('Yesterday was <%= Date::DAYNAMES[Date.today.wday - 1] %>.').result
+# # => "Yesterday was Sunday."
+# ```
+#
+# Note that whitespace before and after the expression
+# is allowed but not required,
+# and that such whitespace is stripped from the result.
+#
+# ```
+# ERB.new('My appointment is on <%=Date::DAYNAMES[Date.today.wday + 2]%>.').result
+# # => "My appointment is on Wednesday."
+# ERB.new('My appointment is on <%= Date::DAYNAMES[Date.today.wday + 2] %>.').result
+# # => "My appointment is on Wednesday."
+# ```
+#
+# ### Execution Tags
+#
+# You can embed Ruby executable code in template using an *execution tag*.
+#
+# Its syntax is `<% _code_ %>`,
+# where *code* is any valid Ruby code.
+#
+# When you call method #result,
+# the method executes the code and removes the entire execution tag
+# (generating no text in the result):
+#
+# ```
+# ERB.new('foo <% Dir.chdir("C:/") %> bar').result # => "foo bar"
+# ```
+#
+# Whitespace before and after the embedded code is optional:
+#
+# ```
+# ERB.new('foo <%Dir.chdir("C:/")%> bar').result # => "foo bar"
+# ```
+#
+# You can interleave text with execution tags to form a control structure
+# such as a conditional, a loop, or a `case` statements.
+#
+# Conditional:
+#
+# ```
+# template = <<TEMPLATE
+# <% if verbosity %>
+# An error has occurred.
+# <% else %>
+# Oops!
+# <% end %>
+# TEMPLATE
+# erb = ERB.new(template)
+# verbosity = true
+# erb.result(binding)
+# # => "\nAn error has occurred.\n\n"
+# verbosity = false
+# erb.result(binding)
+# # => "\nOops!\n\n"
+# ```
+#
+# Note that the interleaved text may itself contain expression tags:
+#
+# Loop:
+#
+# ```
+# template = <<TEMPLATE
+# <% Date::ABBR_DAYNAMES.each do |dayname| %>
+# <%= dayname %>
+# <% end %>
+# TEMPLATE
+# ERB.new(template).result
+# # => "\nSun\n\nMon\n\nTue\n\nWed\n\nThu\n\nFri\n\nSat\n\n"
+# ```
+#
+# Other, non-control, lines of Ruby code may be interleaved with the text,
+# and the Ruby code may itself contain regular Ruby comments:
+#
+# ```
+# template = <<TEMPLATE
+# <% 3.times do %>
+# <%= Time.now %>
+# <% sleep(1) # Let's make the times different. %>
+# <% end %>
+# TEMPLATE
+# ERB.new(template).result
+# # => "\n2025-09-09 11:36:02 -0500\n\n\n2025-09-09 11:36:03 -0500\n\n\n2025-09-09 11:36:04 -0500\n\n\n"
+# ```
+#
+# The execution tag may also contain multiple lines of code:
+#
+# ```
+# template = <<TEMPLATE
+# <%
+# (0..2).each do |i|
+# (0..2).each do |j|
+# %>
+# * <%=i%>,<%=j%>
+# <%
+# end
+# end
+# %>
+# TEMPLATE
+# ERB.new(template).result
+# # => "\n* 0,0\n\n* 0,1\n\n* 0,2\n\n* 1,0\n\n* 1,1\n\n* 1,2\n\n* 2,0\n\n* 2,1\n\n* 2,2\n\n"
+# ```
+#
+# #### Shorthand Format for Execution Tags
+#
+# You can use keyword argument `trim_mode: '%'` to enable a shorthand format for execution tags;
+# this example uses the shorthand format `% _code_` instead of `<% _code_ %>`:
+#
+# ```
+# template = <<TEMPLATE
+# % priorities.each do |priority|
+# * <%= priority %>
+# % end
+# TEMPLATE
+# erb = ERB.new(template, trim_mode: '%')
+# priorities = [ 'Run Ruby Quiz',
+# 'Document Modules',
+# 'Answer Questions on Ruby Talk' ]
+# puts erb.result(binding)
+# * Run Ruby Quiz
+# * Document Modules
+# * Answer Questions on Ruby Talk
+# ```
+#
+# Note that in the shorthand format, the character `'%'` must be the first character in the code line
+# (no leading whitespace).
+#
+# #### Suppressing Unwanted Blank Lines
+#
+# With keyword argument `trim_mode` not given,
+# all blank lines go into the result:
+#
+# ```
+# template = <<TEMPLATE
+# <% if true %>
+# <%= RUBY_VERSION %>
+# <% end %>
+# TEMPLATE
+# ERB.new(template).result.lines.each {|line| puts line.inspect }
+# "\n"
+# "3.4.5\n"
+# "\n"
+# ```
+#
+# You can give `trim_mode: '-'`, you can suppress each blank line
+# whose source line ends with `-%>` (instead of `%>`):
+#
+# ```
+# template = <<TEMPLATE
+# <% if true -%>
+# <%= RUBY_VERSION %>
+# <% end -%>
+# TEMPLATE
+# ERB.new(template, trim_mode: '-').result.lines.each {|line| puts line.inspect }
+# "3.4.5\n"
+# ```
+#
+# It is an error to use the trailing `'-%>'` notation without `trim_mode: '-'`:
+#
+# ```
+# ERB.new(template).result.lines.each {|line| puts line.inspect } # Raises SyntaxError.
+# ```
+#
+# #### Suppressing Unwanted Newlines
+#
+# Consider this template:
+#
+# ```
+# template = <<TEMPLATE
+# <% RUBY_VERSION %>
+# <%= RUBY_VERSION %>
+# foo <% RUBY_VERSION %>
+# foo <%= RUBY_VERSION %>
+# TEMPLATE
+# ```
+#
+# With keyword argument `trim_mode` not given, all newlines go into the result:
+#
+# ```
+# ERB.new(template).result.lines.each {|line| puts line.inspect }
+# "\n"
+# "3.4.5\n"
+# "foo \n"
+# "foo 3.4.5\n"
+# ```
+#
+# You can give `trim_mode: '>'` to suppress the trailing newline
+# for each line that ends with `'%>'` (regardless of its beginning):
+#
+# ```
+# ERB.new(template, trim_mode: '>').result.lines.each {|line| puts line.inspect }
+# "3.4.5foo foo 3.4.5"
+# ```
+#
+# You can give `trim_mode: '<>'` to suppress the trailing newline
+# for each line that both begins with `'<%'` and ends with `'%>'`:
+#
+# ```
+# ERB.new(template, trim_mode: '<>').result.lines.each {|line| puts line.inspect }
+# "3.4.5foo \n"
+# "foo 3.4.5\n"
+# ```
+#
+# #### Combining Trim Modes
+#
+# You can combine certain trim modes:
+#
+# - `'%-'`: Enable shorthand and omit each blank line ending with `'-%>'`.
+# - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`.
+# - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`.
+#
+# ### Comment Tags
+#
+# You can embed a comment in a template using a *comment tag*;
+# its syntax is `<%# _text_ %>`,
+# where *text* is the text of the comment.
+#
+# When you call method #result,
+# it removes the entire comment tag
+# (generating no text in the result).
+#
+# Example:
+#
+# ```
+# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.'
+# ERB.new(template).result # => "Some stuff; more stuff."
+# ```
+#
+# A comment tag may appear anywhere in the template.
+#
+# Note that the beginning of the tag must be `'<%#'`, not `'<% #'`.
+#
+# In this example, the tag begins with `'<% #'`, and so is an execution tag, not a comment tag;
+# the cited code consists entirely of a Ruby-style comment (which is of course ignored):
+#
+# ```
+# ERB.new('Some stuff;<% # Note to self: figure out what the stuff is. %> more stuff.').result
+# # => "Some stuff;"
+# ```
+#
+# ## Encodings
+#
+# An \ERB object has an [encoding][encoding],
+# which is by default the encoding of the template string;
+# the result string will also have that encoding.
#
-# James Edward Gray II
-# }.gsub(/^ /, '')
+# ```
+# template = <<TEMPLATE
+# <%# Comment. %>
+# TEMPLATE
+# erb = ERB.new(template)
+# template.encoding # => #<Encoding:UTF-8>
+# erb.encoding # => #<Encoding:UTF-8>
+# erb.result.encoding # => #<Encoding:UTF-8>
+# ```
#
-# message = ERB.new(template, trim_mode: "%<>")
+# You can specify a different encoding by adding a [magic comment][magic comments]
+# at the top of the given template:
#
-# # Set up template data.
-# to = "Community Spokesman <spokesman@ruby_community.org>"
-# priorities = [ "Run Ruby Quiz",
-# "Document Modules",
-# "Answer Questions on Ruby Talk" ]
+# ```
+# template = <<TEMPLATE
+# <%#-*- coding: Big5 -*-%>
+# <%# Comment. %>
+# TEMPLATE
+# erb = ERB.new(template)
+# template.encoding # => #<Encoding:UTF-8>
+# erb.encoding # => #<Encoding:Big5>
+# erb.result.encoding # => #<Encoding:Big5>
+# ```
#
-# # Produce result.
-# email = message.result
-# puts email
+# ## Error Reporting
#
-# <i>Generates:</i>
+# Consider this template (containing an error):
#
-# From: James Edward Gray II <james@grayproductions.net>
-# To: Community Spokesman <spokesman@ruby_community.org>
-# Subject: Addressing Needs
+# ```
+# template = '<%= nosuch %>'
+# erb = ERB.new(template)
+# ```
#
-# Community:
+# When \ERB reports an error,
+# it includes a file name (if available) and a line number;
+# the file name comes from method #filename, the line number from method #lineno.
#
-# Just wanted to send a quick note assuring that your needs are being addressed.
+# Initially, those values are `nil` and `0`, respectively;
+# these initial values are reported as `'(erb)'` and `1`, respectively:
#
-# I want you to know that my team will keep working on the issues, especially:
+# ```
+# erb.filename # => nil
+# erb.lineno # => 0
+# erb.result
+# (erb):1:in '<main>': undefined local variable or method 'nosuch' for main (NameError)
+# ```
#
-# * Run Ruby Quiz
-# * Document Modules
-# * Answer Questions on Ruby Talk
+# You can use methods #filename= and #lineno= to assign values
+# that are more meaningful in your context:
#
-# Thanks for your patience.
+# ```
+# erb.filename = 't.txt'
+# erb.lineno = 555
+# erb.result
+# t.txt:556:in '<main>': undefined local variable or method 'nosuch' for main (NameError)
+# ```
#
-# James Edward Gray II
+# You can use method #location= to set both values:
#
-# === Ruby in HTML
+# ```
+# erb.location = ['u.txt', 999]
+# erb.result
+# u.txt:1000:in '<main>': undefined local variable or method 'nosuch' for main (NameError)
+# ```
#
-# ERB is often used in <tt>.rhtml</tt> files (HTML with embedded Ruby). Notice the need in
-# this example to provide a special binding when the template is run, so that the instance
-# variables in the Product object can be resolved.
+# ## Plain Text with Embedded Ruby
#
-# require "erb"
+# Here's a plain-text template;
+# it uses the literal notation `'%q{ ... }'` to define the template
+# (see [%q literals][%q literals]);
+# this avoids problems with backslashes.
#
-# # Build template data class.
-# class Product
-# def initialize( code, name, desc, cost )
-# @code = code
-# @name = name
-# @desc = desc
-# @cost = cost
+# ```
+# template = %q{
+# From: James Edward Gray II <james@grayproductions.net>
+# To: <%= to %>
+# Subject: Addressing Needs
#
-# @features = [ ]
-# end
+# <%= to[/\w+/] %>:
#
-# def add_feature( feature )
-# @features << feature
-# end
+# Just wanted to send a quick note assuring that your needs are being
+# addressed.
#
-# # Support templating of member data.
-# def get_binding
-# binding
-# end
+# I want you to know that my team will keep working on the issues,
+# especially:
#
-# # ...
-# end
+# <%# ignore numerous minor requests -- focus on priorities %>
+# % priorities.each do |priority|
+# * <%= priority %>
+# % end
#
-# # Create template.
-# template = %{
-# <html>
-# <head><title>Ruby Toys -- <%= @name %></title></head>
-# <body>
+# Thanks for your patience.
#
-# <h1><%= @name %> (<%= @code %>)</h1>
-# <p><%= @desc %></p>
+# James Edward Gray II
+# }
+# ```
#
-# <ul>
-# <% @features.each do |f| %>
-# <li><b><%= f %></b></li>
-# <% end %>
-# </ul>
+# The template will need these:
#
-# <p>
-# <% if @cost < 10 %>
-# <b>Only <%= @cost %>!!!</b>
-# <% else %>
-# Call for a price, today!
-# <% end %>
-# </p>
+# ```
+# to = 'Community Spokesman <spokesman@ruby_community.org>'
+# priorities = [ 'Run Ruby Quiz',
+# 'Document Modules',
+# 'Answer Questions on Ruby Talk' ]
+# ```
#
-# </body>
-# </html>
-# }.gsub(/^ /, '')
+# Finally, create the \ERB object and get the result
#
-# rhtml = ERB.new(template)
+# ```
+# erb = ERB.new(template, trim_mode: '%<>')
+# puts erb.result(binding)
#
-# # Set up template data.
-# toy = Product.new( "TZ-1002",
-# "Rubysapien",
-# "Geek's Best Friend! Responds to Ruby commands...",
-# 999.95 )
-# toy.add_feature("Listens for verbal commands in the Ruby language!")
-# toy.add_feature("Ignores Perl, Java, and all C variants.")
-# toy.add_feature("Karate-Chop Action!!!")
-# toy.add_feature("Matz signature on left leg.")
-# toy.add_feature("Gem studded eyes... Rubies, of course!")
+# From: James Edward Gray II <james@grayproductions.net>
+# To: Community Spokesman <spokesman@ruby_community.org>
+# Subject: Addressing Needs
#
-# # Produce result.
-# rhtml.run(toy.get_binding)
+# Community:
#
-# <i>Generates (some blank lines removed):</i>
+# Just wanted to send a quick note assuring that your needs are being
+# addressed.
#
-# <html>
-# <head><title>Ruby Toys -- Rubysapien</title></head>
-# <body>
+# I want you to know that my team will keep working on the issues,
+# especially:
#
-# <h1>Rubysapien (TZ-1002)</h1>
-# <p>Geek's Best Friend! Responds to Ruby commands...</p>
+# * Run Ruby Quiz
+# * Document Modules
+# * Answer Questions on Ruby Talk
#
-# <ul>
-# <li><b>Listens for verbal commands in the Ruby language!</b></li>
-# <li><b>Ignores Perl, Java, and all C variants.</b></li>
-# <li><b>Karate-Chop Action!!!</b></li>
-# <li><b>Matz signature on left leg.</b></li>
-# <li><b>Gem studded eyes... Rubies, of course!</b></li>
-# </ul>
+# Thanks for your patience.
#
-# <p>
-# Call for a price, today!
-# </p>
+# James Edward Gray II
+# ```
#
-# </body>
-# </html>
+# ## HTML with Embedded Ruby
#
+# This example shows an HTML template.
#
-# == Notes
+# First, here's a custom class, `Product`:
#
-# There are a variety of templating solutions available in various Ruby projects.
-# For example, RDoc, distributed with Ruby, uses its own template engine, which
-# can be reused elsewhere.
+# ```
+# class Product
+# def initialize(code, name, desc, cost)
+# @code = code
+# @name = name
+# @desc = desc
+# @cost = cost
+# @features = []
+# end
#
-# Other popular engines could be found in the corresponding
-# {Category}[https://www.ruby-toolbox.com/categories/template_engines] of
-# The Ruby Toolbox.
+# def add_feature(feature)
+# @features << feature
+# end
+#
+# # Support templating of member data.
+# def get_binding
+# binding
+# end
+#
+# end
+# ```
+#
+# The template below will need these values:
+#
+# ```
+# toy = Product.new('TZ-1002',
+# 'Rubysapien',
+# "Geek's Best Friend! Responds to Ruby commands...",
+# 999.95
+# )
+# toy.add_feature('Listens for verbal commands in the Ruby language!')
+# toy.add_feature('Ignores Perl, Java, and all C variants.')
+# toy.add_feature('Karate-Chop Action!!!')
+# toy.add_feature('Matz signature on left leg.')
+# toy.add_feature('Gem studded eyes... Rubies, of course!')
+# ```
+#
+# Here's the HTML:
+#
+# ```
+# template = <<TEMPLATE
+# <html>
+# <head><title>Ruby Toys -- <%= @name %></title></head>
+# <body>
+# <h1><%= @name %> (<%= @code %>)</h1>
+# <p><%= @desc %></p>
+# <ul>
+# <% @features.each do |f| %>
+# <li><b><%= f %></b></li>
+# <% end %>
+# </ul>
+# <p>
+# <% if @cost < 10 %>
+# <b>Only <%= @cost %>!!!</b>
+# <% else %>
+# Call for a price, today!
+# <% end %>
+# </p>
+# </body>
+# </html>
+# TEMPLATE
+# ```
+#
+# Finally, create the \ERB object and get the result (omitting some blank lines):
+#
+# ```
+# erb = ERB.new(template)
+# puts erb.result(toy.get_binding)
+# <html>
+# <head><title>Ruby Toys -- Rubysapien</title></head>
+# <body>
+# <h1>Rubysapien (TZ-1002)</h1>
+# <p>Geek's Best Friend! Responds to Ruby commands...</p>
+# <ul>
+# <li><b>Listens for verbal commands in the Ruby language!</b></li>
+# <li><b>Ignores Perl, Java, and all C variants.</b></li>
+# <li><b>Karate-Chop Action!!!</b></li>
+# <li><b>Matz signature on left leg.</b></li>
+# <li><b>Gem studded eyes... Rubies, of course!</b></li>
+# </ul>
+# <p>
+# Call for a price, today!
+# </p>
+# </body>
+# </html>
+# ```
+#
+#
+# ## Other Template Processors
+#
+# Various Ruby projects have their own template processors.
+# The Ruby Processing System [RDoc][rdoc], for example, has one that can be used elsewhere.
+#
+# Other popular template processors may found in the [Template Engines][template engines] page
+# of the Ruby Toolbox.
+#
+# [%q literals]: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-25q-3A+Non-Interpolable+String+Literals
+# [augmented binding]: rdoc-ref:ERB@Augmented+Binding
+# [binding object]: https://docs.ruby-lang.org/en/master/Binding.html
+# [comment tags]: rdoc-ref:ERB@Comment+Tags
+# [default binding]: rdoc-ref:ERB@Default+Binding
+# [encoding]: https://docs.ruby-lang.org/en/master/Encoding.html
+# [execution tags]: rdoc-ref:ERB@Execution+Tags
+# [expression tags]: rdoc-ref:ERB@Expression+Tags
+# [kernel#binding]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-binding
+# [local binding]: rdoc-ref:ERB@Local+Binding
+# [magic comments]: https://docs.ruby-lang.org/en/master/syntax/comments_rdoc.html#label-Magic+Comments
+# [rdoc]: https://ruby.github.io/rdoc
+# [sprintf]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sprintf
+# [template engines]: https://www.ruby-toolbox.com/categories/template_engines
+# [template processor]: https://en.wikipedia.org/wiki/Template_processor
#
class ERB
- Revision = '$Date:: $' # :nodoc: #'
- deprecate_constant :Revision
-
- # Returns revision information for the erb.rb module.
+ # :markup: markdown
+ #
+ # :call-seq:
+ # self.version -> string
+ #
+ # Returns the string \ERB version.
def self.version
VERSION
end
+ # :markup: markdown
#
- # Constructs a new ERB object with the template specified in _str_.
+ # :call-seq:
+ # ERB.new(template, trim_mode: nil, eoutvar: '_erbout')
#
- # An ERB object works by building a chunk of Ruby code that will output
- # the completed template when run.
+ # Returns a new \ERB object containing the given string +template+.
#
- # If _trim_mode_ is passed a String containing one or more of the following
- # modifiers, ERB will adjust its code generation as listed:
+ # For details about `template`, its embedded tags, and generated results, see ERB.
#
- # % enables Ruby code processing for lines beginning with %
- # <> omit newline for lines starting with <% and ending in %>
- # > omit newline for lines ending in %>
- # - omit blank lines ending in -%>
+ # **Keyword Argument `trim_mode`**
#
- # _eoutvar_ can be used to set the name of the variable ERB will build up
- # its output in. This is useful when you need to run multiple ERB
- # templates through the same binding and/or when you want to control where
- # output ends up. Pass the name of the variable to be used inside a String.
+ # You can use keyword argument `trim_mode: '%'`
+ # to enable the [shorthand format][shorthand format] for execution tags.
#
- # === Example
+ # This value allows [blank line control][blank line control]:
#
- # require "erb"
+ # - `'-'`: Omit each blank line ending with `'%>'`.
#
- # # build data class
- # class Listings
- # PRODUCT = { :name => "Chicken Fried Steak",
- # :desc => "A well messaged pattie, breaded and fried.",
- # :cost => 9.95 }
+ # Other values allow [newline control][newline control]:
#
- # attr_reader :product, :price
+ # - `'>'`: Omit newline for each line ending with `'%>'`.
+ # - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`.
#
- # def initialize( product = "", price = "" )
- # @product = product
- # @price = price
- # end
+ # You can also [combine trim modes][combine trim modes].
#
- # def build
- # b = binding
- # # create and run templates, filling member data variables
- # ERB.new(<<~'END_PRODUCT', trim_mode: "", eoutvar: "@product").result b
- # <%= PRODUCT[:name] %>
- # <%= PRODUCT[:desc] %>
- # END_PRODUCT
- # ERB.new(<<~'END_PRICE', trim_mode: "", eoutvar: "@price").result b
- # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %>
- # <%= PRODUCT[:desc] %>
- # END_PRICE
- # end
- # end
+ # **Keyword Argument `eoutvar`**
#
- # # setup template data
- # listings = Listings.new
- # listings.build
+ # The string value of keyword argument `eoutvar` specifies the name of the variable
+ # that method #result uses to construct its result string;
+ # see #src.
#
- # puts listings.product + "\n" + listings.price
+ # This is useful when you need to run multiple \ERB templates through the same binding
+ # and/or when you want to control where output ends up.
#
- # _Generates_
+ # It's good practice to choose a variable name that begins with an underscore: `'_'`.
#
- # Chicken Fried Steak
- # A well massaged pattie, breaded and fried.
+ # [blank line control]: rdoc-ref:ERB@Suppressing+Unwanted+Blank+Lines
+ # [combine trim modes]: rdoc-ref:ERB@Combining+Trim+Modes
+ # [newline control]: rdoc-ref:ERB@Suppressing+Unwanted+Newlines
+ # [shorthand format]: rdoc-ref:ERB@Shorthand+Format+for+Execution+Tags
#
- # Chicken Fried Steak -- 9.95
- # A well massaged pattie, breaded and fried.
- #
- def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout')
- # Complex initializer for $SAFE deprecation at [Feature #14256]. Use keyword arguments to pass trim_mode or eoutvar.
- if safe_level != NOT_GIVEN
- warn 'Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.', uplevel: 1
- end
- if legacy_trim_mode != NOT_GIVEN
- warn 'Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.', uplevel: 1
- trim_mode = legacy_trim_mode
- end
- if legacy_eoutvar != NOT_GIVEN
- warn 'Passing eoutvar with the 4th argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) instead.', uplevel: 1
- eoutvar = legacy_eoutvar
- end
-
+ def initialize(str, trim_mode: nil, eoutvar: '_erbout')
compiler = make_compiler(trim_mode)
set_eoutvar(compiler, eoutvar)
@src, @encoding, @frozen_string = *compiler.compile(str)
@@ -353,54 +837,137 @@ class ERB
@lineno = 0
@_init = self.class.singleton_class
end
- NOT_GIVEN = defined?(Ractor) ? Ractor.make_shareable(Object.new) : Object.new
- private_constant :NOT_GIVEN
-
- ##
- # Creates a new compiler for ERB. See ERB::Compiler.new for details
+ # :markup: markdown
+ #
+ # :call-seq:
+ # make_compiler -> erb_compiler
+ #
+ # Returns a new ERB::Compiler with the given `trim_mode`;
+ # for `trim_mode` values, see ERB.new:
+ #
+ # ```
+ # ERB.new('').make_compiler(nil)
+ # # => #<ERB::Compiler:0x000001cff9467678 @insert_cmd="print", @percent=false, @post_cmd=[], @pre_cmd=[], @put_cmd="print", @trim_mode=nil>
+ # ```
+ #
def make_compiler(trim_mode)
ERB::Compiler.new(trim_mode)
end
- # The Ruby code generated by ERB
+ # :markup: markdown
+ #
+ # Returns the Ruby code that, when executed, generates the result;
+ # the code is executed by method #result,
+ # and by its wrapper methods #result_with_hash and #run:
+ #
+ # ```
+ # template = 'The time is <%= Time.now %>.'
+ # erb = ERB.new(template)
+ # erb.src
+ # # => "#coding:UTF-8\n_erbout = +''; _erbout.<< \"The time is \".freeze; _erbout.<<(( Time.now ).to_s); _erbout.<< \".\".freeze; _erbout"
+ # erb.result
+ # # => "The time is 2025-09-18 15:58:08 -0500."
+ # ```
+ #
+ # In a more readable format:
+ #
+ # ```
+ # # puts erb.src.split('; ')
+ # # #coding:UTF-8
+ # # _erbout = +''
+ # # _erbout.<< "The time is ".freeze
+ # # _erbout.<<(( Time.now ).to_s)
+ # # _erbout.<< ".".freeze
+ # # _erbout
+ # ```
+ #
+ # Variable `_erbout` is used to store the intermediate results in the code;
+ # the name `_erbout` is the default in ERB.new,
+ # and can be changed via keyword argument `eoutvar`:
+ #
+ # ```
+ # erb = ERB.new(template, eoutvar: '_foo')
+ # puts template.src.split('; ')
+ # #coding:UTF-8
+ # _foo = +''
+ # _foo.<< "The time is ".freeze
+ # _foo.<<(( Time.now ).to_s)
+ # _foo.<< ".".freeze
+ # _foo
+ # ```
+ #
attr_reader :src
- # The encoding to eval
+ # :markup: markdown
+ #
+ # Returns the encoding of `self`;
+ # see [Encodings][encodings]:
+ #
+ # [encodings]: rdoc-ref:ERB@Encodings
+ #
attr_reader :encoding
- # The optional _filename_ argument passed to Kernel#eval when the ERB code
- # is run
+ # :markup: markdown
+ #
+ # Sets or returns the file name to be used in reporting errors;
+ # see [Error Reporting][error reporting].
+ #
+ # [error reporting]: rdoc-ref:ERB@Error+Reporting
attr_accessor :filename
- # The optional _lineno_ argument passed to Kernel#eval when the ERB code
- # is run
+ # :markup: markdown
+ #
+ # Sets or returns the line number to be used in reporting errors;
+ # see [Error Reporting][error reporting].
+ #
+ # [error reporting]: rdoc-ref:ERB@Error+Reporting
attr_accessor :lineno
+ # :markup: markdown
#
- # Sets optional filename and line number that will be used in ERB code
- # evaluation and error reporting. See also #filename= and #lineno=
- #
- # erb = ERB.new('<%= some_x %>')
- # erb.render
- # # undefined local variable or method `some_x'
- # # from (erb):1
+ # :call-seq:
+ # location = [filename, lineno] => [filename, lineno]
+ # location = filename -> filename
#
- # erb.location = ['file.erb', 3]
- # # All subsequent error reporting would use new location
- # erb.render
- # # undefined local variable or method `some_x'
- # # from file.erb:4
+ # Sets the values of #filename and, if given, #lineno;
+ # see [Error Reporting][error reporting].
#
+ # [error reporting]: rdoc-ref:ERB@Error+Reporting
def location=((filename, lineno))
@filename = filename
@lineno = lineno if lineno
end
+ # :markup: markdown
#
- # Can be used to set _eoutvar_ as described in ERB::new. It's probably
- # easier to just use the constructor though, since calling this method
- # requires the setup of an ERB _compiler_ object.
+ # :call-seq:
+ # set_eoutvar(compiler, eoutvar = '_erbout') -> [eoutvar]
+ #
+ # Sets the `eoutvar` value in the ERB::Compiler object `compiler`;
+ # returns a 1-element array containing the value of `eoutvar`:
+ #
+ # ```
+ # template = ERB.new('')
+ # compiler = template.make_compiler(nil)
+ # pp compiler
+ # #<ERB::Compiler:0x000001cff8a9aa00
+ # @insert_cmd="print",
+ # @percent=false,
+ # @post_cmd=[],
+ # @pre_cmd=[],
+ # @put_cmd="print",
+ # @trim_mode=nil>
+ # template.set_eoutvar(compiler, '_foo') # => ["_foo"]
+ # pp compiler
+ # #<ERB::Compiler:0x000001cff8a9aa00
+ # @insert_cmd="_foo.<<",
+ # @percent=false,
+ # @post_cmd=["_foo"],
+ # @pre_cmd=["_foo = +''"],
+ # @put_cmd="_foo.<<",
+ # @trim_mode=nil>
+ # ```
#
def set_eoutvar(compiler, eoutvar = '_erbout')
compiler.put_cmd = "#{eoutvar}.<<"
@@ -409,17 +976,34 @@ class ERB
compiler.post_cmd = [eoutvar]
end
- # Generate results and print them. (see ERB#result)
+ # :markup: markdown
+ #
+ # :call-seq:
+ # run(binding = new_toplevel) -> nil
+ #
+ # Like #result, but prints the result string (instead of returning it);
+ # returns `nil`.
def run(b=new_toplevel)
print self.result(b)
end
+ # :markup: markdown
+ #
+ # :call-seq:
+ # result(binding = new_toplevel) -> new_string
+ #
+ # Returns the string result formed by processing \ERB tags found in the stored template in `self`.
+ #
+ # With no argument given, uses the default binding;
+ # see [Default Binding][default binding].
+ #
+ # With argument `binding` given, uses the local binding;
+ # see [Local Binding][local binding].
#
- # Executes the generated ERB code to produce a completed template, returning
- # the results of that code.
+ # See also #result_with_hash.
#
- # _b_ accepts a Binding object which is used to set the context of
- # code evaluation.
+ # [default binding]: rdoc-ref:ERB@Default+Binding
+ # [local binding]: rdoc-ref:ERB@Local+Binding
#
def result(b=new_toplevel)
unless @_init.equal?(self.class.singleton_class)
@@ -428,8 +1012,18 @@ class ERB
eval(@src, b, (@filename || '(erb)'), @lineno)
end
- # Render a template on a new toplevel binding with local variables specified
- # by a Hash object.
+ # :markup: markdown
+ #
+ # :call-seq:
+ # result_with_hash(hash) -> new_string
+ #
+ # Returns the string result formed by processing \ERB tags found in the stored string in `self`;
+ # see [Augmented Binding][augmented binding].
+ #
+ # See also #result.
+ #
+ # [augmented binding]: rdoc-ref:ERB@Augmented+Binding
+ #
def result_with_hash(hash)
b = new_toplevel(hash.keys)
hash.each_pair do |key, value|
@@ -438,10 +1032,22 @@ class ERB
result(b)
end
- ##
- # Returns a new binding each time *near* TOPLEVEL_BINDING for runs that do
- # not specify a binding.
-
+ # :markup: markdown
+ #
+ # :call-seq:
+ # new_toplevel(symbols) -> new_binding
+ #
+ # Returns a new binding based on `TOPLEVEL_BINDING`;
+ # used to create a default binding for a call to #result.
+ #
+ # See [Default Binding][default binding].
+ #
+ # Argument `symbols` is an array of symbols;
+ # each symbol `symbol` is defined as a new variable to hide and
+ # prevent it from overwriting a variable of the same name already
+ # defined within the binding.
+ #
+ # [default binding]: rdoc-ref:ERB@Default+Binding
def new_toplevel(vars = nil)
b = TOPLEVEL_BINDING
if vars
@@ -454,13 +1060,31 @@ class ERB
end
private :new_toplevel
- # Define _methodname_ as instance method of _mod_ from compiled Ruby source.
+ # :markup: markdown
+ #
+ # :call-seq:
+ # def_method(module, method_signature, filename = '(ERB)') -> method_name
+ #
+ # Creates and returns a new instance method in the given module `module`;
+ # returns the method name as a symbol.
+ #
+ # The method is created from the given `method_signature`,
+ # which consists of the method name and its argument names (if any).
+ #
+ # The `filename` sets the value of #filename;
+ # see [Error Reporting][error reporting].
+ #
+ # [error reporting]: rdoc-ref:ERB@Error+Reporting
+ #
+ # ```
+ # template = '<%= arg1 %> <%= arg2 %>'
+ # erb = ERB.new(template)
+ # MyModule = Module.new
+ # erb.def_method(MyModule, 'render(arg1, arg2)') # => :render
+ # class MyClass; include MyModule; end
+ # MyClass.new.render('foo', 123) # => "foo 123"
+ # ```
#
- # example:
- # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml
- # erb = ERB.new(File.read(filename))
- # erb.def_method(MyClass, 'render(arg1, arg2)', filename)
- # print MyClass.new.render('foo', 123)
def def_method(mod, methodname, fname='(ERB)')
src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
mod.module_eval do
@@ -468,35 +1092,81 @@ class ERB
end
end
- # Create unnamed module, define _methodname_ as instance method of it, and return it.
- #
- # example:
- # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml
- # erb = ERB.new(File.read(filename))
- # erb.filename = filename
- # MyModule = erb.def_module('render(arg1, arg2)')
- # class MyClass
- # include MyModule
- # end
+ # :markup: markdown
+ #
+ # :call-seq:
+ # def_module(method_name = 'erb') -> new_module
+ #
+ # Returns a new nameless module that has instance method `method_name`.
+ #
+ # ```
+ # template = '<%= arg1 %> <%= arg2 %>'
+ # erb = ERB.new(template)
+ # MyModule = template.def_module('render(arg1, arg2)')
+ # class MyClass
+ # include MyModule
+ # end
+ # MyClass.new.render('foo', 123)
+ # # => "foo 123"
+ # ```
+ #
def def_module(methodname='erb')
mod = Module.new
def_method(mod, methodname, @filename || '(ERB)')
mod
end
- # Define unnamed class which has _methodname_ as instance method, and return it.
+ # :markup: markdown
#
- # example:
- # class MyClass_
- # def initialize(arg1, arg2)
- # @arg1 = arg1; @arg2 = arg2
- # end
+ # :call-seq:
+ # def_class(super_class = Object, method_name = 'result') -> new_class
+ #
+ # Returns a new nameless class whose superclass is `super_class`,
+ # and which has instance method `method_name`.
+ #
+ # Create a template from HTML that has embedded expression tags that use `@arg1` and `@arg2`:
+ #
+ # ```
+ # html = <<TEMPLATE
+ # <html>
+ # <body>
+ # <p><%= @arg1 %></p>
+ # <p><%= @arg2 %></p>
+ # </body>
+ # </html>
+ # TEMPLATE
+ # template = ERB.new(html)
+ # ```
+ #
+ # Create a base class that has `@arg1` and `@arg2`:
+ #
+ # ```
+ # class MyBaseClass
+ # def initialize(arg1, arg2)
+ # @arg1 = arg1
+ # @arg2 = arg2
# end
- # filename = 'example.rhtml' # @arg1 and @arg2 are used in example.rhtml
- # erb = ERB.new(File.read(filename))
- # erb.filename = filename
- # MyClass = erb.def_class(MyClass_, 'render()')
- # print MyClass.new('foo', 123).render()
+ # end
+ # ```
+ #
+ # Use method #def_class to create a subclass that has method `:render`:
+ #
+ # ```
+ # MySubClass = template.def_class(MyBaseClass, :render)
+ # ```
+ #
+ # Generate the result:
+ #
+ # ```
+ # puts MySubClass.new('foo', 123).render
+ # <html>
+ # <body>
+ # <p>foo</p>
+ # <p>123</p>
+ # </body>
+ # </html>
+ # ```
+ #
def def_class(superklass=Object, methodname='result')
cls = Class.new(superklass)
def_method(cls, methodname, @filename || '(ERB)')
diff --git a/lib/erb/compiler.rb b/lib/erb/compiler.rb
index 08b5eb4ee1..6d70288b4f 100644
--- a/lib/erb/compiler.rb
+++ b/lib/erb/compiler.rb
@@ -225,7 +225,7 @@ class ERB::Compiler # :nodoc:
end
end
- ERB_STAG = %w(<%= <%# <%)
+ ERB_STAG = %w(<%= <%# <%).freeze
def is_erb_stag?(s)
ERB_STAG.member?(s)
end
@@ -480,7 +480,6 @@ class ERB::Compiler # :nodoc:
end
}.new(caller(0)).c
private_constant :WARNING_UPLEVEL
- # :startdoc:
def warn_invalid_trim_mode(mode, uplevel:)
warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + WARNING_UPLEVEL
diff --git a/lib/erb/def_method.rb b/lib/erb/def_method.rb
index aee989a926..e503b37140 100644
--- a/lib/erb/def_method.rb
+++ b/lib/erb/def_method.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-#--
+
# ERB::DefMethod
#
# Utility module to define eRuby script as instance method.
diff --git a/lib/erb.gemspec b/lib/erb/erb.gemspec
index 94a8fd5c3e..3793e5d70f 100644
--- a/lib/erb.gemspec
+++ b/lib/erb/erb.gemspec
@@ -2,23 +2,23 @@ begin
require_relative 'lib/erb/version'
rescue LoadError
# for Ruby core repository
- require_relative 'erb/version'
+ require_relative 'version'
end
Gem::Specification.new do |spec|
spec.name = 'erb'
- spec.version = ERB.const_get(:VERSION, false)
+ spec.version = ERB::VERSION
spec.authors = ['Masatoshi SEKI', 'Takashi Kokubun']
spec.email = ['seki@ruby-lang.org', 'k0kubun@ruby-lang.org']
spec.summary = %q{An easy to use but powerful templating system for Ruby.}
spec.description = %q{An easy to use but powerful templating system for Ruby.}
spec.homepage = 'https://github.com/ruby/erb'
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
spec.licenses = ['Ruby', 'BSD-2-Clause']
spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = spec.homepage
+ spec.metadata['changelog_uri'] = "https://github.com/ruby/erb/blob/v#{spec.version}/NEWS.md"
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
@@ -27,12 +27,11 @@ Gem::Specification.new do |spec|
spec.executables = ['erb']
spec.require_paths = ['lib']
+ spec.required_ruby_version = '>= 3.2.0'
+
if RUBY_ENGINE == 'jruby'
spec.platform = 'java'
else
- spec.required_ruby_version = '>= 2.7.0'
spec.extensions = ['ext/erb/escape/extconf.rb']
end
-
- spec.add_dependency 'cgi', '>= 0.3.3'
end
diff --git a/lib/erb/util.rb b/lib/erb/util.rb
index 1d2a36275d..d7d69eb4f1 100644
--- a/lib/erb/util.rb
+++ b/lib/erb/util.rb
@@ -1,18 +1,25 @@
# frozen_string_literal: true
-#--
-# ERB::Escape
-#
-# A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope
-# Rails will not monkey-patch ERB::Escape#html_escape.
+
+# Load CGI.escapeHTML and CGI.escapeURIComponent.
+# CRuby:
+# cgi.gem v0.1.0+ (Ruby 2.7-3.4) and Ruby 4.0+ stdlib have 'cgi/escape' and CGI.escapeHTML.
+# cgi.gem v0.3.3+ (Ruby 3.2-3.4) and Ruby 4.0+ stdlib have CGI.escapeURIComponent.
+# JRuby: cgi.gem has a Java extension 'cgi/escape'.
+# TruffleRuby: lib/truffle/cgi/escape.rb requires 'cgi/util'.
+require 'cgi/escape'
+
+# Load or define ERB::Escape#html_escape.
+# We don't build the C extension 'cgi/escape' for JRuby, TruffleRuby, and WASM.
+# miniruby (used by CRuby build scripts) also fails to load erb/escape.so.
begin
- # We don't build the C extension for JRuby, TruffleRuby, and WASM
- if $LOAD_PATH.resolve_feature_path('erb/escape')
- require 'erb/escape'
- end
-rescue LoadError # resolve_feature_path raises LoadError on TruffleRuby 22.3.0
-end
-unless defined?(ERB::Escape)
+ require 'erb/escape'
+rescue LoadError
+ # ERB::Escape
+ #
+ # A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope
+ # Rails will not monkey-patch ERB::Escape#html_escape.
module ERB::Escape
+ # :stopdoc:
def html_escape(s)
CGI.escapeHTML(s.to_s)
end
@@ -20,7 +27,6 @@ unless defined?(ERB::Escape)
end
end
-#--
# ERB::Util
#
# A utility module for conversion routines, often handy in HTML generation.
@@ -42,20 +48,28 @@ module ERB::Util
alias h html_escape
module_function :h
- #
- # A utility method for encoding the String _s_ as a URL.
- #
- # require "erb"
- # include ERB::Util
- #
- # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide")
- #
- # _Generates_
- #
- # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide
- #
- def url_encode(s)
- CGI.escapeURIComponent(s.to_s)
+ if CGI.respond_to?(:escapeURIComponent)
+ #
+ # A utility method for encoding the String _s_ as a URL.
+ #
+ # require "erb"
+ # include ERB::Util
+ #
+ # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide")
+ #
+ # _Generates_
+ #
+ # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide
+ #
+ def url_encode(s)
+ CGI.escapeURIComponent(s.to_s)
+ end
+ else # cgi.gem <= v0.3.2
+ def url_encode(s)
+ s.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) do |m|
+ sprintf("%%%02X", m.unpack1("C"))
+ end
+ end
end
alias u url_encode
module_function :u
diff --git a/lib/erb/version.rb b/lib/erb/version.rb
index b5fe39b330..e367a1b5c7 100644
--- a/lib/erb/version.rb
+++ b/lib/erb/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class ERB
- VERSION = '4.0.4'
- private_constant :VERSION
+ # The string \ERB version.
+ VERSION = '6.0.1'
end
diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb
index 14e0ce5785..bc4a62c9d6 100644
--- a/lib/error_highlight/base.rb
+++ b/lib/error_highlight/base.rb
@@ -1,13 +1,13 @@
require_relative "version"
module ErrorHighlight
- # Identify the code fragment at that a given exception occurred.
+ # Identify the code fragment where a given exception occurred.
#
# Options:
#
# point_type: :name | :args
- # :name (default) points the method/variable name that the exception occurred.
- # :args points the arguments of the method call that the exception occurred.
+ # :name (default) points to the method/variable name where the exception occurred.
+ # :args points to the arguments of the method call where the exception occurred.
#
# backtrace_location: Thread::Backtrace::Location
# It locates the code fragment of the given backtrace_location.
@@ -113,7 +113,7 @@ module ErrorHighlight
snippet = @node.script_lines[lineno - 1 .. last_lineno - 1].join("")
snippet += "\n" unless snippet.end_with?("\n")
- # It require some work to support Unicode (or multibyte) characters.
+ # It requires some work to support Unicode (or multibyte) characters.
# Tentatively, we stop highlighting if the code snippet has non-ascii characters.
# See https://github.com/ruby/error_highlight/issues/4
raise NonAscii unless snippet.ascii_only?
@@ -122,56 +122,51 @@ module ErrorHighlight
end
end
- OPT_GETCONSTANT_PATH = (RUBY_VERSION.split(".").map {|s| s.to_i } <=> [3, 2]) >= 0
- private_constant :OPT_GETCONSTANT_PATH
-
def spot
return nil unless @node
- if OPT_GETCONSTANT_PATH
- # In Ruby 3.2 or later, a nested constant access (like `Foo::Bar::Baz`)
- # is compiled to one instruction (opt_getconstant_path).
- # @node points to the node of the whole `Foo::Bar::Baz` even if `Foo`
- # or `Foo::Bar` causes NameError.
- # So we try to spot the sub-node that causes the NameError by using
- # `NameError#name`.
- case @node.type
- when :COLON2
- subnodes = []
- node = @node
- while node.type == :COLON2
- node2, const = node.children
- subnodes << node if const == @name
- node = node2
- end
- if node.type == :CONST || node.type == :COLON3
- if node.children.first == @name
- subnodes << node
- end
-
- # If we found only one sub-node whose name is equal to @name, use it
- return nil if subnodes.size != 1
- @node = subnodes.first
- else
- # Do nothing; opt_getconstant_path is used only when the const base is
- # NODE_CONST (`Foo`) or NODE_COLON3 (`::Foo`)
- end
- when :constant_path_node
- subnodes = []
- node = @node
-
- begin
- subnodes << node if node.name == @name
- end while (node = node.parent).is_a?(Prism::ConstantPathNode)
-
- if node.is_a?(Prism::ConstantReadNode) && node.name == @name
+ # In Ruby 3.2 or later, a nested constant access (like `Foo::Bar::Baz`)
+ # is compiled to one instruction (opt_getconstant_path).
+ # @node points to the node of the whole `Foo::Bar::Baz` even if `Foo`
+ # or `Foo::Bar` causes NameError.
+ # So we try to spot the sub-node that causes the NameError by using
+ # `NameError#name`.
+ case @node.type
+ when :COLON2
+ subnodes = []
+ node = @node
+ while node.type == :COLON2
+ node2, const = node.children
+ subnodes << node if const == @name
+ node = node2
+ end
+ if node.type == :CONST || node.type == :COLON3
+ if node.children.first == @name
subnodes << node
end
# If we found only one sub-node whose name is equal to @name, use it
return nil if subnodes.size != 1
@node = subnodes.first
+ else
+ # Do nothing; opt_getconstant_path is used only when the const base is
+ # NODE_CONST (`Foo`) or NODE_COLON3 (`::Foo`)
end
+ when :constant_path_node
+ subnodes = []
+ node = @node
+
+ begin
+ subnodes << node if node.name == @name
+ end while (node = node.parent).is_a?(Prism::ConstantPathNode)
+
+ if node.is_a?(Prism::ConstantReadNode) && node.name == @name
+ subnodes << node
+ end
+
+ # If we found only one sub-node whose name is equal to @name, use it
+ return nil if subnodes.size != 1
+ @node = subnodes.first
end
case @node.type
@@ -239,6 +234,20 @@ module ErrorHighlight
when :OP_CDECL
spot_op_cdecl
+ when :DEFN
+ raise NotImplementedError if @point_type != :name
+ spot_defn
+
+ when :DEFS
+ raise NotImplementedError if @point_type != :name
+ spot_defs
+
+ when :LAMBDA
+ spot_lambda
+
+ when :ITER
+ spot_iter
+
when :call_node
case @point_type
when :name
@@ -280,6 +289,30 @@ module ErrorHighlight
when :constant_path_operator_write_node
prism_spot_constant_path_operator_write
+ when :def_node
+ case @point_type
+ when :name
+ prism_spot_def_for_name
+ when :args
+ raise NotImplementedError
+ end
+
+ when :lambda_node
+ case @point_type
+ when :name
+ prism_spot_lambda_for_name
+ when :args
+ raise NotImplementedError
+ end
+
+ when :block_node
+ case @point_type
+ when :name
+ prism_spot_block_for_name
+ when :args
+ raise NotImplementedError
+ end
+
end
if @snippet && @beg_column && @end_column && @beg_column < @end_column
@@ -471,7 +504,6 @@ module ErrorHighlight
def spot_fcall_for_args
_mid, nd_args = @node.children
if nd_args && nd_args.first_lineno == nd_args.last_lineno
- # binary operator
fetch_line(nd_args.first_lineno)
@beg_column = nd_args.first_column
@end_column = nd_args.last_column
@@ -621,6 +653,55 @@ module ErrorHighlight
end
end
+ # Example:
+ # def bar; end
+ # ^^^
+ def spot_defn
+ mid, = @node.children
+ fetch_line(@node.first_lineno)
+ if @snippet.match(/\Gdef\s+(#{ Regexp.quote(mid) }\b)/, @node.first_column)
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ end
+
+ # Example:
+ # def Foo.bar; end
+ # ^^^^
+ def spot_defs
+ nd_recv, mid, = @node.children
+ fetch_line(nd_recv.last_lineno)
+ if @snippet.match(/\G\s*(\.\s*#{ Regexp.quote(mid) }\b)/, nd_recv.last_column)
+ @beg_column = $~.begin(1)
+ @end_column = $~.end(1)
+ end
+ end
+
+ # Example:
+ # -> { ... }
+ # ^^
+ def spot_lambda
+ fetch_line(@node.first_lineno)
+ if @snippet.match(/\G->/, @node.first_column)
+ @beg_column = $~.begin(0)
+ @end_column = $~.end(0)
+ end
+ end
+
+ # Example:
+ # lambda { ... }
+ # ^
+ # define_method :foo do
+ # ^^
+ def spot_iter
+ _nd_fcall, nd_scope = @node.children
+ fetch_line(nd_scope.first_lineno)
+ if @snippet.match(/\G(?:do\b|\{)/, nd_scope.first_column)
+ @beg_column = $~.begin(0)
+ @end_column = $~.end(0)
+ end
+ end
+
def fetch_line(lineno)
@beg_lineno = @end_lineno = lineno
@snippet = @fetch[lineno]
@@ -826,6 +907,31 @@ module ErrorHighlight
prism_location(@node.binary_operator_loc.chop)
end
end
+
+ # Example:
+ # def foo()
+ # ^^^
+ def prism_spot_def_for_name
+ location = @node.name_loc
+ location = @node.operator_loc.join(location) if @node.operator_loc
+ prism_location(location)
+ end
+
+ # Example:
+ # -> x, y { }
+ # ^^
+ def prism_spot_lambda_for_name
+ prism_location(@node.operator_loc)
+ end
+
+ # Example:
+ # lambda { }
+ # ^
+ # define_method :foo do |x, y|
+ # ^
+ def prism_spot_block_for_name
+ prism_location(@node.opening_loc)
+ end
end
private_constant :Spotter
diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb
index b69093f74e..c3354f46cd 100644
--- a/lib/error_highlight/core_ext.rb
+++ b/lib/error_highlight/core_ext.rb
@@ -3,9 +3,38 @@ require_relative "formatter"
module ErrorHighlight
module CoreExt
private def generate_snippet
- spot = ErrorHighlight.spot(self)
- return "" unless spot
- return ErrorHighlight.formatter.message_for(spot)
+ if ArgumentError === self && message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/
+ locs = self.backtrace_locations
+ return "" if locs.size < 2
+ callee_loc, caller_loc = locs
+ callee_spot = ErrorHighlight.spot(self, backtrace_location: callee_loc, point_type: :name)
+ caller_spot = ErrorHighlight.spot(self, backtrace_location: caller_loc, point_type: :name)
+ if caller_spot && callee_spot &&
+ caller_loc.path == callee_loc.path &&
+ caller_loc.lineno == callee_loc.lineno &&
+ caller_spot == callee_spot
+ callee_loc = callee_spot = nil
+ end
+ ret = +"\n"
+ [["caller", caller_loc, caller_spot], ["callee", callee_loc, callee_spot]].each do |header, loc, spot|
+ out = nil
+ if loc
+ out = " #{ header }: #{ loc.path }:#{ loc.lineno }"
+ if spot
+ _, _, snippet, highlight = ErrorHighlight.formatter.message_for(spot).lines
+ out += "\n | #{ snippet } #{ highlight }"
+ else
+ # do nothing
+ end
+ end
+ ret << "\n" + out if out
+ end
+ ret
+ else
+ spot = ErrorHighlight.spot(self)
+ return "" unless spot
+ return ErrorHighlight.formatter.message_for(spot)
+ end
end
if Exception.method_defined?(:detailed_message)
diff --git a/lib/error_highlight/error_highlight.gemspec b/lib/error_highlight/error_highlight.gemspec
index b2da18df83..edfc4b776f 100644
--- a/lib/error_highlight/error_highlight.gemspec
+++ b/lib/error_highlight/error_highlight.gemspec
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/ruby/error_highlight"
spec.license = "MIT"
- spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0.dev")
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
diff --git a/lib/error_highlight/formatter.rb b/lib/error_highlight/formatter.rb
index 5180576405..d2fad9e75c 100644
--- a/lib/error_highlight/formatter.rb
+++ b/lib/error_highlight/formatter.rb
@@ -56,11 +56,11 @@ module ErrorHighlight
end
def self.terminal_width
- # lazy load io/console, so it's not loaded when 'max_snippet_width' is set
+ # lazy load io/console to avoid loading it when 'max_snippet_width' is manually set
require "io/console"
$stderr.winsize[1] if $stderr.tty?
rescue LoadError, NoMethodError, SystemCallError
- # do not truncate when window size is not available
+ # skip truncation when terminal window size is unavailable
end
end
diff --git a/lib/error_highlight/version.rb b/lib/error_highlight/version.rb
index d7a29c7c1e..f0a5376b14 100644
--- a/lib/error_highlight/version.rb
+++ b/lib/error_highlight/version.rb
@@ -1,3 +1,3 @@
module ErrorHighlight
- VERSION = "0.7.0"
+ VERSION = "0.7.1"
end
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index b9d683797a..0706e007ca 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -181,7 +181,7 @@ end
#
module FileUtils
# The version number.
- VERSION = "1.7.3"
+ VERSION = "1.8.0"
def self.private_module_function(name) #:nodoc:
module_function name
@@ -706,11 +706,12 @@ module FileUtils
#
def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil)
if relative
- return ln_sr(src, dest, force: force, noop: noop, verbose: verbose)
+ return ln_sr(src, dest, force: force, target_directory: target_directory, noop: noop, verbose: verbose)
end
- fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ fu_output_message "ln -s#{force ? 'f' : ''}#{
+ target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose
return if noop
- fu_each_src_dest0(src, dest) do |s,d|
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
remove_file d, true if force
File.symlink s, d
end
@@ -730,42 +731,37 @@ module FileUtils
# Like FileUtils.ln_s, but create links relative to +dest+.
#
def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)
- options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}"
- dest = File.path(dest)
- srcs = Array(src)
- link = proc do |s, target_dir_p = true|
- s = File.path(s)
- if target_dir_p
- d = File.join(destdirs = dest, File.basename(s))
+ cmd = "ln -s#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" if verbose
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
+ if target_directory
+ parent = File.dirname(d)
+ destdirs = fu_split_path(parent)
+ real_ddirs = fu_split_path(File.realpath(parent))
else
- destdirs = File.dirname(d = dest)
+ destdirs ||= fu_split_path(dest)
+ real_ddirs ||= fu_split_path(File.realdirpath(dest))
end
- destdirs = fu_split_path(File.realpath(destdirs))
- if fu_starting_path?(s)
- srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s)))
- base = fu_relative_components_from(srcdirs, destdirs)
- s = File.join(*base)
+ srcdirs = fu_split_path(s)
+ i = fu_common_components(srcdirs, destdirs)
+ n = destdirs.size - i
+ n -= 1 unless target_directory
+ link1 = fu_clean_components(*Array.new([n, 0].max, '..'), *srcdirs[i..-1])
+ begin
+ real_sdirs = fu_split_path(File.realdirpath(s)) rescue nil
+ rescue
else
- srcdirs = fu_clean_components(*fu_split_path(s))
- base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs)
- while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last)
- srcdirs.shift
- base.pop
- end
- s = File.join(*base, *srcdirs)
+ i = fu_common_components(real_sdirs, real_ddirs)
+ n = real_ddirs.size - i
+ n -= 1 unless target_directory
+ link2 = fu_clean_components(*Array.new([n, 0].max, '..'), *real_sdirs[i..-1])
+ link1 = link2 if link1.size > link2.size
end
- fu_output_message "ln -s#{options} #{s} #{d}" if verbose
+ s = File.join(link1)
+ fu_output_message [cmd, s, d].flatten.join(' ') if verbose
next if noop
remove_file d, true if force
File.symlink s, d
end
- case srcs.size
- when 0
- when 1
- link[srcs[0], target_directory && File.directory?(dest)]
- else
- srcs.each(&link)
- end
end
module_function :ln_sr
@@ -800,13 +796,13 @@ module FileUtils
# File.file?('dest1/dir1/t2.txt') # => true
# File.file?('dest1/dir1/t3.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link.
- # - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
+ # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before creating links (+false+ by default).
#
# Raises an exception if +dest+ is the path to an existing file or directory
- # and keyword argument <tt>remove_destination: true</tt> is not given.
+ # and optional argument +remove_destination+ is not given.
#
# Related: FileUtils.ln (has different options).
#
@@ -1029,12 +1025,12 @@ module FileUtils
# directories, and symbolic links;
# other file types (FIFO streams, device files, etc.) are not supported.
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link,
- # follows the link.
- # - <tt>preserve: true</tt> - preserves file times.
- # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
+ # - +dereference_root+ - if +src+ is a symbolic link,
+ # follows the link (+false+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
@@ -1065,12 +1061,12 @@ module FileUtils
# FileUtils.copy_file('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - <tt>dereference: false</tt> - if +src+ is a symbolic link,
- # does not follow the link.
- # - <tt>preserve: true</tt> - preserves file times.
- # - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
+ # - +dereference+ - if +src+ is a symbolic link,
+ # follows the link (+true+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
@@ -1491,7 +1487,8 @@ module FileUtils
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def remove_dir(path, force = false)
- remove_entry path, force # FIXME?? check if it is a directory
+ raise Errno::ENOTDIR, path unless force or File.directory?(path)
+ remove_entry path, force
end
module_function :remove_dir
@@ -2475,6 +2472,10 @@ module FileUtils
def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
if tmp = Array.try_convert(src)
+ unless target_directory or tmp.size <= 1
+ tmp = tmp.map {|f| File.path(f)} # A workaround for RBS
+ raise ArgumentError, "extra target #{tmp}"
+ end
tmp.each do |s|
s = File.path(s)
yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)
@@ -2509,7 +2510,11 @@ module FileUtils
path = File.path(path)
list = []
until (parent, base = File.split(path); parent == path or parent == ".")
- list << base
+ if base != '..' and list.last == '..' and !(fu_have_symlink? && File.symlink?(path))
+ list.pop
+ else
+ list << base
+ end
path = parent
end
list << path
@@ -2517,14 +2522,14 @@ module FileUtils
end
private_module_function :fu_split_path
- def fu_relative_components_from(target, base) #:nodoc:
+ def fu_common_components(target, base) #:nodoc:
i = 0
while target[i]&.== base[i]
i += 1
end
- Array.new(base.size-i, '..').concat(target[i..-1])
+ i
end
- private_module_function :fu_relative_components_from
+ private_module_function :fu_common_components
def fu_clean_components(*comp) #:nodoc:
comp.shift while comp.first == "."
@@ -2534,7 +2539,7 @@ module FileUtils
while c = comp.shift
if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path))
clean.pop
- path.chomp!(%r((?<=\A|/)[^/]+/\z), "")
+ path.sub!(%r((?<=\A|/)[^/]+/\z), "")
else
clean << c
path << c << "/"
diff --git a/lib/find.rb b/lib/find.rb
index 98a79cc76d..8223eea456 100644
--- a/lib/find.rb
+++ b/lib/find.rb
@@ -27,6 +27,7 @@
#
module Find
+ # The version string
VERSION = "0.2.0"
#
diff --git a/lib/forwardable.rb b/lib/forwardable.rb
index 71b4e6adad..175d6d9c6b 100644
--- a/lib/forwardable.rb
+++ b/lib/forwardable.rb
@@ -109,11 +109,11 @@
# +delegate.rb+.
#
module Forwardable
- require 'forwardable/impl'
-
# Version of +forwardable.rb+
- VERSION = "1.3.3"
+ VERSION = "1.4.0"
VERSION.freeze
+
+ # Version for backward compatibility
FORWARDABLE_VERSION = VERSION
FORWARDABLE_VERSION.freeze
@@ -190,9 +190,7 @@ module Forwardable
# If it's not a class or module, it's an instance
mod = Module === self ? self : singleton_class
- ret = mod.module_eval(&gen)
- mod.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7'
- ret
+ mod.module_eval(&gen)
end
alias delegate instance_delegate
@@ -206,36 +204,33 @@ module Forwardable
if Module === obj ?
obj.method_defined?(accessor) || obj.private_method_defined?(accessor) :
obj.respond_to?(accessor, true)
- accessor = "#{accessor}()"
+ accessor = "(#{accessor}())"
end
- method_call = ".__send__(:#{method}, *args, &block)"
- if _valid_method?(method)
+ args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block'
+ method_call = ".__send__(:#{method}, #{args})"
+ if method.match?(/\A[_a-zA-Z]\w*[?!]?\z/)
loc, = caller_locations(2,1)
pre = "_ ="
mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method "
- method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}"
- begin;
- unless defined? _.#{method}
- ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1
- _#{method_call}
- else
- _.#{method}(*args, &block)
- end
- end;
+ method_call = <<~RUBY.chomp
+ if defined?(_.#{method})
+ _.#{method}(#{args})
+ else
+ ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1
+ _#{method_call}
+ end
+ RUBY
end
- _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1)
- begin;
+ eval(<<~RUBY, nil, __FILE__, __LINE__ + 1)
proc do
- def #{ali}(*args, &block)
- #{pre}
- begin
- #{accessor}
- end#{method_call}
+ def #{ali}(#{args})
+ #{pre}#{accessor}
+ #{method_call}
end
end
- end;
+ RUBY
end
end
@@ -310,9 +305,7 @@ module SingleForwardable
def def_single_delegator(accessor, method, ali = method)
gen = Forwardable._delegator_method(self, accessor, method, ali)
- ret = instance_eval(&gen)
- singleton_class.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7'
- ret
+ instance_eval(&gen)
end
alias delegate single_delegate
diff --git a/lib/forwardable/forwardable.gemspec b/lib/forwardable/forwardable.gemspec
index 9ad59c5f8a..1b539bcfcb 100644
--- a/lib/forwardable/forwardable.gemspec
+++ b/lib/forwardable/forwardable.gemspec
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
spec.licenses = ["Ruby", "BSD-2-Clause"]
spec.required_ruby_version = '>= 2.4.0'
- spec.files = ["forwardable.gemspec", "lib/forwardable.rb", "lib/forwardable/impl.rb"]
+ spec.files = ["forwardable.gemspec", "lib/forwardable.rb"]
spec.bindir = "exe"
spec.executables = []
spec.require_paths = ["lib"]
diff --git a/lib/forwardable/impl.rb b/lib/forwardable/impl.rb
deleted file mode 100644
index 0322c136db..0000000000
--- a/lib/forwardable/impl.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module Forwardable
- # :stopdoc:
-
- def self._valid_method?(method)
- catch {|tag|
- eval("BEGIN{throw tag}; ().#{method}", binding, __FILE__, __LINE__)
- }
- rescue SyntaxError
- false
- else
- true
- end
-
- def self._compile_method(src, file, line)
- eval(src, nil, file, line)
- end
-end
diff --git a/lib/ipaddr.gemspec b/lib/ipaddr.gemspec
index 5719f83fc4..cabc9161ba 100644
--- a/lib/ipaddr.gemspec
+++ b/lib/ipaddr.gemspec
@@ -29,7 +29,7 @@ Both IPv4 and IPv6 are supported.
spec.homepage = "https://github.com/ruby/ipaddr"
spec.licenses = ["Ruby", "BSD-2-Clause"]
- spec.files = ["LICENSE.txt", "README.md", "ipaddr.gemspec", "lib/ipaddr.rb"]
+ spec.files = ["LICENSE.txt", "README.md", "lib/ipaddr.rb"]
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 2.4"
diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb
index a45055496c..6b67d7eec6 100644
--- a/lib/ipaddr.rb
+++ b/lib/ipaddr.rb
@@ -40,7 +40,8 @@ require 'socket'
# p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0>
class IPAddr
- VERSION = "1.2.7"
+ # The version string
+ VERSION = "1.2.8"
# 32 bit mask for IPv4
IN4MASK = 0xffffffff
@@ -151,6 +152,16 @@ class IPAddr
return self.clone.set(addr_mask(~@addr))
end
+ # Returns a new ipaddr greater than the original address by offset
+ def +(offset)
+ self.clone.set(@addr + offset, @family)
+ end
+
+ # Returns a new ipaddr less than the original address by offset
+ def -(offset)
+ self.clone.set(@addr - offset, @family)
+ end
+
# Returns true if two ipaddrs are equal.
def ==(other)
other = coerce_other(other)
@@ -343,7 +354,7 @@ class IPAddr
_ipv4_compat?
end
- def _ipv4_compat?
+ def _ipv4_compat? # :nodoc:
if !ipv6? || (@addr >> 32) != 0
return false
end
@@ -357,7 +368,7 @@ class IPAddr
# into an IPv4-mapped IPv6 address.
def ipv4_mapped
if !ipv4?
- raise InvalidAddressError, "not an IPv4 address: #{@addr}"
+ raise InvalidAddressError, "not an IPv4 address: #{to_s}"
end
clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6)
clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000)
@@ -369,9 +380,11 @@ class IPAddr
def ipv4_compat
warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE
if !ipv4?
- raise InvalidAddressError, "not an IPv4 address: #{@addr}"
+ raise InvalidAddressError, "not an IPv4 address: #{to_s}"
end
- return self.clone.set(@addr, Socket::AF_INET6)
+ clone = self.clone.set(@addr, Socket::AF_INET6)
+ clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000)
+ clone
end
# Returns a new ipaddr built by converting the IPv6 address into a
@@ -400,7 +413,7 @@ class IPAddr
# Returns a string for DNS reverse lookup compatible with RFC3172.
def ip6_arpa
if !ipv6?
- raise InvalidAddressError, "not an IPv6 address: #{@addr}"
+ raise InvalidAddressError, "not an IPv6 address: #{to_s}"
end
return _reverse + ".ip6.arpa"
end
@@ -408,7 +421,7 @@ class IPAddr
# Returns a string for DNS reverse lookup compatible with RFC1886.
def ip6_int
if !ipv6?
- raise InvalidAddressError, "not an IPv6 address: #{@addr}"
+ raise InvalidAddressError, "not an IPv6 address: #{to_s}"
end
return _reverse + ".ip6.int"
end
@@ -533,6 +546,7 @@ class IPAddr
end
protected
+ # :stopdoc:
def begin_addr
@addr & @mask_addr
@@ -548,6 +562,7 @@ class IPAddr
raise AddressFamilyError, "unsupported address family"
end
end
+ #:startdoc:
# Set +@addr+, the internal stored ip address, to given +addr+. The
# parameter +addr+ is validated using the first +family+ member,
@@ -689,6 +704,7 @@ class IPAddr
end
end
+ # :stopdoc:
def coerce_other(other)
case other
when IPAddr
@@ -709,8 +725,8 @@ class IPAddr
octets = addr.split('.')
end
octets.inject(0) { |i, s|
- (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{@addr}"
- (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}"
+ (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{addr}"
+ (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{addr}"
i << 8 | n
}
end
@@ -727,19 +743,19 @@ class IPAddr
right = ''
when RE_IPV6ADDRLIKE_COMPRESSED
if $4
- left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}"
+ left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{left}"
addr = in_addr($~[4,4])
left = $1
right = $3 + '0:0'
else
left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or
- raise InvalidAddressError, "invalid address: #{@addr}"
+ raise InvalidAddressError, "invalid address: #{left}"
left = $1
right = $2
addr = 0
end
else
- raise InvalidAddressError, "invalid address: #{@addr}"
+ raise InvalidAddressError, "invalid address: #{left}"
end
l = left.split(':')
r = right.split(':')
@@ -800,7 +816,7 @@ unless Socket.const_defined? :AF_INET6
class << IPSocket
private
- def valid_v6?(addr)
+ def valid_v6?(addr) # :nodoc:
case addr
when IPAddr::RE_IPV6ADDRLIKE_FULL
if $2
diff --git a/lib/mkmf.rb b/lib/mkmf.rb
index 6f11073eb4..38a5a15fb5 100644
--- a/lib/mkmf.rb
+++ b/lib/mkmf.rb
@@ -40,7 +40,7 @@ class Array # :nodoc:
end
##
-# mkmf.rb is used by Ruby C extensions to generate a Makefile which will
+# \Module \MakeMakefile is used by Ruby C extensions to generate a Makefile which will
# correctly compile and link the C extension to Ruby and a third-party
# library.
module MakeMakefile
@@ -419,7 +419,7 @@ MESSAGE
# disable ASAN leak reporting - conftest programs almost always don't bother
# to free their memory.
- envs['ASAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('ASAN_OPTIONS')
+ envs['LSAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('LSAN_OPTIONS')
return envs, expand[commands]
end
@@ -860,7 +860,7 @@ int main() {printf("%"PRI_CONFTEST_PREFIX"#{neg ? 'd' : 'u'}\\n", conftest_const
v
}
unless strvars.empty?
- prepare << "char " << strvars.map {|v| "#{v}[1024]"}.join(", ") << "; "
+ prepare << "char " << strvars.map {|v| %[#{v}[1024] = ""]}.join(", ") << "; "
end
when nil
call = ""
@@ -2594,7 +2594,7 @@ static: #{$extmk && !$static ? "all" : %[$(STATIC_LIB)#{$extout ? " install-rb"
dest = "#{dir}/#{File.basename(f)}"
mfile.print("do-install-rb#{sfx}: #{dest}\n")
mfile.print("#{dest}: #{f} #{timestamp_file(dir, target_prefix)}\n")
- mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $(@D)\n")
+ mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $@\n")
if defined?($installed_list) and !$extout
mfile.print("\t@echo #{dest}>>$(INSTALLED_LIST)\n")
end
diff --git a/lib/net/http.rb b/lib/net/http.rb
index 797de6aaf5..98d6793aee 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -724,7 +724,7 @@ module Net #:nodoc:
class HTTP < Protocol
# :stopdoc:
- VERSION = "0.6.0"
+ VERSION = "0.9.1"
HTTPVersion = '1.1'
begin
require 'zlib'
@@ -1179,6 +1179,7 @@ module Net #:nodoc:
@debug_output = options[:debug_output]
@response_body_encoding = options[:response_body_encoding]
@ignore_eof = options[:ignore_eof]
+ @tcpsocket_supports_open_timeout = nil
@proxy_from_env = false
@proxy_uri = nil
@@ -1321,6 +1322,9 @@ module Net #:nodoc:
# Sets the proxy password;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
attr_writer :proxy_pass
+
+ # Sets whether the proxy uses SSL;
+ # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
attr_writer :proxy_use_ssl
# Returns the IP address for the connection.
@@ -1527,9 +1531,9 @@ module Net #:nodoc:
:verify_depth,
:verify_mode,
:verify_hostname,
- ] # :nodoc:
+ ].freeze # :nodoc:
- SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym } # :nodoc:
+ SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc:
# Sets or returns the path to a CA certification file in PEM format.
attr_accessor :ca_file
@@ -1632,6 +1636,21 @@ module Net #:nodoc:
self
end
+ # Finishes the \HTTP session:
+ #
+ # http = Net::HTTP.new(hostname)
+ # http.start
+ # http.started? # => true
+ # http.finish # => nil
+ # http.started? # => false
+ #
+ # Raises IOError if not in a session.
+ def finish
+ raise IOError, 'HTTP session not yet started' unless started?
+ do_finish
+ end
+
+ # :stopdoc:
def do_start
connect
@started = true
@@ -1654,14 +1673,15 @@ module Net #:nodoc:
end
debug "opening connection to #{conn_addr}:#{conn_port}..."
- s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
- begin
- TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
- rescue => e
- raise e, "Failed to open TCP connection to " +
- "#{conn_addr}:#{conn_port} (#{e.message})"
+ begin
+ s = timeouted_connect(conn_addr, conn_port)
+ rescue => e
+ if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions
+ e = Net::OpenTimeout.new(e)
end
- }
+ raise e, "Failed to open TCP connection to " +
+ "#{conn_addr}:#{conn_port} (#{e.message})"
+ end
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
debug "opened"
if use_ssl?
@@ -1754,23 +1774,30 @@ module Net #:nodoc:
end
private :connect
- def on_connect
+ tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters
+ TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]]
+ tcp_socket_parameters.include?([:key, :open_timeout])
+ else
+ # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize
+ # See discussion in https://github.com/ruby/net-http/pull/224
+ Socket.method(:tcp).parameters.include?([:key, :open_timeout])
end
- private :on_connect
+ private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT
- # Finishes the \HTTP session:
- #
- # http = Net::HTTP.new(hostname)
- # http.start
- # http.started? # => true
- # http.finish # => nil
- # http.started? # => false
- #
- # Raises IOError if not in a session.
- def finish
- raise IOError, 'HTTP session not yet started' unless started?
- do_finish
+ def timeouted_connect(conn_addr, conn_port)
+ if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT
+ TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout)
+ else
+ Timeout.timeout(@open_timeout, Net::OpenTimeout) {
+ TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
+ }
+ end
+ end
+ private :timeouted_connect
+
+ def on_connect
end
+ private :on_connect
def do_finish
@started = false
@@ -1821,6 +1848,8 @@ module Net #:nodoc:
}
end
+ # :startdoc:
+
class << HTTP
# Returns true if self is a class which was created by HTTP::Proxy.
def proxy_class?
@@ -1915,9 +1944,11 @@ module Net #:nodoc:
alias proxyport proxy_port #:nodoc: obsolete
private
+ # :stopdoc:
def unescape(value)
- require 'cgi/util'
+ require 'cgi/escape'
+ require 'cgi/util' unless defined?(CGI::EscapeExt)
CGI.unescape(value)
end
@@ -1942,6 +1973,7 @@ module Net #:nodoc:
path
end
end
+ # :startdoc:
#
# HTTP operations
@@ -2396,7 +2428,9 @@ module Net #:nodoc:
res
end
- IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc:
+ # :stopdoc:
+
+ IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc:
def transport_request(req)
count = 0
@@ -2553,7 +2587,7 @@ module Net #:nodoc:
alias_method :D, :debug
end
- # for backward compatibility until Ruby 3.5
+ # for backward compatibility until Ruby 4.0
# https://bugs.ruby-lang.org/issues/20900
# https://github.com/bblimke/webmock/pull/1081
HTTPSession = HTTP
diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb
index ceec8f7b0a..4342cfc0ef 100644
--- a/lib/net/http/exceptions.rb
+++ b/lib/net/http/exceptions.rb
@@ -3,7 +3,7 @@ module Net
# Net::HTTP exception class.
# You cannot use Net::HTTPExceptions directly; instead, you must use
# its subclasses.
- module HTTPExceptions
+ module HTTPExceptions # :nodoc:
def initialize(msg, res) #:nodoc:
super msg
@response = res
@@ -12,6 +12,7 @@ module Net
alias data response #:nodoc: obsolete
end
+ # :stopdoc:
class HTTPError < ProtocolError
include HTTPExceptions
end
diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb
index 44e329a0c8..5b01ea4abd 100644
--- a/lib/net/http/generic_request.rb
+++ b/lib/net/http/generic_request.rb
@@ -19,16 +19,13 @@ class Net::HTTPGenericRequest
if URI === uri_or_path then
raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path
- hostname = uri_or_path.hostname
+ hostname = uri_or_path.host
raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0)
@uri = uri_or_path.dup
- host = @uri.hostname.dup
- host << ":" << @uri.port.to_s if @uri.port != @uri.default_port
@path = uri_or_path.request_uri
raise ArgumentError, "no HTTP request path given" unless @path
else
@uri = nil
- host = nil
raise ArgumentError, "no HTTP request path given" unless uri_or_path
raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty?
@path = uri_or_path.dup
@@ -51,7 +48,7 @@ class Net::HTTPGenericRequest
initialize_http_header initheader
self['Accept'] ||= '*/*'
self['User-Agent'] ||= 'Ruby'
- self['Host'] ||= host if host
+ self['Host'] ||= @uri.authority if @uri
@body = nil
@body_stream = nil
@body_data = nil
@@ -102,6 +99,31 @@ class Net::HTTPGenericRequest
"\#<#{self.class} #{@method}>"
end
+ # Returns a string representation of the request with the details for pp:
+ #
+ # require 'pp'
+ # post = Net::HTTP::Post.new(uri)
+ # post.inspect # => "#<Net::HTTP::Post POST>"
+ # post.pretty_inspect
+ # # => #<Net::HTTP::Post
+ # POST
+ # path="/"
+ # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
+ # "accept" => ["*/*"],
+ # "user-agent" => ["Ruby"],
+ # "host" => ["www.ruby-lang.org"]}>
+ #
+ def pretty_print(q)
+ q.object_group(self) {
+ q.breakable
+ q.text @method
+ q.breakable
+ q.text "path="; q.pp @path
+ q.breakable
+ q.text "headers="; q.pp to_hash
+ }
+ end
+
##
# Don't automatically decode response content-encoding if the user indicates
# they want to handle it.
@@ -220,7 +242,7 @@ class Net::HTTPGenericRequest
end
if host = self['host']
- host.sub!(/:.*/m, '')
+ host = URI.parse("//#{host}").host # Remove a port component from the existing Host header
elsif host = @uri.host
else
host = addr
@@ -239,6 +261,8 @@ class Net::HTTPGenericRequest
private
+ # :stopdoc:
+
class Chunker #:nodoc:
def initialize(sock)
@sock = sock
@@ -260,7 +284,6 @@ class Net::HTTPGenericRequest
def send_request_with_body(sock, ver, path, body)
self.content_length = body.bytesize
delete 'Transfer-Encoding'
- supply_default_content_type
write_header sock, ver, path
wait_for_continue sock, ver if sock.continue_timeout
sock.write body
@@ -271,7 +294,6 @@ class Net::HTTPGenericRequest
raise ArgumentError,
"Content-Length not given and Transfer-Encoding is not `chunked'"
end
- supply_default_content_type
write_header sock, ver, path
wait_for_continue sock, ver if sock.continue_timeout
if chunked?
@@ -373,12 +395,6 @@ class Net::HTTPGenericRequest
buf.clear
end
- def supply_default_content_type
- return if content_type()
- warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE
- set_content_type 'application/x-www-form-urlencoded'
- end
-
##
# Waits up to the continue timeout for a response from the server provided
# we're speaking HTTP 1.1 and are expecting a 100-continue response.
@@ -411,4 +427,3 @@ class Net::HTTPGenericRequest
end
end
-
diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb
index f6c36f1b5e..5dcdcc7d74 100644
--- a/lib/net/http/header.rb
+++ b/lib/net/http/header.rb
@@ -179,7 +179,9 @@
# - #each_value: Passes each string field value to the block.
#
module Net::HTTPHeader
+ # The maximum length of HTTP header keys.
MAX_KEY_LENGTH = 1024
+ # The maximum length of HTTP header values.
MAX_FIELD_LENGTH = 65536
def initialize_http_header(initheader) #:nodoc:
@@ -267,6 +269,7 @@ module Net::HTTPHeader
end
end
+ # :stopdoc:
private def set_field(key, val)
case val
when Enumerable
@@ -294,6 +297,7 @@ module Net::HTTPHeader
ary.push val
end
end
+ # :startdoc:
# Returns the array field value for the given +key+,
# or +nil+ if there is no such field;
@@ -490,7 +494,7 @@ module Net::HTTPHeader
alias canonical_each each_capitalized
- def capitalize(name)
+ def capitalize(name) # :nodoc:
name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze)
end
private :capitalize
@@ -957,12 +961,12 @@ module Net::HTTPHeader
@header['proxy-authorization'] = [basic_encode(account, password)]
end
- def basic_encode(account, password)
+ def basic_encode(account, password) # :nodoc:
'Basic ' + ["#{account}:#{password}"].pack('m0')
end
private :basic_encode
-# Returns whether the HTTP session is to be closed.
+ # Returns whether the HTTP session is to be closed.
def connection_close?
token = /(?:\A|,)\s*close\s*(?:\z|,)/i
@header['connection']&.grep(token) {return true}
@@ -970,7 +974,7 @@ module Net::HTTPHeader
false
end
-# Returns whether the HTTP session is to be kept alive.
+ # Returns whether the HTTP session is to be kept alive.
def connection_keep_alive?
token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i
@header['connection']&.grep(token) {return true}
diff --git a/lib/net/http/net-http.gemspec b/lib/net/http/net-http.gemspec
index 70528d58cb..80e94c7bb6 100644
--- a/lib/net/http/net-http.gemspec
+++ b/lib/net/http/net-http.gemspec
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
spec.summary = %q{HTTP client api for Ruby.}
spec.description = %q{HTTP client api for Ruby.}
spec.homepage = "https://github.com/ruby/net-http"
- spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
spec.licenses = ["Ruby", "BSD-2-Clause"]
spec.metadata["changelog_uri"] = spec.homepage + "/releases"
@@ -30,11 +30,10 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) }
- end
+ excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}]
+ spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0")
spec.bindir = "exe"
spec.require_paths = ["lib"]
- spec.add_dependency "uri"
+ spec.add_dependency "uri", ">= 0.11.1"
end
diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb
index e58057adf1..939d413f91 100644
--- a/lib/net/http/requests.rb
+++ b/lib/net/http/requests.rb
@@ -29,6 +29,7 @@
# - Net::HTTP#get: sends +GET+ request, returns response object.
#
class Net::HTTP::Get < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'GET'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -60,6 +61,7 @@ end
# - Net::HTTP#head: sends +HEAD+ request, returns response object.
#
class Net::HTTP::Head < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'HEAD'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = false
@@ -95,6 +97,7 @@ end
# - Net::HTTP#post: sends +POST+ request, returns response object.
#
class Net::HTTP::Post < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'POST'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -130,6 +133,7 @@ end
# - Net::HTTP#put: sends +PUT+ request, returns response object.
#
class Net::HTTP::Put < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PUT'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -162,6 +166,7 @@ end
# - Net::HTTP#delete: sends +DELETE+ request, returns response object.
#
class Net::HTTP::Delete < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'DELETE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -193,6 +198,7 @@ end
# - Net::HTTP#options: sends +OPTIONS+ request, returns response object.
#
class Net::HTTP::Options < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'OPTIONS'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -224,6 +230,7 @@ end
# - Net::HTTP#trace: sends +TRACE+ request, returns response object.
#
class Net::HTTP::Trace < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'TRACE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -258,6 +265,7 @@ end
# - Net::HTTP#patch: sends +PATCH+ request, returns response object.
#
class Net::HTTP::Patch < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PATCH'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -285,6 +293,7 @@ end
# - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object.
#
class Net::HTTP::Propfind < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PROPFIND'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -308,6 +317,7 @@ end
# - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object.
#
class Net::HTTP::Proppatch < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PROPPATCH'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -331,6 +341,7 @@ end
# - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object.
#
class Net::HTTP::Mkcol < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'MKCOL'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -354,6 +365,7 @@ end
# - Net::HTTP#copy: sends +COPY+ request, returns response object.
#
class Net::HTTP::Copy < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'COPY'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -377,6 +389,7 @@ end
# - Net::HTTP#move: sends +MOVE+ request, returns response object.
#
class Net::HTTP::Move < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'MOVE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -400,6 +413,7 @@ end
# - Net::HTTP#lock: sends +LOCK+ request, returns response object.
#
class Net::HTTP::Lock < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'LOCK'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -423,8 +437,8 @@ end
# - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object.
#
class Net::HTTP::Unlock < Net::HTTPRequest
+ # :stopdoc:
METHOD = 'UNLOCK'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
-
diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb
index 40de963868..8804a99c9e 100644
--- a/lib/net/http/response.rb
+++ b/lib/net/http/response.rb
@@ -153,6 +153,7 @@ class Net::HTTPResponse
end
private
+ # :stopdoc:
def read_status_line(sock)
str = sock.readline
@@ -259,7 +260,7 @@ class Net::HTTPResponse
# header.
attr_accessor :ignore_eof
- def inspect
+ def inspect # :nodoc:
"#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
end
diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb
index 6f6fb8d055..941a6fed80 100644
--- a/lib/net/http/responses.rb
+++ b/lib/net/http/responses.rb
@@ -4,7 +4,9 @@
module Net
+ # Unknown HTTP response
class HTTPUnknownResponse < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPError #
end
@@ -19,6 +21,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response].
#
class HTTPInformation < HTTPResponse
+ # :stopdoc:
HAS_BODY = false
EXCEPTION_TYPE = HTTPError #
end
@@ -34,6 +37,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success].
#
class HTTPSuccess < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPError #
end
@@ -49,6 +53,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection].
#
class HTTPRedirection < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPRetriableError #
end
@@ -63,6 +68,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors].
#
class HTTPClientError < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPClientException #
end
@@ -77,6 +83,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors].
#
class HTTPServerError < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPFatalError #
end
@@ -94,6 +101,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100].
#
class HTTPContinue < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -111,6 +119,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101].
#
class HTTPSwitchProtocol < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -127,6 +136,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102].
#
class HTTPProcessing < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -145,6 +155,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103].
#
class HTTPEarlyHints < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -162,6 +173,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200].
#
class HTTPOK < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -179,6 +191,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201].
#
class HTTPCreated < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -196,6 +209,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202].
#
class HTTPAccepted < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -215,6 +229,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203].
#
class HTTPNonAuthoritativeInformation < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -232,6 +247,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204].
#
class HTTPNoContent < HTTPSuccess
+ # :stopdoc:
HAS_BODY = false
end
@@ -250,6 +266,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205].
#
class HTTPResetContent < HTTPSuccess
+ # :stopdoc:
HAS_BODY = false
end
@@ -268,6 +285,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206].
#
class HTTPPartialContent < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -285,6 +303,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207].
#
class HTTPMultiStatus < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -304,6 +323,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208].
#
class HTTPAlreadyReported < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -321,6 +341,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226].
#
class HTTPIMUsed < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -338,6 +359,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300].
#
class HTTPMultipleChoices < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
HTTPMultipleChoice = HTTPMultipleChoices
@@ -356,6 +378,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301].
#
class HTTPMovedPermanently < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -373,6 +396,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302].
#
class HTTPFound < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
HTTPMovedTemporarily = HTTPFound
@@ -390,6 +414,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303].
#
class HTTPSeeOther < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -407,6 +432,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304].
#
class HTTPNotModified < HTTPRedirection
+ # :stopdoc:
HAS_BODY = false
end
@@ -423,6 +449,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305].
#
class HTTPUseProxy < HTTPRedirection
+ # :stopdoc:
HAS_BODY = false
end
@@ -440,6 +467,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307].
#
class HTTPTemporaryRedirect < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -456,6 +484,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308].
#
class HTTPPermanentRedirect < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -472,6 +501,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400].
#
class HTTPBadRequest < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -488,6 +518,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401].
#
class HTTPUnauthorized < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -504,6 +535,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402].
#
class HTTPPaymentRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -521,6 +553,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403].
#
class HTTPForbidden < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -537,6 +570,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404].
#
class HTTPNotFound < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -553,6 +587,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405].
#
class HTTPMethodNotAllowed < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -570,6 +605,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406].
#
class HTTPNotAcceptable < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -586,6 +622,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407].
#
class HTTPProxyAuthenticationRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -602,6 +639,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408].
#
class HTTPRequestTimeout < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestTimeOut = HTTPRequestTimeout
@@ -619,6 +657,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409].
#
class HTTPConflict < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -636,6 +675,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410].
#
class HTTPGone < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -653,6 +693,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411].
#
class HTTPLengthRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -670,6 +711,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412].
#
class HTTPPreconditionFailed < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -686,6 +728,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413].
#
class HTTPPayloadTooLarge < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestEntityTooLarge = HTTPPayloadTooLarge
@@ -703,6 +746,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414].
#
class HTTPURITooLong < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestURITooLong = HTTPURITooLong
@@ -721,6 +765,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415].
#
class HTTPUnsupportedMediaType < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -737,6 +782,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416].
#
class HTTPRangeNotSatisfiable < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable
@@ -754,6 +800,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417].
#
class HTTPExpectationFailed < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -774,6 +821,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421].
#
class HTTPMisdirectedRequest < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -790,6 +838,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422].
#
class HTTPUnprocessableEntity < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -805,6 +854,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423].
#
class HTTPLocked < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -821,6 +871,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424].
#
class HTTPFailedDependency < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -840,6 +891,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426].
#
class HTTPUpgradeRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -856,6 +908,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428].
#
class HTTPPreconditionRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -872,6 +925,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429].
#
class HTTPTooManyRequests < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -889,6 +943,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431].
#
class HTTPRequestHeaderFieldsTooLarge < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -906,6 +961,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451].
#
class HTTPUnavailableForLegalReasons < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
# 444 No Response - Nginx
@@ -926,6 +982,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500].
#
class HTTPInternalServerError < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -943,6 +1000,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501].
#
class HTTPNotImplemented < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -960,6 +1018,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502].
#
class HTTPBadGateway < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -977,6 +1036,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503].
#
class HTTPServiceUnavailable < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -994,6 +1054,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504].
#
class HTTPGatewayTimeout < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
HTTPGatewayTimeOut = HTTPGatewayTimeout
@@ -1011,6 +1072,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505].
#
class HTTPVersionNotSupported < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1027,6 +1089,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506].
#
class HTTPVariantAlsoNegotiates < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1043,6 +1106,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507].
#
class HTTPInsufficientStorage < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1059,6 +1123,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508].
#
class HTTPLoopDetected < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
# 509 Bandwidth Limit Exceeded - Apache bw/limited extension
@@ -1076,6 +1141,7 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510].
#
class HTTPNotExtended < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1092,19 +1158,21 @@ module Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511].
#
class HTTPNetworkAuthenticationRequired < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
end
class Net::HTTPResponse
+ # :stopdoc:
CODE_CLASS_TO_OBJ = {
'1' => Net::HTTPInformation,
'2' => Net::HTTPSuccess,
'3' => Net::HTTPRedirection,
'4' => Net::HTTPClientError,
'5' => Net::HTTPServerError
- }
+ }.freeze
CODE_TO_OBJ = {
'100' => Net::HTTPContinue,
'101' => Net::HTTPSwitchProtocol,
@@ -1170,5 +1238,5 @@ class Net::HTTPResponse
'508' => Net::HTTPLoopDetected,
'510' => Net::HTTPNotExtended,
'511' => Net::HTTPNetworkAuthenticationRequired,
- }
+ }.freeze
end
diff --git a/lib/net/net-protocol.gemspec b/lib/net/net-protocol.gemspec
index f9fd83f12b..2d911a966c 100644
--- a/lib/net/net-protocol.gemspec
+++ b/lib/net/net-protocol.gemspec
@@ -25,9 +25,8 @@ Gem::Specification.new do |spec|
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
- end
+ excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}]
+ spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0")
spec.require_paths = ["lib"]
spec.add_dependency "timeout"
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
index 197ea09089..8c81298c0e 100644
--- a/lib/net/protocol.rb
+++ b/lib/net/protocol.rb
@@ -54,9 +54,20 @@ module Net # :nodoc:
s.connect
end
end
+
+ tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters
+ TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]]
+ tcp_socket_parameters.include?([:key, :open_timeout])
+ else
+ # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize
+ # See discussion in https://github.com/ruby/net-http/pull/224
+ Socket.method(:tcp).parameters.include?([:key, :open_timeout])
+ end
+ private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT
end
+ # :stopdoc:
class ProtocolError < StandardError; end
class ProtoSyntaxError < ProtocolError; end
class ProtoFatalError < ProtocolError; end
@@ -66,6 +77,7 @@ module Net # :nodoc:
class ProtoCommandError < ProtocolError; end
class ProtoRetriableError < ProtocolError; end
ProtocRetryError = ProtoRetriableError
+ # :startdoc:
##
# OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot
@@ -78,6 +90,7 @@ module Net # :nodoc:
# response cannot be read within the read_timeout.
class ReadTimeout < Timeout::Error
+ # :stopdoc:
def initialize(io = nil)
@io = io
end
@@ -97,6 +110,7 @@ module Net # :nodoc:
# response cannot be written within the write_timeout. Not raised on Windows.
class WriteTimeout < Timeout::Error
+ # :stopdoc:
def initialize(io = nil)
@io = io
end
@@ -484,6 +498,7 @@ module Net # :nodoc:
# The writer adapter class
#
class WriteAdapter
+ # :stopdoc:
def initialize(writer)
@writer = writer
end
diff --git a/lib/open-uri.rb b/lib/open-uri.rb
index de710af261..5983c7368b 100644
--- a/lib/open-uri.rb
+++ b/lib/open-uri.rb
@@ -91,8 +91,10 @@ end
module OpenURI
+ # The version string
VERSION = "0.5.0"
+ # The default options
Options = {
:proxy => true,
:proxy_http_basic_authentication => true,
@@ -394,24 +396,28 @@ module OpenURI
end
end
+ # Raised on HTTP session failure
class HTTPError < StandardError
- def initialize(message, io)
+ def initialize(message, io) # :nodoc:
super(message)
@io = io
end
+ # StringIO having the received data
attr_reader :io
end
# Raised on redirection,
# only occurs when +redirect+ option for HTTP is +false+.
class HTTPRedirect < HTTPError
- def initialize(message, io, uri)
+ def initialize(message, io, uri) # :nodoc:
super(message, io)
@uri = uri
end
+ # URI to redirect
attr_reader :uri
end
+ # Raised on too many redirection,
class TooManyRedirects < HTTPError
end
diff --git a/lib/open3/version.rb b/lib/open3/version.rb
index bfcec44ccc..322dd71e2a 100644
--- a/lib/open3/version.rb
+++ b/lib/open3/version.rb
@@ -1,3 +1,4 @@
module Open3
+ # The version string
VERSION = "0.2.1"
end
diff --git a/lib/optparse.rb b/lib/optparse.rb
index e1069b3505..97178e284b 100644
--- a/lib/optparse.rb
+++ b/lib/optparse.rb
@@ -7,6 +7,7 @@
#
# See OptionParser for documentation.
#
+require 'set' unless defined?(Set)
#--
# == Developer Documentation (not for RDoc output)
@@ -425,7 +426,9 @@
#
class OptionParser
# The version string
- OptionParser::Version = "0.7.0.dev.2"
+ VERSION = "0.8.1"
+ # An alias for compatibility
+ Version = VERSION
# :stopdoc:
NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
@@ -469,7 +472,6 @@ class OptionParser
Completion.candidate(key, icase, pat, &method(:each))
end
- public
def complete(key, icase = false, pat = nil)
candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size}
if candidates.size == 1
@@ -559,7 +561,7 @@ class OptionParser
# Parses +arg+ and returns rest of +arg+ and matched portion to the
# argument pattern. Yields when the pattern doesn't match substring.
#
- def parse_arg(arg) # :nodoc:
+ private def parse_arg(arg) # :nodoc:
pattern or return nil, [arg]
unless m = pattern.match(arg)
yield(InvalidArgument, arg)
@@ -577,14 +579,13 @@ class OptionParser
yield(InvalidArgument, arg) # didn't match whole arg
return arg[s.length..-1], m
end
- private :parse_arg
#
# Parses argument, converts and returns +arg+, +block+ and result of
# conversion. Yields at semi-error condition instead of raising an
# exception.
#
- def conv_arg(arg, val = []) # :nodoc:
+ private def conv_arg(arg, val = []) # :nodoc:
v, = *val
if conv
val = conv.call(*val)
@@ -596,7 +597,6 @@ class OptionParser
end
return arg, block, val
end
- private :conv_arg
#
# Produces the summary text. Each line of the summary is yielded to the
@@ -880,14 +880,13 @@ class OptionParser
# +lopts+:: Long style option list.
# +nlopts+:: Negated long style options list.
#
- def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc:
+ private def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc:
sopts.each {|o| @short[o] = sw} if sopts
lopts.each {|o| @long[o] = sw} if lopts
nlopts.each {|o| @long[o] = nsw} if nsw and nlopts
used = @short.invert.update(@long.invert)
@list.delete_if {|o| Switch === o and !used[o]}
end
- private :update
#
# Inserts +switch+ at the head of the list, and associates short, long
@@ -1293,7 +1292,15 @@ XXX
# to $0.
#
def program_name
- @program_name || File.basename($0, '.*')
+ @program_name || strip_ext(File.basename($0))
+ end
+
+ private def strip_ext(name) # :nodoc:
+ exts = /#{
+ require "rbconfig"
+ Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" "))
+ }\z/o
+ name.sub(exts, "")
end
# for experimental cascading :-)
@@ -1448,14 +1455,13 @@ XXX
# +prv+:: Previously specified argument.
# +msg+:: Exception message.
#
- def notwice(obj, prv, msg) # :nodoc:
+ private def notwice(obj, prv, msg) # :nodoc:
unless !prv or prv == obj
raise(ArgumentError, "argument #{msg} given twice: #{obj}",
ParseError.filter_backtrace(caller(2)))
end
obj
end
- private :notwice
SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc:
@@ -1500,7 +1506,7 @@ XXX
case o
when Proc, Method
block = notwice(o, block, 'block')
- when Array, Hash
+ when Array, Hash, Set
if Array === o
o, v = o.partition {|v,| Completion.completable?(v)}
values = notwice(v, values, 'values') unless v.empty?
@@ -1722,7 +1728,7 @@ XXX
parse_in_order(argv, setter, **keywords, &nonopt)
end
- def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc:
+ private def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc:
opt, arg, val, rest = nil
nonopt ||= proc {|a| throw :terminate, a}
argv.unshift(arg) if arg = catch(:terminate) {
@@ -1813,10 +1819,9 @@ XXX
argv
end
- private :parse_in_order
# Calls callback with _val_.
- def callback!(cb, max_arity, *args) # :nodoc:
+ private def callback!(cb, max_arity, *args) # :nodoc:
args.compact!
if (size = args.size) < max_arity and cb.to_proc.lambda?
@@ -1826,7 +1831,6 @@ XXX
end
cb.call(*args)
end
- private :callback!
#
# Parses command line arguments +argv+ in permutation mode and returns
@@ -1846,7 +1850,7 @@ XXX
#
def permute!(argv = default_argv, **keywords)
nonopts = []
- order!(argv, **keywords, &nonopts.method(:<<))
+ order!(argv, **keywords) {|nonopt| nonopts << nonopt}
argv[0, 0] = nonopts
argv
end
@@ -1899,13 +1903,16 @@ XXX
single_options, *long_options = *args
result = {}
+ setter = (symbolize_names ?
+ ->(name, val) {result[name.to_sym] = val}
+ : ->(name, val) {result[name] = val})
single_options.scan(/(.)(:)?/) do |opt, val|
if val
- result[opt] = nil
+ setter[opt, nil]
define("-#{opt} VAL")
else
- result[opt] = false
+ setter[opt, false]
define("-#{opt}")
end
end if single_options
@@ -1914,16 +1921,16 @@ XXX
arg, desc = arg.split(';', 2)
opt, val = arg.split(':', 2)
if val
- result[opt] = val.empty? ? nil : val
+ setter[opt, (val unless val.empty?)]
define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact)
else
- result[opt] = false
+ setter[opt, false]
define("--#{opt}", *[desc].compact)
end
end
- parse_in_order(argv, result.method(:[]=), **keywords)
- symbolize_names ? result.transform_keys(&:to_sym) : result
+ parse_in_order(argv, setter, **keywords)
+ result
end
#
@@ -1937,24 +1944,22 @@ XXX
# Traverses @stack, sending each element method +id+ with +args+ and
# +block+.
#
- def visit(id, *args, &block) # :nodoc:
+ private def visit(id, *args, &block) # :nodoc:
@stack.reverse_each do |el|
el.__send__(id, *args, &block)
end
nil
end
- private :visit
#
# Searches +key+ in @stack for +id+ hash and returns or yields the result.
#
- def search(id, key) # :nodoc:
+ private def search(id, key) # :nodoc:
block_given = block_given?
visit(:search, id, key) do |k|
return block_given ? yield(k) : k
end
end
- private :search
#
# Completes shortened long style option switch and returns pair of
@@ -1965,7 +1970,7 @@ XXX
# +icase+:: Search case insensitive if true.
# +pat+:: Optional pattern for completion.
#
- def complete(typ, opt, icase = false, *pat) # :nodoc:
+ private def complete(typ, opt, icase = false, *pat) # :nodoc:
if pat.empty?
search(typ, opt) {|sw| return [sw, opt]} # exact match or...
end
@@ -1973,9 +1978,8 @@ XXX
visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw}
}
exc = ambiguous ? AmbiguousOption : InvalidOption
- raise exc.new(opt, additional: self.method(:additional_message).curry[typ])
+ raise exc.new(opt, additional: proc {|o| additional_message(typ, o)})
end
- private :complete
#
# Returns additional info.
@@ -2038,19 +2042,27 @@ XXX
def load(filename = nil, **keywords)
unless filename
basename = File.basename($0, '.*')
- return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil
+ return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil
basename << ".options"
+ if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty?
+ # https://specifications.freedesktop.org/basedir-spec/latest/#variables
+ #
+ # If $XDG_CONFIG_HOME is either not set or empty, a default
+ # equal to $HOME/.config should be used.
+ xdg = ['~/.config', true]
+ end
return [
- # XDG
- ENV['XDG_CONFIG_HOME'],
- '~/.config',
+ xdg,
+
*ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR),
# Haiku
- '~/config/settings',
- ].any? {|dir|
+ ['~/config/settings', true],
+ ].any? {|dir, expand|
next if !dir or dir.empty?
- load(File.expand_path(basename, dir), **keywords) rescue nil
+ filename = File.join(dir, basename)
+ filename = File.expand_path(filename) if expand
+ load(filename, **keywords) rescue nil
}
end
begin
@@ -2256,9 +2268,10 @@ XXX
argv
end
+ DIR = File.join(__dir__, '')
def self.filter_backtrace(array)
unless $DEBUG
- array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~))
+ array.delete_if {|bt| bt.start_with?(DIR)}
end
array
end
@@ -2301,42 +2314,42 @@ XXX
# Raises when ambiguously completable string is encountered.
#
class AmbiguousOption < ParseError
- const_set(:Reason, 'ambiguous option')
+ Reason = 'ambiguous option' # :nodoc:
end
#
# Raises when there is an argument for a switch which takes no argument.
#
class NeedlessArgument < ParseError
- const_set(:Reason, 'needless argument')
+ Reason = 'needless argument' # :nodoc:
end
#
# Raises when a switch with mandatory argument has no argument.
#
class MissingArgument < ParseError
- const_set(:Reason, 'missing argument')
+ Reason = 'missing argument' # :nodoc:
end
#
# Raises when switch is undefined.
#
class InvalidOption < ParseError
- const_set(:Reason, 'invalid option')
+ Reason = 'invalid option' # :nodoc:
end
#
# Raises when the given argument does not match required format.
#
class InvalidArgument < ParseError
- const_set(:Reason, 'invalid argument')
+ Reason = 'invalid argument' # :nodoc:
end
#
# Raises when the given argument word can't be completed uniquely.
#
class AmbiguousArgument < InvalidArgument
- const_set(:Reason, 'ambiguous argument')
+ Reason = 'ambiguous argument' # :nodoc:
end
#
@@ -2435,9 +2448,11 @@ XXX
# and DecimalNumeric. See Acceptable argument classes (in source code).
#
module Acceptables
- const_set(:DecimalInteger, OptionParser::DecimalInteger)
- const_set(:OctalInteger, OptionParser::OctalInteger)
- const_set(:DecimalNumeric, OptionParser::DecimalNumeric)
+ # :stopdoc:
+ DecimalInteger = OptionParser::DecimalInteger
+ OctalInteger = OptionParser::OctalInteger
+ DecimalNumeric = OptionParser::DecimalNumeric
+ # :startdoc:
end
end
diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec
index 8589f1857c..885b0ec380 100644
--- a/lib/optparse/optparse.gemspec
+++ b/lib/optparse/optparse.gemspec
@@ -3,7 +3,7 @@
name = File.basename(__FILE__, ".gemspec")
version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
- /^\s*OptionParser::Version\s*=\s*"(.*)"/ =~ line and break $1
+ /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
end rescue nil
end
@@ -25,8 +25,9 @@ Gem::Specification.new do |spec|
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
- spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] +
- %w[README.md ChangeLog COPYING .document .rdoc_options]
+ dir, gemspec = File.split(__FILE__)
+ excludes = %W[#{gemspec} rakelib test/ Gemfile Rakefile .git* .editor*].map {|n| ":^"+n}
+ spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0")
spec.bindir = "exe"
spec.executables = []
spec.require_paths = ["lib"]
diff --git a/lib/pathname.rb b/lib/pathname.rb
new file mode 100644
index 0000000000..b19e379cd4
--- /dev/null
+++ b/lib/pathname.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+#
+# = pathname.rb
+#
+# Object-Oriented Pathname Class
+#
+# Author:: Tanaka Akira <akr@m17n.org>
+# Documentation:: Author and Gavin Sinclair
+#
+# For documentation, see class Pathname.
+#
+
+class Pathname # * Find *
+ #
+ # Iterates over the directory tree in a depth first manner, yielding a
+ # Pathname for each file under "this" directory.
+ #
+ # Note that you need to require 'pathname' to use this method.
+ #
+ # Returns an Enumerator if no block is given.
+ #
+ # Since it is implemented by the standard library module Find, Find.prune can
+ # be used to control the traversal.
+ #
+ # If +self+ is +.+, yielded pathnames begin with a filename in the
+ # current directory, not +./+.
+ #
+ # See Find.find
+ #
+ def find(ignore_error: true) # :yield: pathname
+ return to_enum(__method__, ignore_error: ignore_error) unless block_given?
+ require 'find'
+ if @path == '.'
+ Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) }
+ else
+ Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) }
+ end
+ end
+end
+
+
+class Pathname # * FileUtils *
+ # Recursively deletes a directory, including all directories beneath it.
+ #
+ # Note that you need to require 'pathname' to use this method.
+ #
+ # See FileUtils.rm_rf
+ def rmtree(noop: nil, verbose: nil, secure: nil)
+ # The name "rmtree" is borrowed from File::Path of Perl.
+ # File::Path provides "mkpath" and "rmtree".
+ require 'fileutils'
+ FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure)
+ self
+ end
+end
+
+class Pathname # * tmpdir *
+ # Creates a tmp directory and wraps the returned path in a Pathname object.
+ #
+ # Note that you need to require 'pathname' to use this method.
+ #
+ # See Dir.mktmpdir
+ def self.mktmpdir
+ require 'tmpdir' unless defined?(Dir.mktmpdir)
+ if block_given?
+ Dir.mktmpdir do |dir|
+ dir = self.new(dir)
+ yield dir
+ end
+ else
+ self.new(Dir.mktmpdir)
+ end
+ end
+end
diff --git a/lib/pp.rb b/lib/pp.rb
index 5318395631..5fd29a373a 100644
--- a/lib/pp.rb
+++ b/lib/pp.rb
@@ -64,7 +64,7 @@ require 'prettyprint'
class PP < PrettyPrint
# The version string
- VERSION = "0.6.2"
+ VERSION = "0.6.3"
# Returns the usable width for +out+.
# As the width of +out+:
@@ -145,21 +145,13 @@ class PP < PrettyPrint
# Yields to a block
# and preserves the previous set of objects being printed.
def guard_inspect_key
- if Thread.current[:__recursive_key__] == nil
- Thread.current[:__recursive_key__] = {}.compare_by_identity
- end
-
- if Thread.current[:__recursive_key__][:inspect] == nil
- Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity
- end
-
- save = Thread.current[:__recursive_key__][:inspect]
-
+ recursive_state = Thread.current[:__recursive_key__] ||= {}.compare_by_identity
+ save = recursive_state[:inspect] ||= {}.compare_by_identity
begin
- Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity
+ recursive_state[:inspect] = {}.compare_by_identity
yield
ensure
- Thread.current[:__recursive_key__][:inspect] = save
+ recursive_state[:inspect] = save
end
end
@@ -167,9 +159,8 @@ class PP < PrettyPrint
# to be pretty printed. Used to break cycles in chains of objects to be
# pretty printed.
def check_inspect_key(id)
- Thread.current[:__recursive_key__] &&
- Thread.current[:__recursive_key__][:inspect] &&
- Thread.current[:__recursive_key__][:inspect].include?(id)
+ recursive_state = Thread.current[:__recursive_key__] or return false
+ recursive_state[:inspect]&.include?(id)
end
# Adds the object_id +id+ to the set of objects being pretty printed, so
@@ -183,10 +174,10 @@ class PP < PrettyPrint
Thread.current[:__recursive_key__][:inspect].delete id
end
- private def guard_inspect(object)
+ private def guard_inspect(object) # :nodoc:
recursive_state = Thread.current[:__recursive_key__]
- if recursive_state && recursive_state.key?(:inspect)
+ if recursive_state&.key?(:inspect)
begin
push_inspect_key(object)
yield
@@ -286,7 +277,7 @@ class PP < PrettyPrint
kwsplat ? yield(*v, **kwsplat) : yield(*v)
}
end
- EMPTY_KWHASH = if RUBY_VERSION >= "3.0"
+ EMPTY_KWHASH = if RUBY_VERSION >= "3.0" # :nodoc:
{}.freeze
end
private_constant :EMPTY_KWHASH
@@ -322,12 +313,10 @@ class PP < PrettyPrint
# A pretty print for a pair of Hash
def pp_hash_pair(k, v)
if Symbol === k
- sym_s = k.inspect
- if sym_s[1].match?(/["$@!]/) || sym_s[-1].match?(/[%&*+\-\/<=>@\]^`|~]/)
- text "#{k.to_s.inspect}:"
- else
- text "#{k}:"
+ if k.inspect.match?(%r[\A:["$@!]|[%&*+\-\/<=>@\]^`|~]\z])
+ k = k.to_s.inspect
end
+ text "#{k}:"
else
pp k
text ' '
@@ -399,7 +388,8 @@ class PP < PrettyPrint
# This method should return an array of names of instance variables as symbols or strings as:
# +[:@a, :@b]+.
def pretty_print_instance_variables
- instance_variables.sort
+ ivars = respond_to?(:instance_variables_to_inspect, true) ? instance_variables_to_inspect || instance_variables : instance_variables
+ ivars.sort
end
# Is #inspect implementation using #pretty_print.
@@ -442,22 +432,27 @@ class Hash # :nodoc:
end
end
+if defined?(Set)
+ if set_pp = Set.instance_method(:initialize).source_location
+ set_pp = !set_pp.first.end_with?("/set.rb") # not defined in set.rb
+ else
+ set_pp = true # defined in C
+ end
+end
class Set # :nodoc:
def pretty_print(pp) # :nodoc:
- pp.group(1, '#<Set:', '>') {
- pp.breakable
- pp.group(1, '{', '}') {
- pp.seplist(self) { |o|
- pp.pp o
- }
+ pp.group(1, "#{self.class.name}[", ']') {
+ pp.seplist(self) { |o|
+ pp.pp o
}
}
end
def pretty_print_cycle(pp) # :nodoc:
- pp.text sprintf('#<Set: {%s}>', empty? ? '' : '...')
+ name = self.class.name
+ pp.text(empty? ? "#{name}[]" : "#{name}[...]")
end
-end
+end if set_pp
class << ENV # :nodoc:
def pretty_print(q) # :nodoc:
@@ -489,6 +484,13 @@ class Struct # :nodoc:
end
end
+verbose, $VERBOSE = $VERBOSE, nil
+begin
+ has_data_define = defined?(Data.define)
+ensure
+ $VERBOSE = verbose
+end
+
class Data # :nodoc:
def pretty_print(q) # :nodoc:
class_name = PP.mcall(self, Kernel, :class).name
@@ -521,7 +523,7 @@ class Data # :nodoc:
def pretty_print_cycle(q) # :nodoc:
q.text sprintf("#<data %s:...>", PP.mcall(self, Kernel, :class).name)
end
-end if defined?(Data.define)
+end if has_data_define
class Range # :nodoc:
def pretty_print(q) # :nodoc:
diff --git a/lib/prettyprint.rb b/lib/prettyprint.rb
index 6f50192f5d..a65407c130 100644
--- a/lib/prettyprint.rb
+++ b/lib/prettyprint.rb
@@ -33,6 +33,7 @@
#
class PrettyPrint
+ # The version string
VERSION = "0.2.0"
# This is a convenience method which is same as follows:
diff --git a/lib/prism.rb b/lib/prism.rb
index eaab5cbfed..dab3420377 100644
--- a/lib/prism.rb
+++ b/lib/prism.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
# The Prism Ruby parser.
#
@@ -19,7 +20,7 @@ module Prism
autoload :DSL, "prism/dsl"
autoload :InspectVisitor, "prism/inspect_visitor"
autoload :LexCompat, "prism/lex_compat"
- autoload :LexRipper, "prism/lex_compat"
+ autoload :LexRipper, "prism/lex_ripper"
autoload :MutationCompiler, "prism/mutation_compiler"
autoload :Pack, "prism/pack"
autoload :Pattern, "prism/pattern"
@@ -36,12 +37,31 @@ module Prism
private_constant :LexCompat
private_constant :LexRipper
+ # Raised when requested to parse as the currently running Ruby version but Prism has no support for it.
+ class CurrentVersionError < ArgumentError
+ # Initialize a new exception for the given ruby version string.
+ def initialize(version)
+ message = +"invalid version: Requested to parse as `version: 'current'`; "
+ segments =
+ if version.match?(/\A\d+\.\d+.\d+\z/)
+ version.split(".").map(&:to_i)
+ end
+
+ if segments && ((segments[0] < 3) || (segments[0] == 3 && segments[1] < 3))
+ message << " #{version} is below the minimum supported syntax."
+ else
+ message << " #{version} is unknown. Please update the `prism` gem."
+ end
+
+ super(message)
+ end
+ end
+
# :call-seq:
# Prism::lex_compat(source, **options) -> LexCompat::Result
#
# Returns a parse result whose value is an array of tokens that closely
- # resembles the return value of Ripper::lex. The main difference is that the
- # `:on_sp` token is not emitted.
+ # resembles the return value of Ripper::lex.
#
# For supported options, see Prism::parse.
def self.lex_compat(source, **options)
@@ -51,9 +71,8 @@ module Prism
# :call-seq:
# Prism::lex_ripper(source) -> Array
#
- # This lexes with the Ripper lex. It drops any space events but otherwise
- # returns the same tokens. Raises SyntaxError if the syntax in source is
- # invalid.
+ # This wraps the result of Ripper.lex. It produces almost exactly the
+ # same tokens. Raises SyntaxError if the syntax in source is invalid.
def self.lex_ripper(source)
LexRipper.new(source).result # steep:ignore
end
diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb
index e3b15fc3b0..5d7d38d841 100644
--- a/lib/prism/desugar_compiler.rb
+++ b/lib/prism/desugar_compiler.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
class DesugarAndWriteNode # :nodoc:
diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb
index a0da0b6195..d4c9d60c9a 100644
--- a/lib/prism/ffi.rb
+++ b/lib/prism/ffi.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
# typed: ignore
# This file is responsible for mirroring the API provided by the C extension by
@@ -85,6 +86,7 @@ module Prism
end
callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer
+ callback :pm_parse_stream_feof_t, [:pointer], :int
enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY]
enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE]
@@ -100,7 +102,7 @@ module Prism
"pm_string_query_local",
"pm_string_query_constant",
"pm_string_query_method_name",
- [:pm_parse_stream_fgets_t]
+ [:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t]
)
load_exported_functions_from(
@@ -280,12 +282,14 @@ module Prism
end
}
+ eof_callback = -> (_) { stream.eof? }
+
# In the pm_serialize_parse_stream function it accepts a pointer to the
# IO object as a void* and then passes it through to the callback as the
# third argument, but it never touches it itself. As such, since we have
# access to the IO object already through the closure of the lambda, we
# can pass a null pointer here and not worry.
- LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options))
+ LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, eof_callback, dump_options(options))
Prism.load(source, buffer.read, options.fetch(:freeze, false))
end
end
@@ -419,17 +423,25 @@ module Prism
# Return the value that should be dumped for the version option.
def dump_options_version(version)
- case version
+ current = version == "current"
+
+ case current ? RUBY_VERSION : version
when nil, "latest"
- 0
+ 0 # Handled in pm_parser_init
when /\A3\.3(\.\d+)?\z/
1
when /\A3\.4(\.\d+)?\z/
2
- when /\A3\.5(\.\d+)?\z/
- 0
+ when /\A3\.5(\.\d+)?\z/, /\A4\.0(\.\d+)?\z/
+ 3
+ when /\A4\.1(\.\d+)?\z/
+ 4
else
- raise ArgumentError, "invalid version: #{version}"
+ if current
+ raise CurrentVersionError, RUBY_VERSION
+ else
+ raise ArgumentError, "invalid version: #{version}"
+ end
end
end
diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb
index a83c24cb41..597e63c73e 100644
--- a/lib/prism/lex_compat.rb
+++ b/lib/prism/lex_compat.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
+# :markup: markdown
require "delegate"
-require "ripper"
module Prism
# This class is responsible for lexing the source using prism and then
@@ -225,16 +225,8 @@ module Prism
end
end
- # Ripper doesn't include the rest of the token in the event, so we need to
- # trim it down to just the content on the first line when comparing.
- class EndContentToken < Token
- def ==(other) # :nodoc:
- [self[0], self[1], self[2][0..self[2].index("\n")], self[3]] == other
- end
- end
-
# Tokens where state should be ignored
- # used for :on_comment, :on_heredoc_end, :on_embexpr_end
+ # used for :on_sp, :on_comment, :on_heredoc_end, :on_embexpr_end
class IgnoreStateToken < Token
def ==(other) # :nodoc:
self[0...-1] == other[0...-1]
@@ -248,8 +240,8 @@ module Prism
class IdentToken < Token
def ==(other) # :nodoc:
(self[0...-1] == other[0...-1]) && (
- (other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) ||
- (other[3] & Ripper::EXPR_ARG_ANY != 0)
+ (other[3] == Translation::Ripper::EXPR_LABEL | Translation::Ripper::EXPR_END) ||
+ (other[3] & (Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_CMDARG) != 0)
)
end
end
@@ -260,8 +252,8 @@ module Prism
def ==(other) # :nodoc:
return false unless self[0...-1] == other[0...-1]
- if self[3] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED
- other[3] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED != 0
+ if self[3] == Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED
+ other[3] & Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED != 0
else
self[3] == other[3]
end
@@ -279,8 +271,8 @@ module Prism
class ParamToken < Token
def ==(other) # :nodoc:
(self[0...-1] == other[0...-1]) && (
- (other[3] == Ripper::EXPR_END) ||
- (other[3] == Ripper::EXPR_END | Ripper::EXPR_LABEL)
+ (other[3] == Translation::Ripper::EXPR_END) ||
+ (other[3] == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL)
)
end
end
@@ -614,10 +606,15 @@ module Prism
private_constant :Heredoc
- attr_reader :source, :options
+ # In previous versions of Ruby, Ripper wouldn't flush the bom before the
+ # first token, so we had to have a hack in place to account for that.
+ BOM_FLUSHED = RUBY_VERSION >= "3.3.0"
+ private_constant :BOM_FLUSHED
+
+ attr_reader :options
- def initialize(source, **options)
- @source = source
+ def initialize(code, **options)
+ @code = code
@options = options
end
@@ -627,16 +624,14 @@ module Prism
state = :default
heredoc_stack = [[]] #: Array[Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]]
- result = Prism.lex(source, **options)
+ result = Prism.lex(@code, **options)
+ source = result.source
result_value = result.value
- previous_state = nil #: Ripper::Lexer::State?
+ previous_state = nil #: State?
last_heredoc_end = nil #: Integer?
+ eof_token = nil
- # In previous versions of Ruby, Ripper wouldn't flush the bom before the
- # first token, so we had to have a hack in place to account for that. This
- # checks for that behavior.
- bom_flushed = Ripper.lex("\xEF\xBB\xBF# test")[0][0][1] == 0
- bom = source.byteslice(0..2) == "\xEF\xBB\xBF"
+ bom = source.slice(0, 3) == "\xEF\xBB\xBF"
result_value.each_with_index do |(token, lex_state), index|
lineno = token.location.start_line
@@ -650,7 +645,7 @@ module Prism
if bom && lineno == 1
column -= 3
- if index == 0 && column == 0 && !bom_flushed
+ if index == 0 && column == 0 && !BOM_FLUSHED
flushed =
case token.type
when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE,
@@ -674,12 +669,15 @@ module Prism
event = RIPPER.fetch(token.type)
value = token.value
- lex_state = Ripper::Lexer::State.new(lex_state)
+ lex_state = Translation::Ripper::Lexer::State.cached(lex_state)
token =
case event
when :on___end__
- EndContentToken.new([[lineno, column], event, value, lex_state])
+ # Ripper doesn't include the rest of the token in the event, so we need to
+ # trim it down to just the content on the first line.
+ value = value[0..value.index("\n")]
+ Token.new([[lineno, column], event, value, lex_state])
when :on_comment
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
when :on_heredoc_end
@@ -688,7 +686,7 @@ module Prism
last_heredoc_end = token.location.end_offset
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
when :on_ident
- if lex_state == Ripper::EXPR_END
+ if lex_state == Translation::Ripper::EXPR_END
# If we have an identifier that follows a method name like:
#
# def foo bar
@@ -698,7 +696,7 @@ module Prism
# yet. We do this more accurately, so we need to allow comparing
# against both END and END|LABEL.
ParamToken.new([[lineno, column], event, value, lex_state])
- elsif lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL
+ elsif lex_state == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL
# In the event that we're comparing identifiers, we're going to
# allow a little divergence. Ripper doesn't account for local
# variables introduced through named captures in regexes, and we
@@ -738,13 +736,14 @@ module Prism
counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
end
- Ripper::Lexer::State.new(result_value[current_index][1])
+ Translation::Ripper::Lexer::State.cached(result_value[current_index][1])
else
previous_state
end
Token.new([[lineno, column], event, value, lex_state])
when :on_eof
+ eof_token = token
previous_token = result_value[index - 1][0]
# If we're at the end of the file and the previous token was a
@@ -767,7 +766,7 @@ module Prism
end_offset += 3
end
- tokens << Token.new([[lineno, 0], :on_nl, source.byteslice(start_offset...end_offset), lex_state])
+ tokens << Token.new([[lineno, 0], :on_nl, source.slice(start_offset, end_offset - start_offset), lex_state])
end
end
@@ -861,67 +860,91 @@ module Prism
# We sort by location to compare against Ripper's output
tokens.sort_by!(&:location)
- Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, Source.for(source))
- end
- end
-
- private_constant :LexCompat
-
- # This is a class that wraps the Ripper lexer to produce almost exactly the
- # same tokens.
- class LexRipper # :nodoc:
- attr_reader :source
+ # Add :on_sp tokens
+ tokens = add_on_sp_tokens(tokens, source, result.data_loc, bom, eof_token)
- def initialize(source)
- @source = source
+ Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, source)
end
- def result
- previous = [] #: [[Integer, Integer], Symbol, String, untyped] | []
- results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]]
-
- lex(source).each do |token|
- case token[1]
- when :on_sp
- # skip
- when :on_tstring_content
- if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@"))
- previous[2] << token[2]
- else
- results << token
- previous = token
- end
- when :on_words_sep
- if previous[1] == :on_words_sep
- previous[2] << token[2]
+ def add_on_sp_tokens(tokens, source, data_loc, bom, eof_token)
+ new_tokens = []
+
+ prev_token_state = Translation::Ripper::Lexer::State.cached(Translation::Ripper::EXPR_BEG)
+ prev_token_end = bom ? 3 : 0
+
+ tokens.each do |token|
+ line, column = token.location
+ start_offset = source.line_to_byte_offset(line) + column
+ # Ripper reports columns on line 1 without counting the BOM, so we adjust to get the real offset
+ start_offset += 3 if line == 1 && bom
+
+ if start_offset > prev_token_end
+ sp_value = source.slice(prev_token_end, start_offset - prev_token_end)
+ sp_line = source.line(prev_token_end)
+ sp_column = source.column(prev_token_end)
+ # Ripper reports columns on line 1 without counting the BOM
+ sp_column -= 3 if sp_line == 1 && bom
+ continuation_index = sp_value.byteindex("\\")
+
+ # ripper emits up to three :on_sp tokens when line continuations are used
+ if continuation_index
+ next_whitespace_index = continuation_index + 1
+ next_whitespace_index += 1 if sp_value.byteslice(next_whitespace_index) == "\r"
+ next_whitespace_index += 1
+ first_whitespace = sp_value[0...continuation_index]
+ continuation = sp_value[continuation_index...next_whitespace_index]
+ second_whitespace = sp_value[next_whitespace_index..]
+
+ new_tokens << IgnoreStateToken.new([
+ [sp_line, sp_column],
+ :on_sp,
+ first_whitespace,
+ prev_token_state
+ ]) unless first_whitespace.empty?
+
+ new_tokens << IgnoreStateToken.new([
+ [sp_line, sp_column + continuation_index],
+ :on_sp,
+ continuation,
+ prev_token_state
+ ])
+
+ new_tokens << IgnoreStateToken.new([
+ [sp_line + 1, 0],
+ :on_sp,
+ second_whitespace,
+ prev_token_state
+ ]) unless second_whitespace.empty?
else
- results << token
- previous = token
+ new_tokens << IgnoreStateToken.new([
+ [sp_line, sp_column],
+ :on_sp,
+ sp_value,
+ prev_token_state
+ ])
end
- else
- results << token
- previous = token
end
- end
- results
- end
-
- private
-
- if Ripper.method(:lex).parameters.assoc(:keyrest)
- def lex(source)
- Ripper.lex(source, raise_errors: true)
+ new_tokens << token
+ prev_token_state = token.state
+ prev_token_end = start_offset + token.value.bytesize
end
- else
- def lex(source)
- ripper = Ripper::Lexer.new(source)
- ripper.lex.tap do |result|
- raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any?
+
+ unless data_loc # no trailing :on_sp with __END__ as it is always preceded by :on_nl
+ end_offset = eof_token.location.end_offset
+ if prev_token_end < end_offset
+ new_tokens << IgnoreStateToken.new([
+ [source.line(prev_token_end), source.column(prev_token_end)],
+ :on_sp,
+ source.slice(prev_token_end, end_offset - prev_token_end),
+ prev_token_state
+ ])
end
end
+
+ new_tokens
end
end
- private_constant :LexRipper
+ private_constant :LexCompat
end
diff --git a/lib/prism/lex_ripper.rb b/lib/prism/lex_ripper.rb
new file mode 100644
index 0000000000..2054cf55ac
--- /dev/null
+++ b/lib/prism/lex_ripper.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+# :markup: markdown
+
+require "ripper"
+
+module Prism
+ # This is a class that wraps the Ripper lexer to produce almost exactly the
+ # same tokens.
+ class LexRipper # :nodoc:
+ attr_reader :source
+
+ def initialize(source)
+ @source = source
+ end
+
+ def result
+ previous = [] #: [[Integer, Integer], Symbol, String, untyped] | []
+ results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]]
+
+ lex(source).each do |token|
+ case token[1]
+ when :on_tstring_content
+ if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@"))
+ previous[2] << token[2]
+ else
+ results << token
+ previous = token
+ end
+ when :on_words_sep
+ if previous[1] == :on_words_sep
+ previous[2] << token[2]
+ else
+ results << token
+ previous = token
+ end
+ else
+ results << token
+ previous = token
+ end
+ end
+
+ results
+ end
+
+ private
+
+ if Ripper.method(:lex).parameters.assoc(:keyrest)
+ def lex(source)
+ Ripper.lex(source, raise_errors: true)
+ end
+ else
+ def lex(source)
+ ripper = Ripper::Lexer.new(source)
+ ripper.lex.tap do |result|
+ raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any?
+ end
+ end
+ end
+ end
+
+ private_constant :LexRipper
+end
diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb
index b007a051ea..469e54ca0c 100644
--- a/lib/prism/node_ext.rb
+++ b/lib/prism/node_ext.rb
@@ -1,7 +1,10 @@
# frozen_string_literal: true
+# :markup: markdown
+#--
# Here we are reopening the prism module to provide methods on nodes that aren't
# templated and are meant as convenience methods.
+#++
module Prism
class Node
def deprecated(*replacements) # :nodoc:
diff --git a/lib/prism/pack.rb b/lib/prism/pack.rb
index c0de8ab8b7..166c04c3c0 100644
--- a/lib/prism/pack.rb
+++ b/lib/prism/pack.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
+# :markup: markdown
# typed: ignore
+#
module Prism
# A parser for the pack template language.
module Pack
diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb
index 9a3e7c5b79..12d19da562 100644
--- a/lib/prism/parse_result.rb
+++ b/lib/prism/parse_result.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
# This represents a source of Ruby code that has been parsed. It is used in
@@ -75,6 +76,15 @@ module Prism
source.byteslice(byte_offset, length) or raise
end
+ # Converts the line number to a byte offset corresponding to the start of that line
+ def line_to_byte_offset(line)
+ l = line - @start_line
+ if l < 0 || l >= offsets.size
+ raise ArgumentError, "line #{line} is out of range"
+ end
+ offsets[l]
+ end
+
# Binary search through the offsets to find the line number for the given
# byte offset.
def line(byte_offset)
@@ -154,21 +164,8 @@ module Prism
# Binary search through the offsets to find the line number for the given
# byte offset.
def find_line(byte_offset)
- left = 0
- right = offsets.length - 1
-
- while left <= right
- mid = left + (right - left) / 2
- return mid if (offset = offsets[mid]) == byte_offset
-
- if offset < byte_offset
- left = mid + 1
- else
- right = mid - 1
- end
- end
-
- left - 1
+ index = offsets.bsearch_index { |offset| offset > byte_offset } || offsets.length
+ index - 1
end
end
diff --git a/lib/prism/parse_result/comments.rb b/lib/prism/parse_result/comments.rb
index 22c4148b2c..3e93316aff 100644
--- a/lib/prism/parse_result/comments.rb
+++ b/lib/prism/parse_result/comments.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
class ParseResult < Result
diff --git a/lib/prism/parse_result/errors.rb b/lib/prism/parse_result/errors.rb
index eb4f317248..26c376b3ce 100644
--- a/lib/prism/parse_result/errors.rb
+++ b/lib/prism/parse_result/errors.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
require "stringio"
diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb
index 37f64f8ae2..e7fd62cafe 100644
--- a/lib/prism/parse_result/newlines.rb
+++ b/lib/prism/parse_result/newlines.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
class ParseResult < Result
diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb
index 03fec26789..6ad2d9e5b9 100644
--- a/lib/prism/pattern.rb
+++ b/lib/prism/pattern.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
# A pattern is an object that wraps a Ruby pattern matching expression. The
diff --git a/lib/prism/polyfill/scan_byte.rb b/lib/prism/polyfill/scan_byte.rb
new file mode 100644
index 0000000000..9276e509fc
--- /dev/null
+++ b/lib/prism/polyfill/scan_byte.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require "strscan"
+
+# Polyfill for StringScanner#scan_byte, which didn't exist until Ruby 3.4.
+if !(StringScanner.method_defined?(:scan_byte))
+ StringScanner.include(
+ Module.new {
+ def scan_byte # :nodoc:
+ get_byte&.b&.ord
+ end
+ }
+ )
+end
diff --git a/lib/prism/polyfill/warn.rb b/lib/prism/polyfill/warn.rb
index 560380d308..76a4264623 100644
--- a/lib/prism/polyfill/warn.rb
+++ b/lib/prism/polyfill/warn.rb
@@ -7,17 +7,14 @@ if (method = Kernel.instance_method(:warn)).respond_to?(:parameters) ? method.pa
Kernel.prepend(
Module.new {
def warn(*msgs, uplevel: nil, category: nil) # :nodoc:
- uplevel =
- case uplevel
- when nil
- 1
- when Integer
- uplevel + 1
- else
- uplevel.to_int + 1
- end
-
- super(*msgs, uplevel: uplevel)
+ case uplevel
+ when nil
+ super(*msgs)
+ when Integer
+ super(*msgs, uplevel: uplevel + 1)
+ else
+ super(*msgs, uplevel: uplevel.to_int + 1)
+ end
end
}
)
@@ -25,17 +22,14 @@ if (method = Kernel.instance_method(:warn)).respond_to?(:parameters) ? method.pa
Object.prepend(
Module.new {
def warn(*msgs, uplevel: nil, category: nil) # :nodoc:
- uplevel =
- case uplevel
- when nil
- 1
- when Integer
- uplevel + 1
- else
- uplevel.to_int + 1
- end
-
- super(*msgs, uplevel: uplevel)
+ case uplevel
+ when nil
+ super(*msgs)
+ when Integer
+ super(*msgs, uplevel: uplevel + 1)
+ else
+ super(*msgs, uplevel: uplevel.to_int + 1)
+ end
end
}
)
diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec
index 5cb5a98057..283c7b04aa 100644
--- a/lib/prism/prism.gemspec
+++ b/lib/prism/prism.gemspec
@@ -2,7 +2,7 @@
Gem::Specification.new do |spec|
spec.name = "prism"
- spec.version = "1.4.0"
+ spec.version = "1.8.0"
spec.authors = ["Shopify"]
spec.email = ["ruby@shopify.com"]
@@ -77,6 +77,7 @@ Gem::Specification.new do |spec|
"lib/prism/ffi.rb",
"lib/prism/inspect_visitor.rb",
"lib/prism/lex_compat.rb",
+ "lib/prism/lex_ripper.rb",
"lib/prism/mutation_compiler.rb",
"lib/prism/node_ext.rb",
"lib/prism/node.rb",
@@ -88,6 +89,7 @@ Gem::Specification.new do |spec|
"lib/prism/pattern.rb",
"lib/prism/polyfill/append_as_bytes.rb",
"lib/prism/polyfill/byteindex.rb",
+ "lib/prism/polyfill/scan_byte.rb",
"lib/prism/polyfill/unpack1.rb",
"lib/prism/polyfill/warn.rb",
"lib/prism/reflection.rb",
@@ -97,13 +99,13 @@ Gem::Specification.new do |spec|
"lib/prism/translation.rb",
"lib/prism/translation/parser.rb",
"lib/prism/translation/parser_current.rb",
- "lib/prism/translation/parser33.rb",
- "lib/prism/translation/parser34.rb",
- "lib/prism/translation/parser35.rb",
+ "lib/prism/translation/parser_versions.rb",
"lib/prism/translation/parser/builder.rb",
"lib/prism/translation/parser/compiler.rb",
"lib/prism/translation/parser/lexer.rb",
"lib/prism/translation/ripper.rb",
+ "lib/prism/translation/ripper/filter.rb",
+ "lib/prism/translation/ripper/lexer.rb",
"lib/prism/translation/ripper/sexp.rb",
"lib/prism/translation/ripper/shim.rb",
"lib/prism/translation/ruby_parser.rb",
@@ -119,9 +121,7 @@ Gem::Specification.new do |spec|
"rbi/prism/reflection.rbi",
"rbi/prism/string_query.rbi",
"rbi/prism/translation/parser.rbi",
- "rbi/prism/translation/parser33.rbi",
- "rbi/prism/translation/parser34.rbi",
- "rbi/prism/translation/parser35.rbi",
+ "rbi/prism/translation/parser_versions.rbi",
"rbi/prism/translation/ripper.rbi",
"rbi/prism/visitor.rbi",
"sig/prism.rbs",
diff --git a/lib/prism/relocation.rb b/lib/prism/relocation.rb
index 163d2012c5..3e9210a785 100644
--- a/lib/prism/relocation.rb
+++ b/lib/prism/relocation.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
# Prism parses deterministically for the same input. This provides a nice
diff --git a/lib/prism/string_query.rb b/lib/prism/string_query.rb
index 9011051d2b..547f58d2fa 100644
--- a/lib/prism/string_query.rb
+++ b/lib/prism/string_query.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
# Query methods that allow categorizing strings based on their context for
diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb
index 511c80febc..57b57135bc 100644
--- a/lib/prism/translation.rb
+++ b/lib/prism/translation.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
# This module is responsible for converting the prism syntax tree into other
@@ -6,9 +7,11 @@ module Prism
module Translation # steep:ignore
autoload :Parser, "prism/translation/parser"
autoload :ParserCurrent, "prism/translation/parser_current"
- autoload :Parser33, "prism/translation/parser33"
- autoload :Parser34, "prism/translation/parser34"
- autoload :Parser35, "prism/translation/parser35"
+ autoload :Parser33, "prism/translation/parser_versions"
+ autoload :Parser34, "prism/translation/parser_versions"
+ autoload :Parser35, "prism/translation/parser_versions"
+ autoload :Parser40, "prism/translation/parser_versions"
+ autoload :Parser41, "prism/translation/parser_versions"
autoload :Ripper, "prism/translation/ripper"
autoload :RubyParser, "prism/translation/ruby_parser"
end
diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb
index d43ad7c1e4..fed4ac4cd1 100644
--- a/lib/prism/translation/parser.rb
+++ b/lib/prism/translation/parser.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
begin
required_version = ">= 3.3.7.2"
@@ -18,6 +19,13 @@ module Prism
# whitequark/parser gem's syntax tree. It inherits from the base parser for
# the parser gem, and overrides the parse* methods to parse with prism and
# then translate.
+ #
+ # Note that this version of the parser always parses using the latest
+ # version of Ruby syntax supported by Prism. If you want specific version
+ # support, use one of the version-specific subclasses, such as
+ # `Prism::Translation::Parser34`. If you want to parse using the same
+ # version of Ruby syntax as the currently running version of Ruby, use
+ # `Prism::Translation::ParserCurrent`.
class Parser < ::Parser::Base
Diagnostic = ::Parser::Diagnostic # :nodoc:
private_constant :Diagnostic
@@ -76,7 +84,7 @@ module Prism
end
def version # :nodoc:
- 34
+ 41
end
# The default encoding for Ruby files is UTF-8.
@@ -348,8 +356,10 @@ module Prism
"3.3.1"
when 34
"3.4.0"
- when 35
- "3.5.0"
+ when 35, 40
+ "4.0.0"
+ when 41
+ "4.1.0"
else
"latest"
end
diff --git a/lib/prism/translation/parser/builder.rb b/lib/prism/translation/parser/builder.rb
index d3b51f4275..6b620c25bc 100644
--- a/lib/prism/translation/parser/builder.rb
+++ b/lib/prism/translation/parser/builder.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
module Translation
diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb
index 0bd9d74f93..8805614603 100644
--- a/lib/prism/translation/parser/compiler.rb
+++ b/lib/prism/translation/parser/compiler.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
module Prism
module Translation
@@ -216,7 +217,7 @@ module Prism
rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil,
token(rescue_clause.operator_loc),
visit(rescue_clause.reference),
- srange_find(find_start_offset, find_end_offset, ";"),
+ srange_semicolon(find_start_offset, find_end_offset),
visit(rescue_clause.statements)
)
end until (rescue_clause = rescue_clause.subsequent).nil?
@@ -322,7 +323,7 @@ module Prism
visit_all(arguments),
token(node.closing_loc),
),
- srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, "="),
+ token(node.equal_loc),
visit(node.arguments.arguments.last)
),
block
@@ -339,7 +340,7 @@ module Prism
if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil?
builder.assign(
builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)),
- srange_find(message_loc.end_offset, node.arguments.location.start_offset, "="),
+ token(node.equal_loc),
visit(node.arguments.arguments.last)
)
else
@@ -788,7 +789,7 @@ module Prism
if (do_keyword_loc = node.do_keyword_loc)
token(do_keyword_loc)
else
- srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, ";")
+ srange_semicolon(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset)
end,
visit(node.statements),
token(node.end_keyword_loc)
@@ -920,7 +921,7 @@ module Prism
if (then_keyword_loc = node.then_keyword_loc)
token(then_keyword_loc)
else
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, ";")
+ srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset)
end,
visit(node.statements),
case node.subsequent
@@ -986,7 +987,7 @@ module Prism
if (then_loc = node.then_loc)
token(then_loc)
else
- srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, ";")
+ srange_semicolon(node.pattern.location.end_offset, node.statements&.location&.start_offset)
end,
visit(node.statements)
)
@@ -1807,7 +1808,7 @@ module Prism
if (then_keyword_loc = node.then_keyword_loc)
token(then_keyword_loc)
else
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, ";")
+ srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset)
end,
visit(node.else_clause),
token(node.else_clause&.else_keyword_loc),
@@ -1838,7 +1839,7 @@ module Prism
if (do_keyword_loc = node.do_keyword_loc)
token(do_keyword_loc)
else
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";")
+ srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset)
end,
visit(node.statements),
token(node.closing_loc)
@@ -1862,7 +1863,7 @@ module Prism
if (then_keyword_loc = node.then_keyword_loc)
token(then_keyword_loc)
else
- srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, ";")
+ srange_semicolon(node.conditions.last.location.end_offset, node.statements&.location&.start_offset)
end,
visit(node.statements)
)
@@ -1882,7 +1883,7 @@ module Prism
if (do_keyword_loc = node.do_keyword_loc)
token(do_keyword_loc)
else
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";")
+ srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset)
end,
visit(node.statements),
token(node.closing_loc)
@@ -2011,16 +2012,16 @@ module Prism
Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset])
end
- # Constructs a new source range by finding the given character between
- # the given start offset and end offset. If the needle is not found, it
- # returns nil. Importantly it does not search past newlines or comments.
+ # Constructs a new source range by finding a semicolon between the given
+ # start offset and end offset. If the semicolon is not found, it returns
+ # nil. Importantly it does not search past newlines or comments.
#
# Note that end_offset is allowed to be nil, in which case this will
# search until the end of the string.
- def srange_find(start_offset, end_offset, character)
- if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*#{character}/])
+ def srange_semicolon(start_offset, end_offset)
+ if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*;/])
final_offset = start_offset + match.bytesize
- [character, Range.new(source_buffer, offset_cache[final_offset - character.bytesize], offset_cache[final_offset])]
+ [";", Range.new(source_buffer, offset_cache[final_offset - 1], offset_cache[final_offset])]
end
end
diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb
index 8f2d065b73..75c48ef667 100644
--- a/lib/prism/translation/parser/lexer.rb
+++ b/lib/prism/translation/parser/lexer.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
+# :markup: markdown
require "strscan"
require_relative "../../polyfill/append_as_bytes"
+require_relative "../../polyfill/scan_byte"
module Prism
module Translation
@@ -201,7 +203,7 @@ module Prism
# The following token types are listed as those classified as `tLPAREN`.
LPAREN_CONVERSION_TOKEN_TYPES = Set.new([
:kBREAK, :tCARET, :kCASE, :tDIVIDE, :kFOR, :kIF, :kNEXT, :kRETURN, :kUNTIL, :kWHILE, :tAMPER, :tANDOP, :tBANG, :tCOMMA, :tDOT2, :tDOT3,
- :tEQL, :tLPAREN, :tLPAREN2, :tLPAREN_ARG, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS
+ :tEQL, :tLPAREN, :tLPAREN2, :tLPAREN_ARG, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS, :tLCURLY
])
# Types of tokens that are allowed to continue a method call with comments in-between.
@@ -275,20 +277,20 @@ module Prism
when :tCOMMENT
if token.type == :EMBDOC_BEGIN
- while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1)
+ while !((next_token = lexed[index]&.first) && next_token.type == :EMBDOC_END) && (index < length - 1)
value += next_token.value
index += 1
end
value += next_token.value
- location = range(token.location.start_offset, lexed[index][0].location.end_offset)
+ location = range(token.location.start_offset, next_token.location.end_offset)
index += 1
else
is_at_eol = value.chomp!.nil?
location = range(token.location.start_offset, token.location.end_offset + (is_at_eol ? 0 : -1))
- prev_token = lexed[index - 2][0] if index - 2 >= 0
- next_token = lexed[index][0]
+ prev_token, _ = lexed[index - 2] if index - 2 >= 0
+ next_token, _ = lexed[index]
is_inline_comment = prev_token&.location&.start_line == token.location.start_line
if is_inline_comment && !is_at_eol && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type)
@@ -307,7 +309,7 @@ module Prism
end
end
when :tNL
- next_token = next_token = lexed[index][0]
+ next_token, _ = lexed[index]
# Newlines after comments are emitted out of order.
if next_token&.type == :COMMENT
comment_newline_location = location
@@ -344,8 +346,8 @@ module Prism
location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value))
value = nil
when :tSTRING_BEG
- next_token = lexed[index][0]
- next_next_token = lexed[index + 1][0]
+ next_token, _ = lexed[index]
+ next_next_token, _ = lexed[index + 1]
basic_quotes = value == '"' || value == "'"
if basic_quotes && next_token&.type == :STRING_END
@@ -413,7 +415,8 @@ module Prism
while token.type == :STRING_CONTENT
current_length += token.value.bytesize
# Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line.
- is_first_token_on_line = lexed[index - 1] && token.location.start_line != lexed[index - 2][0].location&.start_line
+ prev_token, _ = lexed[index - 2] if index - 2 >= 0
+ is_first_token_on_line = prev_token && token.location.start_line != prev_token.location.start_line
# The parser gem only removes indentation when the heredoc is not nested
not_nested = heredoc_stack.size == 1
if is_percent_array
@@ -423,11 +426,16 @@ module Prism
end
current_string << unescape_string(value, quote_stack.last)
- if (backslash_count = token.value[/(\\{1,})\n/, 1]&.length).nil? || backslash_count.even? || !interpolation?(quote_stack.last)
+ relevant_backslash_count = if quote_stack.last.start_with?("%W", "%I")
+ 0 # the last backslash escapes the newline
+ else
+ token.value[/(\\{1,})\n/, 1]&.length || 0
+ end
+ if relevant_backslash_count.even? || !interpolation?(quote_stack.last)
tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]]
break
end
- token = lexed[index][0]
+ token, _ = lexed[index]
index += 1
end
else
@@ -482,7 +490,7 @@ module Prism
end
if percent_array?(quote_stack.pop)
- prev_token = lexed[index - 2][0] if index - 2 >= 0
+ prev_token, _ = lexed[index - 2] if index - 2 >= 0
empty = %i[PERCENT_LOWER_I PERCENT_LOWER_W PERCENT_UPPER_I PERCENT_UPPER_W].include?(prev_token&.type)
ends_with_whitespace = prev_token&.type == :WORDS_SEP
# parser always emits a space token after content in a percent array, even if no actual whitespace is present.
@@ -491,7 +499,7 @@ module Prism
end
end
when :tSYMBEG
- if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END
+ if (next_token = lexed[index]&.first) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END
next_location = token.location.join(next_token.location)
type = :tSYMBOL
value = next_token.value
@@ -506,13 +514,13 @@ module Prism
type = :tIDENTIFIER
end
when :tXSTRING_BEG
- if (next_token = lexed[index][0]) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type)
+ if (next_token = lexed[index]&.first) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type)
# self.`()
type = :tBACK_REF2
end
quote_stack.push(value)
when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG
- if (next_token = lexed[index][0]) && next_token.type == :WORDS_SEP
+ if (next_token = lexed[index]&.first) && next_token.type == :WORDS_SEP
index += 1
end
@@ -588,9 +596,9 @@ module Prism
previous_line = -1
result = Float::MAX
- while (lexed[next_token_index] && next_token = lexed[next_token_index][0])
+ while (next_token = lexed[next_token_index]&.first)
next_token_index += 1
- next_next_token = lexed[next_token_index] && lexed[next_token_index][0]
+ next_next_token, _ = lexed[next_token_index]
first_token_on_line = next_token.location.start_column == 0
# String content inside nested heredocs and interpolation is ignored
@@ -761,12 +769,12 @@ module Prism
elsif (value = scanner.scan(/M-\\?(?=[[:print:]])/))
# \M-x where x is an ASCII printable character
escape_read(result, scanner, control, true)
- elsif (byte = scanner.get_byte)
+ elsif (byte = scanner.scan_byte)
# Something else after an escape.
- if control && byte == "?"
+ if control && byte == 0x3f # ASCII '?'
result.append_as_bytes(escape_build(0x7f, false, meta))
else
- result.append_as_bytes(escape_build(byte.ord, control, meta))
+ result.append_as_bytes(escape_build(byte, control, meta))
end
end
end
diff --git a/lib/prism/translation/parser33.rb b/lib/prism/translation/parser33.rb
deleted file mode 100644
index b09266e06a..0000000000
--- a/lib/prism/translation/parser33.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module Prism
- module Translation
- # This class is the entry-point for Ruby 3.3 of `Prism::Translation::Parser`.
- class Parser33 < Parser
- def version # :nodoc:
- 33
- end
- end
- end
-end
diff --git a/lib/prism/translation/parser34.rb b/lib/prism/translation/parser34.rb
deleted file mode 100644
index 0ead70ad3c..0000000000
--- a/lib/prism/translation/parser34.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module Prism
- module Translation
- # This class is the entry-point for Ruby 3.4 of `Prism::Translation::Parser`.
- class Parser34 < Parser
- def version # :nodoc:
- 34
- end
- end
- end
-end
diff --git a/lib/prism/translation/parser35.rb b/lib/prism/translation/parser35.rb
deleted file mode 100644
index a6abc12589..0000000000
--- a/lib/prism/translation/parser35.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module Prism
- module Translation
- # This class is the entry-point for Ruby 3.5 of `Prism::Translation::Parser`.
- class Parser35 < Parser
- def version # :nodoc:
- 35
- end
- end
- end
-end
diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb
index b44769fde7..f13eff6bbe 100644
--- a/lib/prism/translation/parser_current.rb
+++ b/lib/prism/translation/parser_current.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
+# :markup: markdown
# typed: ignore
+#
module Prism
module Translation
case RUBY_VERSION
@@ -8,11 +10,13 @@ module Prism
ParserCurrent = Parser33
when /^3\.4\./
ParserCurrent = Parser34
- when /^3\.5\./
- ParserCurrent = Parser35
+ when /^3\.5\./, /^4\.0\./
+ ParserCurrent = Parser40
+ when /^4\.1\./
+ ParserCurrent = Parser41
else
# Keep this in sync with released Ruby.
- parser = Parser34
+ parser = Parser40
major, minor, _patch = Gem::Version.new(RUBY_VERSION).segments
warn "warning: `Prism::Translation::Current` is loading #{parser.name}, " \
"but you are running #{major}.#{minor}."
diff --git a/lib/prism/translation/parser_versions.rb b/lib/prism/translation/parser_versions.rb
new file mode 100644
index 0000000000..720c7d548c
--- /dev/null
+++ b/lib/prism/translation/parser_versions.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+# :markup: markdown
+
+module Prism
+ module Translation
+ # This class is the entry-point for Ruby 3.3 of `Prism::Translation::Parser`.
+ class Parser33 < Parser
+ def version # :nodoc:
+ 33
+ end
+ end
+
+ # This class is the entry-point for Ruby 3.4 of `Prism::Translation::Parser`.
+ class Parser34 < Parser
+ def version # :nodoc:
+ 34
+ end
+ end
+
+ # This class is the entry-point for Ruby 4.0 of `Prism::Translation::Parser`.
+ class Parser40 < Parser
+ def version # :nodoc:
+ 40
+ end
+ end
+
+ Parser35 = Parser40 # :nodoc:
+
+ # This class is the entry-point for Ruby 4.1 of `Prism::Translation::Parser`.
+ class Parser41 < Parser
+ def version # :nodoc:
+ 41
+ end
+ end
+ end
+end
diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb
index 95f366ac91..735217d2e0 100644
--- a/lib/prism/translation/ripper.rb
+++ b/lib/prism/translation/ripper.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-
-require "ripper"
+# :markup: markdown
module Prism
module Translation
@@ -70,7 +69,7 @@ module Prism
# [[1, 13], :on_kw, "end", END ]]
#
def self.lex(src, filename = "-", lineno = 1, raise_errors: false)
- result = Prism.lex_compat(src, filepath: filename, line: lineno)
+ result = Prism.lex_compat(src, filepath: filename, line: lineno, version: "current")
if result.failure? && raise_errors
raise SyntaxError, result.errors.first.message
@@ -79,6 +78,19 @@ module Prism
end
end
+ # Tokenizes the Ruby program and returns an array of strings.
+ # The +filename+ and +lineno+ arguments are mostly ignored, since the
+ # return value is just the tokenized input.
+ # By default, this method does not handle syntax errors in +src+,
+ # use the +raise_errors+ keyword to raise a SyntaxError for an error in +src+.
+ #
+ # p Ripper.tokenize("def m(a) nil end")
+ # # => ["def", " ", "m", "(", "a", ")", " ", "nil", " ", "end"]
+ #
+ def self.tokenize(...)
+ lex(...).map(&:value)
+ end
+
# This contains a table of all of the parser events and their
# corresponding arity.
PARSER_EVENT_TABLE = {
@@ -425,9 +437,35 @@ module Prism
end
end
+ autoload :Filter, "prism/translation/ripper/filter"
+ autoload :Lexer, "prism/translation/ripper/lexer"
autoload :SexpBuilder, "prism/translation/ripper/sexp"
autoload :SexpBuilderPP, "prism/translation/ripper/sexp"
+ # :stopdoc:
+ # This is not part of the public API but used by some gems.
+
+ # Ripper-internal bitflags.
+ LEX_STATE_NAMES = %i[
+ BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM
+ ].map.with_index.to_h { |name, i| [2 ** i, name] }.freeze
+ private_constant :LEX_STATE_NAMES
+
+ LEX_STATE_NAMES.each do |value, key|
+ const_set("EXPR_#{key}", value)
+ end
+ EXPR_NONE = 0
+ EXPR_VALUE = EXPR_BEG
+ EXPR_BEG_ANY = EXPR_BEG | EXPR_MID | EXPR_CLASS
+ EXPR_ARG_ANY = EXPR_ARG | EXPR_CMDARG
+ EXPR_END_ANY = EXPR_END | EXPR_ENDARG | EXPR_ENDFN
+
+ def self.lex_state_name(state)
+ LEX_STATE_NAMES.filter_map { |flag, name| name if state & flag != 0 }.join("|")
+ end
+
+ # :startdoc:
+
# The source that is being parsed.
attr_reader :source
@@ -794,7 +832,7 @@ module Prism
# foo(bar)
# ^^^
def visit_arguments_node(node)
- arguments, _ = visit_call_node_arguments(node, nil, false)
+ arguments, _, _ = visit_call_node_arguments(node, nil, false)
arguments
end
@@ -1004,16 +1042,16 @@ module Prism
case node.name
when :[]
receiver = visit(node.receiver)
- arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
+ arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
bounds(node.location)
call = on_aref(receiver, arguments)
- if block.nil?
- call
- else
+ if has_ripper_block
bounds(node.location)
on_method_add_block(call, block)
+ else
+ call
end
when :[]=
receiver = visit(node.receiver)
@@ -1072,9 +1110,9 @@ module Prism
if node.variable_call?
on_vcall(message)
else
- arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
+ arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
call =
- if node.opening_loc.nil? && arguments&.any?
+ if node.opening_loc.nil? && get_arguments_and_block(node.arguments, node.block).first.any?
bounds(node.location)
on_command(message, arguments)
elsif !node.opening_loc.nil?
@@ -1085,11 +1123,11 @@ module Prism
on_method_add_arg(on_fcall(message), on_args_new)
end
- if block.nil?
- call
- else
+ if has_ripper_block
bounds(node.block.location)
on_method_add_block(call, block)
+ else
+ call
end
end
end
@@ -1113,7 +1151,7 @@ module Prism
bounds(node.location)
on_assign(on_field(receiver, call_operator, message), value)
else
- arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
+ arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location))
call =
if node.opening_loc.nil?
bounds(node.location)
@@ -1131,27 +1169,35 @@ module Prism
on_method_add_arg(on_call(receiver, call_operator, message), arguments)
end
- if block.nil?
- call
- else
+ if has_ripper_block
bounds(node.block.location)
on_method_add_block(call, block)
+ else
+ call
end
end
end
end
- # Visit the arguments and block of a call node and return the arguments
- # and block as they should be used.
- private def visit_call_node_arguments(arguments_node, block_node, trailing_comma)
+ # Extract the arguments and block Ripper-style, which means if the block
+ # is like `&b` then it's moved to arguments.
+ private def get_arguments_and_block(arguments_node, block_node)
arguments = arguments_node&.arguments || []
block = block_node
if block.is_a?(BlockArgumentNode)
- arguments << block
+ arguments += [block]
block = nil
end
+ [arguments, block]
+ end
+
+ # Visit the arguments and block of a call node and return the arguments
+ # and block as they should be used.
+ private def visit_call_node_arguments(arguments_node, block_node, trailing_comma)
+ arguments, block = get_arguments_and_block(arguments_node, block_node)
+
[
if arguments.length == 1 && arguments.first.is_a?(ForwardingArgumentsNode)
visit(arguments.first)
@@ -1165,7 +1211,8 @@ module Prism
on_args_add_block(args, false)
end
end,
- visit(block)
+ visit(block),
+ block != nil,
]
end
@@ -1602,10 +1649,10 @@ module Prism
end
bounds(node.location)
- if receiver.nil?
- on_def(name, parameters, bodystmt)
- else
+ if receiver
on_defs(receiver, operator, name, parameters, bodystmt)
+ else
+ on_def(name, parameters, bodystmt)
end
end
@@ -2003,7 +2050,7 @@ module Prism
# ^^^^^^^^^^^^^^^
def visit_index_operator_write_node(node)
receiver = visit(node.receiver)
- arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
+ arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
bounds(node.location)
target = on_aref_field(receiver, arguments)
@@ -2020,7 +2067,7 @@ module Prism
# ^^^^^^^^^^^^^^^^
def visit_index_and_write_node(node)
receiver = visit(node.receiver)
- arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
+ arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
bounds(node.location)
target = on_aref_field(receiver, arguments)
@@ -2037,7 +2084,7 @@ module Prism
# ^^^^^^^^^^^^^^^^
def visit_index_or_write_node(node)
receiver = visit(node.receiver)
- arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
+ arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
bounds(node.location)
target = on_aref_field(receiver, arguments)
@@ -2054,7 +2101,7 @@ module Prism
# ^^^^^^^^
def visit_index_target_node(node)
receiver = visit(node.receiver)
- arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
+ arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc))
bounds(node.location)
on_aref_field(receiver, arguments)
@@ -3084,7 +3131,7 @@ module Prism
# super(foo)
# ^^^^^^^^^^
def visit_super_node(node)
- arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location))
+ arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location))
if !node.lparen_loc.nil?
bounds(node.lparen_loc)
@@ -3094,11 +3141,11 @@ module Prism
bounds(node.location)
call = on_super(arguments)
- if block.nil?
- call
- else
+ if has_ripper_block
bounds(node.block.location)
on_method_add_block(call, block)
+ else
+ call
end
end
@@ -3294,7 +3341,7 @@ module Prism
# Lazily initialize the parse result.
def result
- @result ||= Prism.parse(source, partial_script: true)
+ @result ||= Prism.parse(source, partial_script: true, version: "current")
end
##########################################################################
@@ -3408,12 +3455,12 @@ module Prism
# :stopdoc:
def _dispatch_0; end
- def _dispatch_1(_); end
- def _dispatch_2(_, _); end
- def _dispatch_3(_, _, _); end
- def _dispatch_4(_, _, _, _); end
- def _dispatch_5(_, _, _, _, _); end
- def _dispatch_7(_, _, _, _, _, _, _); end
+ def _dispatch_1(arg); arg end
+ def _dispatch_2(arg, _); arg end
+ def _dispatch_3(arg, _, _); arg end
+ def _dispatch_4(arg, _, _, _); arg end
+ def _dispatch_5(arg, _, _, _, _); arg end
+ def _dispatch_7(arg, _, _, _, _, _, _); arg end
# :startdoc:
#
diff --git a/lib/prism/translation/ripper/filter.rb b/lib/prism/translation/ripper/filter.rb
new file mode 100644
index 0000000000..19deef2d37
--- /dev/null
+++ b/lib/prism/translation/ripper/filter.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Prism
+ module Translation
+ class Ripper
+ class Filter # :nodoc:
+ # :stopdoc:
+ def initialize(src, filename = '-', lineno = 1)
+ @__lexer = Lexer.new(src, filename, lineno)
+ @__line = nil
+ @__col = nil
+ @__state = nil
+ end
+
+ def filename
+ @__lexer.filename
+ end
+
+ def lineno
+ @__line
+ end
+
+ def column
+ @__col
+ end
+
+ def state
+ @__state
+ end
+
+ def parse(init = nil)
+ data = init
+ @__lexer.lex.each do |pos, event, tok, state|
+ @__line, @__col = *pos
+ @__state = state
+ data = if respond_to?(event, true)
+ then __send__(event, tok, data)
+ else on_default(event, tok, data)
+ end
+ end
+ data
+ end
+
+ private
+
+ def on_default(event, token, data)
+ data
+ end
+ # :startdoc:
+ end
+ end
+ end
+end
diff --git a/lib/prism/translation/ripper/lexer.rb b/lib/prism/translation/ripper/lexer.rb
new file mode 100644
index 0000000000..bed863af08
--- /dev/null
+++ b/lib/prism/translation/ripper/lexer.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+# :markup: markdown
+
+require_relative "../ripper"
+
+module Prism
+ module Translation
+ class Ripper
+ class Lexer < Ripper # :nodoc:
+ # :stopdoc:
+ class State
+
+ attr_reader :to_int, :to_s
+
+ def initialize(i)
+ @to_int = i
+ @to_s = Ripper.lex_state_name(i)
+ freeze
+ end
+
+ def [](index)
+ case index
+ when 0, :to_int
+ @to_int
+ when 1, :to_s
+ @to_s
+ else
+ nil
+ end
+ end
+
+ alias to_i to_int
+ alias inspect to_s
+ def pretty_print(q) q.text(to_s) end
+ def ==(i) super or to_int == i end
+ def &(i) self.class.new(to_int & i) end
+ def |(i) self.class.new(to_int | i) end
+ def allbits?(i) to_int.allbits?(i) end
+ def anybits?(i) to_int.anybits?(i) end
+ def nobits?(i) to_int.nobits?(i) end
+
+ # Instances are frozen and there are only a handful of them so we cache them here.
+ STATES = Hash.new { |h,k| h[k] = State.new(k) }
+
+ def self.cached(i)
+ STATES[i]
+ end
+ end
+
+ class Elem
+ attr_accessor :pos, :event, :tok, :state, :message
+
+ def initialize(pos, event, tok, state, message = nil)
+ @pos = pos
+ @event = event
+ @tok = tok
+ @state = State.cached(state)
+ @message = message
+ end
+
+ def [](index)
+ case index
+ when 0, :pos
+ @pos
+ when 1, :event
+ @event
+ when 2, :tok
+ @tok
+ when 3, :state
+ @state
+ when 4, :message
+ @message
+ else
+ nil
+ end
+ end
+
+ def inspect
+ "#<#{self.class}: #{event}@#{pos[0]}:#{pos[1]}:#{state}: #{tok.inspect}#{": " if message}#{message}>"
+ end
+
+ alias to_s inspect
+
+ def pretty_print(q)
+ q.group(2, "#<#{self.class}:", ">") {
+ q.breakable
+ q.text("#{event}@#{pos[0]}:#{pos[1]}")
+ q.breakable
+ state.pretty_print(q)
+ q.breakable
+ q.text("token: ")
+ tok.pretty_print(q)
+ if message
+ q.breakable
+ q.text("message: ")
+ q.text(message)
+ end
+ }
+ end
+
+ def to_a
+ if @message
+ [@pos, @event, @tok, @state, @message]
+ else
+ [@pos, @event, @tok, @state]
+ end
+ end
+ end
+
+ # Pretty much just the same as Prism.lex_compat.
+ def lex(raise_errors: false)
+ Ripper.lex(@source, filename, lineno, raise_errors: raise_errors)
+ end
+
+ # Returns the lex_compat result wrapped in `Elem`. Errors are omitted.
+ # Since ripper is a streaming parser, tokens are expected to be emitted in the order
+ # that the parser encounters them. This is not implemented.
+ def parse(...)
+ lex(...).map do |position, event, token, state|
+ Elem.new(position, event, token, state.to_int)
+ end
+ end
+
+ # Similar to parse but ripper sorts the elements by position in the source. Also
+ # includes errors. Since prism does error recovery, in cases of syntax errors
+ # the result may differ greatly compared to ripper.
+ def scan(...)
+ parse(...)
+ end
+
+ # :startdoc:
+ end
+ end
+ end
+end
diff --git a/lib/prism/translation/ripper/sexp.rb b/lib/prism/translation/ripper/sexp.rb
index dc26a639a3..8cfefc8472 100644
--- a/lib/prism/translation/ripper/sexp.rb
+++ b/lib/prism/translation/ripper/sexp.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# :markup: markdown
require_relative "../ripper"
diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb
index 8784e22d10..c026c4ad9c 100644
--- a/lib/prism/translation/ruby_parser.rb
+++ b/lib/prism/translation/ruby_parser.rb
@@ -1,12 +1,18 @@
# frozen_string_literal: true
+# :markup: markdown
begin
- require "ruby_parser"
+ require "sexp"
rescue LoadError
- warn(%q{Error: Unable to load ruby_parser. Add `gem "ruby_parser"` to your Gemfile.})
+ warn(%q{Error: Unable to load sexp. Add `gem "sexp_processor"` to your Gemfile.})
exit(1)
end
+class RubyParser # :nodoc:
+ class SyntaxError < RuntimeError # :nodoc:
+ end
+end
+
module Prism
module Translation
# This module is the entry-point for converting a prism syntax tree into the
@@ -15,7 +21,7 @@ module Prism
# A prism visitor that builds Sexp objects.
class Compiler < ::Prism::Compiler
# This is the name of the file that we are compiling. We set it on every
- # Sexp object that is generated, and also use it to compile __FILE__
+ # Sexp object that is generated, and also use it to compile `__FILE__`
# nodes.
attr_reader :file
@@ -34,26 +40,34 @@ module Prism
@in_pattern = in_pattern
end
+ # ```
# alias foo bar
# ^^^^^^^^^^^^^
+ # ```
def visit_alias_method_node(node)
s(node, :alias, visit(node.new_name), visit(node.old_name))
end
+ # ```
# alias $foo $bar
# ^^^^^^^^^^^^^^^
+ # ```
def visit_alias_global_variable_node(node)
s(node, :valias, node.new_name.name, node.old_name.name)
end
+ # ```
# foo => bar | baz
# ^^^^^^^^^
+ # ```
def visit_alternation_pattern_node(node)
s(node, :or, visit(node.left), visit(node.right))
end
+ # ```
# a and b
# ^^^^^^^
+ # ```
def visit_and_node(node)
left = visit(node.left)
@@ -70,8 +84,10 @@ module Prism
end
end
+ # ```
# []
# ^^
+ # ```
def visit_array_node(node)
if in_pattern
s(node, :array_pat, nil).concat(visit_all(node.elements))
@@ -80,8 +96,10 @@ module Prism
end
end
+ # ```
# foo => [bar]
# ^^^^^
+ # ```
def visit_array_pattern_node(node)
if node.constant.nil? && node.requireds.empty? && node.rest.nil? && node.posts.empty?
s(node, :array_pat)
@@ -103,23 +121,29 @@ module Prism
end
end
+ # ```
# foo(bar)
# ^^^
+ # ```
def visit_arguments_node(node)
raise "Cannot visit arguments directly"
end
+ # ```
# { a: 1 }
# ^^^^
+ # ```
def visit_assoc_node(node)
[visit(node.key), visit(node.value)]
end
+ # ```
# def foo(**); bar(**); end
# ^^
#
# { **foo }
# ^^^^^
+ # ```
def visit_assoc_splat_node(node)
if node.value.nil?
[s(node, :kwsplat)]
@@ -128,14 +152,18 @@ module Prism
end
end
+ # ```
# $+
# ^^
+ # ```
def visit_back_reference_read_node(node)
- s(node, :back_ref, node.name.name.delete_prefix("$").to_sym)
+ s(node, :back_ref, node.name.to_s.delete_prefix("$").to_sym)
end
+ # ```
# begin end
# ^^^^^^^^^
+ # ```
def visit_begin_node(node)
result = node.statements.nil? ? s(node, :nil) : visit(node.statements)
@@ -167,16 +195,20 @@ module Prism
result
end
+ # ```
# foo(&bar)
# ^^^^
+ # ```
def visit_block_argument_node(node)
s(node, :block_pass).tap do |result|
result << visit(node.expression) unless node.expression.nil?
end
end
+ # ```
# foo { |; bar| }
# ^^^
+ # ```
def visit_block_local_variable_node(node)
node.name
end
@@ -186,8 +218,10 @@ module Prism
s(node, :block_pass, visit(node.expression))
end
+ # ```
# def foo(&bar); end
# ^^^^
+ # ```
def visit_block_parameter_node(node)
:"&#{node.name}"
end
@@ -228,11 +262,13 @@ module Prism
result
end
+ # ```
# break
# ^^^^^
#
# break foo
# ^^^^^^^^^
+ # ```
def visit_break_node(node)
if node.arguments.nil?
s(node, :break)
@@ -243,6 +279,7 @@ module Prism
end
end
+ # ```
# foo
# ^^^
#
@@ -251,6 +288,7 @@ module Prism
#
# foo.bar() {}
# ^^^^^^^^^^^^
+ # ```
def visit_call_node(node)
case node.name
when :!~
@@ -289,8 +327,10 @@ module Prism
visit_block(node, result, block)
end
+ # ```
# foo.bar += baz
# ^^^^^^^^^^^^^^^
+ # ```
def visit_call_operator_write_node(node)
if op_asgn?(node)
s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, node.binary_operator)
@@ -299,8 +339,10 @@ module Prism
end
end
+ # ```
# foo.bar &&= baz
# ^^^^^^^^^^^^^^^
+ # ```
def visit_call_and_write_node(node)
if op_asgn?(node)
s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"&&")
@@ -309,8 +351,10 @@ module Prism
end
end
+ # ```
# foo.bar ||= baz
# ^^^^^^^^^^^^^^^
+ # ```
def visit_call_or_write_node(node)
if op_asgn?(node)
s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"||")
@@ -332,32 +376,42 @@ module Prism
node.safe_navigation? ? :"safe_#{type}" : type
end
+ # ```
# foo.bar, = 1
# ^^^^^^^
+ # ```
def visit_call_target_node(node)
s(node, :attrasgn, visit(node.receiver), node.name)
end
+ # ```
# foo => bar => baz
# ^^^^^^^^^^
+ # ```
def visit_capture_pattern_node(node)
visit(node.target) << visit(node.value)
end
+ # ```
# case foo; when bar; end
# ^^^^^^^^^^^^^^^^^^^^^^^
+ # ```
def visit_case_node(node)
s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause)
end
+ # ```
# case foo; in bar; end
# ^^^^^^^^^^^^^^^^^^^^^
+ # ```
def visit_case_match_node(node)
s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause)
end
+ # ```
# class Foo; end
# ^^^^^^^^^^^^^^
+ # ```
def visit_class_node(node)
name =
if node.constant_path.is_a?(ConstantReadNode)
@@ -366,51 +420,67 @@ module Prism
visit(node.constant_path)
end
- if node.body.nil?
- s(node, :class, name, visit(node.superclass))
- elsif node.body.is_a?(StatementsNode)
- compiler = copy_compiler(in_def: false)
- s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) })
- else
- s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false)))
- end
+ result =
+ if node.body.nil?
+ s(node, :class, name, visit(node.superclass))
+ elsif node.body.is_a?(StatementsNode)
+ compiler = copy_compiler(in_def: false)
+ s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) })
+ else
+ s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false)))
+ end
+
+ attach_comments(result, node)
+ result
end
+ # ```
# @@foo
# ^^^^^
+ # ```
def visit_class_variable_read_node(node)
s(node, :cvar, node.name)
end
+ # ```
# @@foo = 1
# ^^^^^^^^^
#
# @@foo, @@bar = 1
# ^^^^^ ^^^^^
+ # ```
def visit_class_variable_write_node(node)
s(node, class_variable_write_type, node.name, visit_write_value(node.value))
end
+ # ```
# @@foo += bar
# ^^^^^^^^^^^^
+ # ```
def visit_class_variable_operator_write_node(node)
s(node, class_variable_write_type, node.name, s(node, :call, s(node, :cvar, node.name), node.binary_operator, visit_write_value(node.value)))
end
+ # ```
# @@foo &&= bar
# ^^^^^^^^^^^^^
+ # ```
def visit_class_variable_and_write_node(node)
s(node, :op_asgn_and, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value)))
end
+ # ```
# @@foo ||= bar
# ^^^^^^^^^^^^^
+ # ```
def visit_class_variable_or_write_node(node)
s(node, :op_asgn_or, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value)))
end
+ # ```
# @@foo, = bar
# ^^^^^
+ # ```
def visit_class_variable_target_node(node)
s(node, class_variable_write_type, node.name)
end
@@ -421,47 +491,61 @@ module Prism
in_def ? :cvasgn : :cvdecl
end
+ # ```
# Foo
# ^^^
+ # ```
def visit_constant_read_node(node)
s(node, :const, node.name)
end
+ # ```
# Foo = 1
# ^^^^^^^
#
# Foo, Bar = 1
# ^^^ ^^^
+ # ```
def visit_constant_write_node(node)
s(node, :cdecl, node.name, visit_write_value(node.value))
end
+ # ```
# Foo += bar
# ^^^^^^^^^^^
+ # ```
def visit_constant_operator_write_node(node)
s(node, :cdecl, node.name, s(node, :call, s(node, :const, node.name), node.binary_operator, visit_write_value(node.value)))
end
+ # ```
# Foo &&= bar
# ^^^^^^^^^^^^
+ # ```
def visit_constant_and_write_node(node)
s(node, :op_asgn_and, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value)))
end
+ # ```
# Foo ||= bar
# ^^^^^^^^^^^^
+ # ```
def visit_constant_or_write_node(node)
s(node, :op_asgn_or, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value)))
end
+ # ```
# Foo, = bar
# ^^^
+ # ```
def visit_constant_target_node(node)
s(node, :cdecl, node.name)
end
+ # ```
# Foo::Bar
# ^^^^^^^^
+ # ```
def visit_constant_path_node(node)
if node.parent.nil?
s(node, :colon3, node.name)
@@ -470,35 +554,45 @@ module Prism
end
end
+ # ```
# Foo::Bar = 1
# ^^^^^^^^^^^^
#
# Foo::Foo, Bar::Bar = 1
# ^^^^^^^^ ^^^^^^^^
+ # ```
def visit_constant_path_write_node(node)
s(node, :cdecl, visit(node.target), visit_write_value(node.value))
end
+ # ```
# Foo::Bar += baz
# ^^^^^^^^^^^^^^^
+ # ```
def visit_constant_path_operator_write_node(node)
s(node, :op_asgn, visit(node.target), node.binary_operator, visit_write_value(node.value))
end
+ # ```
# Foo::Bar &&= baz
# ^^^^^^^^^^^^^^^^
+ # ```
def visit_constant_path_and_write_node(node)
s(node, :op_asgn_and, visit(node.target), visit_write_value(node.value))
end
+ # ```
# Foo::Bar ||= baz
# ^^^^^^^^^^^^^^^^
+ # ```
def visit_constant_path_or_write_node(node)
s(node, :op_asgn_or, visit(node.target), visit_write_value(node.value))
end
+ # ```
# Foo::Bar, = baz
# ^^^^^^^^
+ # ```
def visit_constant_path_target_node(node)
inner =
if node.parent.nil?
@@ -510,11 +604,13 @@ module Prism
s(node, :const, inner)
end
+ # ```
# def foo; end
# ^^^^^^^^^^^^
#
# def self.foo; end
# ^^^^^^^^^^^^^^^^^
+ # ```
def visit_def_node(node)
name = node.name_loc.slice.to_sym
result =
@@ -524,7 +620,9 @@ module Prism
s(node, :defs, visit(node.receiver), name)
end
+ attach_comments(result, node)
result.line(node.name_loc.start_line)
+
if node.parameters.nil?
result << s(node, :args).line(node.name_loc.start_line)
else
@@ -541,55 +639,71 @@ module Prism
end
end
+ # ```
# defined? a
# ^^^^^^^^^^
#
# defined?(a)
# ^^^^^^^^^^^
+ # ```
def visit_defined_node(node)
s(node, :defined, visit(node.value))
end
+ # ```
# if foo then bar else baz end
# ^^^^^^^^^^^^
+ # ```
def visit_else_node(node)
visit(node.statements)
end
+ # ```
# "foo #{bar}"
# ^^^^^^
+ # ```
def visit_embedded_statements_node(node)
result = s(node, :evstr)
result << visit(node.statements) unless node.statements.nil?
result
end
+ # ```
# "foo #@bar"
# ^^^^^
+ # ```
def visit_embedded_variable_node(node)
s(node, :evstr, visit(node.variable))
end
+ # ```
# begin; foo; ensure; bar; end
# ^^^^^^^^^^^^
+ # ```
def visit_ensure_node(node)
node.statements.nil? ? s(node, :nil) : visit(node.statements)
end
+ # ```
# false
# ^^^^^
+ # ```
def visit_false_node(node)
s(node, :false)
end
+ # ```
# foo => [*, bar, *]
# ^^^^^^^^^^^
+ # ```
def visit_find_pattern_node(node)
s(node, :find_pat, visit_pattern_constant(node.constant), :"*#{node.left.expression&.name}", *visit_all(node.requireds), :"*#{node.right.expression&.name}")
end
+ # ```
# if foo .. bar; end
# ^^^^^^^^^^
+ # ```
def visit_flip_flop_node(node)
if node.left.is_a?(IntegerNode) && node.right.is_a?(IntegerNode)
s(node, :lit, Range.new(node.left.value, node.right.value, node.exclude_end?))
@@ -598,86 +712,112 @@ module Prism
end
end
+ # ```
# 1.0
# ^^^
+ # ```
def visit_float_node(node)
s(node, :lit, node.value)
end
+ # ```
# for foo in bar do end
# ^^^^^^^^^^^^^^^^^^^^^
+ # ```
def visit_for_node(node)
s(node, :for, visit(node.collection), visit(node.index), visit(node.statements))
end
+ # ```
# def foo(...); bar(...); end
# ^^^
+ # ```
def visit_forwarding_arguments_node(node)
s(node, :forward_args)
end
+ # ```
# def foo(...); end
# ^^^
+ # ```
def visit_forwarding_parameter_node(node)
s(node, :forward_args)
end
+ # ```
# super
# ^^^^^
#
# super {}
# ^^^^^^^^
+ # ```
def visit_forwarding_super_node(node)
visit_block(node, s(node, :zsuper), node.block)
end
+ # ```
# $foo
# ^^^^
+ # ```
def visit_global_variable_read_node(node)
s(node, :gvar, node.name)
end
+ # ```
# $foo = 1
# ^^^^^^^^
#
# $foo, $bar = 1
# ^^^^ ^^^^
+ # ```
def visit_global_variable_write_node(node)
s(node, :gasgn, node.name, visit_write_value(node.value))
end
+ # ```
# $foo += bar
# ^^^^^^^^^^^
+ # ```
def visit_global_variable_operator_write_node(node)
s(node, :gasgn, node.name, s(node, :call, s(node, :gvar, node.name), node.binary_operator, visit(node.value)))
end
+ # ```
# $foo &&= bar
# ^^^^^^^^^^^^
+ # ```
def visit_global_variable_and_write_node(node)
s(node, :op_asgn_and, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value)))
end
+ # ```
# $foo ||= bar
# ^^^^^^^^^^^^
+ # ```
def visit_global_variable_or_write_node(node)
s(node, :op_asgn_or, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value)))
end
+ # ```
# $foo, = bar
# ^^^^
+ # ```
def visit_global_variable_target_node(node)
s(node, :gasgn, node.name)
end
+ # ```
# {}
# ^^
+ # ```
def visit_hash_node(node)
s(node, :hash).concat(node.elements.flat_map { |element| visit(element) })
end
+ # ```
# foo => {}
# ^^
+ # ```
def visit_hash_pattern_node(node)
result = s(node, :hash_pat, visit_pattern_constant(node.constant)).concat(node.elements.flat_map { |element| visit(element) })
@@ -691,6 +831,7 @@ module Prism
result
end
+ # ```
# if foo then bar end
# ^^^^^^^^^^^^^^^^^^^
#
@@ -699,6 +840,7 @@ module Prism
#
# foo ? bar : baz
# ^^^^^^^^^^^^^^^
+ # ```
def visit_if_node(node)
s(node, :if, visit(node.predicate), visit(node.statements), visit(node.subsequent))
end
@@ -708,18 +850,24 @@ module Prism
s(node, :lit, node.value)
end
+ # ```
# { foo: }
# ^^^^
+ # ```
def visit_implicit_node(node)
end
+ # ```
# foo { |bar,| }
# ^
+ # ```
def visit_implicit_rest_node(node)
end
+ # ```
# case foo; in bar; end
# ^^^^^^^^^^^^^^^^^^^^^
+ # ```
def visit_in_node(node)
pattern =
if node.pattern.is_a?(ConstantPathNode)
@@ -731,8 +879,10 @@ module Prism
s(node, :in, pattern).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body))
end
+ # ```
# foo[bar] += baz
# ^^^^^^^^^^^^^^^
+ # ```
def visit_index_operator_write_node(node)
arglist = nil
@@ -744,8 +894,10 @@ module Prism
s(node, :op_asgn1, visit(node.receiver), arglist, node.binary_operator, visit_write_value(node.value))
end
+ # ```
# foo[bar] &&= baz
# ^^^^^^^^^^^^^^^^
+ # ```
def visit_index_and_write_node(node)
arglist = nil
@@ -757,8 +909,10 @@ module Prism
s(node, :op_asgn1, visit(node.receiver), arglist, :"&&", visit_write_value(node.value))
end
+ # ```
# foo[bar] ||= baz
# ^^^^^^^^^^^^^^^^
+ # ```
def visit_index_or_write_node(node)
arglist = nil
@@ -770,8 +924,10 @@ module Prism
s(node, :op_asgn1, visit(node.receiver), arglist, :"||", visit_write_value(node.value))
end
+ # ```
# foo[bar], = 1
# ^^^^^^^^
+ # ```
def visit_index_target_node(node)
arguments = visit_all(node.arguments&.arguments || [])
arguments << visit(node.block) unless node.block.nil?
@@ -779,53 +935,69 @@ module Prism
s(node, :attrasgn, visit(node.receiver), :[]=).concat(arguments)
end
+ # ```
# @foo
# ^^^^
+ # ```
def visit_instance_variable_read_node(node)
s(node, :ivar, node.name)
end
+ # ```
# @foo = 1
# ^^^^^^^^
#
# @foo, @bar = 1
# ^^^^ ^^^^
+ # ```
def visit_instance_variable_write_node(node)
s(node, :iasgn, node.name, visit_write_value(node.value))
end
+ # ```
# @foo += bar
# ^^^^^^^^^^^
+ # ```
def visit_instance_variable_operator_write_node(node)
s(node, :iasgn, node.name, s(node, :call, s(node, :ivar, node.name), node.binary_operator, visit_write_value(node.value)))
end
+ # ```
# @foo &&= bar
# ^^^^^^^^^^^^
+ # ```
def visit_instance_variable_and_write_node(node)
s(node, :op_asgn_and, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value)))
end
+ # ```
# @foo ||= bar
# ^^^^^^^^^^^^
+ # ```
def visit_instance_variable_or_write_node(node)
s(node, :op_asgn_or, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value)))
end
+ # ```
# @foo, = bar
# ^^^^
+ # ```
def visit_instance_variable_target_node(node)
s(node, :iasgn, node.name)
end
+ # ```
# 1
# ^
+ # ```
def visit_integer_node(node)
s(node, :lit, node.value)
end
+ # ```
# if /foo #{bar}/ then end
# ^^^^^^^^^^^^
+ # ```
def visit_interpolated_match_last_line_node(node)
parts = visit_interpolated_parts(node.parts)
regexp =
@@ -841,8 +1013,10 @@ module Prism
s(node, :match, regexp)
end
+ # ```
# /foo #{bar}/
# ^^^^^^^^^^^^
+ # ```
def visit_interpolated_regular_expression_node(node)
parts = visit_interpolated_parts(node.parts)
@@ -856,22 +1030,28 @@ module Prism
end
end
+ # ```
# "foo #{bar}"
# ^^^^^^^^^^^^
+ # ```
def visit_interpolated_string_node(node)
parts = visit_interpolated_parts(node.parts)
parts.length == 1 ? s(node, :str, parts.first) : s(node, :dstr).concat(parts)
end
+ # ```
# :"foo #{bar}"
# ^^^^^^^^^^^^^
+ # ```
def visit_interpolated_symbol_node(node)
parts = visit_interpolated_parts(node.parts)
parts.length == 1 ? s(node, :lit, parts.first.to_sym) : s(node, :dsym).concat(parts)
end
+ # ```
# `foo #{bar}`
# ^^^^^^^^^^^^
+ # ```
def visit_interpolated_x_string_node(node)
source = node.heredoc? ? node.parts.first : node
parts = visit_interpolated_parts(node.parts)
@@ -951,23 +1131,29 @@ module Prism
results
end
+ # ```
# -> { it }
# ^^
+ # ```
def visit_it_local_variable_read_node(node)
s(node, :call, nil, :it)
end
+ # ```
# foo(bar: baz)
# ^^^^^^^^
+ # ```
def visit_keyword_hash_node(node)
s(node, :hash).concat(node.elements.flat_map { |element| visit(element) })
end
+ # ```
# def foo(**bar); end
# ^^^^^
#
# def foo(**); end
# ^^
+ # ```
def visit_keyword_rest_parameter_node(node)
:"**#{node.name}"
end
@@ -976,8 +1162,8 @@ module Prism
def visit_lambda_node(node)
parameters =
case node.parameters
- when nil, NumberedParametersNode
- s(node, :args)
+ when nil, ItParametersNode, NumberedParametersNode
+ 0
else
visit(node.parameters)
end
@@ -989,8 +1175,10 @@ module Prism
end
end
+ # ```
# foo
# ^^^
+ # ```
def visit_local_variable_read_node(node)
if node.name.match?(/^_\d$/)
s(node, :call, nil, node.name)
@@ -999,59 +1187,77 @@ module Prism
end
end
+ # ```
# foo = 1
# ^^^^^^^
#
# foo, bar = 1
# ^^^ ^^^
+ # ```
def visit_local_variable_write_node(node)
s(node, :lasgn, node.name, visit_write_value(node.value))
end
+ # ```
# foo += bar
# ^^^^^^^^^^
+ # ```
def visit_local_variable_operator_write_node(node)
s(node, :lasgn, node.name, s(node, :call, s(node, :lvar, node.name), node.binary_operator, visit_write_value(node.value)))
end
+ # ```
# foo &&= bar
# ^^^^^^^^^^^
+ # ```
def visit_local_variable_and_write_node(node)
s(node, :op_asgn_and, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value)))
end
+ # ```
# foo ||= bar
# ^^^^^^^^^^^
+ # ```
def visit_local_variable_or_write_node(node)
s(node, :op_asgn_or, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value)))
end
+ # ```
# foo, = bar
# ^^^
+ # ```
def visit_local_variable_target_node(node)
s(node, :lasgn, node.name)
end
+ # ```
# if /foo/ then end
# ^^^^^
+ # ```
def visit_match_last_line_node(node)
s(node, :match, s(node, :lit, Regexp.new(node.unescaped, node.options)))
end
+ # ```
# foo in bar
# ^^^^^^^^^^
+ # ```
def visit_match_predicate_node(node)
s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil)
end
+ # ```
# foo => bar
# ^^^^^^^^^^
+ # ```
def visit_match_required_node(node)
s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil)
end
+ # ```
# /(?<foo>foo)/ =~ bar
# ^^^^^^^^^^^^^^^^^^^^
+ # ```
def visit_match_write_node(node)
s(node, :match2, visit(node.call.receiver), visit(node.call.arguments.arguments.first))
end
@@ -1063,8 +1269,10 @@ module Prism
raise "Cannot visit missing node directly"
end
+ # ```
# module Foo; end
# ^^^^^^^^^^^^^^^
+ # ```
def visit_module_node(node)
name =
if node.constant_path.is_a?(ConstantReadNode)
@@ -1073,18 +1281,24 @@ module Prism
visit(node.constant_path)
end
- if node.body.nil?
- s(node, :module, name)
- elsif node.body.is_a?(StatementsNode)
- compiler = copy_compiler(in_def: false)
- s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) })
- else
- s(node, :module, name, node.body.accept(copy_compiler(in_def: false)))
- end
+ result =
+ if node.body.nil?
+ s(node, :module, name)
+ elsif node.body.is_a?(StatementsNode)
+ compiler = copy_compiler(in_def: false)
+ s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) })
+ else
+ s(node, :module, name, node.body.accept(copy_compiler(in_def: false)))
+ end
+
+ attach_comments(result, node)
+ result
end
+ # ```
# foo, bar = baz
# ^^^^^^^^
+ # ```
def visit_multi_target_node(node)
targets = [*node.lefts]
targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
@@ -1093,8 +1307,10 @@ module Prism
s(node, :masgn, s(node, :array).concat(visit_all(targets)))
end
+ # ```
# foo, bar = baz
# ^^^^^^^^^^^^^^
+ # ```
def visit_multi_write_node(node)
targets = [*node.lefts]
targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
@@ -1114,11 +1330,13 @@ module Prism
s(node, :masgn, s(node, :array).concat(visit_all(targets)), value)
end
+ # ```
# next
# ^^^^
#
# next foo
# ^^^^^^^^
+ # ```
def visit_next_node(node)
if node.arguments.nil?
s(node, :next)
@@ -1130,44 +1348,58 @@ module Prism
end
end
+ # ```
# nil
# ^^^
+ # ```
def visit_nil_node(node)
s(node, :nil)
end
+ # ```
# def foo(**nil); end
# ^^^^^
+ # ```
def visit_no_keywords_parameter_node(node)
in_pattern ? s(node, :kwrest, :"**nil") : :"**nil"
end
+ # ```
# -> { _1 + _2 }
# ^^^^^^^^^^^^^^
+ # ```
def visit_numbered_parameters_node(node)
raise "Cannot visit numbered parameters directly"
end
+ # ```
# $1
# ^^
+ # ```
def visit_numbered_reference_read_node(node)
s(node, :nth_ref, node.number)
end
+ # ```
# def foo(bar: baz); end
# ^^^^^^^^
+ # ```
def visit_optional_keyword_parameter_node(node)
s(node, :kwarg, node.name, visit(node.value))
end
+ # ```
# def foo(bar = 1); end
# ^^^^^^^
+ # ```
def visit_optional_parameter_node(node)
s(node, :lasgn, node.name, visit(node.value))
end
+ # ```
# a or b
# ^^^^^^
+ # ```
def visit_or_node(node)
left = visit(node.left)
@@ -1184,11 +1416,13 @@ module Prism
end
end
+ # ```
# def foo(bar, *baz); end
# ^^^^^^^^^
+ # ```
def visit_parameters_node(node)
children =
- node.compact_child_nodes.map do |element|
+ node.each_child_node.map do |element|
if element.is_a?(MultiTargetNode)
visit_destructured_parameter(element)
else
@@ -1199,8 +1433,10 @@ module Prism
s(node, :args).concat(children)
end
+ # ```
# def foo((bar, baz)); end
# ^^^^^^^^^^
+ # ```
private def visit_destructured_parameter(node)
children =
[*node.lefts, *node.rest, *node.rights].map do |child|
@@ -1219,11 +1455,13 @@ module Prism
s(node, :masgn).concat(children)
end
+ # ```
# ()
# ^^
#
# (1)
# ^^^
+ # ```
def visit_parentheses_node(node)
if node.body.nil?
s(node, :nil)
@@ -1232,14 +1470,18 @@ module Prism
end
end
+ # ```
# foo => ^(bar)
# ^^^^^^
+ # ```
def visit_pinned_expression_node(node)
node.expression.accept(copy_compiler(in_pattern: false))
end
+ # ```
# foo = 1 and bar => ^foo
# ^^^^
+ # ```
def visit_pinned_variable_node(node)
if node.variable.is_a?(LocalVariableReadNode) && node.variable.name.match?(/^_\d$/)
s(node, :lvar, node.variable.name)
@@ -1263,8 +1505,10 @@ module Prism
visit(node.statements)
end
+ # ```
# 0..5
# ^^^^
+ # ```
def visit_range_node(node)
if !in_pattern && !node.left.nil? && !node.right.nil? && ([node.left.type, node.right.type] - %i[nil_node integer_node]).empty?
left = node.left.value if node.left.is_a?(IntegerNode)
@@ -1285,44 +1529,58 @@ module Prism
end
end
+ # ```
# 1r
# ^^
+ # ```
def visit_rational_node(node)
s(node, :lit, node.value)
end
+ # ```
# redo
# ^^^^
+ # ```
def visit_redo_node(node)
s(node, :redo)
end
+ # ```
# /foo/
# ^^^^^
+ # ```
def visit_regular_expression_node(node)
s(node, :lit, Regexp.new(node.unescaped, node.options))
end
+ # ```
# def foo(bar:); end
# ^^^^
+ # ```
def visit_required_keyword_parameter_node(node)
s(node, :kwarg, node.name)
end
+ # ```
# def foo(bar); end
# ^^^
+ # ```
def visit_required_parameter_node(node)
node.name
end
+ # ```
# foo rescue bar
# ^^^^^^^^^^^^^^
+ # ```
def visit_rescue_modifier_node(node)
s(node, :rescue, visit(node.expression), s(node.rescue_expression, :resbody, s(node.rescue_expression, :array), visit(node.rescue_expression)))
end
+ # ```
# begin; rescue; end
# ^^^^^^^
+ # ```
def visit_rescue_node(node)
exceptions =
if node.exceptions.length == 1 && node.exceptions.first.is_a?(SplatNode)
@@ -1338,26 +1596,32 @@ module Prism
s(node, :resbody, exceptions).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body))
end
+ # ```
# def foo(*bar); end
# ^^^^
#
# def foo(*); end
# ^
+ # ```
def visit_rest_parameter_node(node)
:"*#{node.name}"
end
+ # ```
# retry
# ^^^^^
+ # ```
def visit_retry_node(node)
s(node, :retry)
end
+ # ```
# return
# ^^^^^^
#
# return 1
# ^^^^^^^^
+ # ```
def visit_return_node(node)
if node.arguments.nil?
s(node, :return)
@@ -1369,8 +1633,10 @@ module Prism
end
end
+ # ```
# self
# ^^^^
+ # ```
def visit_self_node(node)
s(node, :self)
end
@@ -1380,33 +1646,42 @@ module Prism
visit(node.write)
end
+ # ```
# class << self; end
# ^^^^^^^^^^^^^^^^^^
+ # ```
def visit_singleton_class_node(node)
s(node, :sclass, visit(node.expression)).tap do |sexp|
sexp << node.body.accept(copy_compiler(in_def: false)) unless node.body.nil?
end
end
+ # ```
# __ENCODING__
# ^^^^^^^^^^^^
+ # ```
def visit_source_encoding_node(node)
# TODO
s(node, :colon2, s(node, :const, :Encoding), :UTF_8)
end
+ # ```
# __FILE__
# ^^^^^^^^
+ # ```
def visit_source_file_node(node)
s(node, :str, node.filepath)
end
+ # ```
# __LINE__
# ^^^^^^^^
+ # ```
def visit_source_line_node(node)
s(node, :lit, node.location.start_line)
end
+ # ```
# foo(*bar)
# ^^^^
#
@@ -1415,6 +1690,7 @@ module Prism
#
# def foo(*); bar(*); end
# ^
+ # ```
def visit_splat_node(node)
if node.expression.nil?
s(node, :splat)
@@ -1434,8 +1710,10 @@ module Prism
end
end
+ # ```
# "foo"
# ^^^^^
+ # ```
def visit_string_node(node)
unescaped = node.unescaped
@@ -1447,8 +1725,10 @@ module Prism
s(node, :str, unescaped)
end
+ # ```
# super(foo)
# ^^^^^^^^^^
+ # ```
def visit_super_node(node)
arguments = node.arguments&.arguments || []
block = node.block
@@ -1461,60 +1741,76 @@ module Prism
visit_block(node, s(node, :super).concat(visit_all(arguments)), block)
end
+ # ```
# :foo
# ^^^^
+ # ```
def visit_symbol_node(node)
node.value == "!@" ? s(node, :lit, :"!@") : s(node, :lit, node.unescaped.to_sym)
end
+ # ```
# true
# ^^^^
+ # ```
def visit_true_node(node)
s(node, :true)
end
+ # ```
# undef foo
# ^^^^^^^^^
+ # ```
def visit_undef_node(node)
names = node.names.map { |name| s(node, :undef, visit(name)) }
names.length == 1 ? names.first : s(node, :block).concat(names)
end
+ # ```
# unless foo; bar end
# ^^^^^^^^^^^^^^^^^^^
#
# bar unless foo
# ^^^^^^^^^^^^^^
+ # ```
def visit_unless_node(node)
s(node, :if, visit(node.predicate), visit(node.else_clause), visit(node.statements))
end
+ # ```
# until foo; bar end
# ^^^^^^^^^^^^^^^^^
#
# bar until foo
# ^^^^^^^^^^^^^
+ # ```
def visit_until_node(node)
s(node, :until, visit(node.predicate), visit(node.statements), !node.begin_modifier?)
end
+ # ```
# case foo; when bar; end
# ^^^^^^^^^^^^^
+ # ```
def visit_when_node(node)
s(node, :when, s(node, :array).concat(visit_all(node.conditions))).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body))
end
+ # ```
# while foo; bar end
# ^^^^^^^^^^^^^^^^^^
#
# bar while foo
# ^^^^^^^^^^^^^
+ # ```
def visit_while_node(node)
s(node, :while, visit(node.predicate), visit(node.statements), !node.begin_modifier?)
end
+ # ```
# `foo`
# ^^^^^
+ # ```
def visit_x_string_node(node)
result = s(node, :xstr, node.unescaped)
@@ -1526,17 +1822,30 @@ module Prism
result
end
+ # ```
# yield
# ^^^^^
#
# yield 1
# ^^^^^^^
+ # ```
def visit_yield_node(node)
s(node, :yield).concat(visit_all(node.arguments&.arguments || []))
end
private
+ # Attach prism comments to the given sexp.
+ def attach_comments(sexp, node)
+ return unless node.comments
+ return if node.comments.empty?
+
+ extra = node.location.start_line - node.comments.last.location.start_line
+ comments = node.comments.map(&:slice)
+ comments.concat([nil] * [0, extra].max)
+ sexp.comments = comments.join("\n")
+ end
+
# Create a new compiler with the given options.
def copy_compiler(in_def: self.in_def, in_pattern: self.in_pattern)
Compiler.new(file, in_def: in_def, in_pattern: in_pattern)
@@ -1615,6 +1924,14 @@ module Prism
translate(Prism.parse_file(filepath, partial_script: true), filepath)
end
+ # Parse the give file and translate it into the
+ # seattlerb/ruby_parser gem's Sexp format. This method is
+ # provided for API compatibility to RubyParser and takes an
+ # optional +timeout+ argument.
+ def process(ruby, file = "(string)", timeout = nil)
+ Timeout.timeout(timeout) { parse(ruby, file) }
+ end
+
class << self
# Parse the given source and translate it into the seattlerb/ruby_parser
# gem's Sexp format.
@@ -1639,6 +1956,7 @@ module Prism
raise ::RubyParser::SyntaxError, "#{filepath}:#{error.location.start_line} :: #{error.message}"
end
+ result.attach_comments!
result.value.accept(Compiler.new(filepath))
end
end
diff --git a/lib/resolv.gemspec b/lib/resolv.gemspec
index bfa2f9ff31..66aed34e01 100644
--- a/lib/resolv.gemspec
+++ b/lib/resolv.gemspec
@@ -21,9 +21,8 @@ Gem::Specification.new do |spec|
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
- end
+ excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}]
+ spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0")
spec.bindir = "exe"
spec.executables = []
spec.require_paths = ["lib"]
diff --git a/lib/resolv.rb b/lib/resolv.rb
index ca72f41c5c..fa7d4e2e47 100644
--- a/lib/resolv.rb
+++ b/lib/resolv.rb
@@ -4,6 +4,7 @@ require 'socket'
require 'timeout'
require 'io/wait'
require 'securerandom'
+require 'rbconfig'
# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
# handle multiple DNS requests concurrently without blocking the entire Ruby
@@ -33,7 +34,8 @@ require 'securerandom'
class Resolv
- VERSION = "0.6.0"
+ # The version string
+ VERSION = "0.7.0"
##
# Looks up the first IP address for +name+.
@@ -173,21 +175,19 @@ class Resolv
class ResolvTimeout < Timeout::Error; end
- WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/
- private_constant :WINDOWS
-
##
# Resolv::Hosts is a hostname resolver that uses the system hosts file.
class Hosts
- if WINDOWS
+ if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/
begin
require 'win32/resolv' unless defined?(Win32::Resolv)
- DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
+ hosts = Win32::Resolv.get_hosts_path || IO::NULL
rescue LoadError
end
end
- DefaultFileName ||= '/etc/hosts'
+ # The default file name for host names
+ DefaultFileName = hosts || '/etc/hosts'
##
# Creates a new Resolv::Hosts, using +filename+ for its data source.
@@ -525,6 +525,8 @@ class Resolv
}
end
+ # :stopdoc:
+
def fetch_resource(name, typeclass)
lazy_initialize
truncated = {}
@@ -719,7 +721,8 @@ class Resolv
begin
reply, from = recv_reply(select_result[0])
rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
- Errno::ECONNRESET # Windows
+ Errno::ECONNRESET, # Windows
+ EOFError
# No name server running on the server?
# Don't wait anymore.
raise ResolvTimeout
@@ -928,8 +931,11 @@ class Resolv
end
def recv_reply(readable_socks)
- len = readable_socks[0].read(2).unpack('n')[0]
+ len_data = readable_socks[0].read(2)
+ raise EOFError if len_data.nil? || len_data.bytesize != 2
+ len = len_data.unpack('n')[0]
reply = @socks[0].read(len)
+ raise EOFError if reply.nil? || reply.bytesize != len
return reply, nil
end
@@ -1021,8 +1027,7 @@ class Resolv
def Config.default_config_hash(filename="/etc/resolv.conf")
if File.exist? filename
Config.parse_resolv_conf(filename)
- elsif WINDOWS
- require 'win32/resolv' unless defined?(Win32::Resolv)
+ elsif defined?(Win32::Resolv)
search, nameserver = Win32::Resolv.get_resolv_info
config_hash = {}
config_hash[:nameserver] = nameserver if nameserver
@@ -1679,6 +1684,7 @@ class Resolv
prev_index = @index
save_index = nil
d = []
+ size = -1
while true
raise DecodeError.new("limit exceeded") if @limit <= @index
case @data.getbyte(@index)
@@ -1699,7 +1705,10 @@ class Resolv
end
@index = idx
else
- d << self.get_label
+ l = self.get_label
+ d << l
+ size += 1 + l.string.bytesize
+ raise DecodeError.new("name label data exceed 255 octets") if size > 255
end
end
end
@@ -2601,7 +2610,7 @@ class Resolv
end
##
- # Flags for this proprty:
+ # Flags for this property:
# - Bit 0 : 0 = not critical, 1 = critical
attr_reader :flags
@@ -2922,15 +2931,21 @@ class Resolv
class IPv4
- ##
- # Regular expression IPv4 addresses must match.
-
Regex256 = /0
|1(?:[0-9][0-9]?)?
|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
- |[3-9][0-9]?/x
+ |[3-9][0-9]?/x # :nodoc:
+
+ ##
+ # Regular expression IPv4 addresses must match.
Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
+ ##
+ # Creates a new IPv4 address from +arg+ which may be:
+ #
+ # IPv4:: returns +arg+.
+ # String:: +arg+ must match the IPv4::Regex constant
+
def self.create(arg)
case arg
when IPv4
@@ -3239,13 +3254,15 @@ class Resolv
end
- module LOC
+ module LOC # :nodoc:
##
# A Resolv::LOC::Size
class Size
+ # Regular expression LOC size must match.
+
Regex = /^(\d+\.*\d*)[m]$/
##
@@ -3271,6 +3288,7 @@ class Resolv
end
end
+ # Internal use; use self.create.
def initialize(scalar)
@scalar = scalar
end
@@ -3308,6 +3326,8 @@ class Resolv
class Coord
+ # Regular expression LOC Coord must match.
+
Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
##
@@ -3337,6 +3357,7 @@ class Resolv
end
end
+ # Internal use; use self.create.
def initialize(coordinates,orientation)
unless coordinates.kind_of?(String)
raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
@@ -3399,6 +3420,8 @@ class Resolv
class Alt
+ # Regular expression LOC Alt must match.
+
Regex = /^([+-]*\d+\.*\d*)[m]$/
##
@@ -3424,6 +3447,7 @@ class Resolv
end
end
+ # Internal use; use self.create.
def initialize(altitude)
@altitude = altitude
end
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 1225cbe5cb..b52dd1b9d3 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -9,16 +9,15 @@
require "rbconfig"
module Gem
- VERSION = "3.7.0.dev"
+ VERSION = "4.1.0.dev"
end
-# Must be first since it unloads the prelude from 1.9.2
-require_relative "rubygems/compatibility"
-
require_relative "rubygems/defaults"
require_relative "rubygems/deprecate"
require_relative "rubygems/errors"
require_relative "rubygems/target_rbconfig"
+require_relative "rubygems/win_platform"
+require_relative "rubygems/util/atomic_file_writer"
##
# RubyGems is the Ruby standard for publishing and managing third party
@@ -39,7 +38,7 @@ require_relative "rubygems/target_rbconfig"
# Further RubyGems documentation can be found at:
#
# * {RubyGems Guides}[https://guides.rubygems.org]
-# * {RubyGems API}[https://www.rubydoc.info/github/rubygems/rubygems] (also available from
+# * {RubyGems API}[https://www.rubydoc.info/github/ruby/rubygems] (also available from
# <tt>gem server</tt>)
#
# == RubyGems Plugins
@@ -71,7 +70,7 @@ require_relative "rubygems/target_rbconfig"
# == Bugs
#
# You can submit bugs to the
-# {RubyGems bug tracker}[https://github.com/rubygems/rubygems/issues]
+# {RubyGems bug tracker}[https://github.com/ruby/rubygems/issues]
# on GitHub
#
# == Credits
@@ -107,7 +106,7 @@ require_relative "rubygems/target_rbconfig"
#
# == License
#
-# See {LICENSE.txt}[https://github.com/rubygems/rubygems/blob/master/LICENSE.txt] for permissions.
+# See {LICENSE.txt}[https://github.com/ruby/rubygems/blob/master/LICENSE.txt] for permissions.
#
# Thanks!
#
@@ -116,18 +115,6 @@ require_relative "rubygems/target_rbconfig"
module Gem
RUBYGEMS_DIR = __dir__
- ##
- # An Array of Regexps that match windows Ruby platforms.
-
- WIN_PATTERNS = [
- /bccwin/i,
- /cygwin/i,
- /djgpp/i,
- /mingw/i,
- /mswin/i,
- /wince/i,
- ].freeze
-
GEM_DEP_FILES = %w[
gem.deps.rb
gems.rb
@@ -163,8 +150,6 @@ module Gem
DEFAULT_SOURCE_DATE_EPOCH = 315_619_200
- @@win_platform = nil
-
@configuration = nil
@gemdeps = nil
@loaded_specs = {}
@@ -227,7 +212,7 @@ module Gem
finish_resolve rs
end
- def self.finish_resolve(request_set=Gem::RequestSet.new)
+ def self.finish_resolve(request_set = Gem::RequestSet.new)
request_set.import Gem::Specification.unresolved_deps.values
request_set.import Gem.loaded_specs.values.map {|s| Gem::Dependency.new(s.name, s.version) }
@@ -249,6 +234,16 @@ module Gem
find_spec_for_exe(name, exec_name, requirements).bin_file exec_name
end
+ def self.find_and_activate_spec_for_exe(name, exec_name, requirements)
+ spec = find_spec_for_exe name, exec_name, requirements
+ Gem::LOADED_SPECS_MUTEX.synchronize do
+ spec.activate
+ finish_resolve
+ end
+ spec
+ end
+ private_class_method :find_and_activate_spec_for_exe
+
def self.find_spec_for_exe(name, exec_name, requirements)
raise ArgumentError, "you must supply exec_name" unless exec_name
@@ -274,6 +269,42 @@ module Gem
private_class_method :find_spec_for_exe
##
+ # Find and load the full path to the executable for gem +name+. If the
+ # +exec_name+ is not given, an exception will be raised, otherwise the
+ # specified executable's path is returned. +requirements+ allows
+ # you to specify specific gem versions.
+ #
+ # A side effect of this method is that it will activate the gem that
+ # contains the executable.
+ #
+ # This method should *only* be used in bin stub files.
+
+ def self.activate_and_load_bin_path(name, exec_name = nil, *requirements)
+ spec = find_and_activate_spec_for_exe name, exec_name, requirements
+
+ if spec.name == "bundler"
+ # Old versions of Bundler need a workaround to support nested `bundle
+ # exec` invocations by overriding `Gem.activate_bin_path`. However,
+ # RubyGems now uses this new `Gem.activate_and_load_bin_path` helper in
+ # binstubs, which is of course not overridden in Bundler since it didn't
+ # exist at the time. So, include the override here to workaround that.
+ load ENV["BUNDLE_BIN_PATH"] if ENV["BUNDLE_BIN_PATH"] && spec.version <= Gem::Version.create("2.5.22")
+
+ # Make sure there's no version of Bundler in `$LOAD_PATH` that's different
+ # from the version we just activated. If that was the case (it happens
+ # when testing Bundler from ruby/ruby), we would load Bundler extensions
+ # to RubyGems from the copy in `$LOAD_PATH` but then load the binstub from
+ # an installed copy, causing those copies to be mixed and yet more
+ # redefinition warnings.
+ #
+ require_path = $LOAD_PATH.resolve_feature_path("bundler").last.delete_suffix("/bundler.rb")
+ Gem.load_bundler_extensions(spec.version) if spec.full_require_paths.include?(require_path)
+ end
+
+ load spec.bin_file(exec_name)
+ end
+
+ ##
# Find the full path to the executable for gem +name+. If the +exec_name+
# is not given, an exception will be raised, otherwise the
# specified executable's path is returned. +requirements+ allows
@@ -285,12 +316,7 @@ module Gem
# This method should *only* be used in bin stub files.
def self.activate_bin_path(name, exec_name = nil, *requirements) # :nodoc:
- spec = find_spec_for_exe name, exec_name, requirements
- Gem::LOADED_SPECS_MUTEX.synchronize do
- spec.activate
- finish_resolve
- end
- spec.bin_file exec_name
+ find_and_activate_spec_for_exe(name, exec_name, requirements).bin_file exec_name
end
##
@@ -303,7 +329,7 @@ module Gem
##
# The path where gem executables are to be installed.
- def self.bindir(install_dir=Gem.dir)
+ def self.bindir(install_dir = Gem.dir)
return File.join install_dir, "bin" unless
install_dir.to_s == Gem.default_dir.to_s
Gem.default_bindir
@@ -312,7 +338,7 @@ module Gem
##
# The path were rubygems plugins are to be installed.
- def self.plugindir(install_dir=Gem.dir)
+ def self.plugindir(install_dir = Gem.dir)
File.join install_dir, "plugins"
end
@@ -496,7 +522,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
# Note that find_files will return all files even if they are from different
# versions of the same gem. See also find_latest_files
- def self.find_files(glob, check_load_path=true)
+ def self.find_files(glob, check_load_path = true)
files = []
files = find_files_from_load_path glob if check_load_path
@@ -533,7 +559,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
# Unlike find_files, find_latest_files will return only files from the
# latest version of a gem.
- def self.find_latest_files(glob, check_load_path=true)
+ def self.find_latest_files(glob, check_load_path = true)
files = []
files = find_files_from_load_path glob if check_load_path
@@ -637,6 +663,30 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
end
##
+ # Load Bundler extensions to RubyGems, making sure to avoid redefinition
+ # warnings in platform constants
+
+ def self.load_bundler_extensions(version)
+ return unless version <= Gem::Version.create("2.6.9")
+
+ previous_platforms = {}
+
+ platform_const_list = ["JAVA", "MSWIN", "MSWIN64", "MINGW", "X64_MINGW_LEGACY", "X64_MINGW", "UNIVERSAL_MINGW", "WINDOWS", "X64_LINUX", "X64_LINUX_MUSL"]
+
+ platform_const_list.each do |platform|
+ previous_platforms[platform] = Gem::Platform.const_get(platform)
+ Gem::Platform.send(:remove_const, platform)
+ end
+
+ require "bundler/rubygems_ext"
+
+ platform_const_list.each do |platform|
+ Gem::Platform.send(:remove_const, platform) if Gem::Platform.const_defined?(platform)
+ Gem::Platform.const_set(platform, previous_platforms[platform])
+ end
+ end
+
+ ##
# The file name and line number of the caller of the caller of this method.
#
# +depth+ is how many layers up the call stack it should go.
@@ -784,14 +834,12 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
end
##
- # Safely write a file in binary mode on all platforms.
+ # Atomically write a file in binary mode on all platforms.
def self.write_binary(path, data)
- File.binwrite(path, data)
- rescue Errno::ENOSPC
- # If we ran out of space but the file exists, it's *guaranteed* to be corrupted.
- File.delete(path) if File.exist?(path)
- raise
+ Gem::AtomicFileWriter.open(path) do |file|
+ file.write(data)
+ end
end
##
@@ -1030,18 +1078,6 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
end
##
- # Is this a windows platform?
-
- def self.win_platform?
- if @@win_platform.nil?
- ruby_platform = RbConfig::CONFIG["host_os"]
- @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil?
- end
-
- @@win_platform
- end
-
- ##
# Is this a java platform?
def self.java_platform?
diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb
index a09e8ed0e1..0ed7fc60bb 100644
--- a/lib/rubygems/basic_specification.rb
+++ b/lib/rubygems/basic_specification.rb
@@ -34,15 +34,6 @@ class Gem::BasicSpecification
internal_init
end
- def self.default_specifications_dir
- Gem.default_specifications_dir
- end
-
- class << self
- extend Gem::Deprecate
- rubygems_deprecate :default_specifications_dir, "Gem.default_specifications_dir"
- end
-
##
# The path to the gem.build_complete file within the extension install
# directory.
@@ -199,6 +190,9 @@ class Gem::BasicSpecification
File.expand_path(File.join(gems_dir, full_name, "data", name))
end
+ extend Gem::Deprecate
+ rubygems_deprecate :datadir, :none, "4.1"
+
##
# Full path of the target library file.
# If the file is not in this gem, return nil.
@@ -256,6 +250,13 @@ class Gem::BasicSpecification
raise NotImplementedError
end
+ def installable_on_platform?(target_platform) # :nodoc:
+ return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform)
+ return true if Gem::Platform.new(platform) === target_platform
+
+ false
+ end
+
def raw_require_paths # :nodoc:
raise NotImplementedError
end
diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb
index 4ebbad1c0c..c930c2e19c 100644
--- a/lib/rubygems/bundler_version_finder.rb
+++ b/lib/rubygems/bundler_version_finder.rb
@@ -2,7 +2,10 @@
module Gem::BundlerVersionFinder
def self.bundler_version
+ return if bundle_config_version == "system"
+
v = ENV["BUNDLER_VERSION"]
+ v = nil if v&.empty?
v ||= bundle_update_bundler_version
return if v == true
@@ -64,9 +67,12 @@ module Gem::BundlerVersionFinder
return unless gemfile
- lockfile = case gemfile
- when "gems.rb" then "gems.locked"
- else "#{gemfile}.lock"
+ lockfile = ENV["BUNDLE_LOCKFILE"]
+ lockfile = nil if lockfile&.empty?
+
+ lockfile ||= case gemfile
+ when "gems.rb" then "gems.locked"
+ else "#{gemfile}.lock"
end
return unless File.file?(lockfile)
@@ -74,4 +80,33 @@ module Gem::BundlerVersionFinder
File.read(lockfile)
end
private_class_method :lockfile_contents
+
+ def self.bundle_config_version
+ config_file = bundler_config_file
+ return unless config_file && File.file?(config_file)
+
+ contents = File.read(config_file)
+ contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/
+
+ $1
+ end
+ private_class_method :bundle_config_version
+
+ def self.bundler_config_file
+ # see Bundler::Settings#global_config_file and local_config_file
+ # global
+ if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty?
+ ENV["BUNDLE_CONFIG"]
+ elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty?
+ ENV["BUNDLE_USER_CONFIG"]
+ elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty?
+ ENV["BUNDLE_USER_HOME"] + "config"
+ elsif Gem.user_home && !Gem.user_home.empty?
+ Gem.user_home + ".bundle/config"
+ else
+ # local
+ "config"
+ end
+ end
+ private_class_method :bundler_config_file
end
diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb
index 3a149ea38e..d38363f293 100644
--- a/lib/rubygems/command.rb
+++ b/lib/rubygems/command.rb
@@ -117,7 +117,7 @@ class Gem::Command
# Unhandled arguments (gem names, files, etc.) are left in
# <tt>options[:args]</tt>.
- def initialize(command, summary=nil, defaults={})
+ def initialize(command, summary = nil, defaults = {})
@command = command
@summary = summary
@program_name = "gem #{command}"
diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb
index 15834ce4dd..76b2fba835 100644
--- a/lib/rubygems/command_manager.rb
+++ b/lib/rubygems/command_manager.rb
@@ -58,7 +58,6 @@ class Gem::CommandManager
:owner,
:pristine,
:push,
- :query,
:rdoc,
:rebuild,
:search,
@@ -118,7 +117,7 @@ class Gem::CommandManager
##
# Register the Symbol +command+ as a gem command.
- def register_command(command, obj=false)
+ def register_command(command, obj = false)
@commands[command] = obj
end
@@ -148,7 +147,7 @@ class Gem::CommandManager
##
# Run the command specified by +args+.
- def run(args, build_args=nil)
+ def run(args, build_args = nil)
process_args(args, build_args)
rescue StandardError, Gem::Timeout::Error => ex
if ex.respond_to?(:detailed_message)
@@ -165,7 +164,7 @@ class Gem::CommandManager
terminate_interaction(1)
end
- def process_args(args, build_args=nil)
+ def process_args(args, build_args = nil)
if args.empty?
say Gem::Command::HELP
terminate_interaction 1
diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb
index 2ec8324141..cfe1f8ec3c 100644
--- a/lib/rubygems/commands/build_command.rb
+++ b/lib/rubygems/commands/build_command.rb
@@ -25,13 +25,6 @@ class Gem::Commands::BuildCommand < Gem::Command
add_option "-o", "--output FILE", "output gem with the given filename" do |value, options|
options[:output] = value
end
-
- add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, options|
- options[:build_path] = value
- end
- deprecate_option "-C",
- version: "4.0",
- extra_msg: "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead"
end
def arguments # :nodoc:
diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb
index 72dcf1dd17..fe03841ddb 100644
--- a/lib/rubygems/commands/cert_command.rb
+++ b/lib/rubygems/commands/cert_command.rb
@@ -158,7 +158,7 @@ class Gem::Commands::CertCommand < Gem::Command
cert = Gem::Security.create_cert_email(
email,
key,
- (Gem::Security::ONE_DAY * expiration_length_days)
+ Gem::Security::ONE_DAY * expiration_length_days
)
Gem::Security.write cert, "gem-public_cert.pem"
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 2888b6c55a..70d32013ba 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -239,11 +239,7 @@ You can use `i` command instead of `install`.
# Loads post-install hooks
def load_hooks # :nodoc:
- if options[:install_as_default]
- require_relative "../install_default_message"
- else
- require_relative "../install_message"
- end
+ require_relative "../install_message"
require_relative "../rdoc"
end
diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index 97f1646ba0..942b75fba1 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -88,6 +88,10 @@ If you have made modifications to an installed gem, the pristine command
will revert them. All extensions are rebuilt and all bin stubs for the gem
are regenerated after checking for modifications.
+Rebuilding extensions also refreshes C-extension gems against updated system
+libraries (for example after OS or package upgrades) to avoid mismatches like
+outdated library version warnings.
+
If the cached gem cannot be found it will be downloaded.
If --no-extensions is provided pristine will not attempt to restore a gem
@@ -137,11 +141,14 @@ extensions will be restored.
specs.group_by(&:full_name_with_location).values.each do |grouped_specs|
spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first
- unless only_executables_or_plugins?
+ only_executables = options[:only_executables]
+ only_plugins = options[:only_plugins]
+
+ unless only_executables || only_plugins
# Default gemspecs include changes provided by ruby-core installer that
# can't currently be pristined (inclusion of compiled extension targets in
# the file list). So stick to resetting executables if it's a default gem.
- options[:only_executables] = true if spec.default_gem?
+ only_executables = true if spec.default_gem?
end
if options.key? :skip
@@ -151,14 +158,14 @@ extensions will be restored.
end
end
- unless spec.extensions.empty? || options[:extensions] || only_executables_or_plugins?
+ unless spec.extensions.empty? || options[:extensions] || only_executables || only_plugins
say "Skipped #{spec.full_name_with_location}, it needs to compile an extension"
next
end
gem = spec.cache_file
- unless File.exist?(gem) || only_executables_or_plugins?
+ unless File.exist?(gem) || only_executables || only_plugins
require_relative "../remote_fetcher"
say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..."
@@ -194,10 +201,10 @@ extensions will be restored.
bin_dir: bin_dir,
}
- if options[:only_executables]
+ if only_executables
installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_bin
- elsif options[:only_plugins]
+ elsif only_plugins
installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_plugins
else
@@ -208,10 +215,4 @@ extensions will be restored.
say "Restored #{spec.full_name_with_location}"
end
end
-
- private
-
- def only_executables_or_plugins?
- options[:only_executables] || options[:only_plugins]
- end
end
diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb
deleted file mode 100644
index 3b527974a3..0000000000
--- a/lib/rubygems/commands/query_command.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "../command"
-require_relative "../query_utils"
-require_relative "../deprecate"
-
-class Gem::Commands::QueryCommand < Gem::Command
- extend Gem::Deprecate
- rubygems_deprecate_command
-
- include Gem::QueryUtils
-
- alias_method :warning_without_suggested_alternatives, :deprecation_warning
- def deprecation_warning
- warning_without_suggested_alternatives
-
- message = "It is recommended that you use `gem search` or `gem list` instead.\n"
- alert_warning message unless Gem::Deprecate.skip
- end
-
- def initialize(name = "query", summary = "Query gem information in local or remote repositories")
- super name, summary,
- domain: :local, details: false, versions: true,
- installed: nil, version: Gem::Requirement.default
-
- add_option("-n", "--name-matches REGEXP",
- "Name of gem(s) to query on matches the",
- "provided REGEXP") do |value, options|
- options[:name] = /#{value}/i
- end
-
- add_query_options
- end
-
- def description # :nodoc:
- <<-EOF
-The query command is the basis for the list and search commands.
-
-You should really use the list and search commands instead. This command
-is too hard to use.
- EOF
- end
-end
diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb
index 77a474ef1d..23b9d7b3ba 100644
--- a/lib/rubygems/commands/rebuild_command.rb
+++ b/lib/rubygems/commands/rebuild_command.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require "date"
require "digest"
require "fileutils"
require "tmpdir"
diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb
index 301ce25314..175599967c 100644
--- a/lib/rubygems/commands/setup_command.rb
+++ b/lib/rubygems/commands/setup_command.rb
@@ -7,8 +7,8 @@ require_relative "../command"
# RubyGems checkout or tarball.
class Gem::Commands::SetupCommand < Gem::Command
- HISTORY_HEADER = %r{^#\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
- VERSION_MATCHER = %r{^#\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
+ HISTORY_HEADER = %r{^##\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
+ VERSION_MATCHER = %r{^##\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$}
ENV_PATHS = %w[/usr/bin/env /bin/env].freeze
@@ -393,16 +393,20 @@ By default, this RubyGems will install gem as:
Dir.chdir("bundler") do
built_gem = Gem::Package.build(new_bundler_spec)
begin
- Gem::Installer.at(
+ installer = Gem::Installer.at(
built_gem,
env_shebang: options[:env_shebang],
format_executable: options[:format_executable],
force: options[:force],
- install_as_default: true,
bin_dir: bin_dir,
install_dir: default_dir,
wrappers: true
- ).install
+ )
+ # We need to install only executable and default spec files.
+ # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory.
+ installer.extract_bin
+ installer.generate_bin
+ installer.write_default_spec
ensure
FileUtils.rm_f built_gem
end
diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb
index 976f4a4ea2..7e5c2a2465 100644
--- a/lib/rubygems/commands/sources_command.rb
+++ b/lib/rubygems/commands/sources_command.rb
@@ -18,6 +18,14 @@ class Gem::Commands::SourcesCommand < Gem::Command
options[:add] = value
end
+ add_option "--append SOURCE_URI", "Append source (can be used multiple times)" do |value, options|
+ options[:append] = value
+ end
+
+ add_option "-p", "--prepend SOURCE_URI", "Prepend source (can be used multiple times)" do |value, options|
+ options[:prepend] = value
+ end
+
add_option "-l", "--list", "List sources" do |value, options|
options[:list] = value
end
@@ -26,8 +34,7 @@ class Gem::Commands::SourcesCommand < Gem::Command
options[:remove] = value
end
- add_option "-c", "--clear-all",
- "Remove all sources (clear the cache)" do |value, options|
+ add_option "-c", "--clear-all", "Remove all sources (clear the cache)" do |value, options|
options[:clear_all] = value
end
@@ -68,6 +75,60 @@ class Gem::Commands::SourcesCommand < Gem::Command
end
end
+ def append_source(source_uri) # :nodoc:
+ check_rubygems_https source_uri
+
+ source = Gem::Source.new source_uri
+
+ check_typo_squatting(source)
+
+ begin
+ source.load_specs :released
+ was_present = Gem.sources.include?(source)
+ Gem.sources.append source
+ Gem.configuration.write
+
+ if was_present
+ say "#{source_uri} moved to end of sources"
+ else
+ say "#{source_uri} added to sources"
+ end
+ rescue Gem::URI::Error, ArgumentError
+ say "#{source_uri} is not a URI"
+ terminate_interaction 1
+ rescue Gem::RemoteFetcher::FetchError => e
+ say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}"
+ terminate_interaction 1
+ end
+ end
+
+ def prepend_source(source_uri) # :nodoc:
+ check_rubygems_https source_uri
+
+ source = Gem::Source.new source_uri
+
+ check_typo_squatting(source)
+
+ begin
+ source.load_specs :released
+ was_present = Gem.sources.include?(source)
+ Gem.sources.prepend source
+ Gem.configuration.write
+
+ if was_present
+ say "#{source_uri} moved to top of sources"
+ else
+ say "#{source_uri} added to sources"
+ end
+ rescue Gem::URI::Error, ArgumentError
+ say "#{source_uri} is not a URI"
+ terminate_interaction 1
+ rescue Gem::RemoteFetcher::FetchError => e
+ say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}"
+ terminate_interaction 1
+ end
+ end
+
def check_typo_squatting(source)
if source.typo_squatting?("rubygems.org")
question = <<-QUESTION.chomp
@@ -128,7 +189,7 @@ yourself to use your own gem server.
Without any arguments the sources lists your currently configured sources:
$ gem sources
- *** CURRENT SOURCES ***
+ *** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***
https://rubygems.org
@@ -147,33 +208,49 @@ Since all of these sources point to the same set of gems you only need one
of them in your list. https://rubygems.org is recommended as it brings the
protections of an SSL connection to gem downloads.
-To add a source use the --add argument:
+To add a private gem source use the --prepend argument to insert it before
+the default source. This is usually the best place for private gem sources:
- $ gem sources --add https://rubygems.org
- https://rubygems.org added to sources
+ $ gem sources --prepend https://my.private.source
+ https://my.private.source added to sources
RubyGems will check to see if gems can be installed from the source given
before it is added.
+To add or move a source after all other sources, use --append:
+
+ $ gem sources --append https://rubygems.org
+ https://rubygems.org moved to end of sources
+
To remove a source use the --remove argument:
- $ gem sources --remove https://rubygems.org/
- https://rubygems.org/ removed from sources
+ $ gem sources --remove https://my.private.source/
+ https://my.private.source/ removed from sources
EOF
end
def list # :nodoc:
- say "*** CURRENT SOURCES ***"
+ if configured_sources
+ header = "*** CURRENT SOURCES ***"
+ list = configured_sources
+ else
+ header = "*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***"
+ list = Gem.sources
+ end
+
+ say header
say
- Gem.sources.each do |src|
+ list.each do |src|
say src
end
end
def list? # :nodoc:
!(options[:add] ||
+ options[:prepend] ||
+ options[:append] ||
options[:clear_all] ||
options[:remove] ||
options[:update])
@@ -182,11 +259,13 @@ To remove a source use the --remove argument:
def execute
clear_all if options[:clear_all]
- source_uri = options[:add]
- add_source source_uri if source_uri
+ add_source options[:add] if options[:add]
+
+ prepend_source options[:prepend] if options[:prepend]
+
+ append_source options[:append] if options[:append]
- source_uri = options[:remove]
- remove_source source_uri if source_uri
+ remove_source options[:remove] if options[:remove]
update if options[:update]
@@ -194,13 +273,21 @@ To remove a source use the --remove argument:
end
def remove_source(source_uri) # :nodoc:
- if Gem.sources.include? source_uri
- Gem.sources.delete source_uri
+ source = Gem::Source.new source_uri
+
+ if configured_sources&.include? source
+ Gem.sources.delete source
Gem.configuration.write
- say "#{source_uri} removed from sources"
+ if default_sources.include?(source) && configured_sources.one?
+ alert_warning "Removing a default source when it is the only source has no effect. Add a different source to #{config_file_name} if you want to stop using it as a source."
+ else
+ say "#{source_uri} removed from sources"
+ end
+ elsif configured_sources
+ say "source #{source_uri} cannot be removed because it's not present in #{config_file_name}"
else
- say "source #{source_uri} not present in cache"
+ say "source #{source_uri} cannot be removed because there are no configured sources in #{config_file_name}"
end
end
@@ -224,4 +311,21 @@ To remove a source use the --remove argument:
say "*** Unable to remove #{desc} source cache ***"
end
end
+
+ private
+
+ def default_sources
+ Gem::SourceList.from(Gem.default_sources)
+ end
+
+ def configured_sources
+ return @configured_sources if defined?(@configured_sources)
+
+ configuration_sources = Gem.configuration.sources
+ @configured_sources = Gem::SourceList.from(configuration_sources) if configuration_sources
+ end
+
+ def config_file_name
+ Gem.configuration.config_file_name
+ end
end
diff --git a/lib/rubygems/compatibility.rb b/lib/rubygems/compatibility.rb
deleted file mode 100644
index 0d9df56f8a..0000000000
--- a/lib/rubygems/compatibility.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-#--
-# This file contains all sorts of little compatibility hacks that we've
-# had to introduce over the years. Quarantining them into one file helps
-# us know when we can get rid of them.
-#
-# Ruby 1.9.x has introduced some things that are awkward, and we need to
-# support them, so we define some constants to use later.
-#
-# TODO remove at RubyGems 4
-#++
-
-module Gem
- # :stopdoc:
-
- RubyGemsVersion = VERSION
- deprecate_constant(:RubyGemsVersion)
-
- RbConfigPriorities = %w[
- MAJOR
- MINOR
- TEENY
- EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name
- ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir
- rubylibdir
- ].freeze
-
- if defined?(ConfigMap)
- RbConfigPriorities.each do |key|
- ConfigMap[key.to_sym] = RbConfig::CONFIG[key]
- end
- else
- ##
- # Configuration settings from ::RbConfig
- ConfigMap = Hash.new do |cm, key|
- cm[key] = RbConfig::CONFIG[key.to_s]
- end
- deprecate_constant(:ConfigMap)
- end
-end
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index a2bcb6dfbc..e58a83f6b7 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -345,7 +345,7 @@ if you believe they were disclosed to a third party.
require "fileutils"
FileUtils.mkdir_p(dirname)
- permissions = 0o600 & (~File.umask)
+ permissions = 0o600 & ~File.umask
File.open(credentials_path, "w", permissions) do |f|
f.write self.class.dump_with_rubygems_yaml(config)
end
diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb
index 073966b696..3a9bdbdc9d 100644
--- a/lib/rubygems/core_ext/kernel_require.rb
+++ b/lib/rubygems/core_ext/kernel_require.rb
@@ -64,8 +64,11 @@ module Kernel
rp
end
- Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless
- resolved_path
+ next if resolved_path
+
+ Kernel.send(:gem, name, Gem::Requirement.default_prerelease)
+
+ Gem.load_bundler_extensions(Gem.loaded_specs[name].version) if name == "bundler"
next
end
diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb
index db07681a17..90f09fc191 100644
--- a/lib/rubygems/defaults.rb
+++ b/lib/rubygems/defaults.rb
@@ -13,7 +13,7 @@ module Gem
# An Array of the default sources that come with RubyGems
def self.default_sources
- %w[https://rubygems.org/]
+ @default_sources ||= %w[https://rubygems.org/]
end
##
diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb
index d1ec9222af..1e91f493a6 100644
--- a/lib/rubygems/dependency.rb
+++ b/lib/rubygems/dependency.rb
@@ -217,7 +217,7 @@ class Gem::Dependency
# NOTE: Unlike #matches_spec? this method does not return true when the
# version is a prerelease version unless this is a prerelease dependency.
- def match?(obj, version=nil, allow_prerelease=false)
+ def match?(obj, version = nil, allow_prerelease = false)
if !version
name = obj.name
version = obj.version
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index b119dca1cf..6a6dfa5c20 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -7,14 +7,12 @@ require_relative "installer"
require_relative "spec_fetcher"
require_relative "user_interaction"
require_relative "available_set"
-require_relative "deprecate"
##
# Installs a gem along with all its dependencies from local and remote gems.
class Gem::DependencyInstaller
include Gem::UserInteraction
- extend Gem::Deprecate
DEFAULT_OPTIONS = { # :nodoc:
env_shebang: false,
@@ -28,7 +26,6 @@ class Gem::DependencyInstaller
wrappers: true,
build_args: nil,
build_docs_in_background: false,
- install_as_default: false,
}.freeze
##
@@ -86,8 +83,8 @@ class Gem::DependencyInstaller
@user_install = options[:user_install]
@wrappers = options[:wrappers]
@build_args = options[:build_args]
+ @build_jobs = options[:build_jobs]
@build_docs_in_background = options[:build_docs_in_background]
- @install_as_default = options[:install_as_default]
@dir_mode = options[:dir_mode]
@data_mode = options[:data_mode]
@prog_mode = options[:prog_mode]
@@ -121,78 +118,6 @@ class Gem::DependencyInstaller
@domain == :both || @domain == :remote
end
- ##
- # Returns a list of pairs of gemspecs and source_uris that match
- # Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources)
- # sources. Gems are sorted with newer gems preferred over older gems, and
- # local gems preferred over remote gems.
-
- def find_gems_with_sources(dep, best_only=false) # :nodoc:
- set = Gem::AvailableSet.new
-
- if consider_local?
- sl = Gem::Source::Local.new
-
- if spec = sl.find_gem(dep.name)
- if dep.matches_spec? spec
- set.add spec, sl
- end
- end
- end
-
- if consider_remote?
- begin
- # This is pulled from #spec_for_dependency to allow
- # us to filter tuples before fetching specs.
- tuples, errors = Gem::SpecFetcher.fetcher.search_for_dependency dep
-
- if best_only && !tuples.empty?
- tuples.sort! do |a,b|
- if b[0].version == a[0].version
- if b[0].platform != Gem::Platform::RUBY
- 1
- else
- -1
- end
- else
- b[0].version <=> a[0].version
- end
- end
- tuples = [tuples.first]
- end
-
- specs = []
- tuples.each do |tup, source|
- spec = source.fetch_spec(tup)
- rescue Gem::RemoteFetcher::FetchError => e
- errors << Gem::SourceFetchProblem.new(source, e)
- else
- specs << [spec, source]
- end
-
- if @errors
- @errors += errors
- else
- @errors = errors
- end
-
- set << specs
- rescue Gem::RemoteFetcher::FetchError => e
- # FIX if there is a problem talking to the network, we either need to always tell
- # the user (no really_verbose) or fail hard, not silently tell them that we just
- # couldn't find their requested gem.
- verbose do
- "Error fetching remote data:\t\t#{e.message}\n" \
- "Falling back to local-only install"
- end
- @domain = :local
- end
- end
-
- set
- end
- rubygems_deprecate :find_gems_with_sources
-
def in_background(what) # :nodoc:
fork_happened = false
if @build_docs_in_background && Process.respond_to?(:fork)
@@ -230,6 +155,7 @@ class Gem::DependencyInstaller
options = {
bin_dir: @bin_dir,
build_args: @build_args,
+ build_jobs: @build_jobs,
document: @document,
env_shebang: @env_shebang,
force: @force,
@@ -240,7 +166,6 @@ class Gem::DependencyInstaller
user_install: @user_install,
wrappers: @wrappers,
build_root: @build_root,
- install_as_default: @install_as_default,
dir_mode: @dir_mode,
data_mode: @data_mode,
prog_mode: @prog_mode,
diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb
index ad5e59e8c1..d50cfe2d54 100644
--- a/lib/rubygems/dependency_list.rb
+++ b/lib/rubygems/dependency_list.rb
@@ -7,7 +7,6 @@
#++
require_relative "vendored_tsort"
-require_relative "deprecate"
##
# Gem::DependencyList is used for installing and uninstalling gems in the
@@ -140,7 +139,7 @@ class Gem::DependencyList
# If removing the gemspec creates breaks a currently ok dependency, then it
# is NOT ok to remove the gemspec.
- def ok_to_remove?(full_name, check_dev=true)
+ def ok_to_remove?(full_name, check_dev = true)
gem_to_remove = find_name full_name
# If the state is inconsistent, at least don't crash
diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb
index 7d24f9cbfc..eb503bb269 100644
--- a/lib/rubygems/deprecate.rb
+++ b/lib/rubygems/deprecate.rb
@@ -1,75 +1,75 @@
# frozen_string_literal: true
-##
-# Provides 3 methods for declaring when something is going away.
-#
-# +deprecate(name, repl, year, month)+:
-# Indicate something may be removed on/after a certain date.
-#
-# +rubygems_deprecate(name, replacement=:none)+:
-# Indicate something will be removed in the next major RubyGems version,
-# and (optionally) a replacement for it.
-#
-# +rubygems_deprecate_command+:
-# Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be
-# removed in the next RubyGems version.
-#
-# Also provides +skip_during+ for temporarily turning off deprecation warnings.
-# This is intended to be used in the test suite, so deprecation warnings
-# don't cause test failures if you need to make sure stderr is otherwise empty.
-#
-#
-# Example usage of +deprecate+ and +rubygems_deprecate+:
-#
-# class Legacy
-# def self.some_class_method
-# # ...
-# end
-#
-# def some_instance_method
-# # ...
-# end
-#
-# def some_old_method
-# # ...
-# end
-#
-# extend Gem::Deprecate
-# deprecate :some_instance_method, "X.z", 2011, 4
-# rubygems_deprecate :some_old_method, "Modern#some_new_method"
-#
-# class << self
-# extend Gem::Deprecate
-# deprecate :some_class_method, :none, 2011, 4
-# end
-# end
-#
-#
-# Example usage of +rubygems_deprecate_command+:
-#
-# class Gem::Commands::QueryCommand < Gem::Command
-# extend Gem::Deprecate
-# rubygems_deprecate_command
-#
-# # ...
-# end
-#
-#
-# Example usage of +skip_during+:
-#
-# class TestSomething < Gem::Testcase
-# def test_some_thing_with_deprecations
-# Gem::Deprecate.skip_during do
-# actual_stdout, actual_stderr = capture_output do
-# Gem.something_deprecated
-# end
-# assert_empty actual_stdout
-# assert_equal(expected, actual_stderr)
-# end
-# end
-# end
-
module Gem
+ ##
+ # Provides 3 methods for declaring when something is going away.
+ #
+ # <tt>deprecate(name, repl, year, month)</tt>:
+ # Indicate something may be removed on/after a certain date.
+ #
+ # <tt>rubygems_deprecate(name, replacement=:none)</tt>:
+ # Indicate something will be removed in the next major RubyGems version,
+ # and (optionally) a replacement for it.
+ #
+ # +rubygems_deprecate_command+:
+ # Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be
+ # removed in the next RubyGems version.
+ #
+ # Also provides +skip_during+ for temporarily turning off deprecation warnings.
+ # This is intended to be used in the test suite, so deprecation warnings
+ # don't cause test failures if you need to make sure stderr is otherwise empty.
+ #
+ #
+ # Example usage of +deprecate+ and +rubygems_deprecate+:
+ #
+ # class Legacy
+ # def self.some_class_method
+ # # ...
+ # end
+ #
+ # def some_instance_method
+ # # ...
+ # end
+ #
+ # def some_old_method
+ # # ...
+ # end
+ #
+ # extend Gem::Deprecate
+ # deprecate :some_instance_method, "X.z", 2011, 4
+ # rubygems_deprecate :some_old_method, "Modern#some_new_method"
+ #
+ # class << self
+ # extend Gem::Deprecate
+ # deprecate :some_class_method, :none, 2011, 4
+ # end
+ # end
+ #
+ #
+ # Example usage of +rubygems_deprecate_command+:
+ #
+ # class Gem::Commands::QueryCommand < Gem::Command
+ # extend Gem::Deprecate
+ # rubygems_deprecate_command
+ #
+ # # ...
+ # end
+ #
+ #
+ # Example usage of +skip_during+:
+ #
+ # class TestSomething < Gem::Testcase
+ # def test_some_thing_with_deprecations
+ # Gem::Deprecate.skip_during do
+ # actual_stdout, actual_stderr = capture_output do
+ # Gem.something_deprecated
+ # end
+ # assert_empty actual_stdout
+ # assert_equal(expected, actual_stderr)
+ # end
+ # end
+ # end
+
module Deprecate
def self.skip # :nodoc:
@skip ||= false
@@ -126,17 +126,18 @@ module Gem
# telling the user of +repl+ (unless +repl+ is :none) and the
# Rubygems version that it is planned to go away.
- def rubygems_deprecate(name, replacement=:none)
+ def rubygems_deprecate(name, replacement = :none, version = nil)
class_eval do
old = "_deprecated_#{name}"
alias_method old, name
define_method name do |*args, &block|
klass = is_a? Module
target = klass ? "#{self}." : "#{self.class}#"
+ version ||= Gem::Deprecate.next_rubygems_major_version
msg = [
"NOTE: #{target}#{name} is deprecated",
replacement == :none ? " with no replacement" : "; use #{replacement} instead",
- ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}",
+ ". It will be removed in Rubygems #{version}",
"\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}",
]
warn "#{msg.join}." unless Gem::Deprecate.skip
@@ -147,13 +148,14 @@ module Gem
end
# Deprecation method to deprecate Rubygems commands
- def rubygems_deprecate_command(version = Gem::Deprecate.next_rubygems_major_version)
+ def rubygems_deprecate_command(version = nil)
class_eval do
define_method "deprecated?" do
true
end
define_method "deprecation_warning" do
+ version ||= Gem::Deprecate.next_rubygems_major_version
msg = [
"#{command} command is deprecated",
". It will be removed in Rubygems #{version}.\n",
diff --git a/lib/rubygems/doctor.rb b/lib/rubygems/doctor.rb
index 56b7c081eb..4f26260d83 100644
--- a/lib/rubygems/doctor.rb
+++ b/lib/rubygems/doctor.rb
@@ -113,7 +113,7 @@ class Gem::Doctor
next if installed_specs.include? basename
next if /^rubygems-\d/.match?(basename)
next if sub_directory == "specifications" && basename == "default"
- next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ (basename)
+ next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ basename
type = File.directory?(child) ? "directory" : "file"
diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb
index 57fb3eb120..4bbc5217e0 100644
--- a/lib/rubygems/errors.rb
+++ b/lib/rubygems/errors.rb
@@ -26,7 +26,7 @@ module Gem
# system. Instead of rescuing from this class, make sure to rescue from the
# superclass Gem::LoadError to catch all types of load errors.
class MissingSpecError < Gem::LoadError
- def initialize(name, requirement, extra_message=nil)
+ def initialize(name, requirement, extra_message = nil)
@name = name
@requirement = requirement
@extra_message = extra_message
diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb
index 793324b875..40485bbadf 100644
--- a/lib/rubygems/exceptions.rb
+++ b/lib/rubygems/exceptions.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require_relative "deprecate"
require_relative "unknown_command_spell_checker"
##
@@ -21,20 +20,11 @@ class Gem::UnknownCommandError < Gem::Exception
end
def self.attach_correctable
- return if defined?(@attached)
+ return if method_defined?(:corrections)
- if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable)
- if DidYouMean.respond_to?(:correct_error)
- DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker)
- else
- DidYouMean::SPELL_CHECKERS["Gem::UnknownCommandError"] =
- Gem::UnknownCommandSpellChecker
-
- prepend DidYouMean::Correctable
- end
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
+ DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker)
end
-
- @attached = true
end
end
@@ -110,7 +100,7 @@ class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException
# and +version+. Any +errors+ encountered when attempting to find the gem
# are also stored.
- def initialize(name, version, errors=nil)
+ def initialize(name, version, errors = nil)
super "Could not find a valid gem '#{name}' (#{version}) locally or in a repository"
@name = name
@@ -261,7 +251,7 @@ class Gem::UnsatisfiableDependencyError < Gem::DependencyError
# Creates a new UnsatisfiableDependencyError for the unsatisfiable
# Gem::Resolver::DependencyRequest +dep+
- def initialize(dep, platform_mismatch=nil)
+ def initialize(dep, platform_mismatch = nil)
if platform_mismatch && !platform_mismatch.empty?
plats = platform_mismatch.map {|x| x.platform.to_s }.sort.uniq
super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(", ")}"
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index 05cd735bd9..62d36bcf48 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -11,6 +11,9 @@ require_relative "../user_interaction"
class Gem::Ext::Builder
include Gem::UserInteraction
+ class NoMakefileError < Gem::InstallError
+ end
+
attr_accessor :build_args # :nodoc:
def self.class_name
@@ -19,9 +22,10 @@ class Gem::Ext::Builder
end
def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"],
- target_rbconfig: Gem.target_rbconfig)
+ target_rbconfig: Gem.target_rbconfig, n_jobs: nil)
unless File.exist? File.join(make_dir, "Makefile")
- raise Gem::InstallError, "Makefile not found"
+ # No makefile exists, nothing to do.
+ raise NoMakefileError, "No Makefile found in #{make_dir}"
end
# try to find make program from Ruby configure arguments first
@@ -30,8 +34,18 @@ class Gem::Ext::Builder
make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make"
make_program = shellsplit(make_program_name)
+ is_nmake = /\bnmake/i.match?(make_program_name)
# The installation of the bundled gems is failed when DESTDIR is empty in mswin platform.
- destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : ""
+ destdir = !is_nmake || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : ""
+
+ # nmake doesn't support parallel build
+ unless is_nmake
+ have_make_arguments = make_program.size > 1
+
+ if !have_make_arguments && !ENV["MAKEFLAGS"] && n_jobs
+ make_program << "-j#{n_jobs}"
+ end
+ end
env = [destdir]
@@ -143,13 +157,12 @@ class Gem::Ext::Builder
# have build arguments, saved, set +build_args+ which is an ARGV-style
# array.
- def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig)
+ def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig, build_jobs = nil)
@spec = spec
@build_args = build_args
@gem_dir = spec.full_gem_path
@target_rbconfig = target_rbconfig
-
- @ran_rake = false
+ @build_jobs = build_jobs
end
##
@@ -162,10 +175,9 @@ class Gem::Ext::Builder
when /configure/ then
Gem::Ext::ConfigureBuilder
when /rakefile/i, /mkrf_conf/i then
- @ran_rake = true
Gem::Ext::RakeBuilder
when /CMakeLists.txt/ then
- Gem::Ext::CmakeBuilder
+ Gem::Ext::CmakeBuilder.new
when /Cargo.toml/ then
Gem::Ext::CargoBuilder.new
else
@@ -204,7 +216,7 @@ EOF
FileUtils.mkdir_p dest_path
results = builder.build(extension, dest_path,
- results, @build_args, lib_dir, extension_dir, @target_rbconfig)
+ results, @build_args, lib_dir, extension_dir, @target_rbconfig, n_jobs: @build_jobs)
verbose { results.join("\n") }
@@ -235,8 +247,6 @@ EOF
FileUtils.rm_f @spec.gem_build_complete_path
@spec.extensions.each do |extension|
- break if @ran_rake
-
build_extension extension, dest_path
end
diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb
index 03024a640e..42dca3b102 100644
--- a/lib/rubygems/ext/cargo_builder.rb
+++ b/lib/rubygems/ext/cargo_builder.rb
@@ -15,7 +15,7 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder
end
def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd,
- target_rbconfig=Gem.target_rbconfig)
+ target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
require "tempfile"
require "fileutils"
@@ -158,6 +158,10 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder
# mkmf work properly.
def linker_args
cc_flag = self.class.shellsplit(makefile_config("CC"))
+ # Avoid to ccache like tool from Rust build
+ # see https://github.com/ruby/rubygems/pull/8521#issuecomment-2689854359
+ # ex. CC="ccache gcc" or CC="sccache clang --any --args"
+ cc_flag.shift if cc_flag.size >= 2 && !cc_flag[1].start_with?("-")
linker = cc_flag.shift
link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] }
diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb
index 34564f668d..e660ed558b 100644
--- a/lib/rubygems/ext/cmake_builder.rb
+++ b/lib/rubygems/ext/cmake_builder.rb
@@ -1,21 +1,110 @@
# frozen_string_literal: true
+# This builder creates extensions defined using CMake. Its is invoked if a Gem's spec file
+# sets the `extension` property to a string that contains `CMakeLists.txt`.
+#
+# In general, CMake projects are built in two steps:
+#
+# * configure
+# * build
+#
+# The builder follow this convention. First it runs a configuration step and then it runs a build step.
+#
+# CMake projects can be quite configurable - it is likely you will want to specify options when
+# installing a gem. To pass options to CMake specify them after `--` in the gem install command. For example:
+#
+# gem install <gem_name> -- --preset <preset_name>
+#
+# Note that options are ONLY sent to the configure step - it is not currently possible to specify
+# options for the build step. If this becomes and issue then the CMake builder can be updated to
+# support build options.
+#
+# Useful options to know are:
+#
+# -G to specify a generator (-G Ninja is recommended)
+# -D<CMAKE_VARIABLE> to set a CMake variable (for example -DCMAKE_BUILD_TYPE=Release)
+# --preset <preset_name> to use a preset
+#
+# If the Gem author provides presets, via CMakePresets.json file, you will likely want to use one of them.
+# If not, you may wish to specify a generator. Ninja is recommended because it can build projects in parallel
+# and thus much faster than building them serially like Make does.
+
class Gem::Ext::CmakeBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd,
- target_rbconfig=Gem.target_rbconfig)
+ attr_accessor :runner, :profile
+ def initialize
+ @runner = self.class.method(:run)
+ @profile = :release
+ end
+
+ def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd,
+ target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
if target_rbconfig.path
warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring"
end
- unless File.exist?(File.join(cmake_dir, "Makefile"))
- require_relative "../command"
- cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args]
+ # Figure the build dir
+ build_dir = File.join(cmake_dir, "build")
- run cmd, results, class_name, cmake_dir
- end
+ # Check if the gem defined presets
+ check_presets(cmake_dir, args, results)
+
+ # Configure
+ configure(cmake_dir, build_dir, dest_path, args, results)
- make dest_path, results, cmake_dir, target_rbconfig: target_rbconfig
+ # Compile
+ compile(cmake_dir, build_dir, args, results)
results
end
+
+ def configure(cmake_dir, build_dir, install_dir, args, results)
+ cmd = ["cmake",
+ cmake_dir,
+ "-B",
+ build_dir,
+ "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=#{install_dir}", # Windows
+ "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=#{install_dir}", # Not Windows
+ *Gem::Command.build_args,
+ *args]
+
+ runner.call(cmd, results, "cmake_configure", cmake_dir)
+ end
+
+ def compile(cmake_dir, build_dir, args, results)
+ cmd = ["cmake",
+ "--build",
+ build_dir.to_s,
+ "--config",
+ @profile.to_s]
+
+ runner.call(cmd, results, "cmake_compile", cmake_dir)
+ end
+
+ private
+
+ def check_presets(cmake_dir, args, results)
+ # Return if the user specified a preset
+ return unless args.grep(/--preset/i).empty?
+
+ cmd = ["cmake",
+ "--list-presets"]
+
+ presets = Array.new
+ begin
+ runner.call(cmd, presets, "cmake_presets", cmake_dir)
+
+ # Remove the first two lines of the array which is the current_directory and the command
+ # that was run
+ presets = presets[2..].join
+ results << <<~EOS
+ The gem author provided a list of presets that can be used to build the gem. To use a preset specify it on the command line:
+
+ gem install <gem_name> -- --preset <preset_name>
+
+ #{presets}
+ EOS
+ rescue Gem::InstallError
+ # Do nothing, CMakePresets.json was not included in the Gem
+ end
+ end
end
diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb
index d91b1ec5e8..230b214b3c 100644
--- a/lib/rubygems/ext/configure_builder.rb
+++ b/lib/rubygems/ext/configure_builder.rb
@@ -7,8 +7,8 @@
#++
class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd,
- target_rbconfig=Gem.target_rbconfig)
+ def self.build(extension, dest_path, results, args = [], lib_dir = nil, configure_dir = Dir.pwd,
+ target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
if target_rbconfig.path
warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring"
end
@@ -19,7 +19,7 @@ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder
run cmd, results, class_name, configure_dir
end
- make dest_path, results, configure_dir, target_rbconfig: target_rbconfig
+ make dest_path, results, configure_dir, target_rbconfig: target_rbconfig, n_jobs: n_jobs
results
end
diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb
index e652a221f8..822454355d 100644
--- a/lib/rubygems/ext/ext_conf_builder.rb
+++ b/lib/rubygems/ext/ext_conf_builder.rb
@@ -7,8 +7,8 @@
#++
class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd,
- target_rbconfig=Gem.target_rbconfig)
+ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd,
+ target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
require "fileutils"
require "tempfile"
@@ -41,7 +41,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
ENV["DESTDIR"] = nil
- make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig
+ make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs
full_tmp_dest = File.join(extension_dir, tmp_dest_relative)
@@ -66,6 +66,10 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder
end
results
+ rescue Gem::Ext::Builder::NoMakefileError => error
+ results << error.message
+ results << "Skipping make for #{extension} as no Makefile was found."
+ # We are good, do not re-raise the error.
ensure
FileUtils.rm_rf tmp_dest if tmp_dest
end
diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb
index 8edd8d1373..d702d7f339 100644
--- a/lib/rubygems/ext/rake_builder.rb
+++ b/lib/rubygems/ext/rake_builder.rb
@@ -7,8 +7,8 @@
#++
class Gem::Ext::RakeBuilder < Gem::Ext::Builder
- def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd,
- target_rbconfig=Gem.target_rbconfig)
+ def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd,
+ target_rbconfig = Gem.target_rbconfig, n_jobs: nil)
if target_rbconfig.path
warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring"
end
diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb
index 4cb924677f..e60cebd0cb 100644
--- a/lib/rubygems/gem_runner.rb
+++ b/lib/rubygems/gem_runner.rb
@@ -8,7 +8,6 @@
require_relative "../rubygems"
require_relative "command_manager"
-require_relative "deprecate"
##
# Run an instance of the gem program.
diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb
index d3176d4564..afe7957f43 100644
--- a/lib/rubygems/gemcutter_utilities.rb
+++ b/lib/rubygems/gemcutter_utilities.rb
@@ -263,7 +263,10 @@ module Gem::GemcutterUtilities
port = server.addr[1].to_s
url_with_port = "#{webauthn_url}?port=#{port}"
- say "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option."
+ say "You have enabled multi-factor authentication. Please visit the following URL to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option."
+ say ""
+ say url_with_port
+ say ""
threads = [WebauthnListener.listener_thread(host, server), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)]
otp_thread = wait_for_otp_thread(*threads)
@@ -316,7 +319,7 @@ module Gem::GemcutterUtilities
end
def get_scope_params(scope)
- scope_params = { index_rubygems: true }
+ scope_params = { index_rubygems: true, push_rubygem: true }
if scope
scope_params = { scope => true }
diff --git a/lib/rubygems/install_default_message.rb b/lib/rubygems/install_default_message.rb
deleted file mode 100644
index 0640eaaf08..0000000000
--- a/lib/rubygems/install_default_message.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "../rubygems"
-require_relative "user_interaction"
-
-##
-# A post-install hook that displays "Successfully installed
-# some_gem-1.0 as a default gem"
-
-Gem.post_install do |installer|
- ui = Gem::DefaultUserInteraction.ui
- ui.say "Successfully installed #{installer.spec.full_name} as a default gem"
-end
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index 0d0f0dc211..66cb5c049b 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -31,6 +31,15 @@ module Gem::InstallUpdateOptions
options[:bin_dir] = File.expand_path(value)
end
+ add_option(:"Install/Update", "-j", "--build-jobs VALUE", Integer,
+ "Specify the number of jobs to pass to `make` when installing",
+ "gems with native extensions.",
+ "Defaults to the number of processors.",
+ "This option is ignored on the mswin platform or",
+ "if the MAKEFLAGS environment variable is set.") do |value, options|
+ options[:build_jobs] = value
+ end
+
add_option(:"Install/Update", "--document [TYPES]", Array,
"Generate documentation for installed gems",
"List the documentation types you wish to",
@@ -158,10 +167,9 @@ module Gem::InstallUpdateOptions
options[:without_groups].concat v.map(&:intern)
end
- add_option(:"Install/Update", "--default",
+ add_option(:Deprecated, "--default",
"Add the gem's full specification to",
"specifications/default and extract only its bin") do |v,_o|
- options[:install_as_default] = v
end
add_option(:"Install/Update", "--explain",
diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb
index 7f5d913ac4..914e413677 100644
--- a/lib/rubygems/installer.rb
+++ b/lib/rubygems/installer.rb
@@ -8,7 +8,6 @@
require_relative "installer_uninstaller_utils"
require_relative "exceptions"
-require_relative "deprecate"
require_relative "package"
require_relative "ext"
require_relative "user_interaction"
@@ -27,8 +26,6 @@ require_relative "user_interaction"
# file. See Gem.pre_install and Gem.post_install for details.
class Gem::Installer
- extend Gem::Deprecate
-
##
# Paths where env(1) might live. Some systems are broken and have it in
# /bin
@@ -67,23 +64,6 @@ class Gem::Installer
attr_reader :package
class << self
- #
- # Changes in rubygems to lazily loading `rubygems/command` (in order to
- # lazily load `optparse` as a side effect) affect bundler's custom installer
- # which uses `Gem::Command` without requiring it (up until bundler 2.2.29).
- # This hook is to compensate for that missing require.
- #
- # TODO: Remove when rubygems no longer supports running on bundler older
- # than 2.2.29.
-
- def inherited(klass)
- if klass.name == "Bundler::RubyGemsGemInstaller"
- require "rubygems/command"
- end
-
- super(klass)
- end
-
##
# Overrides the executable format.
#
@@ -170,7 +150,7 @@ class Gem::Installer
# process. If not set, then Gem::Command.build_args is used
# :post_install_message:: Print gem post install message if true
- def initialize(package, options={})
+ def initialize(package, options = {})
require "fileutils"
@options = options
@@ -228,8 +208,7 @@ class Gem::Installer
ruby_executable = true
existing = io.read.slice(/
^\s*(
- gem \s |
- load \s Gem\.bin_path\( |
+ Gem\.activate_and_load_bin_path\( |
load \s Gem\.activate_bin_path\(
)
(['"])(.*?)(\2),
@@ -292,11 +271,7 @@ class Gem::Installer
run_pre_install_hooks
# Set loaded_from to ensure extension_dir is correct
- if @options[:install_as_default]
- spec.loaded_from = default_spec_file
- else
- spec.loaded_from = spec_file
- end
+ spec.loaded_from = spec_file
# Completely remove any previous gem files
FileUtils.rm_rf gem_dir
@@ -305,24 +280,17 @@ class Gem::Installer
dir_mode = options[:dir_mode]
FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755
- if @options[:install_as_default]
- extract_bin
- write_default_spec
- else
- extract_files
+ extract_files
- build_extensions
- write_build_info_file
- run_post_build_hooks
- end
+ build_extensions
+ write_build_info_file
+ run_post_build_hooks
generate_bin
generate_plugins
- unless @options[:install_as_default]
- write_spec
- write_cache_file
- end
+ write_spec
+ write_cache_file
File.chmod(dir_mode, gem_dir) if dir_mode
@@ -411,15 +379,6 @@ class Gem::Installer
end
##
- # Unpacks the gem into the given directory.
-
- def unpack(directory)
- @gem_dir = directory
- extract_files
- end
- rubygems_deprecate :unpack
-
- ##
# The location of the spec file that is installed.
#
@@ -427,12 +386,18 @@ class Gem::Installer
File.join gem_home, "specifications", "#{spec.full_name}.gemspec"
end
+ def default_spec_dir
+ dir = File.join(gem_home, "specifications", "default")
+ FileUtils.mkdir_p dir
+ dir
+ end
+
##
# The location of the default spec file for default gems.
#
def default_spec_file
- File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec"
+ File.join default_spec_dir, "#{spec.full_name}.gemspec"
end
##
@@ -670,6 +635,7 @@ class Gem::Installer
@build_root = options[:build_root]
@build_args = options[:build_args]
+ @build_jobs = options[:build_jobs]
@gem_home = @install_dir || user_install_dir || Gem.dir
@@ -749,54 +715,53 @@ class Gem::Installer
def app_script_text(bin_file_name)
# NOTE: that the `load` lines cannot be indented, as old RG versions match
# against the beginning of the line
- <<-TEXT
-#{shebang bin_file_name}
-#
-# This file was generated by RubyGems.
-#
-# The application '#{spec.name}' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-require 'rubygems'
-#{gemdeps_load(spec.name)}
-version = "#{Gem::Requirement.default_prerelease}"
-
-str = ARGV.first
-if str
- str = str.b[/\\A_(.*)_\\z/, 1]
- if str and Gem::Version.correct?(str)
- #{explicit_version_requirement(spec.name)}
- ARGV.shift
- end
-end
+ <<~TEXT
+ #{shebang bin_file_name}
+ #
+ # This file was generated by RubyGems.
+ #
+ # The application '#{spec.name}' is installed as part of a gem, and
+ # this file is here to facilitate running it.
+ #
+
+ require 'rubygems'
+ #{gemdeps_load(spec.name)}
+ version = "#{Gem::Requirement.default_prerelease}"
+
+ str = ARGV.first
+ if str
+ str = str.b[/\\A_(.*)_\\z/, 1]
+ if str and Gem::Version.correct?(str)
+ #{explicit_version_requirement(spec.name)}
+ ARGV.shift
+ end
+ end
-if Gem.respond_to?(:activate_bin_path)
-load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version)
-else
-gem #{spec.name.dump}, version
-load Gem.bin_path(#{spec.name.dump}, #{bin_file_name.dump}, version)
-end
-TEXT
+ if Gem.respond_to?(:activate_and_load_bin_path)
+ Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version)
+ else
+ load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version)
+ end
+ TEXT
end
def gemdeps_load(name)
return "" if name == "bundler"
- <<-TEXT
+ <<~TEXT
-Gem.use_gemdeps
-TEXT
+ Gem.use_gemdeps
+ TEXT
end
def explicit_version_requirement(name)
code = "version = str"
return code unless name == "bundler"
- code += <<-TEXT
+ code += <<~TEXT
- ENV['BUNDLER_VERSION'] = str
-TEXT
+ ENV['BUNDLER_VERSION'] = str
+ TEXT
end
##
@@ -811,9 +776,9 @@ TEXT
if File.exist?(File.join(bindir, ruby_exe))
# stub & ruby.exe within same folder. Portable
- <<-TEXT
-@ECHO OFF
-@"%~dp0#{ruby_exe}" "%~dpn0" %*
+ <<~TEXT
+ @ECHO OFF
+ @"%~dp0#{ruby_exe}" "%~dpn0" %*
TEXT
elsif bindir.downcase.start_with? rb_topdir.downcase
# stub within ruby folder, but not standard bin. Portable
@@ -821,16 +786,16 @@ TEXT
from = Pathname.new bindir
to = Pathname.new "#{rb_topdir}/bin"
rel = to.relative_path_from from
- <<-TEXT
-@ECHO OFF
-@"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %*
+ <<~TEXT
+ @ECHO OFF
+ @"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %*
TEXT
else
# outside ruby folder, maybe -user-install or bundler. Portable, but ruby
# is dependent on PATH
- <<-TEXT
-@ECHO OFF
-@#{ruby_exe} "%~dpn0" %*
+ <<~TEXT
+ @ECHO OFF
+ @#{ruby_exe} "%~dpn0" %*
TEXT
end
end
@@ -839,7 +804,7 @@ TEXT
# configure scripts and rakefiles or mkrf_conf files.
def build_extensions
- builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig
+ builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs
builder.build_extensions
end
@@ -908,11 +873,7 @@ TEXT
ensure_loadable_spec
- if options[:install_as_default]
- Gem.ensure_default_gem_subdirectories gem_home
- else
- Gem.ensure_gem_subdirectories gem_home
- end
+ Gem.ensure_gem_subdirectories gem_home
return true if @force
@@ -953,11 +914,8 @@ TEXT
end
def ensure_writable_dir(dir) # :nodoc:
- begin
- Dir.mkdir dir, *[options[:dir_mode] && 0o755].compact
- rescue SystemCallError
- raise unless File.directory? dir
- end
+ require "fileutils"
+ FileUtils.mkdir_p dir, mode: options[:dir_mode] && 0o755
raise Gem::FilePermissionError.new(dir) unless File.writable? dir
end
@@ -984,6 +942,15 @@ TEXT
end
end
+ def build_jobs
+ @build_jobs ||= begin
+ require "etc"
+ Etc.nprocessors + 1
+ rescue LoadError
+ 1
+ end
+ end
+
def rb_config
Gem.target_rbconfig
end
diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb
index 3f4a6fcf3d..cbdf4d7ac5 100644
--- a/lib/rubygems/name_tuple.rb
+++ b/lib/rubygems/name_tuple.rb
@@ -6,7 +6,7 @@
# wrap the data returned from the indexes.
class Gem::NameTuple
- def initialize(name, version, platform=Gem::Platform::RUBY)
+ def initialize(name, version, platform = Gem::Platform::RUBY)
@name = name
@version = version
@@ -81,6 +81,12 @@ class Gem::NameTuple
[@name, @version, @platform]
end
+ alias_method :deconstruct, :to_a
+
+ def deconstruct_keys(keys)
+ { name: @name, version: @version, platform: @platform }
+ end
+
def inspect # :nodoc:
"#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>"
end
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index c855423ed7..6b21ff1b95 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -7,6 +7,7 @@
# rubocop:enable Style/AsciiComments
+require_relative "win_platform"
require_relative "security"
require_relative "user_interaction"
@@ -267,7 +268,7 @@ class Gem::Package
tar.add_file_simple file, stat.mode, stat.size do |dst_io|
File.open file, "rb" do |src_io|
- copy_stream(src_io, dst_io)
+ copy_stream(src_io, dst_io, stat.size)
end
end
end
@@ -436,8 +437,6 @@ EOM
symlinks << [full_name, link_target, destination, real_destination]
end
- FileUtils.rm_rf destination
-
mkdir =
if entry.directory?
destination
@@ -451,8 +450,14 @@ EOM
end
if entry.file?
- File.open(destination, "wb") {|out| copy_stream(entry, out) }
- FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination
+ File.open(destination, "wb") do |out|
+ copy_stream(tar.io, out, entry.size)
+ # Flush needs to happen before chmod because there could be data
+ # in the IO buffer that needs to be written, and that could be
+ # written after the chmod (on close) which would mess up the perms
+ out.flush
+ out.chmod file_mode(entry.header.mode) & ~File.umask
+ end
end
verbose destination
@@ -514,10 +519,12 @@ EOM
destination
end
- def normalize_path(pathname)
- if Gem.win_platform?
+ if Gem.win_platform?
+ def normalize_path(pathname) # :nodoc:
pathname.downcase
- else
+ end
+ else
+ def normalize_path(pathname) # :nodoc:
pathname
end
end
@@ -635,6 +642,8 @@ EOM
raise Gem::Package::FormatError.new e.message, @gem
end
+ private
+
##
# Verifies the +checksums+ against the +digests+. This check is not
# cryptographically secure. Missing checksums are ignored.
@@ -715,12 +724,12 @@ EOM
end
if RUBY_ENGINE == "truffleruby"
- def copy_stream(src, dst) # :nodoc:
- dst.write src.read
+ def copy_stream(src, dst, size) # :nodoc:
+ dst.write src.read(size)
end
else
- def copy_stream(src, dst) # :nodoc:
- IO.copy_stream(src, dst)
+ def copy_stream(src, dst, size) # :nodoc:
+ IO.copy_stream(src, dst, size)
end
end
diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb
index 0ebcbd789d..dd20d65080 100644
--- a/lib/rubygems/package/tar_header.rb
+++ b/lib/rubygems/package/tar_header.rb
@@ -56,7 +56,7 @@ class Gem::Package::TarHeader
##
# Pack format for a tar header
- PACK_FORMAT = "a100" + # name
+ PACK_FORMAT = ("a100" + # name
"a8" + # mode
"a8" + # uid
"a8" + # gid
@@ -71,12 +71,12 @@ class Gem::Package::TarHeader
"a32" + # gname
"a8" + # devmajor
"a8" + # devminor
- "a155" # prefix
+ "a155").freeze # prefix
##
# Unpack format for a tar header
- UNPACK_FORMAT = "A100" + # name
+ UNPACK_FORMAT = ("A100" + # name
"A8" + # mode
"A8" + # uid
"A8" + # gid
@@ -91,7 +91,7 @@ class Gem::Package::TarHeader
"A32" + # gname
"A8" + # devmajor
"A8" + # devminor
- "A155" # prefix
+ "A155").freeze # prefix
attr_reader(*FIELDS)
diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb
index 25f9b2f945..b66a8a62bc 100644
--- a/lib/rubygems/package/tar_reader.rb
+++ b/lib/rubygems/package/tar_reader.rb
@@ -30,6 +30,8 @@ class Gem::Package::TarReader
nil
end
+ attr_reader :io # :nodoc:
+
##
# Creates a new tar file reader on +io+ which needs to respond to #pos,
# #eof?, #read, #getc and #pos=
diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb
index b24bdb63e7..39fed9e2af 100644
--- a/lib/rubygems/package/tar_writer.rb
+++ b/lib/rubygems/package/tar_writer.rb
@@ -95,10 +95,11 @@ class Gem::Package::TarWriter
end
##
- # Adds file +name+ with permissions +mode+, and yields an IO for writing the
- # file to
+ # Adds file +name+ with permissions +mode+ and mtime +mtime+ (sets
+ # Gem.source_date_epoch if not specified), and yields an IO for
+ # writing the file to
- def add_file(name, mode) # :yields: io
+ def add_file(name, mode, mtime = nil) # :yields: io
check_closed
name, prefix = split_name name
@@ -118,7 +119,7 @@ class Gem::Package::TarWriter
header = Gem::Package::TarHeader.new name: name, mode: mode,
size: size, prefix: prefix,
- mtime: Gem.source_date_epoch
+ mtime: mtime || Gem.source_date_epoch
@io.write header
@io.pos = final_pos
diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb
index 450c214167..367b00e7e1 100644
--- a/lib/rubygems/platform.rb
+++ b/lib/rubygems/platform.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require_relative "deprecate"
-
##
# Available list of platforms for targeting Gem installations.
#
@@ -21,15 +19,6 @@ class Gem::Platform
end
end
- def self.match(platform)
- match_platforms?(platform, Gem.platforms)
- end
-
- class << self
- extend Gem::Deprecate
- rubygems_deprecate :match, "Gem::Platform.match_spec? or match_gem?"
- end
-
def self.match_platforms?(platform, platforms)
platform = Gem::Platform.new(platform) unless platform.is_a?(Gem::Platform)
platforms.any? do |local_platform|
@@ -88,56 +77,45 @@ class Gem::Platform
when Array then
@cpu, @os, @version = arch
when String then
- arch = arch.split "-"
-
- if arch.length > 2 && !arch.last.match?(/\d+(\.\d+)?$/) # reassemble x86-linux-{libc}
- extra = arch.pop
- arch.last << "-#{extra}"
- end
+ cpu, os = arch.sub(/-+$/, "").split("-", 2)
- cpu = arch.shift
-
- @cpu = case cpu
- when /i\d86/ then "x86"
- else cpu
- end
-
- if arch.length == 2 && arch.last.match?(/^\d+(\.\d+)?$/) # for command-line
- @os, @version = arch
- return
+ @cpu = if cpu&.match?(/i\d86/)
+ "x86"
+ else
+ cpu
end
- os, = arch
if os.nil?
@cpu = nil
os = cpu
end # legacy jruby
@os, @version = case os
- when /aix(\d+)?/ then ["aix", $1]
- when /cygwin/ then ["cygwin", nil]
- when /darwin(\d+)?/ then ["darwin", $1]
- when /^macruby$/ then ["macruby", nil]
- when /freebsd(\d+)?/ then ["freebsd", $1]
- when /^java$/, /^jruby$/ then ["java", nil]
- when /^java([\d.]*)/ then ["java", $1]
- when /^dalvik(\d+)?$/ then ["dalvik", $1]
- when /^dotnet$/ then ["dotnet", nil]
- when /^dotnet([\d.]*)/ then ["dotnet", $1]
- when /linux-?(\w+)?/ then ["linux", $1]
- when /mingw32/ then ["mingw32", nil]
- when /mingw-?(\w+)?/ then ["mingw", $1]
- when /(mswin\d+)(\_(\d+))?/ then
+ when /aix-?(\d+)?/ then ["aix", $1]
+ when /cygwin/ then ["cygwin", nil]
+ when /darwin-?(\d+)?/ then ["darwin", $1]
+ when "macruby" then ["macruby", nil]
+ when /^macruby-?(\d+(?:\.\d+)*)?/ then ["macruby", $1]
+ when /freebsd-?(\d+)?/ then ["freebsd", $1]
+ when "java", "jruby" then ["java", nil]
+ when /^java-?(\d+(?:\.\d+)*)?/ then ["java", $1]
+ when /^dalvik-?(\d+)?$/ then ["dalvik", $1]
+ when /^dotnet$/ then ["dotnet", nil]
+ when /^dotnet-?(\d+(?:\.\d+)*)?/ then ["dotnet", $1]
+ when /linux-?(\w+)?/ then ["linux", $1]
+ when /mingw32/ then ["mingw32", nil]
+ when /mingw-?(\w+)?/ then ["mingw", $1]
+ when /(mswin\d+)(?:[_-](\d+))?/ then
os = $1
- version = $3
- @cpu = "x86" if @cpu.nil? && os =~ /32$/
+ version = $2
+ @cpu = "x86" if @cpu.nil? && os.end_with?("32")
[os, version]
- when /netbsdelf/ then ["netbsdelf", nil]
- when /openbsd(\d+\.\d+)?/ then ["openbsd", $1]
- when /solaris(\d+\.\d+)?/ then ["solaris", $1]
- when /wasi/ then ["wasi", nil]
+ when /netbsdelf/ then ["netbsdelf", nil]
+ when /openbsd-?(\d+\.\d+)?/ then ["openbsd", $1]
+ when /solaris-?(\d+\.\d+)?/ then ["solaris", $1]
+ when /wasi/ then ["wasi", nil]
# test
- when /^(\w+_platform)(\d+)?/ then [$1, $2]
+ when /^(\w+_platform)-?(\d+)?/ then [$1, $2]
else ["unknown", nil]
end
when Gem::Platform then
@@ -154,7 +132,38 @@ class Gem::Platform
end
def to_s
- to_a.compact.join "-"
+ to_a.compact.join(@cpu.nil? ? "" : "-")
+ end
+
+ ##
+ # Deconstructs the platform into an array for pattern matching.
+ # Returns [cpu, os, version].
+ #
+ # Gem::Platform.new("x86_64-linux").deconstruct #=> ["x86_64", "linux", nil]
+ #
+ # This enables array pattern matching:
+ #
+ # case Gem::Platform.new("arm64-darwin-21")
+ # in ["arm64", "darwin", version]
+ # # version => "21"
+ # end
+ alias_method :deconstruct, :to_a
+
+ ##
+ # Deconstructs the platform into a hash for pattern matching.
+ # Returns a hash with keys +:cpu+, +:os+, and +:version+.
+ #
+ # Gem::Platform.new("x86_64-darwin-20").deconstruct_keys(nil)
+ # #=> { cpu: "x86_64", os: "darwin", version: "20" }
+ #
+ # This enables hash pattern matching:
+ #
+ # case Gem::Platform.new("x86_64-linux")
+ # in cpu: "x86_64", os: "linux"
+ # # Matches Linux on x86_64
+ # end
+ def deconstruct_keys(keys)
+ { cpu: @cpu, os: @os, version: @version }
end
##
@@ -266,4 +275,118 @@ class Gem::Platform
# This will be replaced with Gem::Platform::local.
CURRENT = "current"
+
+ JAVA = Gem::Platform.new("java") # :nodoc:
+ MSWIN = Gem::Platform.new("mswin32") # :nodoc:
+ MSWIN64 = Gem::Platform.new("mswin64") # :nodoc:
+ MINGW = Gem::Platform.new("x86-mingw32") # :nodoc:
+ X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc:
+ X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc:
+ UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc:
+ WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc:
+ X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc:
+ X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc:
+
+ GENERICS = [JAVA, *WINDOWS].freeze # :nodoc:
+ private_constant :GENERICS
+
+ GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc:
+ private_constant :GENERIC_CACHE
+
+ class << self
+ ##
+ # Returns the generic platform for the given platform.
+
+ def generic(platform)
+ return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY
+
+ GENERIC_CACHE[platform] ||= begin
+ found = GENERICS.find do |match|
+ platform === match
+ end
+ found || Gem::Platform::RUBY
+ end
+ end
+
+ ##
+ # Returns the platform specificity match for the given spec platform and user platform.
+
+ def platform_specificity_match(spec_platform, user_platform)
+ return -1 if spec_platform == user_platform
+ return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
+
+ os_match(spec_platform, user_platform) +
+ cpu_match(spec_platform, user_platform) * 10 +
+ version_match(spec_platform, user_platform) * 100
+ end
+
+ ##
+ # Sorts and filters the best platform match for the given matching specs and platform.
+
+ def sort_and_filter_best_platform_match(matching, platform)
+ return matching if matching.one?
+
+ exact = matching.select {|spec| spec.platform == platform }
+ return exact if exact.any?
+
+ sorted_matching = sort_best_platform_match(matching, platform)
+ exemplary_spec = sorted_matching.first
+
+ sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) }
+ end
+
+ ##
+ # Sorts the best platform match for the given matching specs and platform.
+
+ def sort_best_platform_match(matching, platform)
+ matching.sort_by.with_index do |spec, i|
+ [
+ platform_specificity_match(spec.platform, platform),
+ i, # for stable sort
+ ]
+ end
+ end
+
+ private
+
+ def same_specificity?(platform, spec, exemplary_spec)
+ platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform)
+ end
+
+ def same_deps?(spec, exemplary_spec)
+ spec.required_ruby_version == exemplary_spec.required_ruby_version &&
+ spec.required_rubygems_version == exemplary_spec.required_rubygems_version &&
+ spec.dependencies.sort == exemplary_spec.dependencies.sort
+ end
+
+ def os_match(spec_platform, user_platform)
+ if spec_platform.os == user_platform.os
+ 0
+ else
+ 1
+ end
+ end
+
+ def cpu_match(spec_platform, user_platform)
+ if spec_platform.cpu == user_platform.cpu
+ 0
+ elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
+ 0
+ elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
+ 1
+ else
+ 2
+ end
+ end
+
+ def version_match(spec_platform, user_platform)
+ if spec_platform.version == user_platform.version
+ 0
+ elsif spec_platform.version.nil?
+ 1
+ else
+ 2
+ end
+ end
+ end
end
diff --git a/lib/rubygems/psych_tree.rb b/lib/rubygems/psych_tree.rb
index 24857adb9d..8b4c425a33 100644
--- a/lib/rubygems/psych_tree.rb
+++ b/lib/rubygems/psych_tree.rb
@@ -22,7 +22,7 @@ module Gem
def register(target, obj)
end
- # This is ported over from the yaml_tree in 1.9.3
+ # This is ported over from the YAMLTree implementation in Ruby 1.9.3
def format_time(time)
if time.utc?
time.strftime("%Y-%m-%d %H:%M:%S.%9N Z")
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb
index 4b5c74e0ea..805f7aaf82 100644
--- a/lib/rubygems/remote_fetcher.rb
+++ b/lib/rubygems/remote_fetcher.rb
@@ -72,7 +72,7 @@ class Gem::RemoteFetcher
# +headers+: A set of additional HTTP headers to be sent to the server when
# fetching the gem.
- def initialize(proxy=nil, dns=nil, headers={})
+ def initialize(proxy = nil, dns = nil, headers = {})
require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled
require_relative "vendored_net_http"
require_relative "vendor/uri/lib/uri"
@@ -82,6 +82,7 @@ class Gem::RemoteFetcher
@proxy = proxy
@pools = {}
@pool_lock = Thread::Mutex.new
+ @pool_size = 1
@cert_files = Gem::Request.get_cert_files
@headers = headers
@@ -245,11 +246,14 @@ class Gem::RemoteFetcher
def fetch_path(uri, mtime = nil, head = false)
uri = Gem::Uri.new uri
- unless uri.scheme
- raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}"
- end
+ method = {
+ "http" => "fetch_http",
+ "https" => "fetch_http",
+ "s3" => "fetch_s3",
+ "file" => "fetch_file",
+ }.fetch(uri.scheme) { raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" }
- data = send "fetch_#{uri.scheme}", uri, mtime, head
+ data = send method, uri, mtime, head
if data && !head && uri.to_s.end_with?(".gz")
begin
@@ -267,7 +271,7 @@ class Gem::RemoteFetcher
def fetch_s3(uri, mtime = nil, head = false)
begin
- public_uri = s3_uri_signer(uri).sign
+ public_uri = s3_uri_signer(uri, head ? "HEAD" : "GET").sign
rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e
raise FetchError.new(e.message, "s3://#{uri.host}")
end
@@ -275,8 +279,8 @@ class Gem::RemoteFetcher
end
# we have our own signing code here to avoid a dependency on the aws-sdk gem
- def s3_uri_signer(uri)
- Gem::S3URISigner.new(uri)
+ def s3_uri_signer(uri, method)
+ Gem::S3URISigner.new(uri, method)
end
##
@@ -335,7 +339,7 @@ class Gem::RemoteFetcher
def pools_for(proxy)
@pool_lock.synchronize do
- @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files
+ @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files, @pool_size
end
end
end
diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb
index 6c1b04ab65..01e7e0629a 100644
--- a/lib/rubygems/request/connection_pools.rb
+++ b/lib/rubygems/request/connection_pools.rb
@@ -7,11 +7,12 @@ class Gem::Request::ConnectionPools # :nodoc:
attr_accessor :client
end
- def initialize(proxy_uri, cert_files)
+ def initialize(proxy_uri, cert_files, pool_size = 1)
@proxy_uri = proxy_uri
@cert_files = cert_files
@pools = {}
@pool_mutex = Thread::Mutex.new
+ @pool_size = pool_size
end
def pool_for(uri)
@@ -20,9 +21,9 @@ class Gem::Request::ConnectionPools # :nodoc:
@pool_mutex.synchronize do
@pools[key] ||=
if https? uri
- Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri)
+ Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri, @pool_size)
else
- Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri)
+ Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri, @pool_size)
end
end
end
diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb
index 52543de41f..468502ca6b 100644
--- a/lib/rubygems/request/http_pool.rb
+++ b/lib/rubygems/request/http_pool.rb
@@ -9,12 +9,14 @@
class Gem::Request::HTTPPool # :nodoc:
attr_reader :cert_files, :proxy_uri
- def initialize(http_args, cert_files, proxy_uri)
+ def initialize(http_args, cert_files, proxy_uri, pool_size)
@http_args = http_args
@cert_files = cert_files
@proxy_uri = proxy_uri
- @queue = Thread::SizedQueue.new 1
- @queue << nil
+ @pool_size = pool_size
+
+ @queue = Thread::SizedQueue.new @pool_size
+ setup_queue
end
def checkout
@@ -31,7 +33,8 @@ class Gem::Request::HTTPPool # :nodoc:
connection.finish
end
end
- @queue.push(nil)
+
+ setup_queue
end
private
@@ -44,4 +47,8 @@ class Gem::Request::HTTPPool # :nodoc:
connection.start
connection
end
+
+ def setup_queue
+ @pool_size.times { @queue.push(nil) }
+ end
end
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index 875df7e019..5a855fdb10 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -181,13 +181,10 @@ class Gem::RequestSet
# Install requested gems after they have been downloaded
sorted_requests.each do |req|
- if req.installed?
+ if req.installed? && @always_install.none? {|spec| spec == req.spec.spec }
req.spec.spec.build_extensions
-
- if @always_install.none? {|spec| spec == req.spec.spec }
- yield req, nil if block_given?
- next
- end
+ yield req, nil if block_given?
+ next
end
spec =
diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb
index c446b3ae51..da6dd329bc 100644
--- a/lib/rubygems/request_set/lockfile.rb
+++ b/lib/rubygems/request_set/lockfile.rb
@@ -38,7 +38,7 @@ class Gem::RequestSet::Lockfile
end
##
- # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+
+ # Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+
# location.
def self.build(request_set, gem_deps_file, dependencies = nil)
diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb
index 35d83abd2d..bc4fef893e 100644
--- a/lib/rubygems/resolver.rb
+++ b/lib/rubygems/resolver.rb
@@ -2,7 +2,6 @@
require_relative "dependency"
require_relative "exceptions"
-require_relative "util/list"
##
# Given a set of Gem::Dependency objects as +needed+ and a way to query the
@@ -144,7 +143,7 @@ class Gem::Resolver
[spec, activation_request]
end
- def requests(s, act, reqs=[]) # :nodoc:
+ def requests(s, act, reqs = []) # :nodoc:
return reqs if @ignore_dependencies
s.fetch_development_dependencies if @development
@@ -241,7 +240,7 @@ class Gem::Resolver
sources.each do |source|
groups[source].
- sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch
+ sort_by {|spec| [spec.version, -Gem::Platform.platform_specificity_match(spec.platform, Gem::Platform.local)] }.
map {|spec| ActivationRequest.new spec, dependency }.
each {|activation_request| activation_requests << activation_request }
end
diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb
index e2307f6e02..e647a2c11b 100644
--- a/lib/rubygems/resolver/best_set.rb
+++ b/lib/rubygems/resolver/best_set.rb
@@ -21,7 +21,7 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet
def pick_sets # :nodoc:
@sources.each_source do |source|
- @sets << source.dependency_resolver_set
+ @sets << source.dependency_resolver_set(@prerelease)
end
end
diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb
index 367a36b43d..77c3add4b3 100644
--- a/lib/rubygems/resolver/conflict.rb
+++ b/lib/rubygems/resolver/conflict.rb
@@ -21,7 +21,7 @@ class Gem::Resolver::Conflict
# Creates a new resolver conflict when +dependency+ is in conflict with an
# already +activated+ specification.
- def initialize(dependency, activated, failed_dep=dependency)
+ def initialize(dependency, activated, failed_dep = dependency)
@dependency = dependency
@activated = activated
@failed_dep = failed_dep
diff --git a/lib/rubygems/resolver/source_set.rb b/lib/rubygems/resolver/source_set.rb
index 296cf41078..074b473edc 100644
--- a/lib/rubygems/resolver/source_set.rb
+++ b/lib/rubygems/resolver/source_set.rb
@@ -42,6 +42,6 @@ class Gem::Resolver::SourceSet < Gem::Resolver::Set
def get_set(name)
link = @links[name]
- @sets[link] ||= Gem::Source.new(link).dependency_resolver_set if link
+ @sets[link] ||= Gem::Source.new(link).dependency_resolver_set(@prerelease) if link
end
end
diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb
index 7c95a9d4f5..148cba38c4 100644
--- a/lib/rubygems/s3_uri_signer.rb
+++ b/lib/rubygems/s3_uri_signer.rb
@@ -1,11 +1,14 @@
# frozen_string_literal: true
require_relative "openssl"
+require_relative "user_interaction"
##
# S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems
# More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
class Gem::S3URISigner
+ include Gem::UserInteraction
+
class ConfigurationError < Gem::Exception
def initialize(message)
super message
@@ -27,9 +30,11 @@ class Gem::S3URISigner
end
attr_accessor :uri
+ attr_accessor :method
- def initialize(uri)
+ def initialize(uri, method)
@uri = uri
+ @method = method
end
##
@@ -38,7 +43,7 @@ class Gem::S3URISigner
s3_config = fetch_s3_config
current_time = Time.now.utc
- date_time = current_time.strftime("%Y%m%dT%H%m%SZ")
+ date_time = current_time.strftime("%Y%m%dT%H%M%SZ")
date = date_time[0,8]
credential_info = "#{date}/#{s3_config.region}/s3/aws4_request"
@@ -73,7 +78,7 @@ class Gem::S3URISigner
def generate_canonical_request(canonical_host, query_params)
[
- "GET",
+ method.upcase,
uri.path,
query_params,
"host:#{canonical_host}",
@@ -145,17 +150,40 @@ class Gem::S3URISigner
require_relative "request/connection_pools"
require "json"
- iam_info = ec2_metadata_request(EC2_IAM_INFO)
+ # First try V2 fallback to V1
+ res = nil
+ begin
+ res = ec2_metadata_credentials_imds_v2
+ rescue InstanceProfileError
+ alert_warning "Unable to access ec2 credentials via IMDSv2, falling back to IMDSv1"
+ res = ec2_metadata_credentials_imds_v1
+ end
+ res
+ end
+
+ def ec2_metadata_credentials_imds_v2
+ token = ec2_metadata_token
+ iam_info = ec2_metadata_request(EC2_IAM_INFO, token:)
# Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
role_name = iam_info["InstanceProfileArn"].split("/").last
- ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name)
+ ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token:)
end
- def ec2_metadata_request(url)
- uri = Gem::URI(url)
- @request_pool ||= create_request_pool(uri)
- request = Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, @request_pool)
- response = request.fetch
+ def ec2_metadata_credentials_imds_v1
+ iam_info = ec2_metadata_request(EC2_IAM_INFO, token: nil)
+ # Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
+ role_name = iam_info["InstanceProfileArn"].split("/").last
+ ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token: nil)
+ end
+
+ def ec2_metadata_request(url, token:)
+ request = ec2_iam_request(Gem::URI(url), Gem::Net::HTTP::Get)
+
+ response = request.fetch do |req|
+ if token
+ req.add_field "X-aws-ec2-metadata-token", token
+ end
+ end
case response
when Gem::Net::HTTPOK then
@@ -165,6 +193,26 @@ class Gem::S3URISigner
end
end
+ def ec2_metadata_token
+ request = ec2_iam_request(Gem::URI(EC2_IAM_TOKEN), Gem::Net::HTTP::Put)
+
+ response = request.fetch do |req|
+ req.add_field "X-aws-ec2-metadata-token-ttl-seconds", 60
+ end
+
+ case response
+ when Gem::Net::HTTPOK then
+ response.body
+ else
+ raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}")
+ end
+ end
+
+ def ec2_iam_request(uri, verb)
+ @request_pool ||= create_request_pool(uri)
+ Gem::Request.new(uri, verb, nil, @request_pool)
+ end
+
def create_request_pool(uri)
proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme))
certs = Gem::Request.get_cert_files
@@ -172,6 +220,7 @@ class Gem::S3URISigner
end
BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze
+ EC2_IAM_TOKEN = "http://169.254.169.254/latest/api/token"
EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info"
EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
end
diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb
index 7b86ac5763..128958ab80 100644
--- a/lib/rubygems/security/policy.rb
+++ b/lib/rubygems/security/policy.rb
@@ -143,7 +143,7 @@ class Gem::Security::Policy
end
##
- # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and
+ # Ensures the root of +chain+ has a trusted certificate in Gem::Security.trust_dir and
# the digests of the two certificates match according to +digester+
def check_trust(chain, digester, trust_dir)
diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb
index 5732fb57fd..eeeeb52906 100644
--- a/lib/rubygems/security/signer.rb
+++ b/lib/rubygems/security/signer.rb
@@ -52,7 +52,7 @@ class Gem::Security::Signer
re_signed_cert = Gem::Security.re_sign(
expired_cert,
private_key,
- (Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days)
+ Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days
)
Gem::Security.write(re_signed_cert, expired_cert_path)
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index bee5681dab..f203b7312b 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -67,28 +67,11 @@ class Gem::Source
##
# Returns a Set that can fetch specifications from this source.
-
- def dependency_resolver_set # :nodoc:
- return Gem::Resolver::IndexSet.new self if uri.scheme == "file"
-
- fetch_uri = if uri.host == "rubygems.org"
- index_uri = uri.dup
- index_uri.host = "index.rubygems.org"
- index_uri
- else
- uri
- end
-
- bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions"
-
- begin
- fetcher = Gem::RemoteFetcher.fetcher
- response = fetcher.fetch_path bundler_api_uri, nil, true
- rescue Gem::RemoteFetcher::FetchError
- Gem::Resolver::IndexSet.new self
- else
- Gem::Resolver::APISet.new response.uri + "./info/"
- end
+ #
+ # The set will optionally fetch prereleases if requested.
+ #
+ def dependency_resolver_set(prerelease = false)
+ new_dependency_resolver_set.tap {|set| set.prerelease = prerelease }
end
def hash # :nodoc:
@@ -119,7 +102,7 @@ class Gem::Source
end
##
- # Fetches a specification for the given +name_tuple+.
+ # Fetches a specification for the given Gem::NameTuple.
def fetch_spec(name_tuple)
fetcher = Gem::RemoteFetcher.fetcher
@@ -207,7 +190,7 @@ class Gem::Source
# Downloads +spec+ and writes it to +dir+. See also
# Gem::RemoteFetcher#download.
- def download(spec, dir=Dir.pwd)
+ def download(spec, dir = Dir.pwd)
fetcher = Gem::RemoteFetcher.fetcher
fetcher.download spec, uri.to_s, dir
end
@@ -227,13 +210,36 @@ class Gem::Source
end
end
- def typo_squatting?(host, distance_threshold=4)
+ def typo_squatting?(host, distance_threshold = 4)
return if @uri.host.nil?
levenshtein_distance(@uri.host, host).between? 1, distance_threshold
end
private
+ def new_dependency_resolver_set
+ return Gem::Resolver::IndexSet.new self if uri.scheme == "file"
+
+ fetch_uri = if uri.host == "rubygems.org"
+ index_uri = uri.dup
+ index_uri.host = "index.rubygems.org"
+ index_uri
+ else
+ uri
+ end
+
+ bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions"
+
+ begin
+ fetcher = Gem::RemoteFetcher.fetcher
+ response = fetcher.fetch_path bundler_api_uri, nil, true
+ rescue Gem::RemoteFetcher::FetchError
+ Gem::Resolver::IndexSet.new self
+ else
+ Gem::Resolver::APISet.new response.uri + "./info/"
+ end
+ end
+
def enforce_trailing_slash(uri)
uri.merge(uri.path.gsub(%r{/+$}, "") + "/")
end
diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb
index 33db64fbc1..19bf4595c4 100644
--- a/lib/rubygems/source_list.rb
+++ b/lib/rubygems/source_list.rb
@@ -60,6 +60,42 @@ class Gem::SourceList
end
##
+ # Prepends +obj+ to the beginning of the source list which may be a Gem::Source, Gem::URI or URI
+ # Moves +obj+ to the beginning of the list if already present.
+ # String.
+
+ def prepend(obj)
+ src = case obj
+ when Gem::Source
+ obj
+ else
+ Gem::Source.new(obj)
+ end
+
+ @sources.delete(src) if @sources.include?(src)
+ @sources.unshift(src)
+ src
+ end
+
+ ##
+ # Appends +obj+ to the end of the source list, moving it if already present.
+ # +obj+ may be a Gem::Source, Gem::URI or URI String.
+ # Moves +obj+ to the end of the list if already present.
+
+ def append(obj)
+ src = case obj
+ when Gem::Source
+ obj
+ else
+ Gem::Source.new(obj)
+ end
+
+ @sources.delete(src) if @sources.include?(src)
+ @sources << src
+ src
+ end
+
+ ##
# Replaces this SourceList with the sources in +other+ See #<< for
# acceptable items in +other+.
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index 285f117b39..835dedf948 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -83,7 +83,7 @@ class Gem::SpecFetcher
#
# If +matching_platform+ is false, gems for all platforms are returned.
- def search_for_dependency(dependency, matching_platform=true)
+ def search_for_dependency(dependency, matching_platform = true)
found = {}
rejected_specs = {}
@@ -130,7 +130,7 @@ class Gem::SpecFetcher
##
# Return all gem name tuples who's names match +obj+
- def detect(type=:complete)
+ def detect(type = :complete)
tuples = []
list, _ = available_specs(type)
@@ -150,7 +150,7 @@ class Gem::SpecFetcher
#
# If +matching_platform+ is false, gems for all platforms are returned.
- def spec_for_dependency(dependency, matching_platform=true)
+ def spec_for_dependency(dependency, matching_platform = true)
tuples, errors = search_for_dependency(dependency, matching_platform)
specs = []
@@ -280,7 +280,7 @@ class Gem::SpecFetcher
# Retrieves NameTuples from +source+ of the given +type+ (:prerelease,
# etc.). If +gracefully_ignore+ is true, errors are ignored.
- def tuples_for(source, type, gracefully_ignore=false) # :nodoc:
+ def tuples_for(source, type, gracefully_ignore = false) # :nodoc:
@caches[type][source.uri] ||=
source.load_specs(type).sort_by(&:name)
rescue Gem::RemoteFetcher::FetchError
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 0b905a7ea7..3d1f2dad91 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -7,12 +7,10 @@
# See LICENSE.txt for permissions.
#++
-require_relative "deprecate"
require_relative "basic_specification"
require_relative "stub_specification"
require_relative "platform"
require_relative "specification_record"
-require_relative "util/list"
require "rbconfig"
@@ -38,8 +36,6 @@ require "rbconfig"
# items you may add to a specification.
class Gem::Specification < Gem::BasicSpecification
- extend Gem::Deprecate
-
# REFACTOR: Consider breaking out this version stuff into a separate
# module. There's enough special stuff around it that it may justify
# a separate class.
@@ -488,8 +484,6 @@ class Gem::Specification < Gem::BasicSpecification
end
@platform = @new_platform.to_s
-
- invalidate_memoized_attributes
end
##
@@ -738,14 +732,6 @@ class Gem::Specification < Gem::BasicSpecification
attr_accessor :autorequire # :nodoc:
##
- # Sets the default executable for this gem.
- #
- # Deprecated: You must now specify the executable name to Gem.bin_path.
-
- attr_writer :default_executable
- rubygems_deprecate :default_executable=
-
- ##
# Allows deinstallation of gems with legacy platforms.
attr_writer :original_platform # :nodoc:
@@ -1002,7 +988,7 @@ class Gem::Specification < Gem::BasicSpecification
def self.find_in_unresolved_tree(path)
unresolved_specs.each do |spec|
spec.traverse do |_from_spec, _dep, to_spec, trail|
- if to_spec.has_conflicts? || to_spec.conficts_when_loaded_with?(trail)
+ if to_spec.has_conflicts? || to_spec.conflicts_when_loaded_with?(trail)
:next
else
return trail.reverse if to_spec.contains_requirable_file? path
@@ -1321,7 +1307,7 @@ class Gem::Specification < Gem::BasicSpecification
spec.instance_variable_set :@authors, array[12]
spec.instance_variable_set :@description, array[13]
spec.instance_variable_set :@homepage, array[14]
- spec.instance_variable_set :@has_rdoc, array[15]
+ # offset due to has_rdoc removal
spec.instance_variable_set :@licenses, array[17]
spec.instance_variable_set :@metadata, array[18]
spec.instance_variable_set :@loaded, false
@@ -1620,14 +1606,14 @@ class Gem::Specification < Gem::BasicSpecification
# spec's cached gem.
def cache_dir
- @cache_dir ||= File.join base_dir, "cache"
+ File.join base_dir, "cache"
end
##
# Returns the full path to the cached gem for this spec.
def cache_file
- @cache_file ||= File.join cache_dir, "#{full_name}.gem"
+ File.join cache_dir, "#{full_name}.gem"
end
##
@@ -1649,7 +1635,7 @@ class Gem::Specification < Gem::BasicSpecification
##
# return true if there will be conflict when spec if loaded together with the list of specs.
- def conficts_when_loaded_with?(list_of_specs) # :nodoc:
+ def conflicts_when_loaded_with?(list_of_specs) # :nodoc:
result = list_of_specs.any? do |spec|
spec.runtime_dependencies.any? {|dep| (dep.name == name) && !satisfies_requirement?(dep) }
end
@@ -1717,24 +1703,6 @@ class Gem::Specification < Gem::BasicSpecification
end
##
- # The default executable for this gem.
- #
- # Deprecated: The name of the gem is assumed to be the name of the
- # executable now. See Gem.bin_path.
-
- def default_executable # :nodoc:
- if defined?(@default_executable) && @default_executable
- result = @default_executable
- elsif @executables && @executables.size == 1
- result = Array(@executables).first
- else
- result = nil
- end
- result
- end
- rubygems_deprecate :default_executable
-
- ##
# The default value for specification attribute +name+
def default_value(name)
@@ -1757,7 +1725,7 @@ class Gem::Specification < Gem::BasicSpecification
#
# [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]]
- def dependent_gems(check_dev=true)
+ def dependent_gems(check_dev = true)
out = []
Gem::Specification.each do |spec|
deps = check_dev ? spec.dependencies : spec.runtime_dependencies
@@ -1903,10 +1871,6 @@ class Gem::Specification < Gem::BasicSpecification
spec
end
- def full_name
- @full_name ||= super
- end
-
##
# Work around old bundler versions removing my methods
# Can be removed once RubyGems can no longer install Bundler 2.5
@@ -1920,29 +1884,6 @@ class Gem::Specification < Gem::BasicSpecification
end
##
- # Deprecated and ignored, defaults to true.
- #
- # Formerly used to indicate this gem was RDoc-capable.
-
- def has_rdoc # :nodoc:
- true
- end
- rubygems_deprecate :has_rdoc
-
- ##
- # Deprecated and ignored.
- #
- # Formerly used to indicate this gem was RDoc-capable.
-
- def has_rdoc=(ignored) # :nodoc:
- @has_rdoc = true
- end
- rubygems_deprecate :has_rdoc=
-
- alias_method :has_rdoc?, :has_rdoc # :nodoc:
- rubygems_deprecate :has_rdoc?
-
- ##
# True if this gem has files in test_files
def has_unit_tests? # :nodoc:
@@ -2044,17 +1985,6 @@ class Gem::Specification < Gem::BasicSpecification
end
end
- ##
- # Expire memoized instance variables that can incorrectly generate, replace
- # or miss files due changes in certain attributes used to compute them.
-
- def invalidate_memoized_attributes
- @full_name = nil
- @cache_file = nil
- end
-
- private :invalidate_memoized_attributes
-
def inspect # :nodoc:
if $DEBUG
super
@@ -2093,8 +2023,6 @@ class Gem::Specification < Gem::BasicSpecification
def internal_init # :nodoc:
super
@bin_dir = nil
- @cache_dir = nil
- @cache_file = nil
@doc_dir = nil
@ri_dir = nil
@spec_dir = nil
@@ -2447,8 +2375,6 @@ class Gem::Specification < Gem::BasicSpecification
:required_rubygems_version,
:specification_version,
:version,
- :has_rdoc,
- :default_executable,
:metadata,
:signing_key,
]
@@ -2586,29 +2512,11 @@ class Gem::Specification < Gem::BasicSpecification
Gem::SpecificationPolicy.new(self).validate_for_resolution
end
- def validate_metadata
- Gem::SpecificationPolicy.new(self).validate_metadata
- end
- rubygems_deprecate :validate_metadata
-
- def validate_dependencies
- Gem::SpecificationPolicy.new(self).validate_dependencies
- end
- rubygems_deprecate :validate_dependencies
-
- def validate_permissions
- Gem::SpecificationPolicy.new(self).validate_permissions
- end
- rubygems_deprecate :validate_permissions
-
##
# Set the version to +version+.
def version=(version)
- @version = Gem::Version.create(version)
- return if @version.nil?
-
- invalidate_memoized_attributes
+ @version = version.nil? ? version : Gem::Version.create(version)
end
def stubbed?
diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb
index d79ee7df92..e5008a24db 100644
--- a/lib/rubygems/specification_policy.rb
+++ b/lib/rubygems/specification_policy.rb
@@ -190,9 +190,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
##
# Checks that the gem does not depend on itself.
- # Checks that dependencies use requirements as we recommend. Warnings are
- # issued when dependencies are open-ended or overly strict for semantic
- # versioning.
def validate_dependencies # :nodoc:
warning_messages = []
@@ -200,39 +197,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use:
if dep.name == @specification.name # warn on self reference
warning_messages << "Self referencing dependency is unnecessary and strongly discouraged."
end
-
- prerelease_dep = dep.requirements_list.any? do |req|
- Gem::Requirement.new(req).prerelease?
- end
-
- warning_messages << "prerelease dependency on #{dep} is not recommended" if
- prerelease_dep && !@specification.version.prerelease?
-
- open_ended = dep.requirement.requirements.all? do |op, version|
- !version.prerelease? && [">", ">="].include?(op)
- end
-
- next unless open_ended
- op, dep_version = dep.requirement.requirements.first
-
- segments = dep_version.segments
-
- base = segments.first 2
-
- recommendation = if [">", ">="].include?(op) && segments == [0]
- " use a bounded requirement, such as \"~> x.y\""
- else
- bugfix = if op == ">"
- ", \"> #{dep_version}\""
- elsif op == ">=" && base != segments
- ", \">= #{dep_version}\""
- end
-
- " if #{dep.name} is semantically versioned, use:\n" \
- " add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join "."}\"#{bugfix}"
- end
-
- warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n"
end
if warning_messages.any?
warning_messages.each {|warning_message| warning warning_message }
diff --git a/lib/rubygems/specification_record.rb b/lib/rubygems/specification_record.rb
index 195a355496..d08410096f 100644
--- a/lib/rubygems/specification_record.rb
+++ b/lib/rubygems/specification_record.rb
@@ -73,7 +73,7 @@ module Gem
end
##
- # Adds +spec+ to the the record, keeping the collection properly sorted.
+ # Adds +spec+ to the record, keeping the collection properly sorted.
def add_spec(spec)
return if all.include? spec
diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem
index 8afb219058..8afb219058 100644
--- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem
+++ b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem
diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem
deleted file mode 100644
index f4ce4ca43d..0000000000
--- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
-A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
-b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
-MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
-YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
-aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
-jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
-xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
-1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
-snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
-U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
-9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
-BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
-AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
-yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
-38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
-AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
-DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
-HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
------END CERTIFICATE-----
diff --git a/lib/rubygems/text.rb b/lib/rubygems/text.rb
index da0795b771..88d4ce59b4 100644
--- a/lib/rubygems/text.rb
+++ b/lib/rubygems/text.rb
@@ -21,7 +21,7 @@ module Gem::Text
# Wraps +text+ to +wrap+ characters and optionally indents by +indent+
# characters
- def format_text(text, wrap, indent=0)
+ def format_text(text, wrap, indent = 0)
result = []
work = clean_text(text)
diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb
index 991bc6fb95..fe4c3a80cf 100644
--- a/lib/rubygems/uninstaller.rb
+++ b/lib/rubygems/uninstaller.rb
@@ -42,10 +42,25 @@ class Gem::Uninstaller
attr_reader :spec
##
- # Constructs an uninstaller that will uninstall +gem+
+ # Constructs an uninstaller that will uninstall gem named +gem+.
+ # +options+ is a Hash with the following keys:
+ #
+ # :version:: Version requirement for the gem to uninstall. If not specified,
+ # uses Gem::Requirement.default.
+ # :install_dir:: The directory where the gem is installed. If not specified,
+ # uses Gem.dir.
+ # :executables:: Whether executables should be removed without confirmation or not. If nil, asks the user explicitly.
+ # :all:: If more than one version matches the requirement, whether to forcefully remove all matching versions or ask the user to select specific matching versions that should be removed.
+ # :ignore:: Ignore broken dependency checks when uninstalling.
+ # :bin_dir:: Directory containing executables to remove. If not specified,
+ # uses Gem.bindir.
+ # :format_executable:: In order to find executables to be removed, format executable names using Gem::Installer.exec_format.
+ # :abort_on_dependent:: Directly abort uninstallation if dependencies would be broken, rather than asking the user for confirmation.
+ # :check_dev:: When checking if uninstalling gem would leave broken dependencies around, also consider development dependencies.
+ # :force:: Set both :all and :ignore to true for forced uninstallation.
+ # :user_install:: Uninstall from user gem directory instead of system directory.
def initialize(gem, options = {})
- # TODO: document the valid options
@gem = gem
@version = options[:version] || Gem::Requirement.default
@install_dir = options[:install_dir]
@@ -57,10 +72,6 @@ class Gem::Uninstaller
@bin_dir = options[:bin_dir]
@format_executable = options[:format_executable]
@abort_on_dependent = options[:abort_on_dependent]
-
- # Indicate if development dependencies should be checked when
- # uninstalling. (default: false)
- #
@check_dev = options[:check_dev]
if options[:force]
diff --git a/lib/rubygems/uri_formatter.rb b/lib/rubygems/uri_formatter.rb
index f941eb1421..8856fdadd2 100644
--- a/lib/rubygems/uri_formatter.rb
+++ b/lib/rubygems/uri_formatter.rb
@@ -17,7 +17,8 @@ class Gem::UriFormatter
# Creates a new URI formatter for +uri+.
def initialize(uri)
- require "cgi/util"
+ require "cgi/escape"
+ require "cgi/util" unless defined?(CGI::EscapeExt)
@uri = uri
end
diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb
index 0172c4ee89..9fe3e755c4 100644
--- a/lib/rubygems/user_interaction.rb
+++ b/lib/rubygems/user_interaction.rb
@@ -6,7 +6,6 @@
# See LICENSE.txt for permissions.
#++
-require_relative "deprecate"
require_relative "text"
##
@@ -170,8 +169,6 @@ end
# Gem::StreamUI implements a simple stream based user interface.
class Gem::StreamUI
- extend Gem::Deprecate
-
##
# The input stream
@@ -193,7 +190,7 @@ class Gem::StreamUI
# then special operations (like asking for passwords) will use the TTY
# commands to disable character echo.
- def initialize(in_stream, out_stream, err_stream=$stderr, usetty=true)
+ def initialize(in_stream, out_stream, err_stream = $stderr, usetty = true)
@ins = in_stream
@outs = out_stream
@errs = err_stream
@@ -246,7 +243,7 @@ class Gem::StreamUI
# to a tty, raises an exception if default is nil, otherwise returns
# default.
- def ask_yes_no(question, default=nil)
+ def ask_yes_no(question, default = nil)
unless tty?
if default.nil?
raise Gem::OperationNotSupportedError,
@@ -325,14 +322,14 @@ class Gem::StreamUI
##
# Display a statement.
- def say(statement="")
+ def say(statement = "")
@outs.puts statement
end
##
# Display an informational alert. Will ask +question+ if it is not nil.
- def alert(statement, question=nil)
+ def alert(statement, question = nil)
@outs.puts "INFO: #{statement}"
ask(question) if question
end
@@ -340,7 +337,7 @@ class Gem::StreamUI
##
# Display a warning on stderr. Will ask +question+ if it is not nil.
- def alert_warning(statement, question=nil)
+ def alert_warning(statement, question = nil)
@errs.puts "WARNING: #{statement}"
ask(question) if question
end
@@ -349,7 +346,7 @@ class Gem::StreamUI
# Display an error message in a location expected to get error messages.
# Will ask +question+ if it is not nil.
- def alert_error(statement, question=nil)
+ def alert_error(statement, question = nil)
@errs.puts "ERROR: #{statement}"
ask(question) if question
end
diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb
index 51f9c2029f..ee4106c6ce 100644
--- a/lib/rubygems/util.rb
+++ b/lib/rubygems/util.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require_relative "deprecate"
-
##
# This module contains various utility methods as module methods.
@@ -57,26 +55,6 @@ module Gem::Util
end
##
- # Invokes system, but silences all output.
-
- def self.silent_system(*command)
- opt = { out: IO::NULL, err: [:child, :out] }
- if Hash === command.last
- opt.update(command.last)
- cmds = command[0...-1]
- else
- cmds = command.dup
- end
- system(*(cmds << opt))
- end
-
- class << self
- extend Gem::Deprecate
-
- rubygems_deprecate :silent_system
- end
-
- ##
# Enumerates the parents of +directory+.
def self.traverse_parents(directory, &block)
diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb
new file mode 100644
index 0000000000..32767c6a79
--- /dev/null
+++ b/lib/rubygems/util/atomic_file_writer.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+# Based on ActiveSupport's AtomicFile implementation
+# Copyright (c) David Heinemeier Hansson
+# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb
+# Licensed under the MIT License
+
+module Gem
+ class AtomicFileWriter
+ ##
+ # Write to a file atomically. Useful for situations where you don't
+ # want other processes or threads to see half-written files.
+
+ def self.open(file_name)
+ require "securerandom" unless defined?(SecureRandom)
+
+ old_stat = begin
+ File.stat(file_name)
+ rescue SystemCallError
+ nil
+ end
+
+ # Names can't be longer than 255B
+ tmp_suffix = ".tmp.#{SecureRandom.hex}"
+ dirname = File.dirname(file_name)
+ basename = File.basename(file_name)
+ tmp_path = File.join(dirname, ".#{basename.byteslice(0, 254 - tmp_suffix.bytesize)}#{tmp_suffix}")
+
+ flags = File::RDWR | File::CREAT | File::EXCL | File::BINARY
+ flags |= File::SHARE_DELETE if defined?(File::SHARE_DELETE)
+
+ File.open(tmp_path, flags) do |temp_file|
+ temp_file.binmode
+ if old_stat
+ # Set correct permissions on new file
+ begin
+ File.chown(old_stat.uid, old_stat.gid, temp_file.path)
+ # This operation will affect filesystem ACL's
+ File.chmod(old_stat.mode, temp_file.path)
+ rescue Errno::EPERM, Errno::EACCES
+ # Changing file ownership failed, moving on.
+ end
+ end
+
+ return_val = yield temp_file
+ rescue StandardError => error
+ begin
+ temp_file.close
+ rescue StandardError
+ nil
+ end
+
+ begin
+ File.unlink(temp_file.path)
+ rescue StandardError
+ nil
+ end
+
+ raise error
+ else
+ begin
+ File.rename(temp_file.path, file_name)
+ rescue StandardError
+ begin
+ File.unlink(temp_file.path)
+ rescue StandardError
+ end
+
+ raise
+ end
+
+ return_val
+ end
+ end
+ end
+end
diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb
index 3d9df5ebcd..fbb7b55075 100644
--- a/lib/rubygems/util/licenses.rb
+++ b/lib/rubygems/util/licenses.rb
@@ -59,12 +59,15 @@ class Gem::Licenses
Artistic-1.0-Perl
Artistic-1.0-cl8
Artistic-2.0
+ Artistic-dist
+ Aspell-RU
BSD-1-Clause
BSD-2-Clause
BSD-2-Clause-Darwin
BSD-2-Clause-Patent
BSD-2-Clause-Views
BSD-2-Clause-first-lines
+ BSD-2-Clause-pkgconf-disclaimer
BSD-3-Clause
BSD-3-Clause-Attribution
BSD-3-Clause-Clear
@@ -205,6 +208,7 @@ class Gem::Licenses
Cornell-Lossless-JPEG
Cronyx
Crossword
+ CryptoSwift
CrystalStacker
Cube
D-FSL-1.0
@@ -215,6 +219,7 @@ class Gem::Licenses
DRL-1.0
DRL-1.1
DSDP
+ DocBook-DTD
DocBook-Schema
DocBook-Stylesheet
DocBook-XML
@@ -240,7 +245,10 @@ class Gem::Licenses
FSFAP-no-warranty-disclaimer
FSFUL
FSFULLR
+ FSFULLRSD
FSFULLRWD
+ FSL-1.1-ALv2
+ FSL-1.1-MIT
FTL
Fair
Ferguson-Twofish
@@ -276,11 +284,13 @@ class Gem::Licenses
GPL-2.0-or-later
GPL-3.0-only
GPL-3.0-or-later
+ Game-Programming-Gems
Giftware
Glide
Glulxe
Graphics-Gems
Gutmann
+ HDF5
HIDAPI
HP-1986
HP-1989
@@ -426,6 +436,7 @@ class Gem::Licenses
NPL-1.1
NPOSL-3.0
NRL
+ NTIA-PD
NTP
NTP-0
Naumen
@@ -528,11 +539,13 @@ class Gem::Licenses
SMLNJ
SMPPL
SNIA
+ SOFA
SPL-1.0
SSH-OpenSSH
SSH-short
SSLeay-standalone
SSPL-1.0
+ SUL-1.0
SWL
Saxpath
SchemeReport
@@ -578,6 +591,8 @@ class Gem::Licenses
Unicode-TOU
UnixCrypt
Unlicense
+ Unlicense-libtelnet
+ Unlicense-libwhirlpool
VOSTROM
VSL-1.0
Vim
@@ -631,6 +646,8 @@ class Gem::Licenses
gtkbook
hdparm
iMatix
+ jove
+ libpng-1.6.35
libpng-2.0
libselinux-1.0
libtiff
@@ -638,10 +655,12 @@ class Gem::Licenses
lsof
magaz
mailprio
+ man2html
metamail
mpi-permissive
mpich2
mplus
+ ngrep
pkgconf
pnmstitch
psfrag
@@ -716,6 +735,7 @@ class Gem::Licenses
CLISP-exception-2.0
Classpath-exception-2.0
DigiRule-FOSS-exception
+ Digia-Qt-LGPL-exception-1.1
FLTK-exception
Fawkes-Runtime-exception
Font-exception-2.0
@@ -772,6 +792,7 @@ class Gem::Licenses
mif-exception
mxml-exception
openvpn-openssl-exception
+ polyparse-exception
romic-exception
stunnel-exception
u-boot-exception-2.0
diff --git a/lib/rubygems/util/list.rb b/lib/rubygems/util/list.rb
deleted file mode 100644
index 2899e8a2b9..0000000000
--- a/lib/rubygems/util/list.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Gem
- # The Gem::List class is currently unused and will be removed in the next major rubygems version
- class List # :nodoc:
- include Enumerable
- attr_accessor :value, :tail
-
- def initialize(value = nil, tail = nil)
- @value = value
- @tail = tail
- end
-
- def each
- n = self
- while n
- yield n.value
- n = n.tail
- end
- end
-
- def to_a
- super.reverse
- end
-
- def prepend(value)
- List.new value, self
- end
-
- def pretty_print(q) # :nodoc:
- q.pp to_a
- end
-
- def self.prepend(list, value)
- return List.new(value) unless list
- List.new value, list
- end
- end
- deprecate_constant :List
-end
diff --git a/lib/rubygems/validator.rb b/lib/rubygems/validator.rb
index 57e0747eb4..eb5b513570 100644
--- a/lib/rubygems/validator.rb
+++ b/lib/rubygems/validator.rb
@@ -59,7 +59,7 @@ class Gem::Validator
#--
# TODO needs further cleanup
- def alien(gems=[])
+ def alien(gems = [])
errors = Hash.new {|h,k| h[k] = {} }
Gem::Specification.each do |spec|
diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb
index ad3e646cca..4800cd25f1 100644
--- a/lib/rubygems/vendor/net-http/lib/net/http.rb
+++ b/lib/rubygems/vendor/net-http/lib/net/http.rb
@@ -46,7 +46,7 @@ module Gem::Net #:nodoc:
# == Strategies
#
# - If you will make only a few GET requests,
- # consider using {OpenURI}[rdoc-ref:OpenURI].
+ # consider using {OpenURI}[https://docs.ruby-lang.org/en/master/OpenURI.html].
# - If you will make only a few requests of all kinds,
# consider using the various singleton convenience methods in this class.
# Each of the following methods automatically starts and finishes
@@ -102,14 +102,14 @@ module Gem::Net #:nodoc:
#
# == URIs
#
- # On the internet, a URI
+ # On the internet, a Gem::URI
# ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier])
# is a string that identifies a particular resource.
# It consists of some or all of: scheme, hostname, path, query, and fragment;
- # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax].
+ # see {Gem::URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax].
#
- # A Ruby {Gem::URI::Generic}[rdoc-ref:Gem::URI::Generic] object
- # represents an internet URI.
+ # A Ruby {Gem::URI::Generic}[https://docs.ruby-lang.org/en/master/Gem::URI/Generic.html] object
+ # represents an internet Gem::URI.
# It provides, among others, methods
# +scheme+, +hostname+, +path+, +query+, and +fragment+.
#
@@ -144,7 +144,7 @@ module Gem::Net #:nodoc:
#
# === Queries
#
- # A host-specific query adds name/value pairs to the URI:
+ # A host-specific query adds name/value pairs to the Gem::URI:
#
# _uri = uri.dup
# params = {userId: 1, completed: false}
@@ -154,7 +154,7 @@ module Gem::Net #:nodoc:
#
# === Fragments
#
- # A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect
+ # A {Gem::URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect
# in \Gem::Net::HTTP;
# the same data is returned, regardless of whether a fragment is included.
#
@@ -327,9 +327,9 @@ module Gem::Net #:nodoc:
# res = http.request(req)
# end
#
- # Or if you simply want to make a GET request, you may pass in a URI
+ # Or if you simply want to make a GET request, you may pass in a Gem::URI
# object that has an \HTTPS URL. \Gem::Net::HTTP automatically turns on TLS
- # verification if the URI object has a 'https' :URI scheme:
+ # verification if the Gem::URI object has a 'https' Gem::URI scheme:
#
# uri # => #<Gem::URI::HTTPS https://jsonplaceholder.typicode.com/>
# Gem::Net::HTTP.get(uri)
@@ -374,7 +374,7 @@ module Gem::Net #:nodoc:
#
# When environment variable <tt>'http_proxy'</tt>
# is set to a \Gem::URI string,
- # the returned +http+ will have the server at that URI as its proxy;
+ # the returned +http+ will have the server at that Gem::URI as its proxy;
# note that the \Gem::URI string must have a protocol
# such as <tt>'http'</tt> or <tt>'https'</tt>:
#
@@ -460,7 +460,7 @@ module Gem::Net #:nodoc:
#
# First, what's elsewhere. Class Gem::Net::HTTP:
#
- # - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
+ # - Inherits from {class Object}[https://docs.ruby-lang.org/en/master/Object.html#class-Object-label-What-27s+Here].
#
# This is a categorized summary of methods and attributes.
#
@@ -475,8 +475,7 @@ module Gem::Net #:nodoc:
#
# - {::start}[rdoc-ref:Gem::Net::HTTP.start]:
# Begins a new session in a new \Gem::Net::HTTP object.
- # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?]
- # (aliased as {#active?}[rdoc-ref:Gem::Net::HTTP#active?]):
+ # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?]:
# Returns whether in a session.
# - {#finish}[rdoc-ref:Gem::Net::HTTP#finish]:
# Ends an active session.
@@ -556,18 +555,15 @@ module Gem::Net #:nodoc:
# Sends a PUT request and returns a response object.
# - {#request}[rdoc-ref:Gem::Net::HTTP#request]:
# Sends a request and returns a response object.
- # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get]
- # (aliased as {#get2}[rdoc-ref:Gem::Net::HTTP#get2]):
+ # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get]:
# Sends a GET request and forms a response object;
# if a block given, calls the block with the object,
# otherwise returns the object.
- # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head]
- # (aliased as {#head2}[rdoc-ref:Gem::Net::HTTP#head2]):
+ # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head]:
# Sends a HEAD request and forms a response object;
# if a block given, calls the block with the object,
# otherwise returns the object.
- # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post]
- # (aliased as {#post2}[rdoc-ref:Gem::Net::HTTP#post2]):
+ # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post]:
# Sends a POST request and forms a response object;
# if a block given, calls the block with the object,
# otherwise returns the object.
@@ -605,8 +601,7 @@ module Gem::Net #:nodoc:
# Returns whether +self+ is a proxy class.
# - {#proxy?}[rdoc-ref:Gem::Net::HTTP#proxy?]:
# Returns whether +self+ has a proxy.
- # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]
- # (aliased as {#proxyaddr}[rdoc-ref:Gem::Net::HTTP#proxyaddr]):
+ # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]:
# Returns the proxy address.
# - {#proxy_from_env?}[rdoc-ref:Gem::Net::HTTP#proxy_from_env?]:
# Returns whether the proxy is taken from an environment variable.
@@ -718,8 +713,7 @@ module Gem::Net #:nodoc:
# === \HTTP Version
#
# - {::version_1_2?}[rdoc-ref:Gem::Net::HTTP.version_1_2?]
- # (aliased as {::is_version_1_2?}[rdoc-ref:Gem::Net::HTTP.is_version_1_2?]
- # and {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]):
+ # (aliased as {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]):
# Returns true; retained for compatibility.
#
# === Debugging
@@ -730,7 +724,7 @@ module Gem::Net #:nodoc:
class HTTP < Protocol
# :stopdoc:
- VERSION = "0.6.0"
+ VERSION = "0.9.1"
HTTPVersion = '1.1'
begin
require 'zlib'
@@ -796,7 +790,7 @@ module Gem::Net #:nodoc:
# "completed": false
# }
#
- # With URI object +uri+ and optional hash argument +headers+:
+ # With Gem::URI object +uri+ and optional hash argument +headers+:
#
# uri = Gem::URI('https://jsonplaceholder.typicode.com/todos/1')
# headers = {'Content-type' => 'application/json; charset=UTF-8'}
@@ -869,7 +863,7 @@ module Gem::Net #:nodoc:
# Posts data to a host; returns a Gem::Net::HTTPResponse object.
#
- # Argument +url+ must be a URI;
+ # Argument +url+ must be a Gem::URI;
# argument +data+ must be a hash:
#
# _uri = uri.dup
@@ -1185,6 +1179,7 @@ module Gem::Net #:nodoc:
@debug_output = options[:debug_output]
@response_body_encoding = options[:response_body_encoding]
@ignore_eof = options[:ignore_eof]
+ @tcpsocket_supports_open_timeout = nil
@proxy_from_env = false
@proxy_uri = nil
@@ -1293,7 +1288,7 @@ module Gem::Net #:nodoc:
# - The name of an encoding.
# - An alias for an encoding name.
#
- # See {Encoding}[rdoc-ref:Encoding].
+ # See {Encoding}[https://docs.ruby-lang.org/en/master/Encoding.html].
#
# Examples:
#
@@ -1327,6 +1322,9 @@ module Gem::Net #:nodoc:
# Sets the proxy password;
# see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
attr_writer :proxy_pass
+
+ # Sets whether the proxy uses SSL;
+ # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server].
attr_writer :proxy_use_ssl
# Returns the IP address for the connection.
@@ -1533,9 +1531,9 @@ module Gem::Net #:nodoc:
:verify_depth,
:verify_mode,
:verify_hostname,
- ] # :nodoc:
+ ].freeze # :nodoc:
- SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym } # :nodoc:
+ SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc:
# Sets or returns the path to a CA certification file in PEM format.
attr_accessor :ca_file
@@ -1552,11 +1550,11 @@ module Gem::Net #:nodoc:
attr_accessor :cert_store
# Sets or returns the available SSL ciphers.
- # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D].
+ # See {OpenSSL::SSL::SSLContext#ciphers=}[OpenSSL::SSL::SSL::Context#ciphers=].
attr_accessor :ciphers
# Sets or returns the extra X509 certificates to be added to the certificate chain.
- # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate].
+ # See {OpenSSL::SSL::SSLContext#add_certificate}[OpenSSL::SSL::SSL::Context#add_certificate].
attr_accessor :extra_chain_cert
# Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
@@ -1566,15 +1564,15 @@ module Gem::Net #:nodoc:
attr_accessor :ssl_timeout
# Sets or returns the SSL version.
- # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D].
+ # See {OpenSSL::SSL::SSLContext#ssl_version=}[OpenSSL::SSL::SSL::Context#ssl_version=].
attr_accessor :ssl_version
# Sets or returns the minimum SSL version.
- # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D].
+ # See {OpenSSL::SSL::SSLContext#min_version=}[OpenSSL::SSL::SSL::Context#min_version=].
attr_accessor :min_version
# Sets or returns the maximum SSL version.
- # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D].
+ # See {OpenSSL::SSL::SSLContext#max_version=}[OpenSSL::SSL::SSL::Context#max_version=].
attr_accessor :max_version
# Sets or returns the callback for the server certification verification.
@@ -1590,7 +1588,7 @@ module Gem::Net #:nodoc:
# Sets or returns whether to verify that the server certificate is valid
# for the hostname.
- # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode].
+ # See {OpenSSL::SSL::SSLContext#verify_hostname=}[OpenSSL::SSL::SSL::Context#verify_hostname=].
attr_accessor :verify_hostname
# Returns the X509 certificate chain (an array of strings)
@@ -1638,6 +1636,21 @@ module Gem::Net #:nodoc:
self
end
+ # Finishes the \HTTP session:
+ #
+ # http = Gem::Net::HTTP.new(hostname)
+ # http.start
+ # http.started? # => true
+ # http.finish # => nil
+ # http.started? # => false
+ #
+ # Raises IOError if not in a session.
+ def finish
+ raise IOError, 'HTTP session not yet started' unless started?
+ do_finish
+ end
+
+ # :stopdoc:
def do_start
connect
@started = true
@@ -1660,14 +1673,15 @@ module Gem::Net #:nodoc:
end
debug "opening connection to #{conn_addr}:#{conn_port}..."
- s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) {
- begin
- TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
- rescue => e
- raise e, "Failed to open TCP connection to " +
- "#{conn_addr}:#{conn_port} (#{e.message})"
+ begin
+ s = timeouted_connect(conn_addr, conn_port)
+ rescue => e
+ if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions
+ e = Gem::Net::OpenTimeout.new(e)
end
- }
+ raise e, "Failed to open TCP connection to " +
+ "#{conn_addr}:#{conn_port} (#{e.message})"
+ end
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
debug "opened"
if use_ssl?
@@ -1760,23 +1774,30 @@ module Gem::Net #:nodoc:
end
private :connect
- def on_connect
+ tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters
+ TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]]
+ tcp_socket_parameters.include?([:key, :open_timeout])
+ else
+ # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize
+ # See discussion in https://github.com/ruby/net-http/pull/224
+ Socket.method(:tcp).parameters.include?([:key, :open_timeout])
end
- private :on_connect
+ private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT
- # Finishes the \HTTP session:
- #
- # http = Gem::Net::HTTP.new(hostname)
- # http.start
- # http.started? # => true
- # http.finish # => nil
- # http.started? # => false
- #
- # Raises IOError if not in a session.
- def finish
- raise IOError, 'HTTP session not yet started' unless started?
- do_finish
+ def timeouted_connect(conn_addr, conn_port)
+ if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT
+ TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout)
+ else
+ Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) {
+ TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
+ }
+ end
+ end
+ private :timeouted_connect
+
+ def on_connect
end
+ private :on_connect
def do_finish
@started = false
@@ -1827,6 +1848,8 @@ module Gem::Net #:nodoc:
}
end
+ # :startdoc:
+
class << HTTP
# Returns true if self is a class which was created by HTTP::Proxy.
def proxy_class?
@@ -1866,7 +1889,7 @@ module Gem::Net #:nodoc:
@proxy_from_env
end
- # The proxy URI determined from the environment for this connection.
+ # The proxy Gem::URI determined from the environment for this connection.
def proxy_uri # :nodoc:
return if @proxy_uri == false
@proxy_uri ||= Gem::URI::HTTP.new(
@@ -1921,9 +1944,11 @@ module Gem::Net #:nodoc:
alias proxyport proxy_port #:nodoc: obsolete
private
+ # :stopdoc:
def unescape(value)
- require 'cgi/util'
+ require 'cgi/escape'
+ require 'cgi/util' unless defined?(CGI::EscapeExt)
CGI.unescape(value)
end
@@ -1948,6 +1973,7 @@ module Gem::Net #:nodoc:
path
end
end
+ # :startdoc:
#
# HTTP operations
@@ -2402,7 +2428,9 @@ module Gem::Net #:nodoc:
res
end
- IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc:
+ # :stopdoc:
+
+ IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc:
def transport_request(req)
count = 0
@@ -2559,7 +2587,7 @@ module Gem::Net #:nodoc:
alias_method :D, :debug
end
- # for backward compatibility until Ruby 3.5
+ # for backward compatibility until Ruby 4.0
# https://bugs.ruby-lang.org/issues/20900
# https://github.com/bblimke/webmock/pull/1081
HTTPSession = HTTP
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb
index c629c0113b..218df9a8bd 100644
--- a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb
+++ b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb
@@ -3,7 +3,7 @@ module Gem::Net
# Gem::Net::HTTP exception class.
# You cannot use Gem::Net::HTTPExceptions directly; instead, you must use
# its subclasses.
- module HTTPExceptions
+ module HTTPExceptions # :nodoc:
def initialize(msg, res) #:nodoc:
super msg
@response = res
@@ -12,6 +12,7 @@ module Gem::Net
alias data response #:nodoc: obsolete
end
+ # :stopdoc:
class HTTPError < ProtocolError
include HTTPExceptions
end
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb
index 5cfe75a7cd..d6496d4ac1 100644
--- a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb
+++ b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb
@@ -19,16 +19,13 @@ class Gem::Net::HTTPGenericRequest
if Gem::URI === uri_or_path then
raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path
- hostname = uri_or_path.hostname
+ hostname = uri_or_path.host
raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0)
@uri = uri_or_path.dup
- host = @uri.hostname.dup
- host << ":" << @uri.port.to_s if @uri.port != @uri.default_port
@path = uri_or_path.request_uri
raise ArgumentError, "no HTTP request path given" unless @path
else
@uri = nil
- host = nil
raise ArgumentError, "no HTTP request path given" unless uri_or_path
raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty?
@path = uri_or_path.dup
@@ -51,7 +48,7 @@ class Gem::Net::HTTPGenericRequest
initialize_http_header initheader
self['Accept'] ||= '*/*'
self['User-Agent'] ||= 'Ruby'
- self['Host'] ||= host if host
+ self['Host'] ||= @uri.authority if @uri
@body = nil
@body_stream = nil
@body_data = nil
@@ -102,6 +99,31 @@ class Gem::Net::HTTPGenericRequest
"\#<#{self.class} #{@method}>"
end
+ # Returns a string representation of the request with the details for pp:
+ #
+ # require 'pp'
+ # post = Gem::Net::HTTP::Post.new(uri)
+ # post.inspect # => "#<Gem::Net::HTTP::Post POST>"
+ # post.pretty_inspect
+ # # => #<Gem::Net::HTTP::Post
+ # POST
+ # path="/"
+ # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
+ # "accept" => ["*/*"],
+ # "user-agent" => ["Ruby"],
+ # "host" => ["www.ruby-lang.org"]}>
+ #
+ def pretty_print(q)
+ q.object_group(self) {
+ q.breakable
+ q.text @method
+ q.breakable
+ q.text "path="; q.pp @path
+ q.breakable
+ q.text "headers="; q.pp to_hash
+ }
+ end
+
##
# Don't automatically decode response content-encoding if the user indicates
# they want to handle it.
@@ -220,7 +242,7 @@ class Gem::Net::HTTPGenericRequest
end
if host = self['host']
- host.sub!(/:.*/m, '')
+ host = Gem::URI.parse("//#{host}").host # Remove a port component from the existing Host header
elsif host = @uri.host
else
host = addr
@@ -239,6 +261,8 @@ class Gem::Net::HTTPGenericRequest
private
+ # :stopdoc:
+
class Chunker #:nodoc:
def initialize(sock)
@sock = sock
@@ -260,7 +284,6 @@ class Gem::Net::HTTPGenericRequest
def send_request_with_body(sock, ver, path, body)
self.content_length = body.bytesize
delete 'Transfer-Encoding'
- supply_default_content_type
write_header sock, ver, path
wait_for_continue sock, ver if sock.continue_timeout
sock.write body
@@ -271,7 +294,6 @@ class Gem::Net::HTTPGenericRequest
raise ArgumentError,
"Content-Length not given and Transfer-Encoding is not `chunked'"
end
- supply_default_content_type
write_header sock, ver, path
wait_for_continue sock, ver if sock.continue_timeout
if chunked?
@@ -373,12 +395,6 @@ class Gem::Net::HTTPGenericRequest
buf.clear
end
- def supply_default_content_type
- return if content_type()
- warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE
- set_content_type 'application/x-www-form-urlencoded'
- end
-
##
# Waits up to the continue timeout for a response from the server provided
# we're speaking HTTP 1.1 and are expecting a 100-continue response.
@@ -411,4 +427,3 @@ class Gem::Net::HTTPGenericRequest
end
end
-
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/header.rb b/lib/rubygems/vendor/net-http/lib/net/http/header.rb
index 5cb1da01ec..bc68cd2eef 100644
--- a/lib/rubygems/vendor/net-http/lib/net/http/header.rb
+++ b/lib/rubygems/vendor/net-http/lib/net/http/header.rb
@@ -179,7 +179,9 @@
# - #each_value: Passes each string field value to the block.
#
module Gem::Net::HTTPHeader
+ # The maximum length of HTTP header keys.
MAX_KEY_LENGTH = 1024
+ # The maximum length of HTTP header values.
MAX_FIELD_LENGTH = 65536
def initialize_http_header(initheader) #:nodoc:
@@ -267,6 +269,7 @@ module Gem::Net::HTTPHeader
end
end
+ # :stopdoc:
private def set_field(key, val)
case val
when Enumerable
@@ -294,6 +297,7 @@ module Gem::Net::HTTPHeader
ary.push val
end
end
+ # :startdoc:
# Returns the array field value for the given +key+,
# or +nil+ if there is no such field;
@@ -490,7 +494,7 @@ module Gem::Net::HTTPHeader
alias canonical_each each_capitalized
- def capitalize(name)
+ def capitalize(name) # :nodoc:
name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze)
end
private :capitalize
@@ -957,12 +961,12 @@ module Gem::Net::HTTPHeader
@header['proxy-authorization'] = [basic_encode(account, password)]
end
- def basic_encode(account, password)
+ def basic_encode(account, password) # :nodoc:
'Basic ' + ["#{account}:#{password}"].pack('m0')
end
private :basic_encode
-# Returns whether the HTTP session is to be closed.
+ # Returns whether the HTTP session is to be closed.
def connection_close?
token = /(?:\A|,)\s*close\s*(?:\z|,)/i
@header['connection']&.grep(token) {return true}
@@ -970,7 +974,7 @@ module Gem::Net::HTTPHeader
false
end
-# Returns whether the HTTP session is to be kept alive.
+ # Returns whether the HTTP session is to be kept alive.
def connection_keep_alive?
token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i
@header['connection']&.grep(token) {return true}
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb
index 45727e7f61..f990761042 100644
--- a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb
+++ b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb
@@ -29,6 +29,7 @@
# - Gem::Net::HTTP#get: sends +GET+ request, returns response object.
#
class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'GET'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -60,6 +61,7 @@ end
# - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object.
#
class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'HEAD'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = false
@@ -95,6 +97,7 @@ end
# - Gem::Net::HTTP#post: sends +POST+ request, returns response object.
#
class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'POST'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -130,6 +133,7 @@ end
# - Gem::Net::HTTP#put: sends +PUT+ request, returns response object.
#
class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PUT'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -162,6 +166,7 @@ end
# - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object.
#
class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'DELETE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -193,6 +198,7 @@ end
# - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object.
#
class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'OPTIONS'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -224,6 +230,7 @@ end
# - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object.
#
class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'TRACE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -258,6 +265,7 @@ end
# - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object.
#
class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PATCH'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -285,6 +293,7 @@ end
# - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object.
#
class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PROPFIND'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -308,6 +317,7 @@ end
# - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object.
#
class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'PROPPATCH'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -331,6 +341,7 @@ end
# - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object.
#
class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'MKCOL'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -354,6 +365,7 @@ end
# - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object.
#
class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'COPY'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -377,6 +389,7 @@ end
# - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object.
#
class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'MOVE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
@@ -400,6 +413,7 @@ end
# - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object.
#
class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'LOCK'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
@@ -423,8 +437,8 @@ end
# - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object.
#
class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest
+ # :stopdoc:
METHOD = 'UNLOCK'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
-
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/response.rb b/lib/rubygems/vendor/net-http/lib/net/http/response.rb
index cbbd191d87..dc164f1504 100644
--- a/lib/rubygems/vendor/net-http/lib/net/http/response.rb
+++ b/lib/rubygems/vendor/net-http/lib/net/http/response.rb
@@ -153,6 +153,7 @@ class Gem::Net::HTTPResponse
end
private
+ # :stopdoc:
def read_status_line(sock)
str = sock.readline
@@ -259,7 +260,7 @@ class Gem::Net::HTTPResponse
# header.
attr_accessor :ignore_eof
- def inspect
+ def inspect # :nodoc:
"#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
end
diff --git a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb
index 0f26ae6c26..62ce1cba1b 100644
--- a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb
+++ b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb
@@ -4,7 +4,9 @@
module Gem::Net
+ # Unknown HTTP response
class HTTPUnknownResponse < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPError #
end
@@ -19,6 +21,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response].
#
class HTTPInformation < HTTPResponse
+ # :stopdoc:
HAS_BODY = false
EXCEPTION_TYPE = HTTPError #
end
@@ -34,6 +37,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success].
#
class HTTPSuccess < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPError #
end
@@ -49,6 +53,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection].
#
class HTTPRedirection < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPRetriableError #
end
@@ -63,6 +68,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors].
#
class HTTPClientError < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPClientException #
end
@@ -77,6 +83,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors].
#
class HTTPServerError < HTTPResponse
+ # :stopdoc:
HAS_BODY = true
EXCEPTION_TYPE = HTTPFatalError #
end
@@ -94,6 +101,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100].
#
class HTTPContinue < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -111,6 +119,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101].
#
class HTTPSwitchProtocol < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -127,6 +136,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102].
#
class HTTPProcessing < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -145,6 +155,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103].
#
class HTTPEarlyHints < HTTPInformation
+ # :stopdoc:
HAS_BODY = false
end
@@ -162,6 +173,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200].
#
class HTTPOK < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -179,6 +191,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201].
#
class HTTPCreated < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -196,6 +209,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202].
#
class HTTPAccepted < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -215,6 +229,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203].
#
class HTTPNonAuthoritativeInformation < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -232,6 +247,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204].
#
class HTTPNoContent < HTTPSuccess
+ # :stopdoc:
HAS_BODY = false
end
@@ -250,6 +266,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205].
#
class HTTPResetContent < HTTPSuccess
+ # :stopdoc:
HAS_BODY = false
end
@@ -268,6 +285,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206].
#
class HTTPPartialContent < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -285,6 +303,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207].
#
class HTTPMultiStatus < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -304,6 +323,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208].
#
class HTTPAlreadyReported < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -321,6 +341,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226].
#
class HTTPIMUsed < HTTPSuccess
+ # :stopdoc:
HAS_BODY = true
end
@@ -338,6 +359,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300].
#
class HTTPMultipleChoices < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
HTTPMultipleChoice = HTTPMultipleChoices
@@ -356,6 +378,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301].
#
class HTTPMovedPermanently < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -373,6 +396,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302].
#
class HTTPFound < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
HTTPMovedTemporarily = HTTPFound
@@ -390,6 +414,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303].
#
class HTTPSeeOther < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -407,6 +432,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304].
#
class HTTPNotModified < HTTPRedirection
+ # :stopdoc:
HAS_BODY = false
end
@@ -423,6 +449,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305].
#
class HTTPUseProxy < HTTPRedirection
+ # :stopdoc:
HAS_BODY = false
end
@@ -440,6 +467,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307].
#
class HTTPTemporaryRedirect < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -456,6 +484,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308].
#
class HTTPPermanentRedirect < HTTPRedirection
+ # :stopdoc:
HAS_BODY = true
end
@@ -472,6 +501,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400].
#
class HTTPBadRequest < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -488,6 +518,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401].
#
class HTTPUnauthorized < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -504,6 +535,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402].
#
class HTTPPaymentRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -521,6 +553,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403].
#
class HTTPForbidden < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -537,6 +570,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404].
#
class HTTPNotFound < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -553,6 +587,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405].
#
class HTTPMethodNotAllowed < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -570,6 +605,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406].
#
class HTTPNotAcceptable < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -586,6 +622,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407].
#
class HTTPProxyAuthenticationRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -602,6 +639,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408].
#
class HTTPRequestTimeout < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestTimeOut = HTTPRequestTimeout
@@ -619,6 +657,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409].
#
class HTTPConflict < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -636,6 +675,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410].
#
class HTTPGone < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -653,6 +693,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411].
#
class HTTPLengthRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -670,6 +711,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412].
#
class HTTPPreconditionFailed < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -686,6 +728,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413].
#
class HTTPPayloadTooLarge < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestEntityTooLarge = HTTPPayloadTooLarge
@@ -703,6 +746,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414].
#
class HTTPURITooLong < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestURITooLong = HTTPURITooLong
@@ -721,6 +765,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415].
#
class HTTPUnsupportedMediaType < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -737,6 +782,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416].
#
class HTTPRangeNotSatisfiable < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable
@@ -754,6 +800,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417].
#
class HTTPExpectationFailed < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -774,6 +821,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421].
#
class HTTPMisdirectedRequest < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -790,6 +838,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422].
#
class HTTPUnprocessableEntity < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -805,6 +854,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423].
#
class HTTPLocked < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -821,6 +871,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424].
#
class HTTPFailedDependency < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -840,6 +891,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426].
#
class HTTPUpgradeRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -856,6 +908,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428].
#
class HTTPPreconditionRequired < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -872,6 +925,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429].
#
class HTTPTooManyRequests < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -889,6 +943,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431].
#
class HTTPRequestHeaderFieldsTooLarge < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
@@ -906,6 +961,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451].
#
class HTTPUnavailableForLegalReasons < HTTPClientError
+ # :stopdoc:
HAS_BODY = true
end
# 444 No Response - Nginx
@@ -926,6 +982,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500].
#
class HTTPInternalServerError < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -943,6 +1000,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501].
#
class HTTPNotImplemented < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -960,6 +1018,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502].
#
class HTTPBadGateway < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -977,6 +1036,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503].
#
class HTTPServiceUnavailable < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -994,6 +1054,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504].
#
class HTTPGatewayTimeout < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
HTTPGatewayTimeOut = HTTPGatewayTimeout
@@ -1011,6 +1072,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505].
#
class HTTPVersionNotSupported < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1027,6 +1089,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506].
#
class HTTPVariantAlsoNegotiates < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1043,6 +1106,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507].
#
class HTTPInsufficientStorage < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1059,6 +1123,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508].
#
class HTTPLoopDetected < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
# 509 Bandwidth Limit Exceeded - Apache bw/limited extension
@@ -1076,6 +1141,7 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510].
#
class HTTPNotExtended < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
@@ -1092,19 +1158,21 @@ module Gem::Net
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511].
#
class HTTPNetworkAuthenticationRequired < HTTPServerError
+ # :stopdoc:
HAS_BODY = true
end
end
class Gem::Net::HTTPResponse
+ # :stopdoc:
CODE_CLASS_TO_OBJ = {
'1' => Gem::Net::HTTPInformation,
'2' => Gem::Net::HTTPSuccess,
'3' => Gem::Net::HTTPRedirection,
'4' => Gem::Net::HTTPClientError,
'5' => Gem::Net::HTTPServerError
- }
+ }.freeze
CODE_TO_OBJ = {
'100' => Gem::Net::HTTPContinue,
'101' => Gem::Net::HTTPSwitchProtocol,
@@ -1170,5 +1238,5 @@ class Gem::Net::HTTPResponse
'508' => Gem::Net::HTTPLoopDetected,
'510' => Gem::Net::HTTPNotExtended,
'511' => Gem::Net::HTTPNetworkAuthenticationRequired,
- }
+ }.freeze
end
diff --git a/lib/rubygems/vendor/optparse/lib/optparse.rb b/lib/rubygems/vendor/optparse/lib/optparse.rb
index 537d06f229..d39d9dd4e0 100644
--- a/lib/rubygems/vendor/optparse/lib/optparse.rb
+++ b/lib/rubygems/vendor/optparse/lib/optparse.rb
@@ -7,6 +7,7 @@
#
# See Gem::OptionParser for documentation.
#
+require 'set' unless defined?(Set)
#--
# == Developer Documentation (not for RDoc output)
@@ -142,7 +143,7 @@
# Used:
#
# $ ruby optparse-test.rb -r
-# optparse-test.rb:9:in `<main>': missing argument: -r (Gem::OptionParser::MissingArgument)
+# optparse-test.rb:9:in '<main>': missing argument: -r (Gem::OptionParser::MissingArgument)
# $ ruby optparse-test.rb -r my-library
# You required my-library!
#
@@ -235,7 +236,7 @@
# $ ruby optparse-test.rb --user 2
# #<struct User id=2, name="Gandalf">
# $ ruby optparse-test.rb --user 3
-# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError)
+# optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError)
#
# === Store options to a Hash
#
@@ -425,7 +426,8 @@
#
class Gem::OptionParser
# The version string
- Gem::OptionParser::Version = "0.6.0"
+ VERSION = "0.8.0"
+ Version = VERSION # for compatibility
# :stopdoc:
NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
@@ -461,6 +463,10 @@ class Gem::OptionParser
candidates
end
+ def self.completable?(key)
+ String.try_convert(key) or defined?(key.id2name)
+ end
+
def candidate(key, icase = false, pat = nil, &_)
Completion.candidate(key, icase, pat, &method(:each))
end
@@ -496,7 +502,6 @@ class Gem::OptionParser
end
end
-
#
# Map from option/keyword string to object with completion.
#
@@ -504,7 +509,6 @@ class Gem::OptionParser
include Completion
end
-
#
# Individual switch class. Not important to the user.
#
@@ -546,11 +550,11 @@ class Gem::OptionParser
def initialize(pattern = nil, conv = nil,
short = nil, long = nil, arg = nil,
- desc = ([] if short or long), block = nil, &_block)
+ desc = ([] if short or long), block = nil, values = nil, &_block)
raise if Array === pattern
block ||= _block
- @pattern, @conv, @short, @long, @arg, @desc, @block =
- pattern, conv, short, long, arg, desc, block
+ @pattern, @conv, @short, @long, @arg, @desc, @block, @values =
+ pattern, conv, short, long, arg, desc, block, values
end
#
@@ -583,11 +587,15 @@ class Gem::OptionParser
# exception.
#
def conv_arg(arg, val = []) # :nodoc:
+ v, = *val
if conv
val = conv.call(*val)
else
val = proc {|v| v}.call(*val)
end
+ if @values
+ @values.include?(val) or raise InvalidArgument, v
+ end
return arg, block, val
end
private :conv_arg
@@ -668,7 +676,7 @@ class Gem::OptionParser
(sopts+lopts).each do |opt|
# "(-x -c -r)-l[left justify]"
- if /^--\[no-\](.+)$/ =~ opt
+ if /\A--\[no-\](.+)$/ =~ opt
o = $1
yield("--#{o}", desc.join(""))
yield("--no-#{o}", desc.join(""))
@@ -1032,7 +1040,6 @@ class Gem::OptionParser
DefaultList.short['-'] = Switch::NoArgument.new {}
DefaultList.long[''] = Switch::NoArgument.new {throw :terminate}
-
COMPSYS_HEADER = <<'XXX' # :nodoc:
typeset -A opt_args
@@ -1051,16 +1058,16 @@ XXX
end
def help_exit
- if STDOUT.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?})
+ if $stdout.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?})
less = ENV["LESS"]
- args = [{"LESS" => "#{!less || less.empty? ? '-' : less}Fe"}, pager, "w"]
+ args = [{"LESS" => "#{less} -Fe"}, pager, "w"]
print = proc do |f|
f.puts help
rescue Errno::EPIPE
# pager terminated
end
if Process.respond_to?(:fork) and false
- IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call(STDOUT)}
+ IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)}
# unreachable
end
IO.popen(*args, &print)
@@ -1102,7 +1109,7 @@ XXX
#
Officious['*-completion-zsh'] = proc do |parser|
Switch::OptionalArgument.new do |arg|
- parser.compsys(STDOUT, arg)
+ parser.compsys($stdout, arg)
exit
end
end
@@ -1288,7 +1295,15 @@ XXX
# to $0.
#
def program_name
- @program_name || File.basename($0, '.*')
+ @program_name || strip_ext(File.basename($0))
+ end
+
+ private def strip_ext(name) # :nodoc:
+ exts = /#{
+ require "rbconfig"
+ Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" "))
+ }\z/o
+ name.sub(exts, "")
end
# for experimental cascading :-)
@@ -1467,6 +1482,7 @@ XXX
klass = nil
q, a = nil
has_arg = false
+ values = nil
opts.each do |o|
# argument class
@@ -1480,7 +1496,7 @@ XXX
end
# directly specified pattern(any object possible to match)
- if (!(String === o || Symbol === o)) and o.respond_to?(:match)
+ if !Completion.completable?(o) and o.respond_to?(:match)
pattern = notwice(o, pattern, 'pattern')
if pattern.respond_to?(:convert)
conv = pattern.method(:convert).to_proc
@@ -1494,7 +1510,12 @@ XXX
case o
when Proc, Method
block = notwice(o, block, 'block')
- when Array, Hash
+ when Array, Hash, Set
+ if Array === o
+ o, v = o.partition {|v,| Completion.completable?(v)}
+ values = notwice(v, values, 'values') unless v.empty?
+ next if o.empty?
+ end
case pattern
when CompletingHash
when nil
@@ -1504,11 +1525,13 @@ XXX
raise ArgumentError, "argument pattern given twice"
end
o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}}
+ when Range
+ values = notwice(o, values, 'values')
when Module
raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4))
when *ArgumentStyle.keys
style = notwice(ArgumentStyle[o], style, 'style')
- when /^--no-([^\[\]=\s]*)(.+)?/
+ when /\A--no-([^\[\]=\s]*)(.+)?/
q, a = $1, $2
o = notwice(a ? Object : TrueClass, klass, 'type')
not_pattern, not_conv = search(:atype, o) unless not_style
@@ -1519,7 +1542,7 @@ XXX
(q = q.downcase).tr!('_', '-')
long << "no-#{q}"
nolong << q
- when /^--\[no-\]([^\[\]=\s]*)(.+)?/
+ when /\A--\[no-\]([^\[\]=\s]*)(.+)?/
q, a = $1, $2
o = notwice(a ? Object : TrueClass, klass, 'type')
if a
@@ -1532,7 +1555,7 @@ XXX
not_pattern, not_conv = search(:atype, FalseClass) unless not_style
not_style = Switch::NoArgument
nolong << "no-#{o}"
- when /^--([^\[\]=\s]*)(.+)?/
+ when /\A--([^\[\]=\s]*)(.+)?/
q, a = $1, $2
if a
o = notwice(NilClass, klass, 'type')
@@ -1542,7 +1565,7 @@ XXX
ldesc << "--#{q}"
(o = q.downcase).tr!('_', '-')
long << o
- when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
+ when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
q, a = $1, $2
o = notwice(Object, klass, 'type')
if a
@@ -1553,7 +1576,7 @@ XXX
end
sdesc << "-#{q}"
short << Regexp.new(q)
- when /^-(.)(.+)?/
+ when /\A-(.)(.+)?/
q, a = $1, $2
if a
o = notwice(NilClass, klass, 'type')
@@ -1562,7 +1585,7 @@ XXX
end
sdesc << "-#{q}"
short << q
- when /^=/
+ when /\A=/
style = notwice(default_style.guess(arg = o), style, 'style')
default_pattern, conv = search(:atype, Object) unless default_pattern
else
@@ -1571,12 +1594,18 @@ XXX
end
default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
+ if Range === values and klass
+ unless (!values.begin or klass === values.begin) and
+ (!values.end or klass === values.end)
+ raise ArgumentError, "range does not match class"
+ end
+ end
if !(short.empty? and long.empty?)
if has_arg and default_style == Switch::NoArgument
default_style = Switch::RequiredArgument
end
s = (style || default_style).new(pattern || default_pattern,
- conv, sdesc, ldesc, arg, desc, block)
+ conv, sdesc, ldesc, arg, desc, block, values)
elsif !block
if style or pattern
raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
@@ -1585,7 +1614,7 @@ XXX
else
short << pattern
s = (style || default_style).new(pattern,
- conv, nil, nil, arg, desc, block)
+ conv, nil, nil, arg, desc, block, values)
end
return s, short, long,
(not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
@@ -1827,7 +1856,7 @@ XXX
#
def permute!(argv = default_argv, **keywords)
nonopts = []
- order!(argv, **keywords, &nonopts.method(:<<))
+ order!(argv, **keywords) {|nonopt| nonopts << nonopt}
argv[0, 0] = nonopts
argv
end
@@ -1880,13 +1909,16 @@ XXX
single_options, *long_options = *args
result = {}
+ setter = (symbolize_names ?
+ ->(name, val) {result[name.to_sym] = val}
+ : ->(name, val) {result[name] = val})
single_options.scan(/(.)(:)?/) do |opt, val|
if val
- result[opt] = nil
+ setter[opt, nil]
define("-#{opt} VAL")
else
- result[opt] = false
+ setter[opt, false]
define("-#{opt}")
end
end if single_options
@@ -1895,16 +1927,16 @@ XXX
arg, desc = arg.split(';', 2)
opt, val = arg.split(':', 2)
if val
- result[opt] = val.empty? ? nil : val
+ setter[opt, (val unless val.empty?)]
define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact)
else
- result[opt] = false
+ setter[opt, false]
define("--#{opt}", *[desc].compact)
end
end
- parse_in_order(argv, result.method(:[]=), **keywords)
- symbolize_names ? result.transform_keys(&:to_sym) : result
+ parse_in_order(argv, setter, **keywords)
+ result
end
#
@@ -1954,7 +1986,7 @@ XXX
visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw}
}
exc = ambiguous ? AmbiguousOption : InvalidOption
- raise exc.new(opt, additional: self.method(:additional_message).curry[typ])
+ raise exc.new(opt, additional: proc {|o| additional_message(typ, o)})
end
private :complete
@@ -2019,19 +2051,27 @@ XXX
def load(filename = nil, **keywords)
unless filename
basename = File.basename($0, '.*')
- return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil
+ return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil
basename << ".options"
+ if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty?
+ # https://specifications.freedesktop.org/basedir-spec/latest/#variables
+ #
+ # If $XDG_CONFIG_HOME is either not set or empty, a default
+ # equal to $HOME/.config should be used.
+ xdg = ['~/.config', true]
+ end
return [
- # XDG
- ENV['XDG_CONFIG_HOME'],
- '~/.config',
+ xdg,
+
*ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR),
# Haiku
- '~/config/settings',
- ].any? {|dir|
+ ['~/config/settings', true],
+ ].any? {|dir, expand|
next if !dir or dir.empty?
- load(File.expand_path(basename, dir), **keywords) rescue nil
+ filename = File.join(dir, basename)
+ filename = File.expand_path(filename) if expand
+ load(filename, **keywords) rescue nil
}
end
begin
@@ -2237,9 +2277,10 @@ XXX
argv
end
+ DIR = File.join(__dir__, '')
def self.filter_backtrace(array)
unless $DEBUG
- array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~))
+ array.delete_if {|bt| bt.start_with?(DIR)}
end
array
end
diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb
index 4d95e5fc7f..168df21f3e 100644
--- a/lib/rubygems/vendor/resolv/lib/resolv.rb
+++ b/lib/rubygems/vendor/resolv/lib/resolv.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'socket'
-require_relative '../../timeout/lib/timeout'
+require_relative '../../../vendored_timeout'
require 'io/wait'
require_relative '../../../vendored_securerandom'
@@ -33,7 +33,7 @@ require_relative '../../../vendored_securerandom'
class Gem::Resolv
- VERSION = "0.6.0"
+ VERSION = "0.6.2"
##
# Looks up the first IP address for +name+.
@@ -173,13 +173,16 @@ class Gem::Resolv
class ResolvTimeout < Gem::Timeout::Error; end
+ WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/
+ private_constant :WINDOWS
+
##
# Gem::Resolv::Hosts is a hostname resolver that uses the system hosts file.
class Hosts
- if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and
+ if WINDOWS
begin
- require 'win32/resolv'
+ require 'win32/resolv' unless defined?(Win32::Resolv)
DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
rescue LoadError
end
@@ -659,8 +662,20 @@ class Gem::Resolv
}
end
- def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
- begin
+ case RUBY_PLATFORM
+ when *[
+ # https://www.rfc-editor.org/rfc/rfc6056.txt
+ # Appendix A. Survey of the Algorithms in Use by Some Popular Implementations
+ /freebsd/, /linux/, /netbsd/, /openbsd/, /solaris/,
+ /darwin/, # the same as FreeBSD
+ ] then
+ def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
+ udpsock.bind(bind_host, 0)
+ end
+ else
+ # Sequential port assignment
+ def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
+ # Ephemeral port number range recommended by RFC 6056
port = random(1024..65535)
udpsock.bind(bind_host, port)
rescue Errno::EADDRINUSE, # POSIX
@@ -983,13 +998,13 @@ class Gem::Resolv
next unless keyword
case keyword
when 'nameserver'
- nameserver.concat(args)
+ nameserver.concat(args.each(&:freeze))
when 'domain'
next if args.empty?
- search = [args[0]]
+ search = [args[0].freeze]
when 'search'
next if args.empty?
- search = args
+ search = args.each(&:freeze)
when 'options'
args.each {|arg|
case arg
@@ -1000,22 +1015,22 @@ class Gem::Resolv
end
}
}
- return { :nameserver => nameserver, :search => search, :ndots => ndots }
+ return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze
end
def Config.default_config_hash(filename="/etc/resolv.conf")
if File.exist? filename
- config_hash = Config.parse_resolv_conf(filename)
+ Config.parse_resolv_conf(filename)
+ elsif WINDOWS
+ require 'win32/resolv' unless defined?(Win32::Resolv)
+ search, nameserver = Win32::Resolv.get_resolv_info
+ config_hash = {}
+ config_hash[:nameserver] = nameserver if nameserver
+ config_hash[:search] = [search].flatten if search
+ config_hash
else
- if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
- require 'win32/resolv'
- search, nameserver = Win32::Resolv.get_resolv_info
- config_hash = {}
- config_hash[:nameserver] = nameserver if nameserver
- config_hash[:search] = [search].flatten if search
- end
+ {}
end
- config_hash || {}
end
def lazy_initialize
@@ -1664,6 +1679,7 @@ class Gem::Resolv
prev_index = @index
save_index = nil
d = []
+ size = -1
while true
raise DecodeError.new("limit exceeded") if @limit <= @index
case @data.getbyte(@index)
@@ -1684,7 +1700,10 @@ class Gem::Resolv
end
@index = idx
else
- d << self.get_label
+ l = self.get_label
+ d << l
+ size += 1 + l.string.bytesize
+ raise DecodeError.new("name label data exceed 255 octets") if size > 255
end
end
end
@@ -2110,7 +2129,14 @@ class Gem::Resolv
attr_reader :ttl
- ClassHash = {} # :nodoc:
+ ClassHash = Module.new do
+ module_function
+
+ def []=(type_class_value, klass)
+ type_value, class_value = type_class_value
+ Resource.const_set(:"Type#{type_value}_Class#{class_value}", klass)
+ end
+ end
def encode_rdata(msg) # :nodoc:
raise NotImplementedError.new
@@ -2148,7 +2174,9 @@ class Gem::Resolv
end
def self.get_class(type_value, class_value) # :nodoc:
- return ClassHash[[type_value, class_value]] ||
+ cache = :"Type#{type_value}_Class#{class_value}"
+
+ return (const_defined?(cache) && const_get(cache)) ||
Generic.create(type_value, class_value)
end
@@ -2577,7 +2605,7 @@ class Gem::Resolv
end
##
- # Flags for this proprty:
+ # Flags for this property:
# - Bit 0 : 0 = not critical, 1 = critical
attr_reader :flags
diff --git a/lib/rubygems/vendor/timeout/lib/timeout.rb b/lib/rubygems/vendor/timeout/lib/timeout.rb
index 455c504f47..376b8c0e2b 100644
--- a/lib/rubygems/vendor/timeout/lib/timeout.rb
+++ b/lib/rubygems/vendor/timeout/lib/timeout.rb
@@ -20,7 +20,7 @@
module Gem::Timeout
# The version
- VERSION = "0.4.3"
+ VERSION = "0.4.4"
# Internal error raised to when a timeout is triggered.
class ExitException < Exception
@@ -123,6 +123,9 @@ module Gem::Timeout
def self.ensure_timeout_thread_created
unless @timeout_thread and @timeout_thread.alive?
+ # If the Mutex is already owned we are in a signal handler.
+ # In that case, just return and let the main thread create the @timeout_thread.
+ return if TIMEOUT_THREAD_MUTEX.owned?
TIMEOUT_THREAD_MUTEX.synchronize do
unless @timeout_thread and @timeout_thread.alive?
@timeout_thread = create_timeout_thread
diff --git a/lib/rubygems/vendor/uri/lib/uri/common.rb b/lib/rubygems/vendor/uri/lib/uri/common.rb
index f0755f5fdc..e9bdfa6a07 100644
--- a/lib/rubygems/vendor/uri/lib/uri/common.rb
+++ b/lib/rubygems/vendor/uri/lib/uri/common.rb
@@ -30,6 +30,9 @@ module Gem::URI
remove_const(:Parser) if defined?(::Gem::URI::Parser)
const_set("Parser", parser.class)
+ remove_const(:PARSER) if defined?(::Gem::URI::PARSER)
+ const_set("PARSER", parser)
+
remove_const(:REGEXP) if defined?(::Gem::URI::REGEXP)
remove_const(:PATTERN) if defined?(::Gem::URI::PATTERN)
if Parser == RFC2396_Parser
@@ -49,10 +52,10 @@ module Gem::URI
warn "Gem::URI::REGEXP is obsolete. Use Gem::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE
Gem::URI::RFC2396_REGEXP
elsif value = RFC2396_PARSER.regexp[const]
- warn "Gem::URI::#{const} is obsolete. Use RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE
+ warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE
value
elsif value = RFC2396_Parser.const_get(const)
- warn "Gem::URI::#{const} is obsolete. Use RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE
+ warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE
value
else
super
@@ -92,6 +95,40 @@ module Gem::URI
end
module Schemes # :nodoc:
+ class << self
+ ReservedChars = ".+-"
+ EscapedChars = "\u01C0\u01C1\u01C2"
+ # Use Lo category chars as escaped chars for TruffleRuby, which
+ # does not allow Symbol categories as identifiers.
+
+ def escape(name)
+ unless name and name.ascii_only?
+ return nil
+ end
+ name.upcase.tr(ReservedChars, EscapedChars)
+ end
+
+ def unescape(name)
+ name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase
+ end
+
+ def find(name)
+ const_get(name, false) if name and const_defined?(name, false)
+ end
+
+ def register(name, klass)
+ unless scheme = escape(name)
+ raise ArgumentError, "invalid character as scheme - #{name}"
+ end
+ const_set(scheme, klass)
+ end
+
+ def list
+ constants.map { |name|
+ [unescape(name.to_s), const_get(name)]
+ }.to_h
+ end
+ end
end
private_constant :Schemes
@@ -104,7 +141,7 @@ module Gem::URI
# Note that after calling String#upcase on +scheme+, it must be a valid
# constant name.
def self.register_scheme(scheme, klass)
- Schemes.const_set(scheme.to_s.upcase, klass)
+ Schemes.register(scheme, klass)
end
# Returns a hash of the defined schemes:
@@ -122,14 +159,14 @@ module Gem::URI
#
# Related: Gem::URI.register_scheme.
def self.scheme_list
- Schemes.constants.map { |name|
- [name.to_s.upcase, Schemes.const_get(name)]
- }.to_h
+ Schemes.list
end
+ # :stopdoc:
INITIAL_SCHEMES = scheme_list
private_constant :INITIAL_SCHEMES
Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
+ # :startdoc:
# Returns a new object constructed from the given +scheme+, +arguments+,
# and +default+:
@@ -148,12 +185,10 @@ module Gem::URI
# # => #<Gem::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
def self.for(scheme, *arguments, default: Generic)
- const_name = scheme.to_s.upcase
+ const_name = Schemes.escape(scheme)
uri_class = INITIAL_SCHEMES[const_name]
- uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
- Schemes.const_get(const_name, false)
- end
+ uri_class ||= Schemes.find(const_name)
uri_class ||= default
return uri_class.new(scheme, *arguments)
@@ -195,7 +230,7 @@ module Gem::URI
# ["fragment", "top"]]
#
def self.split(uri)
- DEFAULT_PARSER.split(uri)
+ PARSER.split(uri)
end
# Returns a new \Gem::URI object constructed from the given string +uri+:
@@ -205,11 +240,11 @@ module Gem::URI
# Gem::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# # => #<Gem::URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
- # It's recommended to first ::escape string +uri+
+ # It's recommended to first Gem::URI::RFC2396_PARSER.escape string +uri+
# if it may contain invalid Gem::URI characters.
#
def self.parse(uri)
- DEFAULT_PARSER.parse(uri)
+ PARSER.parse(uri)
end
# Merges the given Gem::URI strings +str+
@@ -265,7 +300,7 @@ module Gem::URI
#
def self.extract(str, schemes = nil, &block) # :nodoc:
warn "Gem::URI.extract is obsolete", uplevel: 1 if $VERBOSE
- DEFAULT_PARSER.extract(str, schemes, &block)
+ PARSER.extract(str, schemes, &block)
end
#
@@ -302,7 +337,7 @@ module Gem::URI
#
def self.regexp(schemes = nil)# :nodoc:
warn "Gem::URI.regexp is obsolete", uplevel: 1 if $VERBOSE
- DEFAULT_PARSER.make_regexp(schemes)
+ PARSER.make_regexp(schemes)
end
TBLENCWWWCOMP_ = {} # :nodoc:
@@ -407,6 +442,8 @@ module Gem::URI
_decode_uri_component(/%\h\h/, str, enc)
end
+ # Returns a string derived from the given string +str+ with
+ # Gem::URI-encoded characters matching +regexp+ according to +table+.
def self._encode_uri_component(regexp, table, str, enc)
str = str.to_s.dup
if str.encoding != Encoding::ASCII_8BIT
@@ -421,6 +458,8 @@ module Gem::URI
end
private_class_method :_encode_uri_component
+ # Returns a string decoding characters matching +regexp+ from the
+ # given \URL-encoded string +str+.
def self._decode_uri_component(regexp, str, enc)
raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
@@ -859,6 +898,7 @@ module Gem
# Returns a \Gem::URI object derived from the given +uri+,
# which may be a \Gem::URI string or an existing \Gem::URI object:
#
+ # require 'rubygems/vendor/uri/lib/uri'
# # Returns a new Gem::URI.
# uri = Gem::URI('http://github.com/ruby/ruby')
# # => #<Gem::URI::HTTP http://github.com/ruby/ruby>
@@ -866,6 +906,8 @@ module Gem
# Gem::URI(uri)
# # => #<Gem::URI::HTTP http://github.com/ruby/ruby>
#
+ # You must require 'rubygems/vendor/uri/lib/uri' to use this method.
+ #
def URI(uri)
if uri.is_a?(Gem::URI::Generic)
uri
diff --git a/lib/rubygems/vendor/uri/lib/uri/file.rb b/lib/rubygems/vendor/uri/lib/uri/file.rb
index 768755fc2d..391c499716 100644
--- a/lib/rubygems/vendor/uri/lib/uri/file.rb
+++ b/lib/rubygems/vendor/uri/lib/uri/file.rb
@@ -47,7 +47,7 @@ module Gem::URI
# :path => '/ruby/src'})
# uri2.to_s # => "file://host.example.com/ruby/src"
#
- # uri3 = Gem::URI::File.build({:path => Gem::URI::escape('/path/my file.txt')})
+ # uri3 = Gem::URI::File.build({:path => Gem::URI::RFC2396_PARSER.escape('/path/my file.txt')})
# uri3.to_s # => "file:///path/my%20file.txt"
#
def self.build(args)
diff --git a/lib/rubygems/vendor/uri/lib/uri/generic.rb b/lib/rubygems/vendor/uri/lib/uri/generic.rb
index 2eabe2b4e3..d0bc77dfda 100644
--- a/lib/rubygems/vendor/uri/lib/uri/generic.rb
+++ b/lib/rubygems/vendor/uri/lib/uri/generic.rb
@@ -73,7 +73,7 @@ module Gem::URI
#
# At first, tries to create a new Gem::URI::Generic instance using
# Gem::URI::Generic::build. But, if exception Gem::URI::InvalidComponentError is raised,
- # then it does Gem::URI::Escape.escape all Gem::URI components and tries again.
+ # then it does Gem::URI::RFC2396_PARSER.escape all Gem::URI components and tries again.
#
def self.build2(args)
begin
@@ -126,9 +126,9 @@ module Gem::URI
end
end
else
- component = self.class.component rescue ::Gem::URI::Generic::COMPONENT
+ component = self.component rescue ::Gem::URI::Generic::COMPONENT
raise ArgumentError,
- "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})"
+ "expected Array of or Hash of components of #{self} (#{component.join(', ')})"
end
tmp << nil
@@ -186,18 +186,18 @@ module Gem::URI
if arg_check
self.scheme = scheme
- self.userinfo = userinfo
self.hostname = host
self.port = port
+ self.userinfo = userinfo
self.path = path
self.query = query
self.opaque = opaque
self.fragment = fragment
else
self.set_scheme(scheme)
- self.set_userinfo(userinfo)
self.set_host(host)
self.set_port(port)
+ self.set_userinfo(userinfo)
self.set_path(path)
self.query = query
self.set_opaque(opaque)
@@ -284,7 +284,7 @@ module Gem::URI
# Returns the parser to be used.
#
- # Unless a Gem::URI::Parser is defined, DEFAULT_PARSER is used.
+ # Unless the +parser+ is defined, DEFAULT_PARSER is used.
#
def parser
if !defined?(@parser) || !@parser
@@ -315,7 +315,7 @@ module Gem::URI
end
#
- # Checks the scheme +v+ component against the Gem::URI::Parser Regexp for :SCHEME.
+ # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME.
#
def check_scheme(v)
if v && parser.regexp[:SCHEME] !~ v
@@ -385,7 +385,7 @@ module Gem::URI
#
# Checks the user +v+ component for RFC2396 compliance
- # and against the Gem::URI::Parser Regexp for :USERINFO.
+ # and against the +parser+ Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
@@ -409,7 +409,7 @@ module Gem::URI
#
# Checks the password +v+ component for RFC2396 compliance
- # and against the Gem::URI::Parser Regexp for :USERINFO.
+ # and against the +parser+ Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
@@ -511,7 +511,7 @@ module Gem::URI
user, password = split_userinfo(user)
end
@user = user
- @password = password if password
+ @password = password
[@user, @password]
end
@@ -522,7 +522,7 @@ module Gem::URI
# See also Gem::URI::Generic.user=.
#
def set_user(v)
- set_userinfo(v, @password)
+ set_userinfo(v, nil)
v
end
protected :set_user
@@ -574,6 +574,12 @@ module Gem::URI
@password
end
+ # Returns the authority info (array of user, password, host and
+ # port), if any is set. Or returns +nil+.
+ def authority
+ return @user, @password, @host, @port if @user || @password || @host || @port
+ end
+
# Returns the user component after Gem::URI decoding.
def decoded_user
Gem::URI.decode_uri_component(@user) if @user
@@ -586,7 +592,7 @@ module Gem::URI
#
# Checks the host +v+ component for RFC2396 compliance
- # and against the Gem::URI::Parser Regexp for :HOST.
+ # and against the +parser+ Regexp for :HOST.
#
# Can not have a registry or opaque component defined,
# with a host component defined.
@@ -615,6 +621,13 @@ module Gem::URI
end
protected :set_host
+ # Protected setter for the authority info (+user+, +password+, +host+
+ # and +port+). If +port+ is +nil+, +default_port+ will be set.
+ #
+ protected def set_authority(user, password, host, port = nil)
+ @user, @password, @host, @port = user, password, host, port || self.default_port
+ end
+
#
# == Args
#
@@ -639,6 +652,7 @@ module Gem::URI
def host=(v)
check_host(v)
set_host(v)
+ set_userinfo(nil)
v
end
@@ -675,7 +689,7 @@ module Gem::URI
#
# Checks the port +v+ component for RFC2396 compliance
- # and against the Gem::URI::Parser Regexp for :PORT.
+ # and against the +parser+ Regexp for :PORT.
#
# Can not have a registry or opaque component defined,
# with a port component defined.
@@ -729,6 +743,7 @@ module Gem::URI
def port=(v)
check_port(v)
set_port(v)
+ set_userinfo(nil)
port
end
@@ -748,7 +763,7 @@ module Gem::URI
#
# Checks the path +v+ component for RFC2396 compliance
- # and against the Gem::URI::Parser Regexp
+ # and against the +parser+ Regexp
# for :ABS_PATH and :REL_PATH.
#
# Can not have a opaque component defined,
@@ -853,7 +868,7 @@ module Gem::URI
#
# Checks the opaque +v+ component for RFC2396 compliance and
- # against the Gem::URI::Parser Regexp for :OPAQUE.
+ # against the +parser+ Regexp for :OPAQUE.
#
# Can not have a host, port, user, or path component defined,
# with an opaque component defined.
@@ -905,7 +920,7 @@ module Gem::URI
end
#
- # Checks the fragment +v+ component against the Gem::URI::Parser Regexp for :FRAGMENT.
+ # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT.
#
#
# == Args
@@ -1121,7 +1136,7 @@ module Gem::URI
base = self.dup
- authority = rel.userinfo || rel.host || rel.port
+ authority = rel.authority
# RFC2396, Section 5.2, 2)
if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
@@ -1134,9 +1149,7 @@ module Gem::URI
# RFC2396, Section 5.2, 4)
if authority
- base.set_userinfo(rel.userinfo)
- base.set_host(rel.host)
- base.set_port(rel.port || base.default_port)
+ base.set_authority(*authority)
base.set_path(rel.path)
elsif base.path && rel.path
base.set_path(merge_path(base.path, rel.path))
@@ -1527,7 +1540,7 @@ module Gem::URI
else
unless proxy_uri = env[name]
if proxy_uri = env[name.upcase]
- warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1
+ warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1
end
end
end
diff --git a/lib/rubygems/vendor/uri/lib/uri/http.rb b/lib/rubygems/vendor/uri/lib/uri/http.rb
index 658e9941dd..99c78358ac 100644
--- a/lib/rubygems/vendor/uri/lib/uri/http.rb
+++ b/lib/rubygems/vendor/uri/lib/uri/http.rb
@@ -61,6 +61,18 @@ module Gem::URI
super(tmp)
end
+ # Do not allow empty host names, as they are not allowed by RFC 3986.
+ def check_host(v)
+ ret = super
+
+ if ret && v.empty?
+ raise InvalidComponentError,
+ "bad component(expected host component): #{v}"
+ end
+
+ ret
+ end
+
#
# == Description
#
diff --git a/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb
index 04a1d0c869..2bb4181649 100644
--- a/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb
+++ b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb
@@ -67,7 +67,7 @@ module Gem::URI
#
# == Synopsis
#
- # Gem::URI::Parser.new([opts])
+ # Gem::URI::RFC2396_Parser.new([opts])
#
# == Args
#
@@ -86,7 +86,7 @@ module Gem::URI
#
# == Examples
#
- # p = Gem::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
+ # p = Gem::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
# u = p.parse("http://example.jp/%uABCD") #=> #<Gem::URI::HTTP http://example.jp/%uABCD>
# Gem::URI.parse(u.to_s) #=> raises Gem::URI::InvalidURIError
#
@@ -108,12 +108,12 @@ module Gem::URI
# The Hash of patterns.
#
- # See also Gem::URI::Parser.initialize_pattern.
+ # See also #initialize_pattern.
attr_reader :pattern
# The Hash of Regexp.
#
- # See also Gem::URI::Parser.initialize_regexp.
+ # See also #initialize_regexp.
attr_reader :regexp
# Returns a split Gem::URI against +regexp[:ABS_URI]+.
@@ -202,8 +202,7 @@ module Gem::URI
#
# == Usage
#
- # p = Gem::URI::Parser.new
- # p.parse("ldap://ldap.example.com/dc=example?user=john")
+ # Gem::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john")
# #=> #<Gem::URI::LDAP ldap://ldap.example.com/dc=example?user=john>
#
def parse(uri)
@@ -244,7 +243,7 @@ module Gem::URI
# If no +block+ given, then returns the result,
# else it calls +block+ for each element in result.
#
- # See also Gem::URI::Parser.make_regexp.
+ # See also #make_regexp.
#
def extract(str, schemes = nil)
if block_given?
@@ -263,7 +262,7 @@ module Gem::URI
unless schemes
@regexp[:ABS_URI_REF]
else
- /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x
+ /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x
end
end
@@ -524,6 +523,8 @@ module Gem::URI
ret
end
+ # Returns +uri+ as-is if it is Gem::URI, or convert it to Gem::URI if it is
+ # a String.
def convert_to_uri(uri)
if uri.is_a?(Gem::URI::Generic)
uri
diff --git a/lib/rubygems/vendor/uri/lib/uri/version.rb b/lib/rubygems/vendor/uri/lib/uri/version.rb
index c2f617ce25..7ee577887b 100644
--- a/lib/rubygems/vendor/uri/lib/uri/version.rb
+++ b/lib/rubygems/vendor/uri/lib/uri/version.rb
@@ -1,6 +1,6 @@
module Gem::URI
# :stopdoc:
- VERSION_CODE = '010003'.freeze
- VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
+ VERSION = '1.1.1'.freeze
+ VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze
# :startdoc:
end
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index 25c412be4b..90fe1b3c24 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -171,9 +171,7 @@ class Gem::Version
# True if the +version+ string matches RubyGems' requirements.
def self.correct?(version)
- nil_versions_are_discouraged! if version.nil?
-
- ANCHORED_VERSION_PATTERN.match?(version.to_s)
+ version.nil? || ANCHORED_VERSION_PATTERN.match?(version.to_s)
end
##
@@ -182,15 +180,10 @@ class Gem::Version
#
# ver1 = Version.create('1.3.17') # -> (Version object)
# ver2 = Version.create(ver1) # -> (ver1)
- # ver3 = Version.create(nil) # -> nil
def self.create(input)
if self === input # check yourself before you wreck yourself
input
- elsif input.nil?
- nil_versions_are_discouraged!
-
- nil
else
new input
end
@@ -206,14 +199,6 @@ class Gem::Version
@@all[version] ||= super
end
- def self.nil_versions_are_discouraged!
- unless Gem::Deprecate.skip
- warn "nil versions are discouraged and will be deprecated in Rubygems 4"
- end
- end
-
- private_class_method :nil_versions_are_discouraged!
-
##
# Constructs a Version from the +version+ string. A version string is a
# series of digits or ASCII letters separated by dots.
@@ -224,7 +209,7 @@ class Gem::Version
end
# If version is an empty string convert it to 0
- version = 0 if version.is_a?(String) && /\A\s*\Z/.match?(version)
+ version = 0 if version.nil? || (version.is_a?(String) && /\A\s*\Z/.match?(version))
@version = version.to_s
@@ -354,11 +339,14 @@ class Gem::Version
##
# Compares this version with +other+ returning -1, 0, or 1 if the
# other version is larger, the same, or smaller than this
- # one. Attempts to compare to something that's not a
- # <tt>Gem::Version</tt> or a valid version String return +nil+.
+ # one. +other+ must be an instance of Gem::Version, comparing with
+ # other types may raise an exception.
def <=>(other)
- return self <=> self.class.new(other) if (String === other) && self.class.correct?(other)
+ if String === other
+ return unless self.class.correct?(other)
+ return self <=> self.class.new(other)
+ end
return unless Gem::Version === other
return 0 if @version == other.version || canonical_segments == other.canonical_segments
diff --git a/lib/rubygems/win_platform.rb b/lib/rubygems/win_platform.rb
new file mode 100644
index 0000000000..78e968fe49
--- /dev/null
+++ b/lib/rubygems/win_platform.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "rbconfig"
+
+module Gem
+ ##
+ # An Array of Regexps that match windows Ruby platforms.
+
+ WIN_PATTERNS = [
+ /bccwin/i,
+ /cygwin/i,
+ /djgpp/i,
+ /mingw/i,
+ /mswin/i,
+ /wince/i,
+ ].freeze
+
+ @@win_platform = nil
+
+ ##
+ # Is this a windows platform?
+
+ def self.win_platform?
+ if @@win_platform.nil?
+ ruby_platform = RbConfig::CONFIG["host_os"]
+ @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil?
+ end
+
+ @@win_platform
+ end
+end
diff --git a/lib/set/sorted_set.rb b/lib/set/sorted_set.rb
deleted file mode 100644
index bc07bc1fb0..0000000000
--- a/lib/set/sorted_set.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-begin
- require 'sorted_set'
-rescue ::LoadError
- raise "The `SortedSet` class has been extracted from the `set` library. " \
- "You must use the `sorted_set` gem or other alternatives."
-end
diff --git a/lib/set/subclass_compatible.rb b/lib/set/subclass_compatible.rb
new file mode 100644
index 0000000000..f43c34f6a2
--- /dev/null
+++ b/lib/set/subclass_compatible.rb
@@ -0,0 +1,347 @@
+# frozen_string_literal: true
+
+# :markup: markdown
+#
+# set/subclass_compatible.rb - Provides compatibility for set subclasses
+#
+# Copyright (c) 2002-2024 Akinori MUSHA <knu@iDaemons.org>
+#
+# Documentation by Akinori MUSHA and Gavin Sinclair.
+#
+# All rights reserved. You can redistribute and/or modify it under the same
+# terms as Ruby.
+
+
+class Set
+ # This module is automatically included in subclasses of Set, to
+ # make them backwards compatible with the pure-Ruby set implementation
+ # used before Ruby 4. Users who want to use Set subclasses without
+ # this compatibility layer should subclass from Set::CoreSet.
+ #
+ # Note that Set subclasses that access `@hash` are not compatible even
+ # with this support. Such subclasses must be updated to support Ruby 4.
+ module SubclassCompatible
+ module ClassMethods
+ def [](*ary)
+ new(ary)
+ end
+ end
+
+ # Creates a new set containing the elements of the given enumerable
+ # object.
+ #
+ # If a block is given, the elements of enum are preprocessed by the
+ # given block.
+ #
+ # Set.new([1, 2]) #=> #<Set: {1, 2}>
+ # Set.new([1, 2, 1]) #=> #<Set: {1, 2}>
+ # Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}>
+ # Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}>
+ # Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}>
+ def initialize(enum = nil, &block) # :yields: o
+ enum.nil? and return
+
+ if block
+ do_with_enum(enum) { |o| add(block[o]) }
+ else
+ merge(enum)
+ end
+ end
+
+ def do_with_enum(enum, &block) # :nodoc:
+ if enum.respond_to?(:each_entry)
+ enum.each_entry(&block) if block
+ elsif enum.respond_to?(:each)
+ enum.each(&block) if block
+ else
+ raise ArgumentError, "value must be enumerable"
+ end
+ end
+ private :do_with_enum
+
+ def replace(enum)
+ if enum.instance_of?(self.class)
+ super
+ else
+ do_with_enum(enum) # make sure enum is enumerable before calling clear
+ clear
+ merge(enum)
+ end
+ end
+
+ def to_set(&block)
+ return self if instance_of?(Set) && block.nil?
+ Set.new(self, &block)
+ end
+
+ def flatten_merge(set, seen = {}) # :nodoc:
+ set.each { |e|
+ if e.is_a?(Set)
+ case seen[e_id = e.object_id]
+ when true
+ raise ArgumentError, "tried to flatten recursive Set"
+ when false
+ next
+ end
+
+ seen[e_id] = true
+ flatten_merge(e, seen)
+ seen[e_id] = false
+ else
+ add(e)
+ end
+ }
+
+ self
+ end
+ protected :flatten_merge
+
+ def flatten
+ self.class.new.flatten_merge(self)
+ end
+
+ def flatten!
+ replace(flatten()) if any?(Set)
+ end
+
+ def superset?(set)
+ case
+ when set.instance_of?(self.class)
+ super
+ when set.is_a?(Set)
+ size >= set.size && set.all?(self)
+ else
+ raise ArgumentError, "value must be a set"
+ end
+ end
+ alias >= superset?
+
+ def proper_superset?(set)
+ case
+ when set.instance_of?(self.class)
+ super
+ when set.is_a?(Set)
+ size > set.size && set.all?(self)
+ else
+ raise ArgumentError, "value must be a set"
+ end
+ end
+ alias > proper_superset?
+
+ def subset?(set)
+ case
+ when set.instance_of?(self.class)
+ super
+ when set.is_a?(Set)
+ size <= set.size && all?(set)
+ else
+ raise ArgumentError, "value must be a set"
+ end
+ end
+ alias <= subset?
+
+ def proper_subset?(set)
+ case
+ when set.instance_of?(self.class)
+ super
+ when set.is_a?(Set)
+ size < set.size && all?(set)
+ else
+ raise ArgumentError, "value must be a set"
+ end
+ end
+ alias < proper_subset?
+
+ def <=>(set)
+ return unless set.is_a?(Set)
+
+ case size <=> set.size
+ when -1 then -1 if proper_subset?(set)
+ when +1 then +1 if proper_superset?(set)
+ else 0 if self.==(set)
+ end
+ end
+
+ def intersect?(set)
+ case set
+ when Set
+ if size < set.size
+ any?(set)
+ else
+ set.any?(self)
+ end
+ when Enumerable
+ set.any?(self)
+ else
+ raise ArgumentError, "value must be enumerable"
+ end
+ end
+
+ def disjoint?(set)
+ !intersect?(set)
+ end
+
+ def add?(o)
+ add(o) unless include?(o)
+ end
+
+ def delete?(o)
+ delete(o) if include?(o)
+ end
+
+ def delete_if(&block)
+ block_given? or return enum_for(__method__) { size }
+ select(&block).each { |o| delete(o) }
+ self
+ end
+
+ def keep_if(&block)
+ block_given? or return enum_for(__method__) { size }
+ reject(&block).each { |o| delete(o) }
+ self
+ end
+
+ def collect!
+ block_given? or return enum_for(__method__) { size }
+ set = self.class.new
+ each { |o| set << yield(o) }
+ replace(set)
+ end
+ alias map! collect!
+
+ def reject!(&block)
+ block_given? or return enum_for(__method__) { size }
+ n = size
+ delete_if(&block)
+ self if size != n
+ end
+
+ def select!(&block)
+ block_given? or return enum_for(__method__) { size }
+ n = size
+ keep_if(&block)
+ self if size != n
+ end
+
+ alias filter! select!
+
+ def merge(*enums, **nil)
+ enums.each do |enum|
+ if enum.instance_of?(self.class)
+ super(enum)
+ else
+ do_with_enum(enum) { |o| add(o) }
+ end
+ end
+
+ self
+ end
+
+ def subtract(enum)
+ do_with_enum(enum) { |o| delete(o) }
+ self
+ end
+
+ def |(enum)
+ dup.merge(enum)
+ end
+ alias + |
+ alias union |
+
+ def -(enum)
+ dup.subtract(enum)
+ end
+ alias difference -
+
+ def &(enum)
+ n = self.class.new
+ if enum.is_a?(Set)
+ if enum.size > size
+ each { |o| n.add(o) if enum.include?(o) }
+ else
+ enum.each { |o| n.add(o) if include?(o) }
+ end
+ else
+ do_with_enum(enum) { |o| n.add(o) if include?(o) }
+ end
+ n
+ end
+ alias intersection &
+
+ def ^(enum)
+ n = self.class.new(enum)
+ each { |o| n.add(o) unless n.delete?(o) }
+ n
+ end
+
+ def ==(other)
+ if self.equal?(other)
+ true
+ elsif other.instance_of?(self.class)
+ super
+ elsif other.is_a?(Set) && self.size == other.size
+ other.all? { |o| include?(o) }
+ else
+ false
+ end
+ end
+
+ def eql?(o) # :nodoc:
+ return false unless o.is_a?(Set)
+ super
+ end
+
+ def classify
+ block_given? or return enum_for(__method__) { size }
+
+ h = {}
+
+ each { |i|
+ (h[yield(i)] ||= self.class.new).add(i)
+ }
+
+ h
+ end
+
+ def join(separator=nil)
+ to_a.join(separator)
+ end
+
+ InspectKey = :__inspect_key__ # :nodoc:
+
+ # Returns a string containing a human-readable representation of the
+ # set ("#<Set: {element1, element2, ...}>").
+ def inspect
+ ids = (Thread.current[InspectKey] ||= [])
+
+ if ids.include?(object_id)
+ return sprintf('#<%s: {...}>', self.class.name)
+ end
+
+ ids << object_id
+ begin
+ return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2])
+ ensure
+ ids.pop
+ end
+ end
+
+ alias to_s inspect
+
+ def pretty_print(pp) # :nodoc:
+ pp.group(1, sprintf('#<%s:', self.class.name), '>') {
+ pp.breakable
+ pp.group(1, '{', '}') {
+ pp.seplist(self) { |o|
+ pp.pp o
+ }
+ }
+ }
+ end
+
+ def pretty_print_cycle(pp) # :nodoc:
+ pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...')
+ end
+ end
+ private_constant :SubclassCompatible
+end
diff --git a/lib/shellwords.gemspec b/lib/shellwords.gemspec
index 8d0c518ca5..b601508f94 100644
--- a/lib/shellwords.gemspec
+++ b/lib/shellwords.gemspec
@@ -25,7 +25,5 @@ Gem::Specification.new do |spec|
spec.files = Dir.chdir(srcdir) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git|Rake)}) || f == gemspec_file}
end
- spec.bindir = "exe"
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
end
diff --git a/lib/singleton.rb b/lib/singleton.rb
index b8e43a7794..74aec8903c 100644
--- a/lib/singleton.rb
+++ b/lib/singleton.rb
@@ -92,9 +92,10 @@
# p a.strip # => nil
#
module Singleton
+ # The version string
VERSION = "0.3.0"
- module SingletonInstanceMethods
+ module SingletonInstanceMethods # :nodoc:
# Raises a TypeError to prevent cloning.
def clone
raise TypeError, "can't clone instance of singleton #{self.class}"
@@ -143,11 +144,11 @@ module Singleton
end
end
- def self.module_with_class_methods
+ def self.module_with_class_methods # :nodoc:
SingletonClassMethods
end
- module SingletonClassProperties
+ module SingletonClassProperties # :nodoc:
def self.included(c)
# extending an object with Singleton is a bad idea
@@ -196,10 +197,10 @@ module Singleton
end
if defined?(Ractor)
- module RactorLocalSingleton
+ module RactorLocalSingleton # :nodoc:
include Singleton::SingletonInstanceMethods
- module RactorLocalSingletonClassMethods
+ module RactorLocalSingletonClassMethods # :nodoc:
include Singleton::SingletonClassMethods
def instance
set_mutex(Thread::Mutex.new) if Ractor.current[mutex_key].nil?
diff --git a/lib/syntax_suggest/api.rb b/lib/syntax_suggest/api.rb
index 46c9c8adac..0f82d8362a 100644
--- a/lib/syntax_suggest/api.rb
+++ b/lib/syntax_suggest/api.rb
@@ -146,11 +146,7 @@ module SyntaxSuggest
def self.valid_without?(without_lines:, code_lines:)
lines = code_lines - Array(without_lines).flatten
- if lines.empty?
- true
- else
- valid?(lines)
- end
+ lines.empty? || valid?(lines)
end
# SyntaxSuggest.invalid? [Private]
diff --git a/lib/syntax_suggest/code_line.rb b/lib/syntax_suggest/code_line.rb
index 58197e95d0..76ca892ac3 100644
--- a/lib/syntax_suggest/code_line.rb
+++ b/lib/syntax_suggest/code_line.rb
@@ -180,18 +180,17 @@ module SyntaxSuggest
# EOM
# expect(lines.first.trailing_slash?).to eq(true)
#
- if SyntaxSuggest.use_prism_parser?
- def trailing_slash?
- last = @lex.last
- last&.type == :on_tstring_end
- end
- else
- def trailing_slash?
- last = @lex.last
- return false unless last
- return false unless last.type == :on_sp
+ def trailing_slash?
+ last = @lex.last
+ # Older versions of prism diverged slightly from Ripper in compatibility mode
+ case last&.type
+ when :on_sp
last.token == TRAILING_SLASH
+ when :on_tstring_end
+ true
+ else
+ false
end
end
diff --git a/lib/syntax_suggest/version.rb b/lib/syntax_suggest/version.rb
index 1aa908f4e5..db50a1a89a 100644
--- a/lib/syntax_suggest/version.rb
+++ b/lib/syntax_suggest/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module SyntaxSuggest
- VERSION = "2.0.2"
+ VERSION = "2.0.3"
end
diff --git a/lib/tempfile.rb b/lib/tempfile.rb
index f3213c5684..cd512bb1c5 100644
--- a/lib/tempfile.rb
+++ b/lib/tempfile.rb
@@ -29,7 +29,7 @@ require 'tmpdir'
# require 'tempfile'
#
# # Tempfile.create with a block
-# # The filename are choosen automatically.
+# # The filename are chosen automatically.
# # (You can specify the prefix and suffix of the filename by an optional argument.)
# Tempfile.create {|f|
# f.puts "foo"
@@ -550,8 +550,8 @@ end
#
# Implementation note:
#
-# The keyword argument +anonymous=true+ is implemented using FILE_SHARE_DELETE on Windows.
-# O_TMPFILE is used on Linux.
+# The keyword argument <tt>anonymous=true</tt> is implemented using +FILE_SHARE_DELETE+ on Windows.
+# +O_TMPFILE+ is used on Linux.
#
# Related: Tempfile.new.
#
@@ -564,6 +564,8 @@ def Tempfile.create(basename="", tmpdir=nil, mode: 0, anonymous: false, **option
end
class << Tempfile
+# :stopdoc:
+
private def create_with_filename(basename="", tmpdir=nil, mode: 0, **options)
tmpfile = nil
Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
diff --git a/lib/time.gemspec b/lib/time.gemspec
index 4b9f9e1218..73650ab12e 100644
--- a/lib/time.gemspec
+++ b/lib/time.gemspec
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
+ spec.metadata["changelog_uri"] = "https://github.com/ruby/time/releases"
srcdir, gemspec = File.split(__FILE__)
spec.files = Dir.chdir(srcdir) do
diff --git a/lib/time.rb b/lib/time.rb
index a0fc7dfbf5..e6aab3fa5d 100644
--- a/lib/time.rb
+++ b/lib/time.rb
@@ -27,7 +27,7 @@ require 'date'
# #
class Time
- VERSION = "0.4.1" # :nodoc:
+ VERSION = "0.4.2" # :nodoc:
class << Time
@@ -80,7 +80,7 @@ class Time
#
# You must require 'time' to use this method.
#
- def zone_offset(zone, year=self.now.year)
+ def zone_offset(zone, year=nil)
off = nil
zone = zone.upcase
if /\A([+-])(\d\d)(:?)(\d\d)(?:\3(\d\d))?\z/ =~ zone
@@ -89,10 +89,13 @@ class Time
off = zone.to_i * 3600
elsif ZoneOffset.include?(zone)
off = ZoneOffset[zone] * 3600
- elsif ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false)
- off = t.utc_offset
- elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false)
- off = t.utc_offset
+ else
+ year ||= self.now.year
+ if ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false)
+ off = t.utc_offset
+ elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false)
+ off = t.utc_offset
+ end
end
off
end
diff --git a/lib/timeout.rb b/lib/timeout.rb
index 4fd1fa46da..e293e3be7c 100644
--- a/lib/timeout.rb
+++ b/lib/timeout.rb
@@ -20,9 +20,9 @@
module Timeout
# The version
- VERSION = "0.4.3"
+ VERSION = "0.6.0"
- # Internal error raised to when a timeout is triggered.
+ # Internal exception raised to when a timeout is triggered.
class ExitException < Exception
def exception(*) # :nodoc:
self
@@ -44,12 +44,101 @@ module Timeout
end
# :stopdoc:
- CONDVAR = ConditionVariable.new
- QUEUE = Queue.new
- QUEUE_MUTEX = Mutex.new
- TIMEOUT_THREAD_MUTEX = Mutex.new
- @timeout_thread = nil
- private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX
+
+ # We keep a private reference so that time mocking libraries won't break Timeout.
+ GET_TIME = Process.method(:clock_gettime)
+ if defined?(Ractor.make_shareable)
+ # Ractor.make_shareable(Method) only works on Ruby 4+
+ Ractor.make_shareable(GET_TIME) rescue nil
+ end
+ private_constant :GET_TIME
+
+ class State
+ def initialize
+ @condvar = ConditionVariable.new
+ @queue = Queue.new
+ @queue_mutex = Mutex.new
+
+ @timeout_thread = nil
+ @timeout_thread_mutex = Mutex.new
+ end
+
+ if defined?(Ractor.store_if_absent) && defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME)
+ # Ractor support if
+ # 1. Ractor.store_if_absent is available
+ # 2. Method object can be shareable (4.0~)
+ def self.instance
+ Ractor.store_if_absent :timeout_gem_state do
+ State.new
+ end
+ end
+ else
+ GLOBAL_STATE = State.new
+
+ def self.instance
+ GLOBAL_STATE
+ end
+ end
+
+ def create_timeout_thread
+ # Threads unexpectedly inherit the interrupt mask: https://github.com/ruby/timeout/issues/41
+ # So reset the interrupt mask to the default one for the timeout thread
+ Thread.handle_interrupt(Object => :immediate) do
+ watcher = Thread.new do
+ requests = []
+ while true
+ until @queue.empty? and !requests.empty? # wait to have at least one request
+ req = @queue.pop
+ requests << req unless req.done?
+ end
+ closest_deadline = requests.min_by(&:deadline).deadline
+
+ now = 0.0
+ @queue_mutex.synchronize do
+ while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty?
+ @condvar.wait(@queue_mutex, closest_deadline - now)
+ end
+ end
+
+ requests.each do |req|
+ req.interrupt if req.expired?(now)
+ end
+ requests.reject!(&:done?)
+ end
+ end
+
+ if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?)
+ ThreadGroup::Default.add(watcher)
+ end
+
+ watcher.name = "Timeout stdlib thread"
+ watcher.thread_variable_set(:"\0__detached_thread__", true)
+ watcher
+ end
+ end
+
+ def ensure_timeout_thread_created
+ unless @timeout_thread&.alive?
+ # If the Mutex is already owned we are in a signal handler.
+ # In that case, just return and let the main thread create the Timeout thread.
+ return if @timeout_thread_mutex.owned?
+
+ Sync.synchronize @timeout_thread_mutex do
+ unless @timeout_thread&.alive?
+ @timeout_thread = create_timeout_thread
+ end
+ end
+ end
+ end
+
+ def add_request(request)
+ Sync.synchronize @queue_mutex do
+ @queue << request
+ @condvar.signal
+ end
+ end
+ end
+ private_constant :State
class Request
attr_reader :deadline
@@ -64,6 +153,7 @@ module Timeout
@done = false # protected by @mutex
end
+ # Only called by the timeout thread, so does not need Sync.synchronize
def done?
@mutex.synchronize do
@done
@@ -74,6 +164,7 @@ module Timeout
now >= @deadline
end
+ # Only called by the timeout thread, so does not need Sync.synchronize
def interrupt
@mutex.synchronize do
unless @done
@@ -84,61 +175,36 @@ module Timeout
end
def finished
- @mutex.synchronize do
+ Sync.synchronize @mutex do
@done = true
end
end
end
private_constant :Request
- def self.create_timeout_thread
- watcher = Thread.new do
- requests = []
- while true
- until QUEUE.empty? and !requests.empty? # wait to have at least one request
- req = QUEUE.pop
- requests << req unless req.done?
- end
- closest_deadline = requests.min_by(&:deadline).deadline
-
- now = 0.0
- QUEUE_MUTEX.synchronize do
- while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty?
- CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now)
- end
- end
-
- requests.each do |req|
- req.interrupt if req.expired?(now)
- end
- requests.reject!(&:done?)
- end
- end
- ThreadGroup::Default.add(watcher) unless watcher.group.enclosed?
- watcher.name = "Timeout stdlib thread"
- watcher.thread_variable_set(:"\0__detached_thread__", true)
- watcher
- end
- private_class_method :create_timeout_thread
-
- def self.ensure_timeout_thread_created
- unless @timeout_thread and @timeout_thread.alive?
- TIMEOUT_THREAD_MUTEX.synchronize do
- unless @timeout_thread and @timeout_thread.alive?
- @timeout_thread = create_timeout_thread
- end
+ module Sync
+ # Calls mutex.synchronize(&block) but if that fails on CRuby due to being in a trap handler,
+ # run mutex.synchronize(&block) in a separate Thread instead.
+ def self.synchronize(mutex, &block)
+ begin
+ mutex.synchronize(&block)
+ rescue ThreadError => e
+ raise e unless e.message == "can't be called from trap context"
+ # Workaround CRuby issue https://bugs.ruby-lang.org/issues/19473
+ # which raises on Mutex#synchronize in trap handler.
+ # It's expensive to create a Thread just for this,
+ # but better than failing.
+ Thread.new {
+ mutex.synchronize(&block)
+ }.join
end
end
end
-
- # We keep a private reference so that time mocking libraries won't break
- # Timeout.
- GET_TIME = Process.method(:clock_gettime)
- private_constant :GET_TIME
+ private_constant :Sync
# :startdoc:
- # Perform an operation in a block, raising an error if it takes longer than
+ # Perform an operation in a block, raising an exception if it takes longer than
# +sec+ seconds to complete.
#
# +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number
@@ -146,25 +212,70 @@ module Timeout
# value of 0 or +nil+ will execute the block without any timeout.
# Any negative number will raise an ArgumentError.
# +klass+:: Exception Class to raise if the block fails to terminate
- # in +sec+ seconds. Omitting will use the default, Timeout::Error
+ # in +sec+ seconds. Omitting will use the default, Timeout::Error.
# +message+:: Error message to raise with Exception Class.
- # Omitting will use the default, "execution expired"
+ # Omitting will use the default, <tt>"execution expired"</tt>.
#
# Returns the result of the block *if* the block completed before
- # +sec+ seconds, otherwise throws an exception, based on the value of +klass+.
+ # +sec+ seconds, otherwise raises an exception, based on the value of +klass+.
#
- # The exception thrown to terminate the given block cannot be rescued inside
- # the block unless +klass+ is given explicitly. However, the block can use
- # ensure to prevent the handling of the exception. For that reason, this
- # method cannot be relied on to enforce timeouts for untrusted blocks.
+ # The exception raised to terminate the given block is the given +klass+, or
+ # Timeout::ExitException if +klass+ is not given. The reason for that behavior
+ # is that Timeout::Error inherits from RuntimeError and might be caught unexpectedly by +rescue+.
+ # Timeout::ExitException inherits from Exception so it will only be rescued by <tt>rescue Exception</tt>.
+ # Note that the Timeout::ExitException is translated to a Timeout::Error once it reaches the Timeout.timeout call,
+ # so outside that call it will be a Timeout::Error.
+ #
+ # In general, be aware that the code block may rescue the exception, and in such a case not respect the timeout.
+ # Also, the block can use +ensure+ to prevent the handling of the exception.
+ # For those reasons, this method cannot be relied on to enforce timeouts for untrusted blocks.
#
# If a scheduler is defined, it will be used to handle the timeout by invoking
- # Scheduler#timeout_after.
+ # Fiber::Scheduler#timeout_after.
#
# Note that this is both a method of module Timeout, so you can <tt>include
# Timeout</tt> into your classes so they have a #timeout method, as well as
# a module method, so you can call it directly as Timeout.timeout().
- def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
+ #
+ # ==== Ensuring the exception does not fire inside ensure blocks
+ #
+ # When using Timeout.timeout, it can be desirable to ensure the timeout exception does not fire inside an +ensure+ block.
+ # The simplest and best way to do so is to put the Timeout.timeout call inside the body of the +begin+/+ensure+/+end+:
+ #
+ # begin
+ # Timeout.timeout(sec) { some_long_operation }
+ # ensure
+ # cleanup # safe, cannot be interrupted by timeout
+ # end
+ #
+ # If that is not feasible, e.g. if there are +ensure+ blocks inside +some_long_operation+,
+ # they need to not be interrupted by timeout, and it's not possible to move these ensure blocks outside,
+ # one can use Thread.handle_interrupt to delay the timeout exception like so:
+ #
+ # Thread.handle_interrupt(Timeout::Error => :never) {
+ # Timeout.timeout(sec, Timeout::Error) do
+ # setup # timeout cannot happen here, no matter how long it takes
+ # Thread.handle_interrupt(Timeout::Error => :immediate) {
+ # some_long_operation # timeout can happen here
+ # }
+ # ensure
+ # cleanup # timeout cannot happen here, no matter how long it takes
+ # end
+ # }
+ #
+ # An important thing to note is the need to pass an exception +klass+ to Timeout.timeout,
+ # otherwise it does not work. Specifically, using <tt>Thread.handle_interrupt(Timeout::ExitException => ...)</tt>
+ # is unsupported and causes subtle errors like raising the wrong exception outside the block, do not use that.
+ #
+ # Note that Thread.handle_interrupt is somewhat dangerous because if setup or cleanup hangs
+ # then the current thread will hang too and the timeout will never fire.
+ # Also note the block might run for longer than +sec+ seconds:
+ # e.g. +some_long_operation+ executes for +sec+ seconds + whatever time cleanup takes.
+ #
+ # If you want the timeout to only happen on blocking operations, one can use +:on_blocking+
+ # instead of +:immediate+. However, that means if the block uses no blocking operations after +sec+ seconds,
+ # the block will not be interrupted.
+ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
return yield(sec) if sec == nil or sec.zero?
raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec
@@ -174,13 +285,12 @@ module Timeout
return scheduler.timeout_after(sec, klass || Error, message, &block)
end
- Timeout.ensure_timeout_thread_created
+ state = State.instance
+ state.ensure_timeout_thread_created
+
perform = Proc.new do |exc|
request = Request.new(Thread.current, sec, exc, message)
- QUEUE_MUTEX.synchronize do
- QUEUE << request
- CONDVAR.signal
- end
+ state.add_request(request)
begin
return yield(sec)
ensure
@@ -194,5 +304,8 @@ module Timeout
Error.handle_timeout(message, &perform)
end
end
- module_function :timeout
+
+ private def timeout(*args, &block)
+ Timeout.timeout(*args, &block)
+ end
end
diff --git a/lib/tsort.gemspec b/lib/tsort.gemspec
deleted file mode 100644
index 0e2f110a53..0000000000
--- a/lib/tsort.gemspec
+++ /dev/null
@@ -1,29 +0,0 @@
-name = File.basename(__FILE__, ".gemspec")
-version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir|
- break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
- /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
- end rescue nil
-end
-
-Gem::Specification.new do |spec|
- spec.name = name
- spec.version = version
- spec.authors = ["Tanaka Akira"]
- spec.email = ["akr@fsij.org"]
-
- spec.summary = %q{Topological sorting using Tarjan's algorithm}
- spec.description = %q{Topological sorting using Tarjan's algorithm}
- spec.homepage = "https://github.com/ruby/tsort"
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
- spec.licenses = ["Ruby", "BSD-2-Clause"]
-
- spec.metadata["homepage_uri"] = spec.homepage
- spec.metadata["source_code_uri"] = spec.homepage
-
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
- end
- spec.bindir = "exe"
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
- spec.require_paths = ["lib"]
-end
diff --git a/lib/tsort.rb b/lib/tsort.rb
deleted file mode 100644
index dbaed45415..0000000000
--- a/lib/tsort.rb
+++ /dev/null
@@ -1,455 +0,0 @@
-# frozen_string_literal: true
-
-#--
-# tsort.rb - provides a module for topological sorting and strongly connected components.
-#++
-#
-
-#
-# TSort implements topological sorting using Tarjan's algorithm for
-# strongly connected components.
-#
-# TSort is designed to be able to be used with any object which can be
-# interpreted as a directed graph.
-#
-# TSort requires two methods to interpret an object as a graph,
-# tsort_each_node and tsort_each_child.
-#
-# * tsort_each_node is used to iterate for all nodes over a graph.
-# * tsort_each_child is used to iterate for child nodes of a given node.
-#
-# The equality of nodes are defined by eql? and hash since
-# TSort uses Hash internally.
-#
-# == A Simple Example
-#
-# The following example demonstrates how to mix the TSort module into an
-# existing class (in this case, Hash). Here, we're treating each key in
-# the hash as a node in the graph, and so we simply alias the required
-# #tsort_each_node method to Hash's #each_key method. For each key in the
-# hash, the associated value is an array of the node's child nodes. This
-# choice in turn leads to our implementation of the required #tsort_each_child
-# method, which fetches the array of child nodes and then iterates over that
-# array using the user-supplied block.
-#
-# require 'tsort'
-#
-# class Hash
-# include TSort
-# alias tsort_each_node each_key
-# def tsort_each_child(node, &block)
-# fetch(node).each(&block)
-# end
-# end
-#
-# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort
-# #=> [3, 2, 1, 4]
-#
-# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components
-# #=> [[4], [2, 3], [1]]
-#
-# == A More Realistic Example
-#
-# A very simple `make' like tool can be implemented as follows:
-#
-# require 'tsort'
-#
-# class Make
-# def initialize
-# @dep = {}
-# @dep.default = []
-# end
-#
-# def rule(outputs, inputs=[], &block)
-# triple = [outputs, inputs, block]
-# outputs.each {|f| @dep[f] = [triple]}
-# @dep[triple] = inputs
-# end
-#
-# def build(target)
-# each_strongly_connected_component_from(target) {|ns|
-# if ns.length != 1
-# fs = ns.delete_if {|n| Array === n}
-# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}")
-# end
-# n = ns.first
-# if Array === n
-# outputs, inputs, block = n
-# inputs_time = inputs.map {|f| File.mtime f}.max
-# begin
-# outputs_time = outputs.map {|f| File.mtime f}.min
-# rescue Errno::ENOENT
-# outputs_time = nil
-# end
-# if outputs_time == nil ||
-# inputs_time != nil && outputs_time <= inputs_time
-# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i
-# block.call
-# end
-# end
-# }
-# end
-#
-# def tsort_each_child(node, &block)
-# @dep[node].each(&block)
-# end
-# include TSort
-# end
-#
-# def command(arg)
-# print arg, "\n"
-# system arg
-# end
-#
-# m = Make.new
-# m.rule(%w[t1]) { command 'date > t1' }
-# m.rule(%w[t2]) { command 'date > t2' }
-# m.rule(%w[t3]) { command 'date > t3' }
-# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' }
-# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' }
-# m.build('t5')
-#
-# == Bugs
-#
-# * 'tsort.rb' is wrong name because this library uses
-# Tarjan's algorithm for strongly connected components.
-# Although 'strongly_connected_components.rb' is correct but too long.
-#
-# == References
-#
-# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms",
-# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972.
-#
-
-module TSort
-
- VERSION = "0.2.0"
-
- class Cyclic < StandardError
- end
-
- # Returns a topologically sorted array of nodes.
- # The array is sorted from children to parents, i.e.
- # the first element has no child and the last node has no parent.
- #
- # If there is a cycle, TSort::Cyclic is raised.
- #
- # class G
- # include TSort
- # def initialize(g)
- # @g = g
- # end
- # def tsort_each_child(n, &b) @g[n].each(&b) end
- # def tsort_each_node(&b) @g.each_key(&b) end
- # end
- #
- # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
- # p graph.tsort #=> [4, 2, 3, 1]
- #
- # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
- # p graph.tsort # raises TSort::Cyclic
- #
- def tsort
- each_node = method(:tsort_each_node)
- each_child = method(:tsort_each_child)
- TSort.tsort(each_node, each_child)
- end
-
- # Returns a topologically sorted array of nodes.
- # The array is sorted from children to parents, i.e.
- # the first element has no child and the last node has no parent.
- #
- # The graph is represented by _each_node_ and _each_child_.
- # _each_node_ should have +call+ method which yields for each node in the graph.
- # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
- #
- # If there is a cycle, TSort::Cyclic is raised.
- #
- # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- # each_node = lambda {|&b| g.each_key(&b) }
- # each_child = lambda {|n, &b| g[n].each(&b) }
- # p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1]
- #
- # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- # each_node = lambda {|&b| g.each_key(&b) }
- # each_child = lambda {|n, &b| g[n].each(&b) }
- # p TSort.tsort(each_node, each_child) # raises TSort::Cyclic
- #
- def self.tsort(each_node, each_child)
- tsort_each(each_node, each_child).to_a
- end
-
- # The iterator version of the #tsort method.
- # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but
- # modification of _obj_ during the iteration may lead to unexpected results.
- #
- # #tsort_each returns +nil+.
- # If there is a cycle, TSort::Cyclic is raised.
- #
- # class G
- # include TSort
- # def initialize(g)
- # @g = g
- # end
- # def tsort_each_child(n, &b) @g[n].each(&b) end
- # def tsort_each_node(&b) @g.each_key(&b) end
- # end
- #
- # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
- # graph.tsort_each {|n| p n }
- # #=> 4
- # # 2
- # # 3
- # # 1
- #
- def tsort_each(&block) # :yields: node
- each_node = method(:tsort_each_node)
- each_child = method(:tsort_each_child)
- TSort.tsort_each(each_node, each_child, &block)
- end
-
- # The iterator version of the TSort.tsort method.
- #
- # The graph is represented by _each_node_ and _each_child_.
- # _each_node_ should have +call+ method which yields for each node in the graph.
- # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
- #
- # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- # each_node = lambda {|&b| g.each_key(&b) }
- # each_child = lambda {|n, &b| g[n].each(&b) }
- # TSort.tsort_each(each_node, each_child) {|n| p n }
- # #=> 4
- # # 2
- # # 3
- # # 1
- #
- def self.tsort_each(each_node, each_child) # :yields: node
- return to_enum(__method__, each_node, each_child) unless block_given?
-
- each_strongly_connected_component(each_node, each_child) {|component|
- if component.size == 1
- yield component.first
- else
- raise Cyclic.new("topological sort failed: #{component.inspect}")
- end
- }
- end
-
- # Returns strongly connected components as an array of arrays of nodes.
- # The array is sorted from children to parents.
- # Each elements of the array represents a strongly connected component.
- #
- # class G
- # include TSort
- # def initialize(g)
- # @g = g
- # end
- # def tsort_each_child(n, &b) @g[n].each(&b) end
- # def tsort_each_node(&b) @g.each_key(&b) end
- # end
- #
- # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
- # p graph.strongly_connected_components #=> [[4], [2], [3], [1]]
- #
- # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
- # p graph.strongly_connected_components #=> [[4], [2, 3], [1]]
- #
- def strongly_connected_components
- each_node = method(:tsort_each_node)
- each_child = method(:tsort_each_child)
- TSort.strongly_connected_components(each_node, each_child)
- end
-
- # Returns strongly connected components as an array of arrays of nodes.
- # The array is sorted from children to parents.
- # Each elements of the array represents a strongly connected component.
- #
- # The graph is represented by _each_node_ and _each_child_.
- # _each_node_ should have +call+ method which yields for each node in the graph.
- # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
- #
- # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- # each_node = lambda {|&b| g.each_key(&b) }
- # each_child = lambda {|n, &b| g[n].each(&b) }
- # p TSort.strongly_connected_components(each_node, each_child)
- # #=> [[4], [2], [3], [1]]
- #
- # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- # each_node = lambda {|&b| g.each_key(&b) }
- # each_child = lambda {|n, &b| g[n].each(&b) }
- # p TSort.strongly_connected_components(each_node, each_child)
- # #=> [[4], [2, 3], [1]]
- #
- def self.strongly_connected_components(each_node, each_child)
- each_strongly_connected_component(each_node, each_child).to_a
- end
-
- # The iterator version of the #strongly_connected_components method.
- # <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to
- # <tt><em>obj</em>.strongly_connected_components.each</tt>, but
- # modification of _obj_ during the iteration may lead to unexpected results.
- #
- # #each_strongly_connected_component returns +nil+.
- #
- # class G
- # include TSort
- # def initialize(g)
- # @g = g
- # end
- # def tsort_each_child(n, &b) @g[n].each(&b) end
- # def tsort_each_node(&b) @g.each_key(&b) end
- # end
- #
- # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
- # graph.each_strongly_connected_component {|scc| p scc }
- # #=> [4]
- # # [2]
- # # [3]
- # # [1]
- #
- # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
- # graph.each_strongly_connected_component {|scc| p scc }
- # #=> [4]
- # # [2, 3]
- # # [1]
- #
- def each_strongly_connected_component(&block) # :yields: nodes
- each_node = method(:tsort_each_node)
- each_child = method(:tsort_each_child)
- TSort.each_strongly_connected_component(each_node, each_child, &block)
- end
-
- # The iterator version of the TSort.strongly_connected_components method.
- #
- # The graph is represented by _each_node_ and _each_child_.
- # _each_node_ should have +call+ method which yields for each node in the graph.
- # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
- #
- # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- # each_node = lambda {|&b| g.each_key(&b) }
- # each_child = lambda {|n, &b| g[n].each(&b) }
- # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
- # #=> [4]
- # # [2]
- # # [3]
- # # [1]
- #
- # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- # each_node = lambda {|&b| g.each_key(&b) }
- # each_child = lambda {|n, &b| g[n].each(&b) }
- # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
- # #=> [4]
- # # [2, 3]
- # # [1]
- #
- def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes
- return to_enum(__method__, each_node, each_child) unless block_given?
-
- id_map = {}
- stack = []
- each_node.call {|node|
- unless id_map.include? node
- each_strongly_connected_component_from(node, each_child, id_map, stack) {|c|
- yield c
- }
- end
- }
- nil
- end
-
- # Iterates over strongly connected component in the subgraph reachable from
- # _node_.
- #
- # Return value is unspecified.
- #
- # #each_strongly_connected_component_from doesn't call #tsort_each_node.
- #
- # class G
- # include TSort
- # def initialize(g)
- # @g = g
- # end
- # def tsort_each_child(n, &b) @g[n].each(&b) end
- # def tsort_each_node(&b) @g.each_key(&b) end
- # end
- #
- # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
- # graph.each_strongly_connected_component_from(2) {|scc| p scc }
- # #=> [4]
- # # [2]
- #
- # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
- # graph.each_strongly_connected_component_from(2) {|scc| p scc }
- # #=> [4]
- # # [2, 3]
- #
- def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes
- TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block)
- end
-
- # Iterates over strongly connected components in a graph.
- # The graph is represented by _node_ and _each_child_.
- #
- # _node_ is the first node.
- # _each_child_ should have +call+ method which takes a node argument
- # and yields for each child node.
- #
- # Return value is unspecified.
- #
- # #TSort.each_strongly_connected_component_from is a class method and
- # it doesn't need a class to represent a graph which includes TSort.
- #
- # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- # each_child = lambda {|n, &b| graph[n].each(&b) }
- # TSort.each_strongly_connected_component_from(1, each_child) {|scc|
- # p scc
- # }
- # #=> [4]
- # # [2, 3]
- # # [1]
- #
- def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes
- return to_enum(__method__, node, each_child, id_map, stack) unless block_given?
-
- minimum_id = node_id = id_map[node] = id_map.size
- stack_length = stack.length
- stack << node
-
- each_child.call(node) {|child|
- if id_map.include? child
- child_id = id_map[child]
- minimum_id = child_id if child_id && child_id < minimum_id
- else
- sub_minimum_id =
- each_strongly_connected_component_from(child, each_child, id_map, stack) {|c|
- yield c
- }
- minimum_id = sub_minimum_id if sub_minimum_id < minimum_id
- end
- }
-
- if node_id == minimum_id
- component = stack.slice!(stack_length .. -1)
- component.each {|n| id_map[n] = nil}
- yield component
- end
-
- minimum_id
- end
-
- # Should be implemented by a extended class.
- #
- # #tsort_each_node is used to iterate for all nodes over a graph.
- #
- def tsort_each_node # :yields: node
- raise NotImplementedError.new
- end
-
- # Should be implemented by a extended class.
- #
- # #tsort_each_child is used to iterate for child nodes of _node_.
- #
- def tsort_each_child(node) # :yields: child
- raise NotImplementedError.new
- end
-end
diff --git a/lib/unicode_normalize/normalize.rb b/lib/unicode_normalize/normalize.rb
index e67fad187a..0447df8de7 100644
--- a/lib/unicode_normalize/normalize.rb
+++ b/lib/unicode_normalize/normalize.rb
@@ -82,16 +82,22 @@ module UnicodeNormalize # :nodoc:
## Canonical Ordering
def self.canonical_ordering_one(string)
- sorting = string.each_char.collect { |c| [c, CLASS_TABLE[c]] }
- (sorting.length-2).downto(0) do |i| # almost, but not exactly bubble sort
- (0..i).each do |j|
- later_class = sorting[j+1].last
- if 0<later_class and later_class<sorting[j].last
- sorting[j], sorting[j+1] = sorting[j+1], sorting[j]
- end
+ result = ''
+ unordered = []
+ chars = string.chars
+ n = chars.size
+ chars.each_with_index do |char, i|
+ ccc = CLASS_TABLE[char]
+ if ccc == 0
+ unordered.sort!.each { result << chars[it % n] }
+ unordered.clear
+ result << char
+ else
+ unordered << ccc * n + i
end
end
- return sorting.collect(&:first).join('')
+ unordered.sort!.each { result << chars[it % n] }
+ result
end
## Normalization Forms for Patterns (not whole Strings)
@@ -105,17 +111,22 @@ module UnicodeNormalize # :nodoc:
start = nfd_string[0]
last_class = CLASS_TABLE[start]-1
accents = ''
+ result = ''
nfd_string[1..-1].each_char do |accent|
accent_class = CLASS_TABLE[accent]
if last_class<accent_class and composite = COMPOSITION_TABLE[start+accent]
start = composite
+ elsif accent_class == 0
+ result << start << accents
+ start = accent
+ accents = ''
+ last_class = -1
else
accents << accent
last_class = accent_class
end
end
- accents = nfc_one(accents) if accents.length>1 # TODO: change from recursion to loop
- hangul_comp_one(start+accents)
+ hangul_comp_one(result+start+accents)
end
def self.normalize(string, form = :nfc)
diff --git a/lib/unicode_normalize/tables.rb b/lib/unicode_normalize/tables.rb
index b5b708defd..dd5d3499b8 100644
--- a/lib/unicode_normalize/tables.rb
+++ b/lib/unicode_normalize/tables.rb
@@ -1,8 +1,8 @@
# coding: us-ascii
# frozen_string_literal: true
-Encoding::UNICODE_VERSION == "16.0.0" or
- raise "Unicode version mismatch: 16.0.0 expected but #{Encoding::UNICODE_VERSION}"
+Encoding::UNICODE_VERSION == "17.0.0" or
+ raise "Unicode version mismatch: 17.0.0 expected but #{Encoding::UNICODE_VERSION}"
# automatically generated by template/unicode_norm_gen.tmpl
@@ -99,7 +99,8 @@ module UnicodeNormalize # :nodoc:
"\u1A75-\u1A7C" \
"\u1A7F" \
"\u1AB0-\u1ABD" \
- "\u1ABF-\u1ACE" \
+ "\u1ABF-\u1ADD" \
+ "\u1AE0-\u1AEB" \
"\u1B34\u1B35" \
"\u1B44" \
"\u1B6B-\u1B73" \
@@ -154,6 +155,7 @@ module UnicodeNormalize # :nodoc:
"\u{10D24}-\u{10D27}" \
"\u{10D69}-\u{10D6D}" \
"\u{10EAB}\u{10EAC}" \
+ "\u{10EFA}\u{10EFB}" \
"\u{10EFD}-\u{10EFF}" \
"\u{10F46}-\u{10F50}" \
"\u{10F82}-\u{10F85}" \
@@ -230,6 +232,10 @@ module UnicodeNormalize # :nodoc:
"\u{1E2EC}-\u{1E2EF}" \
"\u{1E4EC}-\u{1E4EF}" \
"\u{1E5EE}\u{1E5EF}" \
+ "\u{1E6E3}" \
+ "\u{1E6E6}" \
+ "\u{1E6EE}\u{1E6EF}" \
+ "\u{1E6F5}" \
"\u{1E8D0}-\u{1E8D6}" \
"\u{1E944}-\u{1E94A}" \
"]"
@@ -1460,7 +1466,7 @@ module UnicodeNormalize # :nodoc:
"\u3280-\u33FF" \
"\uA69C\uA69D" \
"\uA770" \
- "\uA7F2-\uA7F4" \
+ "\uA7F1-\uA7F4" \
"\uA7F8\uA7F9" \
"\uAB5C-\uAB5F" \
"\uAB69" \
@@ -2019,6 +2025,33 @@ module UnicodeNormalize # :nodoc:
"\u1ACC"=>230,
"\u1ACD"=>230,
"\u1ACE"=>230,
+ "\u1ACF"=>230,
+ "\u1AD0"=>230,
+ "\u1AD1"=>230,
+ "\u1AD2"=>230,
+ "\u1AD3"=>230,
+ "\u1AD4"=>230,
+ "\u1AD5"=>230,
+ "\u1AD6"=>230,
+ "\u1AD7"=>230,
+ "\u1AD8"=>230,
+ "\u1AD9"=>230,
+ "\u1ADA"=>230,
+ "\u1ADB"=>230,
+ "\u1ADC"=>230,
+ "\u1ADD"=>220,
+ "\u1AE0"=>230,
+ "\u1AE1"=>230,
+ "\u1AE2"=>230,
+ "\u1AE3"=>230,
+ "\u1AE4"=>230,
+ "\u1AE5"=>230,
+ "\u1AE6"=>220,
+ "\u1AE7"=>230,
+ "\u1AE8"=>230,
+ "\u1AE9"=>230,
+ "\u1AEA"=>230,
+ "\u1AEB"=>234,
"\u1B34"=>7,
"\u1B44"=>9,
"\u1B6B"=>230,
@@ -2293,6 +2326,8 @@ module UnicodeNormalize # :nodoc:
"\u{10D6D}"=>230,
"\u{10EAB}"=>230,
"\u{10EAC}"=>230,
+ "\u{10EFA}"=>220,
+ "\u{10EFB}"=>220,
"\u{10EFD}"=>220,
"\u{10EFE}"=>220,
"\u{10EFF}"=>220,
@@ -2479,6 +2514,11 @@ module UnicodeNormalize # :nodoc:
"\u{1E4EF}"=>230,
"\u{1E5EE}"=>230,
"\u{1E5EF}"=>220,
+ "\u{1E6E3}"=>230,
+ "\u{1E6E6}"=>230,
+ "\u{1E6EE}"=>230,
+ "\u{1E6EF}"=>230,
+ "\u{1E6F5}"=>230,
"\u{1E8D0}"=>220,
"\u{1E8D1}"=>220,
"\u{1E8D2}"=>220,
@@ -5922,6 +5962,7 @@ module UnicodeNormalize # :nodoc:
"\uA69C"=>"\u044A",
"\uA69D"=>"\u044C",
"\uA770"=>"\uA76F",
+ "\uA7F1"=>"S",
"\uA7F2"=>"C",
"\uA7F3"=>"F",
"\uA7F4"=>"Q",
diff --git a/lib/uri/common.rb b/lib/uri/common.rb
index 1115736297..0b3bb4f099 100644
--- a/lib/uri/common.rb
+++ b/lib/uri/common.rb
@@ -30,6 +30,9 @@ module URI
remove_const(:Parser) if defined?(::URI::Parser)
const_set("Parser", parser.class)
+ remove_const(:PARSER) if defined?(::URI::PARSER)
+ const_set("PARSER", parser)
+
remove_const(:REGEXP) if defined?(::URI::REGEXP)
remove_const(:PATTERN) if defined?(::URI::PATTERN)
if Parser == RFC2396_Parser
@@ -92,6 +95,40 @@ module URI
end
module Schemes # :nodoc:
+ class << self
+ ReservedChars = ".+-"
+ EscapedChars = "\u01C0\u01C1\u01C2"
+ # Use Lo category chars as escaped chars for TruffleRuby, which
+ # does not allow Symbol categories as identifiers.
+
+ def escape(name)
+ unless name and name.ascii_only?
+ return nil
+ end
+ name.upcase.tr(ReservedChars, EscapedChars)
+ end
+
+ def unescape(name)
+ name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase
+ end
+
+ def find(name)
+ const_get(name, false) if name and const_defined?(name, false)
+ end
+
+ def register(name, klass)
+ unless scheme = escape(name)
+ raise ArgumentError, "invalid character as scheme - #{name}"
+ end
+ const_set(scheme, klass)
+ end
+
+ def list
+ constants.map { |name|
+ [unescape(name.to_s), const_get(name)]
+ }.to_h
+ end
+ end
end
private_constant :Schemes
@@ -104,7 +141,7 @@ module URI
# Note that after calling String#upcase on +scheme+, it must be a valid
# constant name.
def self.register_scheme(scheme, klass)
- Schemes.const_set(scheme.to_s.upcase, klass)
+ Schemes.register(scheme, klass)
end
# Returns a hash of the defined schemes:
@@ -122,14 +159,14 @@ module URI
#
# Related: URI.register_scheme.
def self.scheme_list
- Schemes.constants.map { |name|
- [name.to_s.upcase, Schemes.const_get(name)]
- }.to_h
+ Schemes.list
end
+ # :stopdoc:
INITIAL_SCHEMES = scheme_list
private_constant :INITIAL_SCHEMES
Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
+ # :startdoc:
# Returns a new object constructed from the given +scheme+, +arguments+,
# and +default+:
@@ -148,12 +185,10 @@ module URI
# # => #<URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
def self.for(scheme, *arguments, default: Generic)
- const_name = scheme.to_s.upcase
+ const_name = Schemes.escape(scheme)
uri_class = INITIAL_SCHEMES[const_name]
- uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
- Schemes.const_get(const_name, false)
- end
+ uri_class ||= Schemes.find(const_name)
uri_class ||= default
return uri_class.new(scheme, *arguments)
@@ -195,7 +230,7 @@ module URI
# ["fragment", "top"]]
#
def self.split(uri)
- DEFAULT_PARSER.split(uri)
+ PARSER.split(uri)
end
# Returns a new \URI object constructed from the given string +uri+:
@@ -205,11 +240,11 @@ module URI
# URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# # => #<URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
- # It's recommended to first ::escape string +uri+
+ # It's recommended to first URI::RFC2396_PARSER.escape string +uri+
# if it may contain invalid URI characters.
#
def self.parse(uri)
- DEFAULT_PARSER.parse(uri)
+ PARSER.parse(uri)
end
# Merges the given URI strings +str+
@@ -265,7 +300,7 @@ module URI
#
def self.extract(str, schemes = nil, &block) # :nodoc:
warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE
- DEFAULT_PARSER.extract(str, schemes, &block)
+ PARSER.extract(str, schemes, &block)
end
#
@@ -302,7 +337,7 @@ module URI
#
def self.regexp(schemes = nil)# :nodoc:
warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE
- DEFAULT_PARSER.make_regexp(schemes)
+ PARSER.make_regexp(schemes)
end
TBLENCWWWCOMP_ = {} # :nodoc:
@@ -407,6 +442,8 @@ module URI
_decode_uri_component(/%\h\h/, str, enc)
end
+ # Returns a string derived from the given string +str+ with
+ # URI-encoded characters matching +regexp+ according to +table+.
def self._encode_uri_component(regexp, table, str, enc)
str = str.to_s.dup
if str.encoding != Encoding::ASCII_8BIT
@@ -421,6 +458,8 @@ module URI
end
private_class_method :_encode_uri_component
+ # Returns a string decoding characters matching +regexp+ from the
+ # given \URL-encoded string +str+.
def self._decode_uri_component(regexp, str, enc)
raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
@@ -859,6 +898,7 @@ module Kernel
# Returns a \URI object derived from the given +uri+,
# which may be a \URI string or an existing \URI object:
#
+ # require 'uri'
# # Returns a new URI.
# uri = URI('http://github.com/ruby/ruby')
# # => #<URI::HTTP http://github.com/ruby/ruby>
@@ -866,6 +906,8 @@ module Kernel
# URI(uri)
# # => #<URI::HTTP http://github.com/ruby/ruby>
#
+ # You must require 'uri' to use this method.
+ #
def URI(uri)
if uri.is_a?(URI::Generic)
uri
diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb
index 07f329e3d1..6a0f638d76 100644
--- a/lib/uri/generic.rb
+++ b/lib/uri/generic.rb
@@ -126,9 +126,9 @@ module URI
end
end
else
- component = self.class.component rescue ::URI::Generic::COMPONENT
+ component = self.component rescue ::URI::Generic::COMPONENT
raise ArgumentError,
- "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})"
+ "expected Array of or Hash of components of #{self} (#{component.join(', ')})"
end
tmp << nil
@@ -186,18 +186,18 @@ module URI
if arg_check
self.scheme = scheme
- self.userinfo = userinfo
self.hostname = host
self.port = port
+ self.userinfo = userinfo
self.path = path
self.query = query
self.opaque = opaque
self.fragment = fragment
else
self.set_scheme(scheme)
- self.set_userinfo(userinfo)
self.set_host(host)
self.set_port(port)
+ self.set_userinfo(userinfo)
self.set_path(path)
self.query = query
self.set_opaque(opaque)
@@ -284,7 +284,7 @@ module URI
# Returns the parser to be used.
#
- # Unless a URI::Parser is defined, DEFAULT_PARSER is used.
+ # Unless the +parser+ is defined, DEFAULT_PARSER is used.
#
def parser
if !defined?(@parser) || !@parser
@@ -315,7 +315,7 @@ module URI
end
#
- # Checks the scheme +v+ component against the URI::Parser Regexp for :SCHEME.
+ # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME.
#
def check_scheme(v)
if v && parser.regexp[:SCHEME] !~ v
@@ -385,7 +385,7 @@ module URI
#
# Checks the user +v+ component for RFC2396 compliance
- # and against the URI::Parser Regexp for :USERINFO.
+ # and against the +parser+ Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
@@ -409,7 +409,7 @@ module URI
#
# Checks the password +v+ component for RFC2396 compliance
- # and against the URI::Parser Regexp for :USERINFO.
+ # and against the +parser+ Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
@@ -466,7 +466,7 @@ module URI
#
# uri = URI.parse("http://john:S3nsit1ve@my.example.com")
# uri.user = "sam"
- # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com"
+ # uri.to_s #=> "http://sam@my.example.com"
#
def user=(user)
check_user(user)
@@ -511,7 +511,7 @@ module URI
user, password = split_userinfo(user)
end
@user = user
- @password = password if password
+ @password = password
[@user, @password]
end
@@ -522,7 +522,7 @@ module URI
# See also URI::Generic.user=.
#
def set_user(v)
- set_userinfo(v, @password)
+ set_userinfo(v, nil)
v
end
protected :set_user
@@ -574,6 +574,12 @@ module URI
@password
end
+ # Returns the authority info (array of user, password, host and
+ # port), if any is set. Or returns +nil+.
+ def authority
+ return @user, @password, @host, @port if @user || @password || @host || @port
+ end
+
# Returns the user component after URI decoding.
def decoded_user
URI.decode_uri_component(@user) if @user
@@ -586,7 +592,7 @@ module URI
#
# Checks the host +v+ component for RFC2396 compliance
- # and against the URI::Parser Regexp for :HOST.
+ # and against the +parser+ Regexp for :HOST.
#
# Can not have a registry or opaque component defined,
# with a host component defined.
@@ -615,6 +621,13 @@ module URI
end
protected :set_host
+ # Protected setter for the authority info (+user+, +password+, +host+
+ # and +port+). If +port+ is +nil+, +default_port+ will be set.
+ #
+ protected def set_authority(user, password, host, port = nil)
+ @user, @password, @host, @port = user, password, host, port || self.default_port
+ end
+
#
# == Args
#
@@ -639,6 +652,7 @@ module URI
def host=(v)
check_host(v)
set_host(v)
+ set_userinfo(nil)
v
end
@@ -675,7 +689,7 @@ module URI
#
# Checks the port +v+ component for RFC2396 compliance
- # and against the URI::Parser Regexp for :PORT.
+ # and against the +parser+ Regexp for :PORT.
#
# Can not have a registry or opaque component defined,
# with a port component defined.
@@ -729,6 +743,7 @@ module URI
def port=(v)
check_port(v)
set_port(v)
+ set_userinfo(nil)
port
end
@@ -748,7 +763,7 @@ module URI
#
# Checks the path +v+ component for RFC2396 compliance
- # and against the URI::Parser Regexp
+ # and against the +parser+ Regexp
# for :ABS_PATH and :REL_PATH.
#
# Can not have a opaque component defined,
@@ -853,7 +868,7 @@ module URI
#
# Checks the opaque +v+ component for RFC2396 compliance and
- # against the URI::Parser Regexp for :OPAQUE.
+ # against the +parser+ Regexp for :OPAQUE.
#
# Can not have a host, port, user, or path component defined,
# with an opaque component defined.
@@ -905,7 +920,7 @@ module URI
end
#
- # Checks the fragment +v+ component against the URI::Parser Regexp for :FRAGMENT.
+ # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT.
#
#
# == Args
@@ -1121,7 +1136,7 @@ module URI
base = self.dup
- authority = rel.userinfo || rel.host || rel.port
+ authority = rel.authority
# RFC2396, Section 5.2, 2)
if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
@@ -1134,9 +1149,7 @@ module URI
# RFC2396, Section 5.2, 4)
if authority
- base.set_userinfo(rel.userinfo)
- base.set_host(rel.host)
- base.set_port(rel.port || base.default_port)
+ base.set_authority(*authority)
base.set_path(rel.path)
elsif base.path && rel.path
base.set_path(merge_path(base.path, rel.path))
@@ -1527,7 +1540,7 @@ module URI
else
unless proxy_uri = env[name]
if proxy_uri = env[name.upcase]
- warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1
+ warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1
end
end
end
diff --git a/lib/uri/http.rb b/lib/uri/http.rb
index 900b132c8c..3c41cd4e93 100644
--- a/lib/uri/http.rb
+++ b/lib/uri/http.rb
@@ -61,6 +61,18 @@ module URI
super(tmp)
end
+ # Do not allow empty host names, as they are not allowed by RFC 3986.
+ def check_host(v)
+ ret = super
+
+ if ret && v.empty?
+ raise InvalidComponentError,
+ "bad component(expected host component): #{v}"
+ end
+
+ ret
+ end
+
#
# == Description
#
diff --git a/lib/uri/rfc2396_parser.rb b/lib/uri/rfc2396_parser.rb
index 75a2d2dbde..cefd126cc6 100644
--- a/lib/uri/rfc2396_parser.rb
+++ b/lib/uri/rfc2396_parser.rb
@@ -67,7 +67,7 @@ module URI
#
# == Synopsis
#
- # URI::Parser.new([opts])
+ # URI::RFC2396_Parser.new([opts])
#
# == Args
#
@@ -86,7 +86,7 @@ module URI
#
# == Examples
#
- # p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
+ # p = URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
# u = p.parse("http://example.jp/%uABCD") #=> #<URI::HTTP http://example.jp/%uABCD>
# URI.parse(u.to_s) #=> raises URI::InvalidURIError
#
@@ -108,12 +108,12 @@ module URI
# The Hash of patterns.
#
- # See also URI::Parser.initialize_pattern.
+ # See also #initialize_pattern.
attr_reader :pattern
# The Hash of Regexp.
#
- # See also URI::Parser.initialize_regexp.
+ # See also #initialize_regexp.
attr_reader :regexp
# Returns a split URI against +regexp[:ABS_URI]+.
@@ -202,8 +202,7 @@ module URI
#
# == Usage
#
- # p = URI::Parser.new
- # p.parse("ldap://ldap.example.com/dc=example?user=john")
+ # URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john")
# #=> #<URI::LDAP ldap://ldap.example.com/dc=example?user=john>
#
def parse(uri)
@@ -244,7 +243,7 @@ module URI
# If no +block+ given, then returns the result,
# else it calls +block+ for each element in result.
#
- # See also URI::Parser.make_regexp.
+ # See also #make_regexp.
#
def extract(str, schemes = nil)
if block_given?
@@ -263,7 +262,7 @@ module URI
unless schemes
@regexp[:ABS_URI_REF]
else
- /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x
+ /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x
end
end
@@ -524,6 +523,8 @@ module URI
ret
end
+ # Returns +uri+ as-is if it is URI, or convert it to URI if it is
+ # a String.
def convert_to_uri(uri)
if uri.is_a?(URI::Generic)
uri
diff --git a/lib/uri/version.rb b/lib/uri/version.rb
index b6a8ce1543..1f810602eb 100644
--- a/lib/uri/version.rb
+++ b/lib/uri/version.rb
@@ -1,6 +1,6 @@
module URI
# :stopdoc:
- VERSION_CODE = '010003'.freeze
- VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
+ VERSION = '1.1.1'.freeze
+ VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze
# :startdoc:
end
diff --git a/lib/weakref.rb b/lib/weakref.rb
index f0a7e7b318..c7274f9664 100644
--- a/lib/weakref.rb
+++ b/lib/weakref.rb
@@ -17,7 +17,8 @@ require "delegate"
#
class WeakRef < Delegator
- VERSION = "0.1.3"
+ # The version string
+ VERSION = "0.1.4"
##
# RefError is raised when a referenced object has been recycled by the
diff --git a/lib/yaml.rb b/lib/yaml.rb
index 2cf11fc3df..c6f0f89fd2 100644
--- a/lib/yaml.rb
+++ b/lib/yaml.rb
@@ -66,5 +66,6 @@ YAML = Psych # :nodoc:
#
# Syck can also be found on github: https://github.com/ruby/syck
module YAML
+ # The version of YAML wrapper
LOADER_VERSION = "0.4.0"
end