diff options
Diffstat (limited to 'lib/bundler/cli/gem.rb')
| -rw-r--r-- | lib/bundler/cli/gem.rb | 245 |
1 files changed, 157 insertions, 88 deletions
diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index a034aea324..c8c24c8e66 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 @@ -9,13 +7,9 @@ module Bundler end class CLI::Gem - TEST_FRAMEWORK_VERSIONS = { - "rspec" => "3.0", - "minitest" => "5.0", - "test-unit" => "3.0", - }.freeze + DEFAULT_GITHUB_USERNAME = "[USERNAME]" - attr_reader :options, :gem_name, :thor, :name, :target + attr_reader :options, :gem_name, :thor, :name, :target, :extension def initialize(options, gem_name, thor) @options = options @@ -26,9 +20,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 options[:ext] + validate_ext_name if @extension end def run @@ -38,44 +34,56 @@ module Bundler namespaced_path = name.tr("-", "/") constant_name = name.gsub(/-[_-]*(?![_-]|$)/) { "::" }.gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase } constant_array = constant_name.split("::") + minitest_constant_name = constant_array.clone.tap {|a| a[-1] = "Test#{a[-1]}" }.join("::") # Foo::Bar => Foo::TestBar use_git = Bundler.git_present? && options[:git] 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 = { - :name => name, - :underscored_name => underscored_name, - :namespaced_path => namespaced_path, - :makefile_path => "#{underscored_name}/#{underscored_name}", - :constant_name => constant_name, - :constant_array => constant_array, - :author => git_author_name.empty? ? "TODO: Write your name" : git_author_name, - :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email, - :test => options[:test], - :ext => options[:ext], - :exe => options[:exe], - :bundler_version => bundler_dependency_version, - :git => use_git, - :github_username => github_username.empty? ? "[USERNAME]" : github_username, - :required_ruby_version => Gem.ruby_version < Gem::Version.new("2.4.a") ? "2.3.0" : "2.4.0", + name: name, + underscored_name: underscored_name, + namespaced_path: namespaced_path, + makefile_path: "#{underscored_name}/#{underscored_name}", + constant_name: constant_name, + constant_array: constant_array, + author: git_author_name.empty? ? "TODO: Write your name" : git_author_name, + email: git_user_email.empty? ? "TODO: Write your email address" : git_user_email, + test: options[:test], + ext: extension, + exe: options[:exe], + bundle: options[:bundle], + bundler_version: bundler_dependency_version, + git: use_git, + 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) templates = { - "#{Bundler.preferred_gemfile_name}.tt" => Bundler.preferred_gemfile_name, + "Gemfile.tt" => Bundler.preferred_gemfile_name, "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb", "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb", + "sig/newgem.rbs.tt" => "sig/#{namespaced_path}.rbs", "newgem.gemspec.tt" => "#{name}.gemspec", "Rakefile.tt" => "Rakefile", "README.md.tt" => "README.md", @@ -88,11 +96,21 @@ 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 - config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework] case test_framework when "rspec" @@ -102,18 +120,30 @@ 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 + # foo-bar => test/foo/test_bar.rb + # foo_bar => test/test_foo_bar.rb + paths = namespaced_path.rpartition("/") + paths[2] = "test_#{paths[2]}" + minitest_namespaced_path = paths.join("") + templates.merge!( "test/minitest/test_helper.rb.tt" => "test/test_helper.rb", - "test/minitest/test_newgem.rb.tt" => "test/test_#{namespaced_path}.rb" + "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 @@ -121,18 +151,22 @@ module Bundler case config[:ci] when "github" templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml") - when "travis" - templates.merge!("travis.yml.tt" => ".travis.yml") + if extension == "rust" + templates.merge!("github/workflows/build-gems.yml.tt" => ".github/workflows/build-gems.yml") + end + config[:ignore_paths] << ".github/" when "gitlab" templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml") + config[:ignore_paths] << ".gitlab-ci.yml" when "circle" templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml") + 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") @@ -140,12 +174,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") @@ -166,32 +196,57 @@ module Bundler config[:linter] = ask_and_set_linter case config[:linter] when "rubocop" - config[:linter_version] = Gem.ruby_version < Gem::Version.new("2.4.a") ? "0.81.0" : "1.7" Bundler.ui.info "RuboCop enabled in config" templates.merge!("rubocop.yml.tt" => ".rubocop.yml") + config[:ignore_paths] << ".rubocop.yml" when "standard" - config[:linter_version] = Gem.ruby_version < Gem::Version.new("2.4.a") ? "0.2.5" : "1.0" Bundler.ui.info "Standard enabled in config" templates.merge!("standard.yml.tt" => ".standard.yml") + config[:ignore_paths] << ".standard.yml" end - templates.merge!("exe/newgem.tt" => "exe/#{name}") if config[:exe] + if config[:exe] + templates.merge!("exe/newgem.tt" => "exe/#{name}") + executables.push("exe/#{name}") + end - if options[:ext] + if extension == "c" templates.merge!( - "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/extconf-c.rb.tt" => "ext/#{name}/extconf.rb", "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", "ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c" ) end + if extension == "rust" + templates.merge!( + "Cargo.toml.tt" => "Cargo.toml", + "ext/newgem/Cargo.toml.tt" => "ext/#{name}/Cargo.toml", + "ext/newgem/build.rs.tt" => "ext/#{name}/build.rs", + "ext/newgem/extconf-rust.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs", + ) + 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}` @@ -210,31 +265,36 @@ module Bundler end if use_git + 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 - `git add .` + 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. " \ - "For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html" + Bundler.ui.info "\nGem '#{name}' was successfully created. " \ + "For more information on making a RubyGem visit https://guides.rubygems.org/make-your-own-gem/" 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 @@ -252,14 +312,15 @@ module Bundler end def ask_and_set_test_framework + return if skip?(:test) 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):" - if result =~ /rspec|minitest|test-unit/ + if /rspec|minitest|test-unit/.match?(result) test_framework = result else test_framework = false @@ -277,6 +338,10 @@ module Bundler test_framework end + def skip?(option) + options.key?(option) && options[option].nil? + end + def hint_text(setting) if Bundler.settings["gem.#{setting}"] == false "Your choice will only be applied to this gem." @@ -287,20 +352,19 @@ module Bundler end def ask_and_set_ci + return if skip?(:ci) 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" \ - "* Travis CI: https://travis-ci.org/\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/travis/gitlab/circle/(none):" - if result =~ /github|travis|gitlab|circle/ + result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):" + if /github|gitlab|circle/.match?(result) ci_template = result else ci_template = false @@ -319,19 +383,18 @@ module Bundler end 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/testdouble/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):" - if result =~ /rubocop|standard/ + if /rubocop|standard/.match?(result) linter_template = result else linter_template = false @@ -354,22 +417,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" - "rubocop" - else - Bundler::SharedHelpers.major_deprecation 2, "--no-rubocop is deprecated, 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" - Bundler.settings["gem.rubocop"] ? "rubocop" : false - end - end - def bundler_dependency_version v = Gem::Version.new(Bundler::VERSION) req = v.segments[0..1] @@ -378,11 +425,15 @@ module Bundler end def ensure_safe_gem_name(name, constant_array) - if name =~ /^\d/ + if /^\d/.match?(name) Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers." exit 1 end + if /[A-Z]/.match?(name) + Bundler.ui.warn "Gem names with capital letters are not recommended. Please use only lowercase letters, numbers, and hyphens." + end + constant_name = constant_array.join("::") existing_constant = constant_array.inject(Object) do |c, s| @@ -403,5 +454,23 @@ module Bundler def open_editor(editor, file) thor.run(%(#{editor} "#{file}")) end + + def rust_builder_required_rubygems_version + "3.3.11" + end + + def required_ruby_version + "3.2.0" + end + + def github_username(git_username) + if options[:github_username].nil? + git_username + elsif options[:github_username] == false + "" + else + options[:github_username] + end + end end end |
