gempilot 0.2.2 → 0.2.3
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 +4 -4
- data/.rubocop.yml +16 -1
- data/CLAUDE.md +0 -8
- data/issues.rec +29 -2
- data/lib/gempilot/cli/commands/console.rb +2 -3
- data/lib/gempilot/cli/commands/create.rb +6 -3
- data/lib/gempilot/cli/commands/new.rb +2 -3
- data/lib/gempilot/cli/commands/release.rb +2 -3
- data/lib/gempilot/cli/generator.rb +2 -1
- data/lib/gempilot/project.rb +25 -5
- data/lib/gempilot/version.rb +1 -1
- data/vendor/vendored.gemv +0 -0
- metadata +4 -14
- data/docs/command_kit_comparison.md +0 -249
- data/docs/command_kit_reference.md +0 -517
- data/docs/plans/2026-02-18-gempilot-add-command.md +0 -718
- data/docs/superpowers/plans/2026-04-01-rubocop-new-config.md +0 -838
- data/docs/superpowers/plans/2026-04-06-dogfood-inflectable.md +0 -659
- data/docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md +0 -166
- data/docs/superpowers/plans/2026-04-06-integrate-version-tools.md +0 -162
- data/docs/superpowers/plans/2026-04-06-new-readme.md +0 -185
- data/docs/superpowers/plans/2026-06-09-address-review-critiques.md +0 -679
- data/docs/version-management-redesign.md +0 -44
- data/notes.md +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4b895f3da5d0d8866cd13f835dd3c1ca71d8d433571c2d7531735636eeb6ad0
|
|
4
|
+
data.tar.gz: 878dad31a22bdecdb9ff607ebecf2330432070219edfc3e08b66a5022c205d77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ccfc9410fe3ccd3cc8f49e3ca34f68ef334a50183eb95d0788f09c9a5e972fcb4b1244cdfeee188b9898e25ba1c4542d6c4cf24ee24e54edebd4e8b8e907f6a1
|
|
7
|
+
data.tar.gz: e7deb74172aa7bcd75d84f41ad2d80022d9befcd7f1b69990c6f8b9fe6002168d8b25581242dbded279bdd601986c24552b1431573ff0ed62bec8316734e50fe
|
data/.rubocop.yml
CHANGED
|
@@ -15,10 +15,11 @@ plugins:
|
|
|
15
15
|
- rubocop-performance
|
|
16
16
|
- rubocop-rake
|
|
17
17
|
- rubocop-rspec
|
|
18
|
+
- rubocop-design
|
|
18
19
|
|
|
19
20
|
AllCops:
|
|
20
21
|
NewCops: enable
|
|
21
|
-
TargetRubyVersion:
|
|
22
|
+
TargetRubyVersion: 4.0
|
|
22
23
|
Exclude:
|
|
23
24
|
- bin/*
|
|
24
25
|
- vendor/**/*
|
|
@@ -279,3 +280,17 @@ RSpec/NamedSubject:
|
|
|
279
280
|
# have_file_content matcher accepts a filename string in expect()
|
|
280
281
|
RSpec/ExpectActual:
|
|
281
282
|
Enabled: false
|
|
283
|
+
|
|
284
|
+
Design/PositionalArguments:
|
|
285
|
+
Enabled: false
|
|
286
|
+
|
|
287
|
+
Design/KeywordArguments:
|
|
288
|
+
Enabled: false
|
|
289
|
+
|
|
290
|
+
Design/TotalArguments:
|
|
291
|
+
Enabled: false
|
|
292
|
+
|
|
293
|
+
Design/AgentNounClassName:
|
|
294
|
+
Exclude:
|
|
295
|
+
- 'lib/gempilot/generator.rb'
|
|
296
|
+
- 'test/gempilot/cli/generator_test.rb'
|
data/CLAUDE.md
CHANGED
|
@@ -36,11 +36,3 @@ A CLI tool for creating and managing Ruby gems, built on CommandKit.
|
|
|
36
36
|
- GitHub Actions CI workflow (`.github/workflows/ci.yml`)
|
|
37
37
|
- `git ls-files`-based gemspec with glob fallback for non-git repos
|
|
38
38
|
- Version lifecycle rake tasks installed via `Gempilot::VersionTask.new` (a `Rake::TaskLib`): `version:current/bump/commit/tag/untag/reset/revert`, composite `version:release`/`version:unrelease`, and `version:github:release/unrelease/list`
|
|
39
|
-
|
|
40
|
-
### Notes
|
|
41
|
-
- AutoLoad uses block form in `cli.rb` with explicit `summary:` per command (lazy loading means descriptions aren't available at help time without this)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## ISSUES
|
|
45
|
-
|
|
46
|
-
1. RESOLVED — `exe/gempilot` previously had an unconditional `ENV["BUNDLE_GEMFILE"]` + `require "bundler/setup"`, breaking `gem install` usage. It now guards both behind `if File.exist?(gemfile)` (the Ronin pattern), so installed gems skip Bundler while in-repo runs still use it.
|
data/issues.rec
CHANGED
|
@@ -26,7 +26,7 @@ Id: F0833E66-A35C-4054-884B-F90C81F08838
|
|
|
26
26
|
Updated: Mon, 02 Mar 2026 10:37:33 -0500
|
|
27
27
|
Title: Error handling should be graceful
|
|
28
28
|
Description: I did "gempilot create", and entered "A foo" as the title, and it crashed horrifically. This particular example should have validation at the minimum. Need to also use command kit's error bubbling/handling mechanism to swallow errors and print message to user. Can always use verbose or debug mode to see full trace.
|
|
29
|
-
Status:
|
|
29
|
+
Status: closed
|
|
30
30
|
|
|
31
31
|
Id: 741FFD05-9BD7-44EB-BB99-E7D705F12681
|
|
32
32
|
Updated: Mon, 02 Mar 2026 11:25:24 -0500
|
|
@@ -68,7 +68,7 @@ Id: 74D0FD90-0421-4452-8591-2798EE6FD23E
|
|
|
68
68
|
Updated: Sat, 28 Mar 2026 12:59:40 -0400
|
|
69
69
|
Title: Publish gem
|
|
70
70
|
Description: At some point the repo should be moved to public viz, and the gem should be published.
|
|
71
|
-
Status:
|
|
71
|
+
Status: closed
|
|
72
72
|
|
|
73
73
|
Id: 8C356024-3122-11F1-89F3-FE6CB9572C2F
|
|
74
74
|
Updated: Mon, 06 Apr 2026 00:00:00 -0400
|
|
@@ -141,3 +141,30 @@ Description: "
|
|
|
141
141
|
+ ```
|
|
142
142
|
+ "
|
|
143
143
|
Status: closed
|
|
144
|
+
|
|
145
|
+
Id: 1C46F71C-70E9-11F1-B65B-FE6CB9572C2E
|
|
146
|
+
Updated: Thu, 25 Jun 2026 18:56:32 -0400
|
|
147
|
+
Title: Incorrectly creates test file
|
|
148
|
+
Description: When doing `gempilot new Reversal::Server`, in a project called "reversal-store", I get:
|
|
149
|
+
+
|
|
150
|
+
+ gempilot new class Reversal::Server
|
|
151
|
+
+
|
|
152
|
+
+ Adding class Reversal::Server...
|
|
153
|
+
+
|
|
154
|
+
+ create lib/reversal/server.rb
|
|
155
|
+
+ create test/reversal/store_test.rb
|
|
156
|
+
+
|
|
157
|
+
+ The test file is incorrectly named. Should be server_test.rb
|
|
158
|
+
Status: open
|
|
159
|
+
|
|
160
|
+
Id: F5486B48-71DB-11F1-980F-FE6CB9572C2E
|
|
161
|
+
Updated: Fri, 26 Jun 2026 23:54:55 -0400
|
|
162
|
+
Title: Rubymine <=> Minitest usage causes "no reporters allowed error"
|
|
163
|
+
Description: Has to do with how the ENV var RM_INFO is being interpreted
|
|
164
|
+
Status: open
|
|
165
|
+
|
|
166
|
+
Id: 8D847572-7273-11F1-B508-FE6CB9572C2E
|
|
167
|
+
Updated: Sat, 27 Jun 2026 18:00:04 -0400
|
|
168
|
+
Title: Gem extensions are broken
|
|
169
|
+
Description: when using gempilot on a gem extension, like 'foo-support', using idiomatic ruby patterns for dir structure, gempilot Project class throws an error
|
|
170
|
+
Status: open
|
|
@@ -27,9 +27,8 @@ module Gempilot
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def print_console_banner
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
colors.bright_white("...")
|
|
30
|
+
styled_name = colors.bold(colors.cyan(@gem_name))
|
|
31
|
+
puts colors.bright_white("Starting console for ") + styled_name + colors.bright_white("...")
|
|
33
32
|
puts
|
|
34
33
|
end
|
|
35
34
|
end
|
|
@@ -156,7 +156,8 @@ module Gempilot
|
|
|
156
156
|
|
|
157
157
|
puts
|
|
158
158
|
puts colors.bright_black("An executable in exe/ lets users run your gem from the command line.")
|
|
159
|
-
|
|
159
|
+
prompt = colors.green("Create an executable")
|
|
160
|
+
options[:exe] = ask_yes_or_no(prompt, default: false)
|
|
160
161
|
end
|
|
161
162
|
|
|
162
163
|
def collect_git_options
|
|
@@ -169,7 +170,8 @@ module Gempilot
|
|
|
169
170
|
|
|
170
171
|
puts
|
|
171
172
|
puts colors.bright_black("Initialize a git repository with an initial commit.")
|
|
172
|
-
|
|
173
|
+
prompt = colors.green("Initialize git repo")
|
|
174
|
+
options[:git] = ask_yes_or_no(prompt, default: true)
|
|
173
175
|
end
|
|
174
176
|
|
|
175
177
|
def collect_branch
|
|
@@ -181,8 +183,9 @@ module Gempilot
|
|
|
181
183
|
end
|
|
182
184
|
|
|
183
185
|
def print_header
|
|
186
|
+
styled_name = colors.bold(colors.cyan(@gem_name))
|
|
184
187
|
puts
|
|
185
|
-
puts colors.bright_white("Creating gem '") +
|
|
188
|
+
puts colors.bright_white("Creating gem '") + styled_name + colors.bright_white("'...")
|
|
186
189
|
puts
|
|
187
190
|
end
|
|
188
191
|
|
|
@@ -77,10 +77,9 @@ module Gempilot
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def print_adding_banner(kind, label)
|
|
80
|
+
styled_label = colors.bold(colors.cyan(label))
|
|
80
81
|
puts
|
|
81
|
-
puts colors.bright_white("Adding #{kind} ") +
|
|
82
|
-
colors.bold(colors.cyan(label)) +
|
|
83
|
-
colors.bright_white("...")
|
|
82
|
+
puts colors.bright_white("Adding #{kind} ") + styled_label + colors.bright_white("...")
|
|
84
83
|
puts
|
|
85
84
|
end
|
|
86
85
|
|
|
@@ -18,9 +18,8 @@ module Gempilot
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
20
|
def print_release_banner
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
colors.bright_white("...")
|
|
21
|
+
styled_name = colors.bold(colors.cyan(@gem_name))
|
|
22
|
+
puts colors.bright_white("Releasing ") + styled_name + colors.bright_white("...")
|
|
24
23
|
puts
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -37,8 +37,9 @@ module Gempilot
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def print_action(command, dest, source: nil)
|
|
40
|
+
styled_command = colors.bold(colors.green(command))
|
|
40
41
|
line = +""
|
|
41
|
-
line << "\t" <<
|
|
42
|
+
line << "\t" << styled_command
|
|
42
43
|
line << "\t" << colors.green(source) if source
|
|
43
44
|
line << "\t" << colors.green(dest) if dest
|
|
44
45
|
|
data/lib/gempilot/project.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
require "pathname"
|
|
2
1
|
require "warning"
|
|
3
2
|
|
|
4
3
|
module Gempilot
|
|
5
4
|
## Introspects a gem project to discover its name, module, and version.
|
|
5
|
+
## Works for both regular gems (+lib/my_gem.rb+) and extension gems whose
|
|
6
|
+
## entry point nests deeper (+lib/my_gem/extension.rb+ for +my_gem-extension+).
|
|
6
7
|
class Project
|
|
7
8
|
class ProjectIntrospectionError < StandardError; end
|
|
8
9
|
|
|
@@ -29,11 +30,11 @@ module Gempilot
|
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def name
|
|
32
|
-
|
|
33
|
+
project_segments.join("-")
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def klass
|
|
36
|
-
Object.const_get(
|
|
37
|
+
Object.const_get(project_segments.map(&:camelize).join("::"))
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def version
|
|
@@ -67,6 +68,10 @@ module Gempilot
|
|
|
67
68
|
|
|
68
69
|
private
|
|
69
70
|
|
|
71
|
+
def project_segments
|
|
72
|
+
lib_project.relative_path_from(lib).each_filename.to_a
|
|
73
|
+
end
|
|
74
|
+
|
|
70
75
|
def with_version_file
|
|
71
76
|
version.path.open(File::RDWR, 0o644) do |f|
|
|
72
77
|
f.flock File::LOCK_EX
|
|
@@ -76,8 +81,7 @@ module Gempilot
|
|
|
76
81
|
end
|
|
77
82
|
|
|
78
83
|
def fetch_lib_project
|
|
79
|
-
|
|
80
|
-
dirs = files.map { it.sub_ext("") }.select(&:directory?)
|
|
84
|
+
dirs = shallowest_entry_dirs
|
|
81
85
|
case dirs.count
|
|
82
86
|
in 0 then raise ProjectIntrospectionError, "Could not identify project dir"
|
|
83
87
|
in (2..)
|
|
@@ -87,6 +91,22 @@ module Gempilot
|
|
|
87
91
|
end
|
|
88
92
|
end
|
|
89
93
|
|
|
94
|
+
def shallowest_entry_dirs
|
|
95
|
+
entry_dirs.group_by { depth_below_lib(it) }
|
|
96
|
+
.min_by(&:first)
|
|
97
|
+
&.last || []
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def entry_dirs
|
|
101
|
+
lib.glob("**/*.rb")
|
|
102
|
+
.map { it.sub_ext("") }
|
|
103
|
+
.select(&:directory?)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def depth_below_lib(path)
|
|
107
|
+
path.relative_path_from(lib).each_filename.count
|
|
108
|
+
end
|
|
109
|
+
|
|
90
110
|
def fetch_version
|
|
91
111
|
Warning.ignore(REDEFINITION_WARNING)
|
|
92
112
|
Warning.ignore(REINITIALIZATION_WARNING)
|
data/lib/gempilot/version.rb
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gempilot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Gillis
|
|
@@ -106,16 +106,6 @@ files:
|
|
|
106
106
|
- data/templates/gem/test/zeitwerk_test.rb.erb
|
|
107
107
|
- data/templates/new/.keep
|
|
108
108
|
- data/templates/new/command.rb.erb
|
|
109
|
-
- docs/command_kit_comparison.md
|
|
110
|
-
- docs/command_kit_reference.md
|
|
111
|
-
- docs/plans/2026-02-18-gempilot-add-command.md
|
|
112
|
-
- docs/superpowers/plans/2026-04-01-rubocop-new-config.md
|
|
113
|
-
- docs/superpowers/plans/2026-04-06-dogfood-inflectable.md
|
|
114
|
-
- docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md
|
|
115
|
-
- docs/superpowers/plans/2026-04-06-integrate-version-tools.md
|
|
116
|
-
- docs/superpowers/plans/2026-04-06-new-readme.md
|
|
117
|
-
- docs/superpowers/plans/2026-06-09-address-review-critiques.md
|
|
118
|
-
- docs/version-management-redesign.md
|
|
119
109
|
- exe/gempilot
|
|
120
110
|
- issues.rec
|
|
121
111
|
- lib/core_ext/string/inflection_methods.rb
|
|
@@ -140,7 +130,7 @@ files:
|
|
|
140
130
|
- lib/gempilot/version.rb
|
|
141
131
|
- lib/gempilot/version_tag.rb
|
|
142
132
|
- lib/gempilot/version_task.rb
|
|
143
|
-
-
|
|
133
|
+
- vendor/vendored.gemv
|
|
144
134
|
homepage: https://github.com/gillisd/gempilot
|
|
145
135
|
licenses:
|
|
146
136
|
- MIT
|
|
@@ -154,14 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
154
144
|
requirements:
|
|
155
145
|
- - ">="
|
|
156
146
|
- !ruby/object:Gem::Version
|
|
157
|
-
version: '
|
|
147
|
+
version: '4.0'
|
|
158
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
149
|
requirements:
|
|
160
150
|
- - ">="
|
|
161
151
|
- !ruby/object:Gem::Version
|
|
162
152
|
version: '0'
|
|
163
153
|
requirements: []
|
|
164
|
-
rubygems_version: 4.0.
|
|
154
|
+
rubygems_version: 4.0.15
|
|
165
155
|
specification_version: 4
|
|
166
156
|
summary: A toolkit for creating, managing, and releasing your own rubygems
|
|
167
157
|
test_files: []
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
# Gempilot vs Ronin: CommandKit Usage Comparison
|
|
2
|
-
|
|
3
|
-
## How Gempilot Uses CommandKit
|
|
4
|
-
|
|
5
|
-
### Current Usage (after restructuring)
|
|
6
|
-
|
|
7
|
-
| CommandKit Module | Used? | Where |
|
|
8
|
-
|---|---|---|
|
|
9
|
-
| `CommandKit::Commands` | Yes | `Gempilot::CLI` - top-level command dispatcher |
|
|
10
|
-
| `CommandKit::Commands::AutoLoad` | Yes | `Gempilot::CLI` - auto-discovers commands from `cli/commands/` |
|
|
11
|
-
| `CommandKit::Options::Version` | Yes | `Gempilot::CLI` - `--version` flag |
|
|
12
|
-
| `CommandKit::Command` | Yes | `Gempilot::CLI::Command` - base class for all commands |
|
|
13
|
-
| `CommandKit::Colors` | Yes | `Gempilot::CLI::Command` and `Gempilot::CLI::Generator` |
|
|
14
|
-
| `CommandKit::FileUtils` | Yes | `Gempilot::CLI::Generator` - `erb()` method |
|
|
15
|
-
| `CommandKit::Options` | Yes | `Commands::New` - all option definitions (implicit via Command) |
|
|
16
|
-
| `CommandKit::Arguments` | Yes | `Commands::New` - `gem_name` argument (implicit via Command) |
|
|
17
|
-
| `CommandKit::Inflector` | **No** | Hand-rolled `inflect_module` in `Commands::New` instead |
|
|
18
|
-
| `CommandKit::Interactive` | **No** | Not included in base Command |
|
|
19
|
-
| `CommandKit::Description` | Yes | `Commands::New` - `description "Create a new gem"` |
|
|
20
|
-
| `CommandKit::Usage` | Yes | `Commands::New` - `usage "[options] GEM_NAME"` |
|
|
21
|
-
| `CommandKit::Printing` | Yes | Implicit via Command |
|
|
22
|
-
| `CommandKit::Printing::Indent` | **No** | |
|
|
23
|
-
| `CommandKit::Printing::Fields` | **No** | |
|
|
24
|
-
| `CommandKit::Printing::Lists` | **No** | |
|
|
25
|
-
| `CommandKit::Printing::Tables` | **No** | |
|
|
26
|
-
| `CommandKit::Terminal` | **No** | |
|
|
27
|
-
| `CommandKit::Pager` | **No** | |
|
|
28
|
-
| `CommandKit::OS` | **No** | |
|
|
29
|
-
| `CommandKit::XDG` | **No** | |
|
|
30
|
-
| `CommandKit::BugReport` | **No** | |
|
|
31
|
-
| `CommandKit::Help::Man` | **No** | |
|
|
32
|
-
| `CommandKit::Edit` | **No** | |
|
|
33
|
-
| `CommandKit::Examples` | **No** | No examples defined on any command |
|
|
34
|
-
| `CommandKit::ExceptionHandler` | Yes | Implicit via Command |
|
|
35
|
-
|
|
36
|
-
### What Gempilot Hand-Rolls Instead of Using CommandKit
|
|
37
|
-
|
|
38
|
-
1. **Inflector** - `Commands::New#inflect_module` (line 113-115) does:
|
|
39
|
-
```ruby
|
|
40
|
-
def inflect_module(name)
|
|
41
|
-
name.split(/[-_]/).map(&:capitalize).join
|
|
42
|
-
end
|
|
43
|
-
```
|
|
44
|
-
Should use `CommandKit::Inflector.camelize(name)` which handles the same transformation plus edge cases (e.g., `/` to `::` for nested modules).
|
|
45
|
-
|
|
46
|
-
2. **Generator module** - `Gempilot::CLI::Generator` reimplements ronin-core's Generator pattern. This is intentional (own implementation, no ronin-core dependency), but it duplicates logic that `CommandKit::FileUtils` already partly provides (`erb` method). The Generator adds `print_action`, `mkdir`, `cp`, `sh`, `touch`, `chmod` wrappers with colored output.
|
|
47
|
-
|
|
48
|
-
3. **Shell execution** - `Gempilot::Commands#sh` uses a custom `Runner` class with PTY/Open3 pipeline for command execution. The Generator module has its own simpler `sh` that calls `system()`. Two different shell execution paths exist.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## How Ronin Uses CommandKit (Reference Implementation)
|
|
53
|
-
|
|
54
|
-
### Module Usage
|
|
55
|
-
|
|
56
|
-
| CommandKit Module | Used? | Where |
|
|
57
|
-
|---|---|---|
|
|
58
|
-
| `CommandKit::Commands` | Yes | `Ronin::CLI` |
|
|
59
|
-
| `CommandKit::Commands::AutoLoad` | Yes | `Ronin::CLI`, `Commands::New`, and many others |
|
|
60
|
-
| `CommandKit::Options::Version` | Yes | `Ronin::CLI` |
|
|
61
|
-
| `CommandKit::Command` | Yes (via ronin-core) | `Core::CLI::Command` base class |
|
|
62
|
-
| `CommandKit::Colors` | Yes | `Core::CLI::Generator`, syntax highlighting |
|
|
63
|
-
| `CommandKit::FileUtils` | Yes | `Core::CLI::Generator` |
|
|
64
|
-
| `CommandKit::Options` | Yes | Every command |
|
|
65
|
-
| `CommandKit::Arguments` | Yes | Every command |
|
|
66
|
-
| `CommandKit::Interactive` | **No** | Not used in ronin itself |
|
|
67
|
-
| `CommandKit::Description` | Yes | Every command |
|
|
68
|
-
| `CommandKit::Usage` | Yes | Every command |
|
|
69
|
-
| `CommandKit::Examples` | Yes | Most commands have examples |
|
|
70
|
-
| `CommandKit::Printing` | Yes | Implicit via Command |
|
|
71
|
-
| `CommandKit::Printing::Indent` | Yes | Output formatting |
|
|
72
|
-
| `CommandKit::Printing::Tables` | Yes | Database query output |
|
|
73
|
-
| `CommandKit::Terminal` | Yes | Width-aware output |
|
|
74
|
-
| `CommandKit::Pager` | Yes | Long output paging |
|
|
75
|
-
| `CommandKit::OS` | Yes | OS-specific behavior |
|
|
76
|
-
| `CommandKit::BugReport` | Yes | `Core::CLI::Command` base class |
|
|
77
|
-
| `CommandKit::Help::Man` | Yes | Every command has `man_page` |
|
|
78
|
-
| `CommandKit::Inflector` | Yes | Used internally for command name derivation |
|
|
79
|
-
|
|
80
|
-
### Ronin Patterns Worth Adopting
|
|
81
|
-
|
|
82
|
-
1. **Every command has `examples`** - Ronin defines usage examples on almost every command:
|
|
83
|
-
```ruby
|
|
84
|
-
examples [
|
|
85
|
-
'project foo',
|
|
86
|
-
'script foo.rb'
|
|
87
|
-
]
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
2. **`bug_report_url` on base command** - Set once, inherited everywhere:
|
|
91
|
-
```ruby
|
|
92
|
-
class Command < Core::CLI::Command
|
|
93
|
-
bug_report_url 'https://github.com/ronin-rb/ronin/issues/new'
|
|
94
|
-
end
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
3. **Man pages** - Every command declares `man_page 'ronin-command.1'` for full documentation.
|
|
98
|
-
|
|
99
|
-
4. **Nested AutoLoad** - The `new` command is itself a Commands container with AutoLoad:
|
|
100
|
-
```ruby
|
|
101
|
-
class New < Command
|
|
102
|
-
include CommandKit::Commands::AutoLoad.new(
|
|
103
|
-
dir: "#{__dir__}/new",
|
|
104
|
-
namespace: "#{self}"
|
|
105
|
-
)
|
|
106
|
-
end
|
|
107
|
-
```
|
|
108
|
-
This allows `ronin new project`, `ronin new script`, etc. Each subcommand is a separate file in `commands/new/`.
|
|
109
|
-
|
|
110
|
-
5. **Core::CLI::Generator** builds on CommandKit::FileUtils - It adds `template_dir`, `print_action`, and wrappers for `mkdir`, `cp`, `erb`, `chmod`, `sh`. Gempilot's Generator follows this pattern.
|
|
111
|
-
|
|
112
|
-
6. **Consistent use of Inflector** - Ronin never hand-rolls string case conversion.
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Side-by-Side: CLI Entry Points
|
|
117
|
-
|
|
118
|
-
### Gempilot
|
|
119
|
-
```ruby
|
|
120
|
-
# lib/gempilot/cli.rb
|
|
121
|
-
class Gempilot::CLI
|
|
122
|
-
include CommandKit::Commands
|
|
123
|
-
include CommandKit::Commands::AutoLoad.new(
|
|
124
|
-
dir: "#{__dir__}/cli/commands",
|
|
125
|
-
namespace: "#{self}::Commands"
|
|
126
|
-
)
|
|
127
|
-
include CommandKit::Options::Version
|
|
128
|
-
|
|
129
|
-
command_name "gempilot"
|
|
130
|
-
version Gempilot::VERSION
|
|
131
|
-
end
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Ronin
|
|
135
|
-
```ruby
|
|
136
|
-
# lib/ronin/cli.rb
|
|
137
|
-
class Ronin::CLI
|
|
138
|
-
include CommandKit::Commands
|
|
139
|
-
include CommandKit::Commands::AutoLoad.new(
|
|
140
|
-
dir: "#{__dir__}/cli/commands",
|
|
141
|
-
namespace: "#{self}::Commands"
|
|
142
|
-
)
|
|
143
|
-
include CommandKit::Options::Version
|
|
144
|
-
include Core::CLI::Help::Banner
|
|
145
|
-
|
|
146
|
-
command_name 'ronin'
|
|
147
|
-
version Ronin::VERSION
|
|
148
|
-
|
|
149
|
-
command_aliases['enc'] = 'encode'
|
|
150
|
-
command_aliases['dec'] = 'decode'
|
|
151
|
-
end
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
**Verdict:** Nearly identical. Gempilot follows the pattern correctly. Ronin adds `command_aliases` and a help banner, both nice touches.
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## Side-by-Side: Generator Commands
|
|
159
|
-
|
|
160
|
-
### Gempilot - `Commands::New`
|
|
161
|
-
```ruby
|
|
162
|
-
class New < Command
|
|
163
|
-
include Generator
|
|
164
|
-
|
|
165
|
-
template_dir File.join(Gempilot::ROOT, "data", "templates", "gem")
|
|
166
|
-
|
|
167
|
-
option :summary, value: { type: String }, desc: "Gem summary"
|
|
168
|
-
option :test, value: { type: { "minitest" => :minitest, "rspec" => :rspec }, default: :minitest }, desc: "Test framework"
|
|
169
|
-
argument :gem_name, required: true, desc: "Name of the gem"
|
|
170
|
-
|
|
171
|
-
def run(gem_name)
|
|
172
|
-
@gem_name = gem_name
|
|
173
|
-
@module_name = inflect_module(gem_name)
|
|
174
|
-
mkdir gem_name
|
|
175
|
-
erb "gemspec.erb", "#{gem_name}/#{gem_name}.gemspec"
|
|
176
|
-
# ...
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Ronin - `Commands::New::Project`
|
|
182
|
-
```ruby
|
|
183
|
-
class Project < Command
|
|
184
|
-
include Core::CLI::Generator
|
|
185
|
-
|
|
186
|
-
template_dir File.join(ROOT, 'data', 'templates', 'project')
|
|
187
|
-
|
|
188
|
-
option :git, desc: 'Initializes a git repo'
|
|
189
|
-
option :ruby_version, value: { type: String, usage: 'VERSION', default: RUBY_VERSION }, desc: '...'
|
|
190
|
-
argument :path, required: true, desc: 'The directory to create'
|
|
191
|
-
|
|
192
|
-
description 'Creates a new Ruby project directory'
|
|
193
|
-
man_page 'ronin-new-project.1'
|
|
194
|
-
|
|
195
|
-
def run(path)
|
|
196
|
-
@project_name = File.basename(path)
|
|
197
|
-
@ruby_version = options[:ruby_version]
|
|
198
|
-
mkdir path
|
|
199
|
-
erb 'Gemfile.erb', File.join(path, 'Gemfile')
|
|
200
|
-
# ...
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
**Verdict:** Same pattern. Gempilot's version is correct. Ronin adds `man_page` declaration and uses `usage:` on option values for better help output.
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## Grading: Gempilot's Idiomatic Use of CommandKit
|
|
210
|
-
|
|
211
|
-
### Grade: **B+**
|
|
212
|
-
|
|
213
|
-
Gempilot's restructured codebase follows the command_kit patterns well. The CLI entry, base command, AutoLoad, option/argument definitions, and Generator module all match the Ronin reference closely.
|
|
214
|
-
|
|
215
|
-
### What Earns the Grade
|
|
216
|
-
|
|
217
|
-
**Done right:**
|
|
218
|
-
- CLI with `Commands` + `AutoLoad` - textbook implementation
|
|
219
|
-
- Base `Command` class with `Colors` - follows the pattern
|
|
220
|
-
- Generator module following ronin-core's design
|
|
221
|
-
- Option definitions with `value:` Hash, proper types
|
|
222
|
-
- Argument definitions with `required: true`
|
|
223
|
-
- `template_dir` class method pattern
|
|
224
|
-
- `description` and `usage` on commands
|
|
225
|
-
- ERB templates with instance variable binding
|
|
226
|
-
- Executable entry point: thin `CLI.start`
|
|
227
|
-
|
|
228
|
-
**Holding it back from A:**
|
|
229
|
-
|
|
230
|
-
1. **`CommandKit::Inflector` not used** - Hand-rolled `inflect_module` instead of `Inflector.camelize`. This is specifically called out in CLAUDE.md issue #2.
|
|
231
|
-
|
|
232
|
-
2. **No `CommandKit::Interactive`** - CLAUDE.md issue #1 asks for interactive prompting when arguments are missing. `CommandKit::Interactive` provides `ask`, `ask_yes_or_no`, `ask_multiple_choice` - exactly what's needed.
|
|
233
|
-
|
|
234
|
-
3. **No `examples` on any command** - Ronin defines examples on every command. Gempilot defines none. Easy win for help output.
|
|
235
|
-
|
|
236
|
-
4. **No `bug_report_url`** - Low effort, high value for user experience.
|
|
237
|
-
|
|
238
|
-
5. **Two shell execution paths** - `Generator#sh` (uses `system`) and `Commands#sh` / `Runner` (uses Open3 pipeline with PTY). Should converge.
|
|
239
|
-
|
|
240
|
-
6. **`CommandMapper` integration unclear** - `CommandMappers::Bundle` is a 1091-line DSL that's not wired into any command yet. The `Commands::New#run` calls `sh 'bundle', 'install'` directly instead of using the typed DSL.
|
|
241
|
-
|
|
242
|
-
### What Would Make It an A
|
|
243
|
-
|
|
244
|
-
1. Replace `inflect_module` with `CommandKit::Inflector.camelize`
|
|
245
|
-
2. Include `CommandKit::Interactive` in base Command, use it for missing-argument prompting
|
|
246
|
-
3. Add `examples` to `Commands::New`
|
|
247
|
-
4. Add `bug_report_url` to base Command
|
|
248
|
-
5. Wire `CommandMappers::Bundle` into the `new` command for `bundle install` / `bundle config` operations
|
|
249
|
-
6. Add `CommandKit::Printing::Indent` for structured output during generation
|