summaryrefslogtreecommitdiff
path: root/lib/bundler/cli/gem.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/cli/gem.rb')
-rw-r--r--lib/bundler/cli/gem.rb245
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