Initial commit

This commit is contained in:
Sami Samhuri 2022-01-16 15:35:53 -08:00
commit 76427e214b
27 changed files with 949 additions and 0 deletions

82
.circleci/config.yml Normal file
View file

@ -0,0 +1,82 @@
version: 2.1
executors:
ruby:
parameters:
version:
description: "Ruby version number"
default: "3.1.0"
type: string
docker:
- image: cimg/ruby:<< parameters.version >>
commands:
bundle_install:
description: Install Ruby dependencies with Bundler
parameters:
version:
description: "Ruby version number"
default: "3.1.0"
type: string
steps:
- restore_cache:
keys:
- bundle-v1-{{ arch }}-<< parameters.version >>
- run:
name: Install Ruby Dependencies
command: |
gem install bundler -v 2.3.4 --conservative --no-document
bundle config --local path vendor/bundle
bundle check || (bundle install --jobs=4 --retry=3 && bundle clean)
- save_cache:
paths:
- ./vendor/bundle
key: bundle-v1-{{ arch }}-<< parameters.version >>-{{ checksum "Gemfile.lock" }}
jobs:
rubocop:
executor: ruby
steps:
- checkout
- bundle_install
- run: bundle exec rubocop
test:
parameters:
version:
description: "Ruby version number"
default: "3.1.0"
type: string
executor:
name: ruby
version: << parameters.version >>
steps:
- checkout
- bundle_install:
version: << parameters.version >>
- run: bundle exec rake test TESTOPTS="--ci-dir=./reports"
- store_test_results:
path: ./reports
workflows:
version: 2
commit-workflow:
jobs:
- rubocop
- test:
matrix:
parameters:
version: ["2.6.9", "2.7.5", "3.0.3", "3.1.0"]
cron-workflow:
jobs:
- rubocop
- test:
matrix:
parameters:
version: ["2.6.9", "2.7.5", "3.0.3", "3.1.0"]
triggers:
- schedule:
cron: "0 13 * * 6"
filters:
branches:
only:
- main

20
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: monthly
time: "06:00"
timezone: America/Los_Angeles
open-pull-requests-limit: 10
labels:
- "🏠 Housekeeping"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
time: "06:00"
timezone: America/Los_Angeles
open-pull-requests-limit: 10
labels:
- "🏠 Housekeeping"

23
.github/release-drafter.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name-template: "$RESOLVED_VERSION"
tag-template: "v$RESOLVED_VERSION"
categories:
- title: "⚠️ Breaking Changes"
label: "⚠️ Breaking"
- title: "✨ New Features"
label: "✨ Feature"
- title: "🐛 Bug Fixes"
label: "🐛 Bug Fix"
- title: "📚 Documentation"
label: "📚 Docs"
- title: "🏠 Housekeeping"
label: "🏠 Housekeeping"
version-resolver:
minor:
labels:
- "⚠️ Breaking"
- "✨ Feature"
default: patch
change-template: "- $TITLE (#$NUMBER) @$AUTHOR"
no-changes-template: "- No changes"
template: |
$CHANGES

14
.github/workflows/push.yml.dist vendored Normal file
View file

@ -0,0 +1,14 @@
name: Release Drafter
on:
push:
branches:
- main
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/site/
/spec/reports/
/tmp/
/Gemfile.lock

3
.kodiak.toml Normal file
View file

@ -0,0 +1,3 @@
# .kodiak.toml
# Minimal config. version is the only required field.
version = 1

32
.overcommit.yml Normal file
View file

@ -0,0 +1,32 @@
# Overcommit hooks run automatically on certain git operations, like "git commit".
# For a complete list of options that you can use to customize hooks, see:
# https://github.com/sds/overcommit
gemfile: false
verify_signatures: false
PreCommit:
BundleCheck:
enabled: true
FixMe:
enabled: true
keywords: ["FIXME"]
exclude:
- .overcommit.yml
LocalPathsInGemfile:
enabled: true
RuboCop:
enabled: true
required_executable: bundle
command: ["bundle", "exec", "rubocop"]
on_warn: fail
YamlSyntax:
enabled: true
PostCheckout:
ALL:
quiet: true

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
/CODE_OF_CONDUCT.md

