bundle_alphabetically 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/README.md +95 -0
- data/lib/bundle_alphabetically/common.rb +71 -0
- data/lib/bundle_alphabetically/gemfile_sorter.rb +174 -0
- data/lib/bundle_alphabetically/group.rb +148 -0
- data/lib/bundle_alphabetically/version.rb +5 -0
- data/lib/bundle_alphabetically.rb +25 -0
- data/plugins.rb +3 -0
- metadata +77 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 78e0866723d9bd83432fbb9477da3181b8159e1d72c57e128b3d1d2d208bc0d4
|
|
4
|
+
data.tar.gz: 5d2b084f5b292ed3fccf27313171ba00a1b57faa908c1ef617b83049ec0f0a7a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0bb63059900621a58af5939cb6a97cd3960e5c66b574978ff25763fa8ecc983a75b502eda3640aad95c19296e3170f6247084566f5c4d35793f1c5cb207a63c9
|
|
7
|
+
data.tar.gz: 80fe2cc000aec12bcfde0cff162184a8e0a16e2e1db6f3ee27f0dd7c75e4ea9a02f7ac9fddbc57e4bca5d318f5c163d9d720a97f7d2deb289954f9cd92fbf114
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-12-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release
|
|
12
|
+
- Automatic sorting after `bundle add`/`bundle install`
|
|
13
|
+
- Manual `bundle sort_gemfile` command
|
|
14
|
+
- `--check` flag for CI verification
|
|
15
|
+
- Sorts gems alphabetically within each group
|
|
16
|
+
- Preserves group structure, comments, and formatting
|
data/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
## bundle_alphabetically
|
|
2
|
+
|
|
3
|
+
**bundle_alphabetically** is a Bundler plugin that keeps your `Gemfile` organized by alphabetizing `gem` declarations **within each group**.
|
|
4
|
+
|
|
5
|
+
- **Automatic mode**: after `bundle add` / `bundle install`, it rewrites your `Gemfile` so gems are sorted alphabetically inside each group.
|
|
6
|
+
- **Manual mode**: run `bundle sort_gemfile` whenever you want to clean things up (with an optional `--check` for CI).
|
|
7
|
+
|
|
8
|
+
### Installation
|
|
9
|
+
|
|
10
|
+
Install the plugin from RubyGems:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bundle plugin install bundle_alphabetically
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or add it to your `Gemfile` and install:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
plugin "bundle_alphabetically"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For development, you can install from a local checkout:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd /path/to/bundle_alphabetically
|
|
30
|
+
bundle plugin install bundle_alphabetically --path .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Once installed, Bundler will load `plugins.rb` and register the command and hooks.
|
|
34
|
+
|
|
35
|
+
### What it does
|
|
36
|
+
|
|
37
|
+
- Sorts `gem` entries alphabetically by name **within each context**:
|
|
38
|
+
- top-level (no group)
|
|
39
|
+
- each `group :name do ... end` block, separately
|
|
40
|
+
- Preserves:
|
|
41
|
+
- non-gem lines like `source`, `ruby`, `plugin`, `path`, etc.
|
|
42
|
+
- group block structure and indentation
|
|
43
|
+
- comments and most surrounding blank lines
|
|
44
|
+
- multi-line `gem` declarations (e.g. options hashes split across lines)
|
|
45
|
+
|
|
46
|
+
It operates on the current `Bundler.default_gemfile` and rewrites it in place.
|
|
47
|
+
|
|
48
|
+
### Automatic sorting (hook)
|
|
49
|
+
|
|
50
|
+
The plugin registers an `after-install-all` hook:
|
|
51
|
+
|
|
52
|
+
- After a successful install (including `bundle add`), Bundler calls into `bundle_alphabetically`.
|
|
53
|
+
- The plugin sorts the `Gemfile` and prints a short message like:
|
|
54
|
+
- `Gemfile gems alphabetized by bundle_alphabetically`
|
|
55
|
+
|
|
56
|
+
If the `Gemfile` is already sorted, it does nothing.
|
|
57
|
+
|
|
58
|
+
If it encounters an error parsing the `Gemfile`, it raises a `Bundler::BundlerError` and reports a message via `Bundler.ui.error`, but leaves the file unchanged.
|
|
59
|
+
|
|
60
|
+
### Manual command
|
|
61
|
+
|
|
62
|
+
The plugin adds a `bundle sort_gemfile` command via `Bundler::Plugin::API`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bundle sort_gemfile
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- Sorts the current `Gemfile` in place using the same rules as the hook.
|
|
69
|
+
- Prints `Gemfile already sorted` if no changes were needed.
|
|
70
|
+
|
|
71
|
+
For CI, you can use `--check` (or `-c`) to verify sorting without modifying the file:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bundle sort_gemfile --check
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
In `--check` mode:
|
|
78
|
+
|
|
79
|
+
- Exit successfully if the `Gemfile` is already sorted.
|
|
80
|
+
- Raise `Bundler::BundlerError` (non-zero exit) if changes would be required.
|
|
81
|
+
|
|
82
|
+
### Limitations
|
|
83
|
+
|
|
84
|
+
- Designed for conventional `Gemfile`s:
|
|
85
|
+
- top-level `gem` calls and `group ... do` blocks
|
|
86
|
+
- straightforward multi-line `gem` entries
|
|
87
|
+
- It does **not** attempt to fully evaluate arbitrary Ruby or heavy metaprogramming inside the `Gemfile`.
|
|
88
|
+
|
|
89
|
+
If you have an unusually dynamic `Gemfile` and hit issues, you can temporarily uninstall the plugin with:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bundler plugin uninstall bundle_alphabetically
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module BundleAlphabetically
|
|
2
|
+
module Common
|
|
3
|
+
def collect_gem_entry(lines, start_index, body_end_index = nil)
|
|
4
|
+
body_end_index ||= lines.length - 1
|
|
5
|
+
formatting_lines = []
|
|
6
|
+
|
|
7
|
+
while start_index <= body_end_index && start_index < lines.length && blank_or_comment?(lines[start_index])
|
|
8
|
+
formatting_lines << lines[start_index]
|
|
9
|
+
start_index += 1
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
if start_index > body_end_index || start_index >= lines.length
|
|
13
|
+
return [formatting_lines, [], start_index]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
entry_lines = [lines[start_index]]
|
|
17
|
+
base_indent = indent_of(lines[start_index])
|
|
18
|
+
i = start_index + 1
|
|
19
|
+
|
|
20
|
+
while i <= body_end_index && i < lines.length
|
|
21
|
+
line = lines[i]
|
|
22
|
+
stripped = line.lstrip
|
|
23
|
+
indent = indent_of(line)
|
|
24
|
+
|
|
25
|
+
break if stripped.empty?
|
|
26
|
+
|
|
27
|
+
if blank_or_comment?(line) && indent <= base_indent
|
|
28
|
+
break
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if indent <= base_indent && starter_keyword?(stripped)
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
entry_lines << line
|
|
36
|
+
i += 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
[formatting_lines, entry_lines, i]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def blank_or_comment?(line)
|
|
43
|
+
line.strip.empty? || line.strip.start_with?("#")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def indent_of(line)
|
|
47
|
+
line[/\A[ \t]*/].size
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def starter_keyword?(stripped)
|
|
51
|
+
stripped.start_with?(
|
|
52
|
+
"gem ", "gem(", "group ", "group(", "source ", "source(", "ruby ",
|
|
53
|
+
"ruby(", "path ", "path(", "plugin ", "plugin(", "platforms ",
|
|
54
|
+
"platforms(", "end", "gemspec", "git ", "git(", "if ", "else",
|
|
55
|
+
"local_gemfile", "instance_eval", "rack_version"
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def gem_start?(line)
|
|
60
|
+
return false if blank_or_comment?(line)
|
|
61
|
+
line.lstrip.start_with?("gem ") || line.lstrip.start_with?("gem(")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def extract_gem_name(line)
|
|
65
|
+
stripped = line.lstrip
|
|
66
|
+
match = stripped.match(/\Agem\s+["']([^"']+)["']/)
|
|
67
|
+
match && match[1]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
require "bundler"
|
|
2
|
+
require "set"
|
|
3
|
+
|
|
4
|
+
require_relative "group"
|
|
5
|
+
require_relative "common"
|
|
6
|
+
|
|
7
|
+
module BundleAlphabetically
|
|
8
|
+
class GemfileSorter
|
|
9
|
+
extend Common
|
|
10
|
+
|
|
11
|
+
class CheckFailed < Bundler::BundlerError
|
|
12
|
+
def status_code
|
|
13
|
+
1
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
attr_accessor :groups, :lines
|
|
19
|
+
|
|
20
|
+
def run!(check: false, io: $stdout)
|
|
21
|
+
gemfile = Bundler.default_gemfile
|
|
22
|
+
|
|
23
|
+
unless gemfile && File.file?(gemfile.to_s)
|
|
24
|
+
raise Bundler::BundlerError, "Gemfile not found at #{gemfile}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
path = gemfile.to_s
|
|
28
|
+
original = File.read(path)
|
|
29
|
+
sorted = sort_contents(original)
|
|
30
|
+
|
|
31
|
+
if original == sorted
|
|
32
|
+
io.puts "Gemfile already sorted" unless check
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if check
|
|
37
|
+
raise CheckFailed, "Gemfile is not alphabetically sorted"
|
|
38
|
+
else
|
|
39
|
+
File.write(path, sorted)
|
|
40
|
+
io.puts "Gemfile gems alphabetized by bundle_alphabetically"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sort_contents(contents)
|
|
45
|
+
@groups = []
|
|
46
|
+
@lines = contents.lines
|
|
47
|
+
return contents if lines.empty?
|
|
48
|
+
|
|
49
|
+
collect_groups!
|
|
50
|
+
sort_groups!
|
|
51
|
+
|
|
52
|
+
lines.join
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def sort_groups!
|
|
58
|
+
groups.each do |group|
|
|
59
|
+
group.sort!
|
|
60
|
+
lines[group.body_start_index..group.body_end_index] = group.body if group.body
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def collect_groups!
|
|
65
|
+
i = 0
|
|
66
|
+
current_group_start = nil
|
|
67
|
+
|
|
68
|
+
while i < lines.size
|
|
69
|
+
# Peek ahead of blanks/comments to find next group
|
|
70
|
+
peek_index = i
|
|
71
|
+
while peek_index < lines.size && blank_or_comment?(lines[peek_index])
|
|
72
|
+
peek_index += 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if peek_index < lines.size && group_header?(lines[peek_index])
|
|
76
|
+
|
|
77
|
+
# Close current group
|
|
78
|
+
if current_group_start
|
|
79
|
+
process_gem_block(current_group_start, peek_index - 1)
|
|
80
|
+
current_group_start = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
i = process_group_block(peek_index)
|
|
84
|
+
next
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 3. Not a group. Consume the next "entry" (gem, source, ruby, etc.)
|
|
88
|
+
_, entry_lines, next_index = collect_gem_entry(lines, i)
|
|
89
|
+
|
|
90
|
+
if gem_entry?(entry_lines)
|
|
91
|
+
current_group_start ||= i
|
|
92
|
+
else
|
|
93
|
+
# We hit something that isn't a gem and isn't a group (e.g. source, ruby version, etc.)
|
|
94
|
+
process_gem_block(current_group_start, i - 1) if current_group_start
|
|
95
|
+
current_group_start = nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
i = next_index
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Finish any trailing gem block
|
|
102
|
+
process_gem_block(current_group_start, lines.size - 1) if current_group_start
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def process_group_block(header_index)
|
|
106
|
+
end_index = find_matching_end(lines, header_index)
|
|
107
|
+
|
|
108
|
+
# If we can't find an end, just skip this line
|
|
109
|
+
return header_index + 1 unless end_index
|
|
110
|
+
|
|
111
|
+
@groups << Group.new(lines, {
|
|
112
|
+
header: header_index,
|
|
113
|
+
body_start: header_index + 1,
|
|
114
|
+
body_end: end_index - 1,
|
|
115
|
+
end: end_index
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
end_index + 1
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def group_header?(line)
|
|
122
|
+
stripped = line.lstrip
|
|
123
|
+
stripped.start_with?("group ") || stripped.start_with?("group(")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def find_matching_end(lines, header_index)
|
|
127
|
+
header_indent = indent_of(lines[header_index])
|
|
128
|
+
i = header_index + 1
|
|
129
|
+
|
|
130
|
+
while i < lines.length
|
|
131
|
+
line = lines[i]
|
|
132
|
+
stripped = line.lstrip
|
|
133
|
+
indent = indent_of(line)
|
|
134
|
+
|
|
135
|
+
if group_header?(line) && indent > header_indent
|
|
136
|
+
nested_end = find_matching_end(lines, i)
|
|
137
|
+
return nil unless nested_end
|
|
138
|
+
i = nested_end + 1
|
|
139
|
+
next
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if stripped.start_with?("end") && indent == header_indent
|
|
143
|
+
return i
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
i += 1
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def gem_entry?(entry_lines)
|
|
153
|
+
entry_lines.any? && gem_start?(entry_lines.first)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def process_gem_block(start_index, end_index)
|
|
157
|
+
return unless start_index
|
|
158
|
+
|
|
159
|
+
create_and_sort_group(lines, start_index, end_index)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def create_and_sort_group(lines, start_index, end_index)
|
|
163
|
+
return if start_index >= end_index
|
|
164
|
+
|
|
165
|
+
@groups << Group.new(lines, {
|
|
166
|
+
header: start_index,
|
|
167
|
+
body_start: start_index,
|
|
168
|
+
body_end: end_index,
|
|
169
|
+
end: end_index
|
|
170
|
+
})
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require_relative "common"
|
|
2
|
+
|
|
3
|
+
class BundleAlphabetically::Group
|
|
4
|
+
include BundleAlphabetically::Common
|
|
5
|
+
|
|
6
|
+
attr_accessor :header_index, :body_start_index, :body_end_index, :end_index, :unsortable, :entries, :lines, :trailing_lines, :leading_lines
|
|
7
|
+
|
|
8
|
+
def initialize(lines, indices = {})
|
|
9
|
+
@lines = lines
|
|
10
|
+
@header_index = indices[:header]
|
|
11
|
+
@body_start_index = indices[:body_start]
|
|
12
|
+
@body_end_index = indices[:body_end]
|
|
13
|
+
@end_index = indices[:end]
|
|
14
|
+
@entries = []
|
|
15
|
+
@unsortable = false
|
|
16
|
+
@trailing_lines = []
|
|
17
|
+
@leading_lines = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def sort!
|
|
21
|
+
return unless valid?
|
|
22
|
+
|
|
23
|
+
find_entries
|
|
24
|
+
sort_entries!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def sort_entries!
|
|
28
|
+
return if unsortable || entries.empty?
|
|
29
|
+
|
|
30
|
+
collect_trailing_lines
|
|
31
|
+
collect_leading_lines
|
|
32
|
+
normalize_formatting_lines
|
|
33
|
+
|
|
34
|
+
sorted_entries = entries.sort_by { |e| e[:name].downcase }
|
|
35
|
+
|
|
36
|
+
self.body = rebuild_body(sorted_entries)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def rebuild_body(sorted_entries)
|
|
40
|
+
new_body = []
|
|
41
|
+
|
|
42
|
+
leading_lines.each { |line| new_body << line } if !leading_separator_exists?
|
|
43
|
+
|
|
44
|
+
sorted_entries.each_with_index do |entry, index|
|
|
45
|
+
# Always move leading blanks for the first entry
|
|
46
|
+
move_leading_blanks = entry[:formatting_lines].empty? || index.zero?
|
|
47
|
+
|
|
48
|
+
new_body = append_entry(entry, new_body, move_leading_blanks)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
trailing_lines.each { |line| new_body << line } if trailing_lines.any?
|
|
52
|
+
|
|
53
|
+
new_body
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# If an entry has no comments/formatting (just a plain gem), treat blanks as trailing
|
|
57
|
+
# separators by placing them after the entry. This keeps "section" spacing stable when
|
|
58
|
+
# gems reorder.
|
|
59
|
+
def append_entry(entry, new_body, move_leading_blanks)
|
|
60
|
+
leading_blanks = entry[:leading_blanks] || []
|
|
61
|
+
|
|
62
|
+
leading_blanks.each { |line| new_body << line } unless move_leading_blanks
|
|
63
|
+
|
|
64
|
+
entry[:formatting_lines].each { |line| new_body << line }
|
|
65
|
+
entry[:lines].each { |line| new_body << line }
|
|
66
|
+
|
|
67
|
+
leading_blanks.each { |line| new_body << line } if move_leading_blanks
|
|
68
|
+
|
|
69
|
+
new_body
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def normalize_formatting_lines
|
|
73
|
+
entries.each do |entry|
|
|
74
|
+
entry[:leading_blanks] = []
|
|
75
|
+
next unless entry[:formatting_lines]&.any?
|
|
76
|
+
|
|
77
|
+
while entry[:formatting_lines].any? && entry[:formatting_lines].first.strip.empty?
|
|
78
|
+
entry[:leading_blanks] << entry[:formatting_lines].shift
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# If the group starts with blank lines (e.g. spacing after `plugin` / `gemspec`),
|
|
84
|
+
# keep them at the start of the group rather than attaching them to a gem
|
|
85
|
+
# that might move during sorting.
|
|
86
|
+
def collect_leading_lines
|
|
87
|
+
return unless entries.first[:formatting_lines]&.any?
|
|
88
|
+
|
|
89
|
+
while entries.first[:formatting_lines].any? && entries.first[:formatting_lines].first.strip.empty?
|
|
90
|
+
@leading_lines << entries.first[:formatting_lines].shift
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Don't sort trailing comments and blank lines
|
|
95
|
+
def collect_trailing_lines
|
|
96
|
+
return unless entries.last[:name].empty? && entries.last[:lines].empty?
|
|
97
|
+
|
|
98
|
+
@trailing_lines = entries.pop[:formatting_lines]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# If we're starting from the top or the prior group ended with a blank line,
|
|
102
|
+
# we don't need to move leading blank lines before the first entry.
|
|
103
|
+
def leading_separator_exists?
|
|
104
|
+
!body_start_index.positive? || lines[body_start_index - 1].strip.empty?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def find_entries
|
|
108
|
+
index = body_start_index
|
|
109
|
+
|
|
110
|
+
while index <= body_end_index
|
|
111
|
+
formatting_lines, entry_lines, after_entry_index = collect_gem_entry(lines, index, body_end_index)
|
|
112
|
+
|
|
113
|
+
break if formatting_lines.empty? && entry_lines.empty?
|
|
114
|
+
|
|
115
|
+
# If we haven't advanced, force a break to avoid infinite loops
|
|
116
|
+
break if after_entry_index == index
|
|
117
|
+
|
|
118
|
+
if entry_lines.any?
|
|
119
|
+
name = extract_gem_name(entry_lines.first)
|
|
120
|
+
|
|
121
|
+
if name
|
|
122
|
+
entries << { name: name, lines: entry_lines, formatting_lines: formatting_lines }
|
|
123
|
+
else
|
|
124
|
+
@unsortable = true
|
|
125
|
+
entries << { name: "", lines: entry_lines, formatting_lines: formatting_lines }
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
# Trailing formatting lines
|
|
129
|
+
entries << { name: "", lines: [], formatting_lines: formatting_lines }
|
|
130
|
+
@unsortable = true unless formatting_lines.all? { |l| l.strip.empty? }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
index = after_entry_index
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def valid?
|
|
138
|
+
body_end_index > body_start_index
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def body
|
|
142
|
+
@body ||= lines[body_start_index..body_end_index]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def body=(new_lines)
|
|
146
|
+
@body = new_lines
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "bundler"
|
|
2
|
+
require "bundler/plugin/api"
|
|
3
|
+
|
|
4
|
+
require_relative "bundle_alphabetically/version"
|
|
5
|
+
require_relative "bundle_alphabetically/gemfile_sorter"
|
|
6
|
+
require_relative "bundle_alphabetically/group"
|
|
7
|
+
|
|
8
|
+
module BundleAlphabetically
|
|
9
|
+
class SortGemfileCommand < Bundler::Plugin::API
|
|
10
|
+
command "sort_gemfile"
|
|
11
|
+
|
|
12
|
+
def exec(_command, args)
|
|
13
|
+
check = args.include?("--check") || args.include?("-c")
|
|
14
|
+
GemfileSorter.run!(check: check)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Bundler::Plugin.add_hook("after-install-all") do |_dependencies|
|
|
20
|
+
begin
|
|
21
|
+
BundleAlphabetically::GemfileSorter.run!
|
|
22
|
+
rescue Bundler::BundlerError => e
|
|
23
|
+
Bundler.ui.error("bundle_alphabetically: #{e.message}")
|
|
24
|
+
end
|
|
25
|
+
end
|
data/plugins.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bundle_alphabetically
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Elijah Rogers
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
description: A Bundler plugin that keeps your Gemfile organized by alphabetizing gem
|
|
41
|
+
declarations within each group automatically.
|
|
42
|
+
executables: []
|
|
43
|
+
extensions: []
|
|
44
|
+
extra_rdoc_files: []
|
|
45
|
+
files:
|
|
46
|
+
- CHANGELOG.md
|
|
47
|
+
- README.md
|
|
48
|
+
- lib/bundle_alphabetically.rb
|
|
49
|
+
- lib/bundle_alphabetically/common.rb
|
|
50
|
+
- lib/bundle_alphabetically/gemfile_sorter.rb
|
|
51
|
+
- lib/bundle_alphabetically/group.rb
|
|
52
|
+
- lib/bundle_alphabetically/version.rb
|
|
53
|
+
- plugins.rb
|
|
54
|
+
homepage: https://github.com/elijahrogers/bundle_alphabetically
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
57
|
+
metadata:
|
|
58
|
+
source_code_uri: https://github.com/elijahrogers/bundle_alphabetically
|
|
59
|
+
changelog_uri: https://github.com/elijahrogers/bundle_alphabetically/blob/main/CHANGELOG.md
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.7'
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 4.0.3
|
|
75
|
+
specification_version: 4
|
|
76
|
+
summary: Bundler plugin that alphabetizes gem entries
|
|
77
|
+
test_files: []
|