Skip to content

Commit

Permalink
Introduce suspenders:cleanup:organize_gemfile task (#1181)
Browse files Browse the repository at this point in the history
Introduce `Suspenders::Cleanup::OrganizeGemfile` class and corresponding
task in an effort to reduce duplicate groups in the modified Gemfile.

This is because the [gem_group][] method does not modify existing
groups. I've opened [#49512][] in an effort to fix this, but until then,
this task will suffice.

This class is designed to be run after `suspenders:install:web`, and
does not account for all edge cases. For example, it assumes gems are
grouped by symbols (i.e. :test and not "test"), and does not account for
inline syntax:

```ruby
gem 'my-gem', group: [:cucumber, :test]
```

We could consider extracting this into a Gem (with a fun name, of
course. Maybe "Polish"), but for now, I think this simple procedural
code if just fine.

Additionally, this commit removes duplicate Rake task that was generated
with the plugin.

[gem_group]: https://api.rubyonrails.org/classes/Rails/Generators/Actions.html#method-i-gem_group
[#49512]: rails/rails#49512
  • Loading branch information
stevepolitodesign authored Apr 2, 2024
1 parent f76fb25 commit f6c6f45
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 4 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Unreleased
* Introduce `suspenders:testing` generator
* Introduce `suspenders:prerequisites` generator
* Introduce `suspenders:ci` generator
* Introduce `suspenders:cleanup:organize_gemfile` task

20230113.0 (January, 13, 2023)

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ Custom Suspenders tasks
```
bin/rails suspenders:rake
bin/rails suspenders:db:migrate
bin/rails suspenders:cleanup:organize_gemfile
```

### Email
Expand Down
1 change: 1 addition & 0 deletions lib/suspenders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "suspenders/engine"
require "suspenders/railtie"
require "suspenders/generators"
require "suspenders/cleanup/organize_gemfile"

module Suspenders
# Your code goes here...
Expand Down
134 changes: 134 additions & 0 deletions lib/suspenders/cleanup/organize_gemfile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
module Suspenders
module Cleanup
class OrganizeGemfile
def self.perform(gemfile)
new(gemfile).perform
end

attr_reader :gemfile, :current_lines, :new_lines, :new_line_markers,
:current_group, :gem_groups

def initialize(gemfile)
@gemfile = gemfile

@current_lines = File.read(gemfile).lines
@new_lines = []
@new_line_markers = []

@current_group = nil
@gem_groups = {}
end

def perform
remove_line_breaks
sort_gems_and_groups
add_gem_groups_to_gemfile
add_line_breaks
cleanup

File.open(gemfile, "w+") { _1.write new_lines.join }
end

private

def remove_line_breaks
current_lines.delete("\n")
end

def sort_gems_and_groups
current_lines.each do |line|
if line.starts_with?(/group/)
@current_group = line
end

# Consolidate gem groups
if current_group
if line.starts_with?(/end/)
@current_group = nil
elsif !line.starts_with?(/group/)
gem_groups[current_group] ||= []
gem_groups[current_group] << line
end
# Add non-grouped gems
elsif !line.starts_with?(/\n/)
new_lines << line
@current_group = nil
end
end
end

def add_gem_groups_to_gemfile
gem_groups.keys.each do |group|
gems = gem_groups[group]

gems.each_with_index do |gem, index|
if index == 0
new_lines << group
end

new_lines << gem

if gems.size == (index + 1)
new_lines << "end\n"
end
end
end
end

def add_line_breaks
new_lines.each_with_index do |line, index|
previous_line = new_lines[index - 1] if index > 0
next_line = new_lines[index + 1]
marker = index + 1

# Add line break if it's a gem and the next line is commented out
if (line.starts_with?(/\s*gem/) || line.starts_with?(/\s*\#\s*gem/)) && next_line&.starts_with?(/\s*\#/)
new_line_markers << marker
end

# Add line break if it's a commented out gem and the next line is a gem
if line.starts_with?(/\s*\#\s*gem/) && next_line&.starts_with?(/\s*gem/)
new_line_markers << marker
end

# Add line break if it's a gem with a comment and the next line is a gem
if previous_line&.starts_with?(/\s*\#/) \
&& line.starts_with?(/\s*gem/) \
&& next_line&.starts_with?(/\s*gem/) \
&& !previous_line.starts_with?(/\s*\#\s*gem/)
new_line_markers << marker
end

# Add a line break if it's /end/
if line.starts_with?(/end/)
new_line_markers << marker
end

# Add a line break if it's a gem and the next line is a group
if line.starts_with?(/gem/) && next_line&.starts_with?(/group/)
new_line_markers << marker
end

# Add line break if it's /source/ or /ruby/
if line.starts_with?(/\w/) && !line.starts_with?(/\s*(gem|group|end)/)
new_line_markers << marker
end
end

new_line_markers.each_with_index do |marker, index|
# Each time we insert, the original marker if off by 1
marker_offset = marker + index

new_lines.insert(marker_offset, "\n")
end
end

def cleanup
# Remove last line
if /\n/.match?(new_lines.last)
new_lines.pop
end
end
end
end
end
7 changes: 7 additions & 0 deletions lib/tasks/suspenders.rake
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ namespace :suspenders do
Rake::Task["db:test:prepare"].invoke
end
end

namespace :cleanup do
desc "Organizes Gemfile"
task :organize_gemfile do
Suspenders::Cleanup::OrganizeGemfile.perform(Rails.root.join("Gemfile"))
end
end
end
4 changes: 0 additions & 4 deletions lib/tasks/suspenders_tasks.rake

This file was deleted.

85 changes: 85 additions & 0 deletions test/fixtures/files/gemfile_clean
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
source "https://rubygems.org"

ruby "3.3.0"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.3", ">= 7.1.3.2"

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"

# Use postgresql as the database for Active Record
gem "pg", "~> 1.1"

# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"

# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"

# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"

# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"

# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"

# Use Redis adapter to run Action Cable in production
gem "redis", ">= 4.0.1"

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[windows jruby]

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

gem "cssbundling-rails"
gem "inline_svg"
gem "sidekiq"
gem "title"

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[mri windows]

gem "suspenders", github: "thoughtbot/suspenders", branch: "suspenders-3-0-0-web-generator"
gem "bundler-audit", ">= 0.7.0", require: false
gem "factory_bot_rails"
gem "rspec-rails", "~> 6.1.0"
gem "better_html", require: false
gem "erb_lint", require: false
gem "erblint-github", require: false
gem "standard"
end

group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"

# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"

# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
end

group :test do
gem "capybara_accessibility_audit"
gem "capybara_accessible_selectors", github: "citizensadvice/capybara_accessible_selectors"
gem "capybara"
gem "action_dispatch-testing-integration-capybara", github: "thoughtbot/action_dispatch-testing-integration-capybara", tag: "v0.1.1", require: "action_dispatch/testing/integration/capybara/rspec"
gem "selenium-webdriver"
gem "shoulda-matchers", "~> 6.0"
gem "webmock"
end
101 changes: 101 additions & 0 deletions test/fixtures/files/gemfile_messy
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
source "https://rubygems.org"

ruby "3.3.0"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.3", ">= 7.1.3.2"

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"

# Use postgresql as the database for Active Record
gem "pg", "~> 1.1"

# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"

# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"

# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"

# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"

# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"

# Use Redis adapter to run Action Cable in production
gem "redis", ">= 4.0.1"

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[windows jruby]

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[mri windows]
end

group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"

# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"

# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
end

group :development, :test do
gem "suspenders", github: "thoughtbot/suspenders", branch: "suspenders-3-0-0-web-generator"
end

group :test do
gem "capybara_accessibility_audit"
gem "capybara_accessible_selectors", github: "citizensadvice/capybara_accessible_selectors"
end
gem "cssbundling-rails"

group :development, :test do
gem "bundler-audit", ">= 0.7.0", require: false
end
gem "inline_svg"

group :development, :test do
gem "factory_bot_rails"
end
gem "sidekiq"
gem "title"

group :development, :test do
gem "rspec-rails", "~> 6.1.0"
end

group :test do
gem "capybara"
gem "action_dispatch-testing-integration-capybara", github: "thoughtbot/action_dispatch-testing-integration-capybara", tag: "v0.1.1", require: "action_dispatch/testing/integration/capybara/rspec"
gem "selenium-webdriver"
gem "shoulda-matchers", "~> 6.0"
gem "webmock"
end

group :development, :test do
gem "better_html", require: false
gem "erb_lint", require: false
gem "erblint-github", require: false
gem "standard"
end
23 changes: 23 additions & 0 deletions test/suspenders/cleanup/organize_gemfile_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "test_helper"
require "tempfile"
require_relative "../../../lib/suspenders/cleanup/organize_gemfile"

module Suspenders
module Cleanup
class OrganizeGemfileTest < ActiveSupport::TestCase
test "organizes Gemfile by group" do
original = file_fixture("gemfile_messy").read
modified = file_fixture("gemfile_clean").read

Tempfile.create "Gemfile" do |gemfile|
gemfile.write original
gemfile.rewind

Suspenders::Cleanup::OrganizeGemfile.perform(gemfile.path)

assert_equal modified, gemfile.read
end
end
end
end
end

0 comments on commit f6c6f45

Please sign in to comment.