Initialize the template project

This commit is contained in:
Sami Samhuri 2022-01-16 15:45:40 -08:00
parent 76427e214b
commit 0a294f8faf
No known key found for this signature in database
GPG key ID: 4B4195422742FC16
25 changed files with 116 additions and 484 deletions

View file

@ -1,83 +1,26 @@
require:
- rubocop-minitest
- rubocop-packaging
- rubocop-performance
- rubocop-rake
AllCops:
DisplayCopNames: true
DisplayStyleGuide: true
NewCops: enable
TargetRubyVersion: 2.6
Exclude:
- "tmp/**/*"
- "vendor/**/*"
Layout/HashAlignment:
EnforcedColonStyle:
- table
- key
EnforcedHashRocketStyle:
- table
- key
Layout/EmptyLineAfterGuardClause:
Enabled: false
Layout/SpaceAroundEqualsInParameterDefault:
EnforcedStyle: no_space
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
Metrics/AbcSize:
Max: 20
Exclude:
- "test/**/*"
Metrics/BlockLength:
Exclude:
- "*.gemspec"
- "Rakefile"
Metrics/ClassLength:
Exclude:
- "test/**/*"
Max: 25
Metrics/MethodLength:
Max: 18
Exclude:
- "test/**/*"
Metrics/ParameterLists:
Max: 6
Naming/MemoizedInstanceVariableName:
Enabled: false
Naming/VariableNumber:
Enabled: false
Rake/Desc:
Enabled: false
Style/BarePercentLiterals:
EnforcedStyle: percent_q
Style/ClassAndModuleChildren:
Enabled: false
Max: 20
Style/Documentation:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/EmptyMethod:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false
Style/NumericPredicate:
Enabled: false
Style/HashSyntax:
EnforcedShorthandSyntax: never
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/TrivialAccessors:
AllowPredicates: true
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: consistent_comma

View file

@ -1 +1 @@
Release notes for this project are kept here: https://github.com/mattbrictson/gem/releases
Release notes for this project are kept here: https://github.com/samsonjs/wordexp/releases

View file

@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at owner@example.com. All complaints will be reviewed and investigated promptly and fairly.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at sami@samhuri.net. 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.

20
Gemfile
View file