83
.rubocop.yml Normal file
View file

@ -0,0 +1,83 @@
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/SpaceAroundEqualsInParameterDefault:
EnforcedStyle: no_space
Metrics/AbcSize:
Max: 20
Exclude:
- "test/**/*"
Metrics/BlockLength:
Exclude:
- "*.gemspec"
- "Rakefile"
Metrics/ClassLength:
Exclude:
- "test/**/*"
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
Style/Documentation:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/EmptyMethod:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false
Style/NumericPredicate:
Enabled: false
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/TrivialAccessors:
AllowPredicates: true

1
CHANGELOG.md Normal file
View file

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

84
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,84 @@
# Contributor Covenant Code of Conduct
## Our Pledge
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, religion, or sexual identity and orientation.
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 e-mail 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 owner@example.com. 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.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.

12
Gemfile Normal file
View file

@ -0,0 +1,12 @@
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"

21
LICENSE.txt Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2022 Example Owner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

62
README.md Normal file
View file

@ -0,0 +1,62 @@
# gem template
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)
TODO: Description of this gem goes here.
---
- [Quick start](#quick-start)
- [Support](#support)
- [License](#license)
- [Code of conduct](#code-of-conduct)
- [Contribution guide](#contribution-guide)
## Quick start
```
$ gem install example
```
```ruby
require "example"
```
## 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!
## License
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
## Code of conduct
Everyone interacting in this projects codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
## Contribution guide
Pull requests are welcome!

122
Rakefile Normal file
View file

@ -0,0 +1,122 @@
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"]
end
RuboCop::RakeTask.new
task default: %i[test rubocop]
# == "rake release" enhancements ==============================================
Rake::Task["release"].enhance do
puts "Don't forget to publish the release on GitHub!"
system "open https://github.com/mattbrictson/gem/releases"
end
task :disable_overcommit do
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
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
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:
#{ignored_by_git.join("\n").gsub(/^/, ' ')}
ERROR
end
Rake::Task[:build].enhance [:verify_gemspec_files]
# == "rake bump" tasks ========================================================
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
end
task :ruby do
lowest = RubyVersions.lowest_supported
lowest_minor = RubyVersions.lowest_supported_minor
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
end
task :year do
replace_in_file "LICENSE.txt", /\(c\) (\d+)/ => Date.today.year.to_s
end
end
require "date"
require "open-uri"
require "yaml"
def replace_in_file(path, replacements)
contents = File.read(path)
orig_contents = contents.dup
replacements.each do |regexp, text|
raise "Can't find #{regexp} in #{path}" unless regexp.match?(contents)
contents.gsub!(regexp) do |match|
match[regexp, 1] = text
match
end
end
File.write(path, contents) if contents != orig_contents
end
module RubyVersions
class << self
def lowest_supported
"#{lowest_supported_minor}.0"
end
def lowest_supported_minor
latest_supported_patches.first[/\d+\.\d+/]
end
def latest
latest_supported_patches.last
end
def latest_supported_patches
patches = [versions[:stable], versions[:security_maintenance]].flatten
patches.map { |p| Gem::Version.new(p) }.sort.map(&:to_s)
end
private
def versions
@_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
end
end

14
bin/console Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env ruby
require "bundler/setup"
require "example"
# 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.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)

9
bin/setup Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
which overcommit > /dev/null 2>&1 && overcommit --install
bundle install
# Do any other automated setup that you need to do here

27
example.gemspec Normal file
View file

@ -0,0 +1,27 @@
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

4
exe/example Executable file
View file

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

4
lib/example.rb Normal file
View file

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

6
lib/example/cli.rb Normal file
View file

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

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

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

291
rename_template.rb Executable file
View file

@ -0,0 +1,291 @@
#!/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__

7
test/example_test.rb Normal file
View file

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

2
test/support/circleci.rb Normal file
View file

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

View file

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

5
test/test_helper.rb Normal file
View file

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