lux-hammer 0.2.3 → 0.2.5
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/.version +1 -1
- data/AGENTS.md +294 -0
- data/README.md +34 -32
- data/bin/hammer +7 -6
- data/lib/hammer/builder.rb +2 -2
- data/lib/hammer/command_builder.rb +1 -1
- data/lib/lux-hammer.rb +78 -19
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb668fabef5892decb2ee58add685f52dad7fe742ae0cd8597ba62da576c3d13
|
|
4
|
+
data.tar.gz: f74beef4ccb0af5aaa546c7c4a2dec29bc08fffdb929edfa5f3dcdf261627b69
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fddfa7b7f657cb1baa30793e46fe331cd3f65704ac31ce582bcfc851b154a15eb72e5610e0a0b8af29ed99a763823f7c7bbdd1ef7ad33f40b95fcd803a6216f
|
|
7
|
+
data.tar.gz: 6b176bb61def54c305a28f0c9b9419074ed16c14c1b8412ccc084cd2ed5f1a0bfa93d947aac9fcce704cde00c2307bb897e0255e200f1bf1bb077f2795f40330
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.5
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# AGENTS.md - lux-hammer
|
|
2
|
+
|
|
3
|
+
Conventions and constraints for AI agents working on this gem.
|
|
4
|
+
|
|
5
|
+
## What this gem is
|
|
6
|
+
|
|
7
|
+
A tiny CLI builder stitched together from three influences:
|
|
8
|
+
namespaces and prereqs from Rake, typed option parsing from Thor, and
|
|
9
|
+
the `task :name do ... end` block DSL from Joshua. Users drop a
|
|
10
|
+
`Hammerfile` in their project, run `hammer`, get a structured CLI. Also
|
|
11
|
+
usable as a library (`require 'lux-hammer'`, subclass `Hammer`, call
|
|
12
|
+
`.start(ARGV)`).
|
|
13
|
+
|
|
14
|
+
## Hard constraints
|
|
15
|
+
|
|
16
|
+
* **One root constant**: `Hammer`. Never pollute `Object` or introduce
|
|
17
|
+
another top-level constant. Sub-types live as `Hammer::Shell`,
|
|
18
|
+
`Hammer::Option`, `Hammer::Parser`, `Hammer::Command`,
|
|
19
|
+
`Hammer::Loader`, `Hammer::Builder`, `Hammer::CommandBuilder`. The one
|
|
20
|
+
exception is `String#color(:name)` (defined in `lib/hammer/shell.rb`)
|
|
21
|
+
- a tiny convenience for `"hi".color(:cyan)`. Do not add more
|
|
22
|
+
monkey-patches.
|
|
23
|
+
* **Zero runtime dependencies**. The gem must work with stdlib only. New
|
|
24
|
+
dependencies require explicit user approval.
|
|
25
|
+
* **Ruby >= 2.7**. Do not use language features introduced after 2.7
|
|
26
|
+
without flagging.
|
|
27
|
+
* **`desc` is single-arg, multi-line allowed**. The argument is one
|
|
28
|
+
string; it may contain newlines. The first line is the brief shown in
|
|
29
|
+
command listings, the full string renders (indented) in per-command
|
|
30
|
+
help. Trailing whitespace is stripped so heredocs work cleanly. `desc`
|
|
31
|
+
is never multi-arg - that form was tried and reverted.
|
|
32
|
+
* **The `proc` is the trailing expression of a `task` block.** Do not
|
|
33
|
+
introduce `run do ... end` or similar - the user explicitly chose this.
|
|
34
|
+
|
|
35
|
+
## File layout
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
bin/hammer # CLI entry point
|
|
39
|
+
lib/lux-hammer.rb # Entry - defines class Hammer and its DSL
|
|
40
|
+
lib/hammer/shell.rb # ANSI/IO helpers
|
|
41
|
+
lib/hammer/option.rb # One option definition
|
|
42
|
+
lib/hammer/parser.rb # ARGV -> [positional, opts_hash]
|
|
43
|
+
lib/hammer/command.rb # One registered command (name, opts, alts, handler)
|
|
44
|
+
lib/hammer/loader.rb # `*_hammer.rb` fragment loader (auto/glob/file)
|
|
45
|
+
lib/hammer/builder.rb # Block-DSL context (Hammerfile / Hammer.run)
|
|
46
|
+
lib/hammer/command_builder.rb # `task :name do ... end` context
|
|
47
|
+
test/dsl_test.rb # DSL surface, dispatch, help formatting
|
|
48
|
+
test/load_test.rb # `load` / `*_hammer.rb` fragment loader
|
|
49
|
+
test/parser_test.rb # ARGV parsing edge cases
|
|
50
|
+
test/option_test.rb # Option declaration / casting
|
|
51
|
+
test/command_test.rb # Command data type
|
|
52
|
+
test/shell_test.rb # ANSI / IO helpers
|
|
53
|
+
test/cli_test.rb # `hammer` binary end-to-end
|
|
54
|
+
examples/Hammerfile # Reference Hammerfile
|
|
55
|
+
examples/class_dsl.rb # Reference class DSL usage
|
|
56
|
+
examples/block_dsl.rb # Reference block DSL usage
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## DSL surface (do not break compatibility silently)
|
|
60
|
+
|
|
61
|
+
Inside a `task :name do ... end` block (CommandBuilder context):
|
|
62
|
+
|
|
63
|
+
* `desc 'short description'` (string may contain `\n`; first line is
|
|
64
|
+
the brief shown in listings, full text renders in per-command help)
|
|
65
|
+
* `example 'invocation example'` (callable many times)
|
|
66
|
+
* `opt :name, type:, default:, alias:, desc:, req:` - any other kwarg
|
|
67
|
+
raises `Hammer::Error` ("unknown opt parameter(s)")
|
|
68
|
+
* `alt :other_name` (callable many times)
|
|
69
|
+
* `needs :other_cmd, 'ns:cmd'` - prereqs run before the handler;
|
|
70
|
+
resolved against root (same lookup as `hammer`), deduped per
|
|
71
|
+
top-level `start` so each prereq fires at most once. Dedupe also
|
|
72
|
+
spans `+`-chained segments. Unknown prereq raises `Hammer::Error`.
|
|
73
|
+
* `proc do |opts| ... end` - **the last expression**, becomes handler
|
|
74
|
+
|
|
75
|
+
At class scope (for `def`-style commands):
|
|
76
|
+
|
|
77
|
+
* `desc`, `example`, `opt`, `alt`, `needs` set pending state
|
|
78
|
+
* The next `def` consumes the pending state IF `desc` was set; otherwise
|
|
79
|
+
the method is treated as a plain helper
|
|
80
|
+
* Methods with arity 0 are called without opts; methods that take an arg
|
|
81
|
+
receive the opts hash
|
|
82
|
+
|
|
83
|
+
At class or `Hammerfile` scope:
|
|
84
|
+
|
|
85
|
+
* `task :name do ... end`
|
|
86
|
+
* `namespace :name do ... end`
|
|
87
|
+
* `load` / `load auto: true` / `load 'path/file.rb'` / `load 'glob/*.rb'` /
|
|
88
|
+
`load 'some/dir'` - pull in Hammerfile fragments from `*_hammer.rb`
|
|
89
|
+
files. Paths resolve relative to the caller's file. A directory
|
|
90
|
+
argument triggers the same recursive scan as `load auto: true` but
|
|
91
|
+
anchored at the given dir (empty result is OK, no error - apps with
|
|
92
|
+
no fragments are normal). Globs and explicit file paths still raise
|
|
93
|
+
on zero matches as a typo guard. Fragments are de-duplicated per
|
|
94
|
+
target class, so re-entrant `load` is safe. Skipped dirs:
|
|
95
|
+
`.git`, `.bundle`, `node_modules`, `tmp`, `vendor`, `dist`, `build`,
|
|
96
|
+
`coverage`, plus any hidden dir. Fragment shape is the block DSL
|
|
97
|
+
(`task`, `namespace`); class-DSL fragments belong in plain `.rb`
|
|
98
|
+
files loaded with `require_relative`.
|
|
99
|
+
|
|
100
|
+
Runtime cross-invocation:
|
|
101
|
+
|
|
102
|
+
* `hammer(name, *args, **opts)` - `name` is a symbol for a top-level
|
|
103
|
+
command (`hammer :build`) or a colon-path string for namespaced ones
|
|
104
|
+
(`hammer 'db:users:list'`). Trailing positionals become positional
|
|
105
|
+
ARGV. Kwargs become flags: underscores in keys map to dashes,
|
|
106
|
+
`true` -> `--flag`, `false` -> skipped (use `no_x: true` to negate),
|
|
107
|
+
any other value -> `--key=value`. Available both on a class
|
|
108
|
+
(`MyCli.hammer ...`) and inside a handler proc.
|
|
109
|
+
|
|
110
|
+
## Entry points
|
|
111
|
+
|
|
112
|
+
* `Hammer.run(argv = ARGV) { ... }` - inline block DSL, builds an
|
|
113
|
+
anonymous subclass, dispatches `argv` against it.
|
|
114
|
+
* `Hammer.run(argv = ARGV)` - **without a block**: load `./Hammerfile`
|
|
115
|
+
if present, otherwise auto-discover `*_hammer.rb` under `Dir.pwd`,
|
|
116
|
+
then dispatch. No walk-up, no chdir. The "do the obvious thing"
|
|
117
|
+
entry point for a fixed bin script; users who need
|
|
118
|
+
walk-up + chdir + missing-Hammerfile error use `Hammer.cli`
|
|
119
|
+
(`bin/hammer`).
|
|
120
|
+
* `Hammer.cli(argv = ARGV)` - internal entry for `bin/hammer` only:
|
|
121
|
+
walks up from `Dir.pwd` for a `Hammerfile`, chdirs into its dir,
|
|
122
|
+
errors if none found anywhere up the tree. Not part of the
|
|
123
|
+
user-facing surface - don't recommend it in examples; `Hammer.run`
|
|
124
|
+
is what library users should reach for.
|
|
125
|
+
* `class MyCli < Hammer; ... end; MyCli.start(ARGV)` - classic class
|
|
126
|
+
DSL. `.start` is the dispatch primitive everything else funnels into.
|
|
127
|
+
|
|
128
|
+
## `opts` hash contract
|
|
129
|
+
|
|
130
|
+
Always a `Hash` with **symbol keys**. Never change this without an
|
|
131
|
+
explicit ADR-level discussion. Keys:
|
|
132
|
+
|
|
133
|
+
* one per declared option
|
|
134
|
+
* `opts[:args]` - leftover positional ARGV (after declaration-order
|
|
135
|
+
fill)
|
|
136
|
+
|
|
137
|
+
## Dispatch model
|
|
138
|
+
|
|
139
|
+
* Commands are addressed by colon path: `db:users:list`.
|
|
140
|
+
* The root `Hammer` subclass holds `@commands` and `@namespaces`.
|
|
141
|
+
* `resolve('a:b:c')` walks `@namespaces['a'].namespaces['b']` and
|
|
142
|
+
finds command `c` in the last class.
|
|
143
|
+
* There is **no per-level dispatch**. A namespace is a container, not a
|
|
144
|
+
CLI of its own. Do not reintroduce `subclass.start(remaining_argv)`.
|
|
145
|
+
* `start(argv)` is a two-step pipeline: `split_chain(argv)` (private)
|
|
146
|
+
splits on bare `+` tokens and unescapes `++` -> `+`, then `dispatch`
|
|
147
|
+
(private) runs each segment. `start` is the only public entry that
|
|
148
|
+
also sets up the per-invocation `needs`/`before` dedupe state, so the
|
|
149
|
+
whole `+` chain shares one dedupe scope. Don't have `dispatch` call
|
|
150
|
+
back into `start` for sub-segments - that re-triggers chain detection
|
|
151
|
+
on already-unescaped `+` tokens (bug we fixed; the test
|
|
152
|
+
`test_chain_escape_double_plus_yields_literal_plus_arg` guards it).
|
|
153
|
+
|
|
154
|
+
## Help formatting
|
|
155
|
+
|
|
156
|
+
* Program name in usage lines is computed automatically (invocation path
|
|
157
|
+
relative to cwd if the bin lives inside cwd, otherwise the basename of
|
|
158
|
+
`$PROGRAM_NAME`). There is no user-facing override; the auto-detection
|
|
159
|
+
is the whole API. `Hammer.cli` warms the cache before chdir-ing into
|
|
160
|
+
the Hammerfile's directory so the resolved name stays relative to the
|
|
161
|
+
cwd the user invoked from.
|
|
162
|
+
* `hammer` (no args), `hammer -h`, and `hammer --help` all print top-level help.
|
|
163
|
+
* `hammer COMMAND -h` / `--help` prints per-command help (reserved on every command).
|
|
164
|
+
* Commands listed flat with colon paths, grouped by top-level namespace.
|
|
165
|
+
* Bare namespace (`hammer db`) prints the same listing scoped to that
|
|
166
|
+
namespace.
|
|
167
|
+
* Per-command help: usage line shows declared opts inline (required
|
|
168
|
+
bare, optional bracketed), then options with `(default: ...)` /
|
|
169
|
+
`(required)` annotations, then examples.
|
|
170
|
+
|
|
171
|
+
## Inline helpers (`Hammer::Shell`)
|
|
172
|
+
|
|
173
|
+
Mixed into every handler. Also callable directly as `Hammer::Shell.<x>`.
|
|
174
|
+
Complete surface:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
say 'plain' # print
|
|
178
|
+
say.green 'ok' # colored via proxy (preferred form)
|
|
179
|
+
say 'ok', :green # equivalent two-arg form
|
|
180
|
+
say '' # blank line (NOT `say` with no args)
|
|
181
|
+
|
|
182
|
+
'OK'.color(:green) # paint without printing
|
|
183
|
+
Hammer::Shell.paint('x', :red) # the underlying primitive
|
|
184
|
+
|
|
185
|
+
error 'config missing' unless ok # raise Hammer::Error -> dispatcher exits 1
|
|
186
|
+
name = ask 'name' # read line
|
|
187
|
+
env = ask 'env', default: 'dev' # blank input -> default
|
|
188
|
+
exit 0 unless yes? 'continue?' # y/Y prefix -> true, else false
|
|
189
|
+
idx = choose 'Pick env', envs # arrow-key picker, returns Integer or nil
|
|
190
|
+
sh 'bundle install' # echo "$ cmd" gray, raise on non-zero
|
|
191
|
+
|
|
192
|
+
Hammer::Shell.color!(true|false) # force ANSI on/off
|
|
193
|
+
Hammer::Shell.color? # current state
|
|
194
|
+
Hammer::Shell.print_error 'msg' # stderr, no raise - dispatcher-only
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Doc rule: prefer `say.<color> 'x'` over `say 'x', :<color>` in README
|
|
198
|
+
and examples - both work, but the proxy form reads better and is what
|
|
199
|
+
new code in this gem should show.
|
|
200
|
+
|
|
201
|
+
`choose` invariants worth knowing:
|
|
202
|
+
|
|
203
|
+
* Uses `io/console` (stdlib, no new dependency).
|
|
204
|
+
* TTY path renders the list, redraws on each keystroke (`\e[NA\e[J`),
|
|
205
|
+
hides/restores the cursor, and reads bytes via `$stdin.raw { ... }`.
|
|
206
|
+
* Keys: `j`/`k` and arrow keys (ESC `[` `A`/`B`) move; Enter confirms;
|
|
207
|
+
`q`, ESC alone, and Ctrl-C cancel. ESC-vs-arrow is disambiguated with
|
|
208
|
+
a tiny `IO.select` timeout - the arrow's `[A` follows ESC immediately,
|
|
209
|
+
a bare ESC keystroke is alone.
|
|
210
|
+
* Non-TTY path (`!$stdin.tty?` or stdin without `raw`) prints a numbered
|
|
211
|
+
list and reads one line. Both paths return the same: `Integer` index
|
|
212
|
+
or `nil`. Don't break this contract - it's why `choose` is scriptable.
|
|
213
|
+
* On confirm, the rendered list is collapsed to one green line; on
|
|
214
|
+
cancel, the list is cleared. Don't leave the raw-mode list behind.
|
|
215
|
+
* Empty `items` raises `Hammer::Error` ('at least one item') - same
|
|
216
|
+
failure pattern as bad colors.
|
|
217
|
+
|
|
218
|
+
## Colors
|
|
219
|
+
|
|
220
|
+
* Color set: `:black :red :green :yellow :blue :magenta :cyan :white :gray`
|
|
221
|
+
(see `Hammer::Shell::COLORS`). No bold, no bright variants, no
|
|
222
|
+
background colors. Don't add more without an explicit ask.
|
|
223
|
+
* All four entry points (`say x, :c`, `say.c x`, `paint x, :c`,
|
|
224
|
+
`'x'.color(:c)`) flow through `Shell.paint` - it's the only place
|
|
225
|
+
ANSI codes live.
|
|
226
|
+
* Unknown colors must raise `Hammer::Error` with the list of valid
|
|
227
|
+
names. This is enforced in `Shell.paint` and in `SayProxy#method_missing`
|
|
228
|
+
- don't bypass it. Validation runs even when colors are disabled, so
|
|
229
|
+
a typo fails loudly in CI.
|
|
230
|
+
* `say` with no args returns the `SayProxy`; `say ''` is how you print
|
|
231
|
+
a blank line. Don't restore `say` (no args) -> blank-line semantics.
|
|
232
|
+
|
|
233
|
+
## Error handling
|
|
234
|
+
|
|
235
|
+
* `Hammer::Shell.error 'msg'` raises `Hammer::Error`. The dispatcher
|
|
236
|
+
catches it inside `run_command`, prints `[error] msg` in red to
|
|
237
|
+
stderr, and exits 1 - no backtrace, no per-command help.
|
|
238
|
+
* `Hammer::Shell.print_error 'msg'` prints without raising; reserved
|
|
239
|
+
for the dispatcher's own diagnostics (unknown command, missing
|
|
240
|
+
Hammerfile).
|
|
241
|
+
* `Hammer::Parser::Error` is also caught in `run_command` and
|
|
242
|
+
additionally prints per-command help (because the input *was* aimed
|
|
243
|
+
at that command).
|
|
244
|
+
* Inside a handler, just call `error 'msg'` - it resolves via the
|
|
245
|
+
`Shell` mixin.
|
|
246
|
+
|
|
247
|
+
## Coding rules
|
|
248
|
+
|
|
249
|
+
* No trailing spaces on empty lines. End every file with a newline.
|
|
250
|
+
* Use ASCII only - `-`, not unicode dashes; `*` for bullets in docs.
|
|
251
|
+
* Preserve existing style. Minimize diff size.
|
|
252
|
+
* Comments only when the *why* is non-obvious. Don't restate the code.
|
|
253
|
+
* No `// removed` markers - delete cleanly.
|
|
254
|
+
* Constants: prefer `FOO ||=` over `FOO =` (re-load safety).
|
|
255
|
+
* Never commit or push without explicit confirmation.
|
|
256
|
+
|
|
257
|
+
## Testing
|
|
258
|
+
|
|
259
|
+
* Run: `bundle exec rake test`
|
|
260
|
+
* Single file: `bundle exec ruby -Ilib -Itest test/parser_test.rb`
|
|
261
|
+
* Single test by name: `... test/parser_test.rb -n test_boolean_via_short_alias`
|
|
262
|
+
* Helper `CaptureIO` in `test/test_helper.rb` swaps `$stdout`/`$stderr`
|
|
263
|
+
for `StringIO`. Use `capture { ... }` for non-exiting code and
|
|
264
|
+
`capture_exit { ... }` for code that calls `exit`.
|
|
265
|
+
|
|
266
|
+
When you add a feature, add a test in the matching `_test.rb`. When you
|
|
267
|
+
fix a bug, write the failing test first.
|
|
268
|
+
|
|
269
|
+
## What NOT to do
|
|
270
|
+
|
|
271
|
+
* Don't add generators / templates (Thor-style file scaffolding).
|
|
272
|
+
* Don't add shell-completion generation to core - that belongs in a
|
|
273
|
+
separate plugin gem.
|
|
274
|
+
* Don't introduce a class hierarchy for commands. Commands are data
|
|
275
|
+
(`Hammer::Command` instances), not classes.
|
|
276
|
+
* Don't reintroduce a user-facing `program` / `program_name` setter -
|
|
277
|
+
the auto-detected name is the whole API and was deliberately reduced
|
|
278
|
+
to that.
|
|
279
|
+
* Don't propagate `program_name` into namespaces as a longer prefix -
|
|
280
|
+
there is only one program name.
|
|
281
|
+
* Don't make `desc` multi-arg again (it once took `usage, description`
|
|
282
|
+
- that was reverted intentionally).
|
|
283
|
+
* Don't add a `subcommand 'name', SomeClass` form - it was replaced by
|
|
284
|
+
`namespace :name do ... end` intentionally.
|
|
285
|
+
* Don't auto-namespace fragments by filename (e.g. `db_hammer.rb` does
|
|
286
|
+
not implicitly wrap in `namespace :db do ... end`). Be explicit, as
|
|
287
|
+
Rake's `import` is. If a fragment wants a namespace, it writes it.
|
|
288
|
+
|
|
289
|
+
## When in doubt
|
|
290
|
+
|
|
291
|
+
* Read `lib/lux-hammer.rb` top-to-bottom. It's the whole DSL surface.
|
|
292
|
+
* Run the example: `cd examples && ruby -I../lib ../bin/hammer`
|
|
293
|
+
* Check `test/dsl_test.rb` for the canonical behavior of each feature.
|
|
294
|
+
* Check `test/load_test.rb` for `load` / fragment loader behavior.
|
data/README.md
CHANGED
|
@@ -4,11 +4,11 @@ The bastard Frankenstein child of Rake, Thor, and Joshua. Sewn
|
|
|
4
4
|
together from three good ideas, with the rest of each parent left on
|
|
5
5
|
the cutting room floor.
|
|
6
6
|
|
|
7
|
-
Drop a `Hammerfile`, run `hammer`, ship.
|
|
7
|
+
Drop a `Hammerfile`, run `hammer`, ship. AI LLM-s love `hammer`.
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
10
|
namespace :db do # Rake-style colon paths
|
|
11
|
-
|
|
11
|
+
task :migrate do # Joshua-style task block
|
|
12
12
|
desc 'Run pending migrations'
|
|
13
13
|
opt :pretend, type: :boolean, alias: :p # Thor-style typed opts
|
|
14
14
|
proc do |o|
|
|
@@ -40,7 +40,7 @@ migrating pretend=true
|
|
|
40
40
|
that most CLIs never reach for.
|
|
41
41
|
|
|
42
42
|
* **From [Joshua](https://github.com/dux/joshua)** we took the
|
|
43
|
-
*`
|
|
43
|
+
*`task :name do ... end` block DSL* - declarative metadata up
|
|
44
44
|
top, one `proc do |opts| ... end` at the bottom doing the work. No
|
|
45
45
|
`def`-and-`desc` split, no class required, no boilerplate between
|
|
46
46
|
"what this command is" and "what it does".
|
|
@@ -67,7 +67,7 @@ This installs the `hammer` binary and exposes `require 'lux-hammer'`.
|
|
|
67
67
|
Create a `Hammerfile` in your project root:
|
|
68
68
|
|
|
69
69
|
```ruby
|
|
70
|
-
|
|
70
|
+
task :hello do
|
|
71
71
|
desc 'say hi'
|
|
72
72
|
proc do |opts|
|
|
73
73
|
say.green "hello #{opts[:args].first || 'world'}"
|
|
@@ -115,7 +115,7 @@ Hammer takes typed options, positional fill, and any common flag form:
|
|
|
115
115
|
|
|
116
116
|
```ruby
|
|
117
117
|
# Hammerfile
|
|
118
|
-
|
|
118
|
+
task :greet do
|
|
119
119
|
desc 'Say hello'
|
|
120
120
|
opt :name
|
|
121
121
|
opt :loud, type: :boolean, alias: :l
|
|
@@ -159,7 +159,7 @@ end
|
|
|
159
159
|
|
|
160
160
|
```ruby
|
|
161
161
|
# hammer - one arg system, real aliases, no usage string to maintain
|
|
162
|
-
|
|
162
|
+
task :greet do
|
|
163
163
|
desc 'Say hello'
|
|
164
164
|
alt :g
|
|
165
165
|
opt :name
|
|
@@ -173,13 +173,13 @@ and there's one place to look for everything the command takes.
|
|
|
173
173
|
|
|
174
174
|
## The two styles
|
|
175
175
|
|
|
176
|
-
### `
|
|
176
|
+
### `task :name do ... end` (block DSL)
|
|
177
177
|
|
|
178
178
|
The block's **last expression must be `proc do |opts| ... end`**. That
|
|
179
179
|
proc is the handler. Everything before it is metadata.
|
|
180
180
|
|
|
181
181
|
```ruby
|
|
182
|
-
|
|
182
|
+
task :build do
|
|
183
183
|
desc 'Build the project'
|
|
184
184
|
example 'build prod -v'
|
|
185
185
|
opt :verbose, type: :boolean, alias: :v
|
|
@@ -371,7 +371,7 @@ Anything in ARGV without `-` / `--` fills the next un-set
|
|
|
371
371
|
**non-boolean** opt, in declaration order:
|
|
372
372
|
|
|
373
373
|
```ruby
|
|
374
|
-
|
|
374
|
+
task :deploy do
|
|
375
375
|
opt :url
|
|
376
376
|
opt :env, default: 'dev'
|
|
377
377
|
proc { |opts| ... }
|
|
@@ -403,7 +403,7 @@ Always a `Hash` with **symbol keys**. Keys present:
|
|
|
403
403
|
* `opts[:args]` - array of positional ARGV not absorbed by an opt
|
|
404
404
|
|
|
405
405
|
```ruby
|
|
406
|
-
|
|
406
|
+
task :show do
|
|
407
407
|
opt :env, default: 'dev'
|
|
408
408
|
opt :loud, type: :boolean
|
|
409
409
|
proc { |opts| p opts }
|
|
@@ -435,12 +435,12 @@ colon-paths from the root binary - just like `rake db:migrate`:
|
|
|
435
435
|
|
|
436
436
|
```ruby
|
|
437
437
|
namespace :db do
|
|
438
|
-
|
|
438
|
+
task :migrate do
|
|
439
439
|
proc { |opts| ... }
|
|
440
440
|
end
|
|
441
441
|
|
|
442
442
|
namespace :users do
|
|
443
|
-
|
|
443
|
+
task :list do
|
|
444
444
|
proc { |opts| ... }
|
|
445
445
|
end
|
|
446
446
|
end
|
|
@@ -453,7 +453,9 @@ Then:
|
|
|
453
453
|
hammer db:migrate
|
|
454
454
|
hammer db:users:list
|
|
455
455
|
hammer db # bare namespace lists everything under it
|
|
456
|
+
hammer db: # trailing colon: full per-task help for every command
|
|
456
457
|
hammer db:migrate -h # per-command help
|
|
458
|
+
hammer : # trailing colon at root: full help for every command
|
|
457
459
|
```
|
|
458
460
|
|
|
459
461
|
Namespaces nest to any depth. There is no per-level dispatch - the root
|
|
@@ -470,13 +472,13 @@ before { Dotenv.load } # runs before every command
|
|
|
470
472
|
|
|
471
473
|
namespace :db do
|
|
472
474
|
before { hammer :env } # runs before every db:* command
|
|
473
|
-
|
|
475
|
+
task :migrate do
|
|
474
476
|
proc { |opts| ... } # no boilerplate require inside
|
|
475
477
|
end
|
|
476
478
|
end
|
|
477
479
|
```
|
|
478
480
|
|
|
479
|
-
`before` is intentionally not available inside `
|
|
481
|
+
`before` is intentionally not available inside `task` - the proc body
|
|
480
482
|
*is* the command body, just put the setup line at the top of the proc.
|
|
481
483
|
|
|
482
484
|
Pairs naturally with hidden commands (next section): keep `:env` /
|
|
@@ -488,13 +490,13 @@ A command declared without a `desc` is **hidden from help listings**
|
|
|
488
490
|
but stays fully dispatchable and `hammer`-callable:
|
|
489
491
|
|
|
490
492
|
```ruby
|
|
491
|
-
|
|
493
|
+
task :env do
|
|
492
494
|
proc { |_| require './config/env' } # no desc -> hidden
|
|
493
495
|
end
|
|
494
496
|
|
|
495
497
|
namespace :db do
|
|
496
498
|
before { hammer :env } # call it from a hook
|
|
497
|
-
|
|
499
|
+
task :migrate do
|
|
498
500
|
desc 'Run migrations'
|
|
499
501
|
proc { |_| ... }
|
|
500
502
|
end
|
|
@@ -509,17 +511,17 @@ end
|
|
|
509
511
|
Declare commands that must run before this one (Rake-style task deps):
|
|
510
512
|
|
|
511
513
|
```ruby
|
|
512
|
-
|
|
514
|
+
task :env do
|
|
513
515
|
proc { |_| require './config/env' } # hidden helper
|
|
514
516
|
end
|
|
515
517
|
|
|
516
|
-
|
|
518
|
+
task :app do
|
|
517
519
|
needs :env # runs `env` first
|
|
518
520
|
desc 'start the app'
|
|
519
521
|
proc { |opts| App.start }
|
|
520
522
|
end
|
|
521
523
|
|
|
522
|
-
|
|
524
|
+
task :deploy do
|
|
523
525
|
needs :env, :build # multiple prereqs, in order
|
|
524
526
|
proc { |opts| ... }
|
|
525
527
|
end
|
|
@@ -542,7 +544,7 @@ only once. Prereqs run with default options (no argv passed through).
|
|
|
542
544
|
`alt :short_name` (or several) registers extra names for a command:
|
|
543
545
|
|
|
544
546
|
```ruby
|
|
545
|
-
|
|
547
|
+
task :server do
|
|
546
548
|
alt :s, :srv
|
|
547
549
|
proc { |opts| ... }
|
|
548
550
|
end
|
|
@@ -558,7 +560,7 @@ From inside any command's proc - or from outside via the class - you can
|
|
|
558
560
|
invoke other commands without re-shelling out:
|
|
559
561
|
|
|
560
562
|
```ruby
|
|
561
|
-
|
|
563
|
+
task :deploy do
|
|
562
564
|
proc do |opts|
|
|
563
565
|
hammer :build, env: 'prod', verbose: true
|
|
564
566
|
hammer 'db:migrate'
|
|
@@ -729,7 +731,7 @@ load auto: true # recursive scan for *_hammer.rb from here
|
|
|
729
731
|
```ruby
|
|
730
732
|
# tasks/db_hammer.rb
|
|
731
733
|
namespace :db do
|
|
732
|
-
|
|
734
|
+
task :migrate do
|
|
733
735
|
desc 'Run pending migrations'
|
|
734
736
|
opt :pretend, type: :boolean, alias: :p
|
|
735
737
|
proc { |o| say.green "migrating pretend=#{o[:pretend].inspect}" }
|
|
@@ -739,7 +741,7 @@ end
|
|
|
739
741
|
|
|
740
742
|
```ruby
|
|
741
743
|
# tasks/deploy_hammer.rb
|
|
742
|
-
|
|
744
|
+
task :deploy do
|
|
743
745
|
desc 'Deploy to prod'
|
|
744
746
|
proc do |_|
|
|
745
747
|
hammer 'db:migrate' # cross-file invocation just works
|
|
@@ -777,7 +779,7 @@ hidden directory.
|
|
|
777
779
|
### Fragment shape
|
|
778
780
|
|
|
779
781
|
A `*_hammer.rb` file is a **block-DSL fragment** - same surface as a
|
|
780
|
-
`Hammerfile`: `
|
|
782
|
+
`Hammerfile`: `task`, `namespace`, and nested `load`. Not a class
|
|
781
783
|
re-open. If you want to extend a `Hammer` subclass in the classic
|
|
782
784
|
`desc` + `def` style across files, use plain `require_relative`.
|
|
783
785
|
|
|
@@ -801,7 +803,7 @@ Same shape as a Hammerfile, just inline:
|
|
|
801
803
|
require 'lux-hammer'
|
|
802
804
|
|
|
803
805
|
Hammer.run(ARGV) do
|
|
804
|
-
|
|
806
|
+
task :hello do
|
|
805
807
|
desc 'say hi'
|
|
806
808
|
opt :loud, type: :boolean, alias: :l
|
|
807
809
|
proc do |opts|
|
|
@@ -845,7 +847,7 @@ end
|
|
|
845
847
|
|
|
846
848
|
```ruby
|
|
847
849
|
# Simple top-level command
|
|
848
|
-
|
|
850
|
+
task :build do
|
|
849
851
|
desc 'Build the project'
|
|
850
852
|
example 'build prod -v'
|
|
851
853
|
example 'build --env=staging'
|
|
@@ -860,7 +862,7 @@ define :build do
|
|
|
860
862
|
end
|
|
861
863
|
|
|
862
864
|
# Command that calls another command
|
|
863
|
-
|
|
865
|
+
task :deploy do
|
|
864
866
|
desc 'Deploy to URL'
|
|
865
867
|
alt :ship
|
|
866
868
|
opt :url, req: true
|
|
@@ -875,7 +877,7 @@ end
|
|
|
875
877
|
|
|
876
878
|
# Namespace with two levels of nesting
|
|
877
879
|
namespace :db do
|
|
878
|
-
|
|
880
|
+
task :migrate do
|
|
879
881
|
desc 'Run pending migrations'
|
|
880
882
|
alt :m
|
|
881
883
|
example 'db:migrate 3 --pretend'
|
|
@@ -888,7 +890,7 @@ namespace :db do
|
|
|
888
890
|
end
|
|
889
891
|
|
|
890
892
|
namespace :users do
|
|
891
|
-
|
|
893
|
+
task :list do
|
|
892
894
|
desc 'List users'
|
|
893
895
|
opt :role, default: 'all'
|
|
894
896
|
opt :limit, type: :integer, default: 100
|
|
@@ -898,7 +900,7 @@ namespace :db do
|
|
|
898
900
|
end
|
|
899
901
|
end
|
|
900
902
|
|
|
901
|
-
|
|
903
|
+
task :create do
|
|
902
904
|
desc 'Create a user'
|
|
903
905
|
opt :email, req: true
|
|
904
906
|
opt :admin, type: :boolean
|
|
@@ -968,7 +970,7 @@ directly. Useful for embedding or testing:
|
|
|
968
970
|
require 'lux-hammer'
|
|
969
971
|
|
|
970
972
|
class MyCli < Hammer
|
|
971
|
-
|
|
973
|
+
task :greet do
|
|
972
974
|
opt :loud, type: :boolean
|
|
973
975
|
proc do |opts|
|
|
974
976
|
msg = "hello #{opts[:args].first}"
|
|
@@ -1006,7 +1008,7 @@ few small things that have been bugging me about both for years.
|
|
|
1006
1008
|
| Lines of code | ~6,000 | ~400 |
|
|
1007
1009
|
| Runtime deps | a few | zero |
|
|
1008
1010
|
| Root constants | `Thor`, `Thor::Group`, `Thor::Shell`, `Thor::Actions`, ... | just `Hammer` |
|
|
1009
|
-
| Command DSL | `desc 'usage', 'help'` + `method_option` + `def name(arg)` | `
|
|
1011
|
+
| Command DSL | `desc 'usage', 'help'` + `method_option` + `def name(arg)` | `task :name do ... proc do \|opts\| end end` (or classic `desc` + `def`) |
|
|
1010
1012
|
| Opts container | `Thor::CoreExt::HashWithIndifferentAccess` | plain `Hash` with symbol keys |
|
|
1011
1013
|
| Positional args | method positional params + `method_option`, two parallel systems | declared-order opts fill from positional, single system |
|
|
1012
1014
|
| Sub-namespaces | `register SubClass, 'name', '...'` (inheritance ceremony) | `namespace :name do ... end` (no classes needed) |
|
data/bin/hammer
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
# Prefer the adjacent lib/ when this script lives in a repo checkout
|
|
3
|
+
# (lib/lux-hammer.rb sits next to bin/), otherwise fall back to the
|
|
4
|
+
# installed gem. Otherwise an installed older gem shadows local edits.
|
|
5
|
+
local_lib = File.expand_path('../lib/lux-hammer.rb', __dir__)
|
|
6
|
+
if File.file?(local_lib)
|
|
7
|
+
$LOAD_PATH.unshift File.dirname(local_lib)
|
|
8
8
|
end
|
|
9
|
+
require 'lux-hammer'
|
|
9
10
|
Hammer.cli(ARGV)
|
data/lib/hammer/builder.rb
CHANGED
data/lib/lux-hammer.rb
CHANGED
|
@@ -11,7 +11,7 @@ require_relative 'hammer/command_builder'
|
|
|
11
11
|
# Class DSL:
|
|
12
12
|
#
|
|
13
13
|
# class MyCli < Hammer
|
|
14
|
-
#
|
|
14
|
+
# task :build do
|
|
15
15
|
# desc 'Build the project'
|
|
16
16
|
# example 'build -v --env=prod'
|
|
17
17
|
# opt :verbose, type: :boolean, alias: :v
|
|
@@ -27,7 +27,7 @@ require_relative 'hammer/command_builder'
|
|
|
27
27
|
# Block DSL is identical, just inside `Hammer.run`:
|
|
28
28
|
#
|
|
29
29
|
# Hammer.run(ARGV) do
|
|
30
|
-
#
|
|
30
|
+
# task :hello do
|
|
31
31
|
# desc 'Greet someone'
|
|
32
32
|
# opt :loud, type: :boolean, alias: :l
|
|
33
33
|
# proc do |opts|
|
|
@@ -124,17 +124,17 @@ class Hammer
|
|
|
124
124
|
# return a Proc as its last expression. That proc is the handler and
|
|
125
125
|
# receives a single `opts` hash with symbol keys; positional ARGV
|
|
126
126
|
# lives at `opts[:args]`.
|
|
127
|
-
def
|
|
127
|
+
def task(name, &block)
|
|
128
128
|
cmd = Command.new(name: name.to_s)
|
|
129
129
|
handler = CommandBuilder.new(cmd).instance_eval(&block)
|
|
130
130
|
unless handler.is_a?(Proc)
|
|
131
131
|
raise Error, <<~MSG
|
|
132
|
-
|
|
132
|
+
task(:#{name}) block must end with a `proc do |opts| ... end`.
|
|
133
133
|
The proc's return value is what becomes the command handler.
|
|
134
134
|
|
|
135
135
|
Example:
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
task :#{name} do
|
|
138
138
|
desc 'what it does'
|
|
139
139
|
example '#{name} foo --env=prod'
|
|
140
140
|
opt :env, default: 'dev'
|
|
@@ -148,7 +148,7 @@ class Hammer
|
|
|
148
148
|
cmd.handler = handler
|
|
149
149
|
commands[cmd.name] = cmd
|
|
150
150
|
|
|
151
|
-
# `
|
|
151
|
+
# `task` ignores pending class-level state, but clear it so a
|
|
152
152
|
# later `def` doesn't accidentally consume stale metadata.
|
|
153
153
|
@pending_desc = nil
|
|
154
154
|
@pending_examples = []
|
|
@@ -158,11 +158,11 @@ class Hammer
|
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
# Open a namespace (group of commands). Everything inside the block
|
|
161
|
-
# (
|
|
161
|
+
# (task, nested namespace, ...) belongs to that namespace, evaluated
|
|
162
162
|
# against an anonymous Hammer subclass.
|
|
163
163
|
#
|
|
164
164
|
# namespace :db do
|
|
165
|
-
#
|
|
165
|
+
# task :migrate do ... end
|
|
166
166
|
# namespace :users do ... end
|
|
167
167
|
# end
|
|
168
168
|
def namespace(name, &block)
|
|
@@ -189,7 +189,7 @@ class Hammer
|
|
|
189
189
|
# before { |opts| Dotenv.load }
|
|
190
190
|
# namespace :db do
|
|
191
191
|
# before { hammer :env }
|
|
192
|
-
#
|
|
192
|
+
# task :migrate do ... end
|
|
193
193
|
# end
|
|
194
194
|
def before(&block)
|
|
195
195
|
before_hooks << block
|
|
@@ -300,6 +300,19 @@ class Hammer
|
|
|
300
300
|
return print_help(target)
|
|
301
301
|
end
|
|
302
302
|
|
|
303
|
+
# Trailing colon ("db:") -> expanded namespace listing with full
|
|
304
|
+
# per-command help on every task. Bare ":" expands the root.
|
|
305
|
+
if name.end_with?(':') && name != ':'
|
|
306
|
+
bare = name.chomp(':')
|
|
307
|
+
ns = resolve_namespace(bare)
|
|
308
|
+
return print_namespace_help(bare, ns, full: true) if ns
|
|
309
|
+
Shell.print_error("unknown namespace: #{bare}")
|
|
310
|
+
print_help
|
|
311
|
+
exit 1
|
|
312
|
+
elsif name == ':'
|
|
313
|
+
return print_help(nil, full: true)
|
|
314
|
+
end
|
|
315
|
+
|
|
303
316
|
cmd, owner = resolve(name)
|
|
304
317
|
return owner.run_command(cmd, argv, full: name) if cmd
|
|
305
318
|
|
|
@@ -429,29 +442,52 @@ class Hammer
|
|
|
429
442
|
scan.include?('-h') || scan.include?('--help')
|
|
430
443
|
end
|
|
431
444
|
|
|
432
|
-
def print_help(target = nil)
|
|
445
|
+
def print_help(target = nil, full: false)
|
|
433
446
|
if target
|
|
447
|
+
# `help ns:` is equivalent to `ns:` - expanded namespace listing.
|
|
448
|
+
if target.end_with?(':') && target != ':'
|
|
449
|
+
bare = target.chomp(':')
|
|
450
|
+
ns = resolve_namespace(bare)
|
|
451
|
+
return print_namespace_help(bare, ns, full: true) if ns
|
|
452
|
+
Shell.print_error("unknown: #{target}")
|
|
453
|
+
return
|
|
454
|
+
end
|
|
434
455
|
cmd, _ = resolve(target)
|
|
435
456
|
return print_command_help(cmd, target) if cmd
|
|
436
457
|
ns = resolve_namespace(target)
|
|
437
|
-
return print_namespace_help(target, ns) if ns
|
|
458
|
+
return print_namespace_help(target, ns, full: full) if ns
|
|
438
459
|
Shell.print_error("unknown: #{target}")
|
|
439
460
|
return
|
|
440
461
|
end
|
|
441
462
|
|
|
442
463
|
Shell.say "Usage: #{program_name} COMMAND [ARGS]", :cyan
|
|
443
|
-
|
|
444
|
-
|
|
464
|
+
if full
|
|
465
|
+
each_command { |path, c| print_full_block(path, c) unless c.desc.empty? }
|
|
466
|
+
else
|
|
467
|
+
Shell.say ''
|
|
468
|
+
print_command_list(self)
|
|
469
|
+
end
|
|
445
470
|
print_footer
|
|
446
471
|
end
|
|
447
472
|
|
|
448
|
-
def print_namespace_help(prefix, ns)
|
|
473
|
+
def print_namespace_help(prefix, ns, full: false)
|
|
449
474
|
Shell.say "Usage: #{program_name} #{prefix}:COMMAND [ARGS]", :cyan
|
|
450
|
-
|
|
451
|
-
|
|
475
|
+
if full
|
|
476
|
+
ns.each_command(prefix) { |path, c| print_full_block(path, c) unless c.desc.empty? }
|
|
477
|
+
else
|
|
478
|
+
Shell.say ''
|
|
479
|
+
print_command_list(ns, prefix)
|
|
480
|
+
end
|
|
452
481
|
print_footer
|
|
453
482
|
end
|
|
454
483
|
|
|
484
|
+
# One "task block" for the expanded listing: blank line separator
|
|
485
|
+
# then the standard per-command help (usage + desc + options + examples).
|
|
486
|
+
def print_full_block(path, cmd)
|
|
487
|
+
Shell.say ''
|
|
488
|
+
print_command_help(cmd, path)
|
|
489
|
+
end
|
|
490
|
+
|
|
455
491
|
HOMEPAGE ||= 'https://github.com/dux/hammer'.freeze
|
|
456
492
|
|
|
457
493
|
def print_footer
|
|
@@ -548,7 +584,7 @@ class Hammer
|
|
|
548
584
|
|
|
549
585
|
# Inside a command's `proc do |opts| ... end`, call sibling commands:
|
|
550
586
|
#
|
|
551
|
-
#
|
|
587
|
+
# task :deploy do
|
|
552
588
|
# proc do |opts|
|
|
553
589
|
# hammer :build
|
|
554
590
|
# hammer 'db:migrate', pretend: true
|
|
@@ -564,7 +600,7 @@ class Hammer
|
|
|
564
600
|
# ----- block DSL -----------------------------------------------------
|
|
565
601
|
|
|
566
602
|
# Define and run a CLI inline. Inside the block use
|
|
567
|
-
# `
|
|
603
|
+
# `task :name do ... end`, `namespace`, and `load`.
|
|
568
604
|
#
|
|
569
605
|
# Without a block: load ./Hammerfile if it exists, otherwise
|
|
570
606
|
# auto-discover *_hammer.rb under Dir.pwd, then dispatch ARGV.
|
|
@@ -583,10 +619,31 @@ class Hammer
|
|
|
583
619
|
klass.start(argv)
|
|
584
620
|
end
|
|
585
621
|
|
|
622
|
+
# Dump the gem's AGENTS.md to stdout - AI-optimized guide for
|
|
623
|
+
# writing Hammerfiles. Bundled with the gem and resolved relative
|
|
624
|
+
# to this file so it works from any install location.
|
|
625
|
+
def self.print_ai_help
|
|
626
|
+
path = File.expand_path('../AGENTS.md', __dir__)
|
|
627
|
+
if File.file?(path)
|
|
628
|
+
puts File.read(path)
|
|
629
|
+
else
|
|
630
|
+
Shell.print_error "AGENTS.md not found at #{path}"
|
|
631
|
+
exit 1
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
586
635
|
# Entry point for the `hammer` binary. Walks up from CWD until it
|
|
587
636
|
# finds a Hammerfile, evaluates it as the block DSL, then dispatches
|
|
588
637
|
# ARGV against the resulting CLI.
|
|
638
|
+
#
|
|
639
|
+
# `--ai` is a meta-flag handled here, before Hammerfile lookup,
|
|
640
|
+
# so it works anywhere (no project required).
|
|
589
641
|
def self.cli(argv = ARGV)
|
|
642
|
+
if argv.include?('--ai')
|
|
643
|
+
print_ai_help
|
|
644
|
+
exit 0
|
|
645
|
+
end
|
|
646
|
+
|
|
590
647
|
path = find_hammerfile(Dir.pwd)
|
|
591
648
|
unless path
|
|
592
649
|
Shell.print_error "no Hammerfile found in #{Dir.pwd} or any parent directory"
|
|
@@ -606,13 +663,15 @@ class Hammer
|
|
|
606
663
|
Shell.say "create one - example:"
|
|
607
664
|
puts
|
|
608
665
|
Shell.say <<~RUBY
|
|
609
|
-
|
|
666
|
+
task :hello do
|
|
610
667
|
desc 'say hello'
|
|
611
668
|
proc do |opts|
|
|
612
669
|
say.green "hello \#{opts[:args].first || 'world'}"
|
|
613
670
|
end
|
|
614
671
|
end
|
|
615
672
|
RUBY
|
|
673
|
+
Shell.say ''
|
|
674
|
+
Shell.say "tip: run `#{File.basename($PROGRAM_NAME)} --ai` for AI-friendly Hammerfile authoring docs", :gray
|
|
616
675
|
exit 1
|
|
617
676
|
end
|
|
618
677
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lux-hammer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dino Reic
|
|
@@ -32,6 +32,7 @@ extensions: []
|
|
|
32
32
|
extra_rdoc_files: []
|
|
33
33
|
files:
|
|
34
34
|
- "./.version"
|
|
35
|
+
- "./AGENTS.md"
|
|
35
36
|
- "./README.md"
|
|
36
37
|
- "./bin/hammer"
|
|
37
38
|
- "./lib/hammer/builder.rb"
|
|
@@ -61,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
61
62
|
- !ruby/object:Gem::Version
|
|
62
63
|
version: '0'
|
|
63
64
|
requirements: []
|
|
64
|
-
rubygems_version: 4.0.
|
|
65
|
+
rubygems_version: 4.0.10
|
|
65
66
|
specification_version: 4
|
|
66
67
|
summary: Thor-inspired tiny CLI builder
|
|
67
68
|
test_files: []
|