@ -1,12 +1,12 @@
source "https://rubygems.org"
source 'https://rubygems.org'
gemspec
gem "minitest", "~> 5.11"
gem "minitest-ci", "~> 3.4"
gem "minitest-reporters", "~> 1.3"
gem "rake", "~> 13.0"
gem "rubocop", "1.24.1"
gem "rubocop-minitest", "0.17.0"
gem "rubocop-packaging", "0.5.1"
gem "rubocop-performance", "1.13.1"
gem "rubocop-rake", "0.6.0"
gem 'minitest', '~> 5.11'
gem 'minitest-ci', '~> 3.4'
gem 'minitest-reporters', '~> 1.3'
gem 'rake', '~> 13.0'
gem 'rubocop', '1.24.1'
gem 'rubocop-minitest', '0.17.0'
gem 'rubocop-packaging', '0.5.1'
gem 'rubocop-performance', '1.13.1'
gem 'rubocop-rake', '0.6.0'

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2022 Example Owner
Copyright (c) 2022 Sami Samhuri
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,29 +1,8 @@
# gem template
# wordexp
This is a GitHub template for creating Ruby gems. Press [**Use this template**](https://github.com/mattbrictson/gem/generate) to generate a project from this template. In the generated project, run this script to rename the gem to meet your needs:
```
$ ruby rename_template.rb
```
This template is based on `bundle gem` with some notable improvements:
- Circle CI configuration
- Minitest, with minitest-reporters for nicely formatted test output
- Rubocop with a good set of configuration
- [release-drafter](https://github.com/apps/release-drafter) GitHub Action for automating release notes
- A `rake bump` task to keep your Ruby and Bundler dependencies up to date
- A nice README with badges ready to go (see below)
---
<!-- END FRONT MATTER -->
# example
[![Gem Version](https://badge.fury.io/rb/replace_with_gem_name.svg)](https://rubygems.org/gems/replace_with_gem_name)
[![Circle](https://circleci.com/gh/mattbrictson/gem/tree/main.svg?style=shield)](https://app.circleci.com/pipelines/github/mattbrictson/gem?branch=main)
[![Code Climate](https://codeclimate.com/github/mattbrictson/gem/badges/gpa.svg)](https://codeclimate.com/github/mattbrictson/gem)
[![Gem Version](https://badge.fury.io/rb/wordexp.svg)](https://rubygems.org/gems/wordexp)
[![Circle](https://circleci.com/gh/samsonjs/wordexp/tree/main.svg?style=shield)](https://app.circleci.com/pipelines/github/samsonjs/wordexp?branch=main)
[![Code Climate](https://codeclimate.com/github/samsonjs/wordexp/badges/gpa.svg)](https://codeclimate.com/github/samsonjs/wordexp)
TODO: Description of this gem goes here.
@ -38,16 +17,16 @@ TODO: Description of this gem goes here.
## Quick start
```
$ gem install example
$ gem install wordexp
```
```ruby
require "example"
require "wordexp"
```
## Support
If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/mattbrictson/gem/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/samsonjs/wordexp/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
## License

View file

@ -1,11 +1,11 @@
require "bundler/gem_tasks"
require "rake/testtask"
require "rubocop/rake_task"
require 'bundler/gem_tasks'
require 'rake/testtask'
require 'rubocop/rake_task'
Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/*_test.rb"]
t.libs << 'test'
t.libs << 'lib'
t.test_files = FileList['test/**/*_test.rb']
end
RuboCop::RakeTask.new
@ -14,26 +14,26 @@ task default: %i[test rubocop]
# == "rake release" enhancements ==============================================
Rake::Task["release"].enhance do
Rake::Task['release'].enhance do
puts "Don't forget to publish the release on GitHub!"
system "open https://github.com/mattbrictson/gem/releases"
system 'open https://github.com/samsonjs/wordexp/releases'
end
task :disable_overcommit do
ENV["OVERCOMMIT_DISABLE"] = "1"
ENV['OVERCOMMIT_DISABLE'] = '1'
end
Rake::Task[:build].enhance [:disable_overcommit]
task :verify_gemspec_files do
git_files = `git ls-files -z`.split("\x0")
gemspec_files = Gem::Specification.load("example.gemspec").files.sort
gemspec_files = Gem::Specification.load('wordexp.gemspec').files.sort
ignored_by_git = gemspec_files - git_files
next if ignored_by_git.empty?
raise <<~ERROR
The `spec.files` specified in example.gemspec include the following files
The `spec.files` specified in wordexp.gemspec include the following files
that are being ignored by git. Did you forget to add them to the repo? If
not, you may need to delete these files or modify the gemspec to ensure
that they are not included in the gem by mistake:
@ -51,9 +51,9 @@ task bump: %w[bump:bundler bump:ruby bump:year]
namespace :bump do
task :bundler do
version = Gem.latest_version_for("bundler").to_s
replace_in_file ".circleci/config.yml", /bundler -v (\S+)/ => version
replace_in_file "Gemfile.lock", /^BUNDLED WITH\n\s+(\d\S+)$/ => version
version = Gem.latest_version_for('bundler').to_s
replace_in_file '.circleci/config.yml', /bundler -v (\S+)/ => version
replace_in_file 'Gemfile.lock', /^BUNDLED WITH\n\s+(\d\S+)$/ => version
end
task :ruby do
@ -62,20 +62,20 @@ namespace :bump do
latest = RubyVersions.latest
latest_patches = RubyVersions.latest_supported_patches
replace_in_file "example.gemspec", /ruby_version = .*">= (.*)"/ => lowest
replace_in_file ".rubocop.yml", /TargetRubyVersion: (.*)/ => lowest_minor
replace_in_file ".circleci/config.yml", /default: "([\d.]+)"/ => latest
replace_in_file ".circleci/config.yml", /version: (\[.+\])/ => latest_patches.inspect
replace_in_file 'wordexp.gemspec', /ruby_version = .*">= (.*)"/ => lowest
replace_in_file '.rubocop.yml', /TargetRubyVersion: (.*)/ => lowest_minor
replace_in_file '.circleci/config.yml', /default: "([\d.]+)"/ => latest
replace_in_file '.circleci/config.yml', /version: (\[.+\])/ => latest_patches.inspect
end
task :year do
replace_in_file "LICENSE.txt", /\(c\) (\d+)/ => Date.today.year.to_s
replace_in_file 'LICENSE.txt', /\(c\) (\d+)/ => Date.today.year.to_s
end
end
require "date"
require "open-uri"
require "yaml"
require 'date'
require 'open-uri'
require 'yaml'
def replace_in_file(path, replacements)
contents = File.read(path)
@ -113,8 +113,8 @@ module RubyVersions
private
def versions
@_versions ||= begin
yaml = URI.open("https://raw.githubusercontent.com/ruby/www.ruby-lang.org/HEAD/_data/downloads.yml")
@versions ||= begin
yaml = URI.open('https://raw.githubusercontent.com/ruby/www.ruby-lang.org/HEAD/_data/downloads.yml')
YAML.safe_load(yaml, symbolize_names: true)
end
end

View file

@ -1,7 +1,7 @@
#!/usr/bin/env ruby
require "bundler/setup"
require "example"
require 'bundler/setup'
require 'wordexp'
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
@ -10,5 +10,5 @@ require "example"
# require "pry"
# Pry.start
require "irb"
require 'irb'
IRB.start(__FILE__)

View file

@ -1,27 +0,0 @@
require_relative "lib/example/version"
Gem::Specification.new do |spec|
spec.name = "example"
spec.version = Example::VERSION
spec.authors = ["Example Owner"]
spec.email = ["owner@example.com"]
spec.summary = ""
spec.homepage = "https://github.com/mattbrictson/gem"
spec.license = "MIT"
spec.required_ruby_version = ">= 2.6.0"
spec.metadata = {
"bug_tracker_uri" => "https://github.com/mattbrictson/gem/issues",
"changelog_uri" => "https://github.com/mattbrictson/gem/releases",
"source_code_uri" => "https://github.com/mattbrictson/gem",
"homepage_uri" => spec.homepage,
"rubygems_mfa_required" => "true"
}
# Specify which files should be added to the gem when it is released.
spec.files = Dir.glob(%w[LICENSE.txt README.md {exe,lib}/**/*]).reject { |f| File.directory?(f) }
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
end

View file

@ -1,4 +0,0 @@
#!/usr/bin/env ruby
require "example"
Example::CLI.new.call(ARGV)

4
exe/wordexp Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env ruby -w
require 'wordexp'
Wordexp::CLI.new.call(ARGV)

View file

@ -1,4 +0,0 @@
module Example
autoload :CLI, "example/cli"
autoload :VERSION, "example/version"
end

View file

@ -1,6 +0,0 @@
module Example
class CLI
def call(_argv)
end
end
end

View file

@ -1,3 +0,0 @@
module Example
VERSION = "0.1.0".freeze
end

4
lib/wordexp.rb Normal file
View file

@ -0,0 +1,4 @@
module Wordexp
autoload :CLI, 'wordexp/cli'
autoload :VERSION, 'wordexp/version'
end

7
lib/wordexp/cli.rb Normal file
View file

@ -0,0 +1,7 @@
module Wordexp
class CLI
def call(argv)
puts argv.join(' ')
end
end
end

3
lib/wordexp/version.rb Normal file
View file

@ -0,0 +1,3 @@
module Wordexp
VERSION = '0.1.0'.freeze
end

View file

@ -1,291 +0,0 @@
#!/usr/bin/env ruby
require "bundler/inline"
require "fileutils"
require "io/console"
require "open3"
gemfile do
source "https://rubygems.org"
gem "octokit", "~> 4.14"
end
def main # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
assert_git_repo!
git_meta = read_git_data
gem_name = ask("Gem name?", default: git_meta[:origin_repo_name])
gem_summary = ask("Gem summary (< 60 chars)?", default: "")
author_email = ask("Author email?", default: git_meta[:user_email])
author_name = ask("Author name?", default: git_meta[:user_name])
github_repo = ask("GitHub repository?", default: git_meta[:origin_repo_path])
exe = ask_yes_or_no("Include an executable (CLI) in this gem?", default: "N")
if ask_yes_or_no("Create GitHub labels?", default: "Y")
puts
puts "I need to ask for your GitHub credentials in order to create labels."
puts "Don't worry, your GitHub credentials will NOT be saved."
puts
login = ask("GitHub username?", default: github_repo.split("/").first)
password = ask("GitHub password?", echo: false)
client = authenticate_github(login, password)
create_labels(
client,
github_repo,
["automerge", "fbca04", "Automatically merge PR once all required checks pass"],
["⚠️ Breaking", "d12d1b", "Introduces a backwards-incompatible change"],
["🐛 Bug Fix", "c0fc80", "Fixes a bug"],
["📚 Docs", "bfdadc", "Improves documentation"],
["✨ Feature", "ba1ecc", "Adds a new feature"],
["🏠 Housekeeping", "ccccff", "Non-user facing cleanup and maintenance"]
)
end
git "mv", ".github/workflows/push.yml.dist", ".github/workflows/push.yml"
FileUtils.mkdir_p "lib/#{as_path(gem_name)}"
FileUtils.mkdir_p "test/#{as_path(gem_name)}"
ensure_executable "bin/console"
ensure_executable "bin/setup"
if exe
replace_in_file "exe/example",
"example" => as_path(gem_name),
"Example" => as_module(gem_name)
git "mv", "exe/example", "exe/#{gem_name}"
ensure_executable "exe/#{gem_name}"
replace_in_file "lib/example/cli.rb",
"Example" => as_module(gem_name)
git "mv", "lib/example/cli.rb", "lib/#{as_path(gem_name)}/cli.rb"
reindent_module "lib/#{as_path(gem_name)}/cli.rb"
else
git "rm", "exe/example", "lib/example/cli.rb"
remove_line "lib/example.rb", /autoload :CLI/
end
replace_in_file "LICENSE.txt",
"Example Owner" => author_name
replace_in_file "Rakefile",
"example.gemspec" => "#{gem_name}.gemspec",
"mattbrictson/gem" => github_repo
replace_in_file "README.md",
"mattbrictson/gem" => github_repo,
'require "example"' => %Q(require "#{as_path(gem_name)}"),
"example" => gem_name,
"replace_with_gem_name" => gem_name,
/\A.*<!-- END FRONT MATTER -->\n+/m => ""
replace_in_file "CHANGELOG.md",
"mattbrictson/gem" => github_repo
replace_in_file "CODE_OF_CONDUCT.md",
"owner@example.com" => author_email
replace_in_file "bin/console",
'require "example"' => %Q(require "#{as_path(gem_name)}")
replace_in_file "example.gemspec",
"mattbrictson/gem" => github_repo,
'"Example Owner"' => author_name.inspect,
'"owner@example.com"' => author_email.inspect,
'"example"' => gem_name.inspect,
"example/version" => "#{as_path(gem_name)}/version",
"Example::VERSION" => "#{as_module(gem_name)}::VERSION",
/summary\s*=\s*("")/ => gem_summary.inspect
git "mv", "example.gemspec", "#{gem_name}.gemspec"
replace_in_file "lib/example.rb",
"example" => as_path(gem_name),
"Example" => as_module(gem_name)
git "mv", "lib/example.rb", "lib/#{as_path(gem_name)}.rb"
reindent_module "lib/#{as_path(gem_name)}.rb"
replace_in_file "lib/example/version.rb",
"Example" => as_module(gem_name)
git "mv", "lib/example/version.rb", "lib/#{as_path(gem_name)}/version.rb"
reindent_module "lib/#{as_path(gem_name)}/version.rb"
replace_in_file "test/example_test.rb",
"Example" => as_module(gem_name)
git "mv", "test/example_test.rb", "test/#{as_path(gem_name)}_test.rb"
replace_in_file "test/test_helper.rb",
'require "example"' => %Q(require "#{as_path(gem_name)}")
git "rm", "rename_template.rb"
puts <<~MESSAGE
All set!
The project has been renamed from "example" to "#{gem_name}".
Review the changes and then run:
git commit && git push
MESSAGE
end
def assert_git_repo!
return if File.file?(".git/config")
warn("This doesn't appear to be a git repo. Can't continue. :(")
exit(1)
end
def git(*args)
sh! "git", *args
end
def ensure_executable(path)
return if File.executable?(path)
FileUtils.chmod 0o755, path
git "add", path
end
def sh!(*args)
puts ">>>> #{args.join(' ')}"
stdout, status = Open3.capture2(*args)
raise("Failed to execute: #{args.join(' ')}") unless status.success?
stdout
end
def remove_line(file, pattern)
text = File.read(file)
text = text.lines.filter.grep_v(pattern).join
File.write(file, text)
git "add", file
end
def ask(question, default: nil, echo: true)
prompt = "#{question} "
prompt << "[#{default}] " unless default.nil?
print prompt
answer = if echo
$stdin.gets.chomp
else
$stdin.noecho(&:gets).tap { $stdout.print "\n" }.chomp
end
answer.to_s.strip.empty? ? default : answer
end
def ask_yes_or_no(question, default: "N")
default = default == "Y" ? "Y/n" : "y/N"
answer = ask(question, default: default)
answer != "y/N" && answer.match?(/^y/i)
end
def read_git_data
return {} unless git("remote", "-v").match?(/^origin/)
origin_url = git("remote", "get-url", "origin").chomp
origin_repo_path = origin_url[%r{[:/]([^/]+/[^/]+)(?:\.git)$}, 1]
{
origin_repo_name: origin_repo_path.split("/").last,
origin_repo_path: origin_repo_path,
user_email: git("config", "user.email").chomp,
user_name: git("config", "user.name").chomp
}
end
def replace_in_file(path, replacements)
contents = File.read(path)
replacements.each do |regexp, text|
contents.gsub!(regexp) do |match|
next text if Regexp.last_match(1).nil?
match[regexp, 1] = text
match
end
end
File.write(path, contents)
git "add", path
end
def as_path(gem_name)
gem_name.tr("-", "/")
end
def as_module(gem_name)
parts = gem_name.split("-")
parts.map do |part|
part.gsub(/^[a-z]|_[a-z]/) { |str| str[-1].upcase }
end.join("::")
end
def reindent_module(path)
contents = File.read(path)
namespace_mod = contents[/(?:module|class) (\S+)/, 1]
return unless namespace_mod.include?("::")
contents.sub!(namespace_mod, namespace_mod.split("::").last)
namespace_mod.split("::")[0...-1].reverse_each do |mod|
contents = "module #{mod}\n#{contents.gsub(/^/, ' ')}end\n"
end
File.write(path, contents)
git "add", path
end
def authenticate_github(login, password)
octokit = Octokit::Client.new(login: login, password: password, netrc: false)
octokit.user
GithubClient.new(octokit)
rescue Octokit::OneTimePasswordRequired
token = ask("2FA token?")
GithubClient.new(octokit, otp_token: token)
end
def create_labels(client, github_repo, *labels)
labels.each do |name, color, description|
client.add_label(github_repo, name, color, description)
end
puts "Created labels: #{labels.map(&:first).join(', ')}"
end
class GithubClient
def initialize(octokit, otp_token: nil)
@octokit = octokit
@otp_token = otp_token
end
def add_label(repo, name, color, description)
octokit.add_label(
repo,
name,
color,
auth_options.merge(
description: description,
accept: "application/vnd.github.symmetra-preview+json"
)
)
end
private
attr_reader :octokit, :otp_token
def auth_options
return {} if otp_token.nil?
{ headers: { "X-GitHub-OTP" => otp_token } }
end
end
main if $PROGRAM_NAME == __FILE__

View file

@ -1,7 +0,0 @@
require "test_helper"
class ExampleTest < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::Example::VERSION
end
end

View file

@ -1,2 +1,2 @@
# Generate XML test reports that can be parsed by CircleCI
require "minitest/ci" if ENV["CIRCLECI"]
require 'minitest/ci' if ENV['CIRCLECI']

View file

@ -1,6 +1,6 @@
require "minitest/reporters"
require 'minitest/reporters'
if ENV["CI"]
if ENV['CI']
Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new)
else
Minitest::Reporters.use!(Minitest::Reporters::DefaultReporter.new)

View file

@ -1,5 +1,5 @@
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "example"
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
require 'wordexp'
require "minitest/autorun"
Dir[File.expand_path("support/**/*.rb", __dir__)].sort.each { |rb| require(rb) }
require 'minitest/autorun'
Dir[File.expand_path('support/**/*.rb', __dir__)].sort.each { |rb| require(rb) }

7
test/wordexp_test.rb Normal file
View file

@ -0,0 +1,7 @@
require 'test_helper'
class WordexpTest < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::Wordexp::VERSION
end
end

27
wordexp.gemspec Normal file
View file

@ -0,0 +1,27 @@
require_relative 'lib/wordexp/version'
Gem::Specification.new do |spec|
spec.name = 'wordexp'
spec.version = Wordexp::VERSION
spec.authors = ['Sami Samhuri']
spec.email = ['sami@samhuri.net']
spec.summary = 'A Ruby gem for performing shell word expansion using GNU wordexp'
spec.homepage = 'https://github.com/samsonjs/wordexp'
spec.license = 'MIT'
spec.required_ruby_version = '>= 2.6.0'
spec.metadata = {
'bug_tracker_uri' => 'https://github.com/samsonjs/wordexp/issues',
'changelog_uri' => 'https://github.com/samsonjs/wordexp/releases',
'source_code_uri' => 'https://github.com/samsonjs/wordexp',
'homepage_uri' => spec.homepage,
'rubygems_mfa_required' => 'true',
}
# Specify which files should be added to the gem when it is released.
spec.files = Dir.glob(%w[LICENSE.txt README.md lib/**/*]).reject { |f| File.directory?(f) }
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
end