lux-hammer 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/.version +1 -1
- data/README.md +97 -28
- data/lib/lux-hammer.rb +73 -42
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae2c5c9dbcd3b17941440e327e04e84dbffaec7209e4ce15b205cf83525b3724
|
|
4
|
+
data.tar.gz: 516eb4a4cb0f8f02f489eb00d2e4500cfe70f600eaebe192fb1749c81fe37074
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d922e58738cb3afc95c54864fc79d9a6ec556e3634a94f258711652abf220ba7f436e1d0f0cf24f48ba9c6372af22339d8377b491c9de93f708e19a7ca13692d
|
|
7
|
+
data.tar.gz: 6ad385997b87bf70ba58ec3ae6d701b3717d5e9584ef00aae29ebeb5c9b3666a39f6032277e275987f6de94161809767eeb838e288718fda09287327009756b6
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.2.
|
|
1
|
+
0.2.3
|
data/README.md
CHANGED
|
@@ -1,9 +1,52 @@
|
|
|
1
1
|
# hammer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
The bastard Frankenstein child of Rake, Thor, and Joshua. Sewn
|
|
4
|
+
together from three good ideas, with the rest of each parent left on
|
|
5
|
+
the cutting room floor.
|
|
6
|
+
|
|
7
|
+
Drop a `Hammerfile`, run `hammer`, ship.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
namespace :db do # Rake-style colon paths
|
|
11
|
+
define :migrate do # Joshua-style define block
|
|
12
|
+
desc 'Run pending migrations'
|
|
13
|
+
opt :pretend, type: :boolean, alias: :p # Thor-style typed opts
|
|
14
|
+
proc do |o|
|
|
15
|
+
say.green "migrating pretend=#{o[:pretend].inspect}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
$ hammer db:migrate --pretend
|
|
23
|
+
migrating pretend=true
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## The bloodline
|
|
27
|
+
|
|
28
|
+
* **From [Rake](https://github.com/ruby/rake)** we took the *namespace
|
|
29
|
+
tree* and *task prereqs* - `db:users:list` colon paths, `needs :env`
|
|
30
|
+
dependencies, the one-file `Hammerfile` at the project root. Left on
|
|
31
|
+
the table: the build-system DNA (file/mtime tasks) and that
|
|
32
|
+
`task[a,b,c]` argument syntax nobody enjoys typing into a shell.
|
|
33
|
+
|
|
34
|
+
* **From [Thor](https://github.com/rails/thor)** we took *real CLI
|
|
35
|
+
ergonomics* - typed options, defaults, aliases, required flags, and
|
|
36
|
+
generated `-h` help. Left on the table: the four-constant top-level
|
|
37
|
+
(`Thor`, `Thor::Group`, `Thor::Shell`, `Thor::Actions`), the
|
|
38
|
+
parallel method-params-vs-`method_option` arg systems, the
|
|
39
|
+
`HashWithIndifferentAccess`, and the file generator half of the gem
|
|
40
|
+
that most CLIs never reach for.
|
|
41
|
+
|
|
42
|
+
* **From [Joshua](https://github.com/dux/joshua)** we took the
|
|
43
|
+
*`define :name do ... end` block DSL* - declarative metadata up
|
|
44
|
+
top, one `proc do |opts| ... end` at the bottom doing the work. No
|
|
45
|
+
`def`-and-`desc` split, no class required, no boilerplate between
|
|
46
|
+
"what this command is" and "what it does".
|
|
47
|
+
|
|
48
|
+
The result: ~400 lines of code, zero runtime dependencies, and a file
|
|
49
|
+
that reads top-to-bottom like a recipe.
|
|
7
50
|
|
|
8
51
|
## Install
|
|
9
52
|
|
|
@@ -93,7 +136,7 @@ Rake::Task['db:migrate'].invoke('prod')
|
|
|
93
136
|
```
|
|
94
137
|
```ruby
|
|
95
138
|
# hammer - reads like a normal Ruby method call
|
|
96
|
-
|
|
139
|
+
hammer 'db:migrate', env: 'prod'
|
|
97
140
|
```
|
|
98
141
|
|
|
99
142
|
### Thor: `desc` welded to a method, no aliases, two arg systems
|
|
@@ -426,7 +469,7 @@ Hooks fire outer -> inner, then the command's handler:
|
|
|
426
469
|
before { Dotenv.load } # runs before every command
|
|
427
470
|
|
|
428
471
|
namespace :db do
|
|
429
|
-
before {
|
|
472
|
+
before { hammer :env } # runs before every db:* command
|
|
430
473
|
define :migrate do
|
|
431
474
|
proc { |opts| ... } # no boilerplate require inside
|
|
432
475
|
end
|
|
@@ -442,7 +485,7 @@ Pairs naturally with hidden commands (next section): keep `:env` /
|
|
|
442
485
|
## Hidden commands (no `desc`)
|
|
443
486
|
|
|
444
487
|
A command declared without a `desc` is **hidden from help listings**
|
|
445
|
-
but stays fully dispatchable and `
|
|
488
|
+
but stays fully dispatchable and `hammer`-callable:
|
|
446
489
|
|
|
447
490
|
```ruby
|
|
448
491
|
define :env do
|
|
@@ -450,7 +493,7 @@ define :env do
|
|
|
450
493
|
end
|
|
451
494
|
|
|
452
495
|
namespace :db do
|
|
453
|
-
before {
|
|
496
|
+
before { hammer :env } # call it from a hook
|
|
454
497
|
define :migrate do
|
|
455
498
|
desc 'Run migrations'
|
|
456
499
|
proc { |_| ... }
|
|
@@ -459,7 +502,7 @@ end
|
|
|
459
502
|
```
|
|
460
503
|
|
|
461
504
|
`hammer` and `hammer db` won't list `env`, but `hammer env`,
|
|
462
|
-
`
|
|
505
|
+
`hammer :env` from another proc, and `before { hammer :env }` all work.
|
|
463
506
|
|
|
464
507
|
## Prereqs (`needs`)
|
|
465
508
|
|
|
@@ -483,7 +526,7 @@ end
|
|
|
483
526
|
```
|
|
484
527
|
|
|
485
528
|
Prereq names are colon paths resolved against the root class - same
|
|
486
|
-
lookup as `
|
|
529
|
+
lookup as `hammer`. Use `needs 'db:env'` to depend on a namespaced
|
|
487
530
|
command.
|
|
488
531
|
|
|
489
532
|
Each prereq fires **at most once per top-level invocation**, so if
|
|
@@ -491,7 +534,7 @@ Each prereq fires **at most once per top-level invocation**, so if
|
|
|
491
534
|
only once. Prereqs run with default options (no argv passed through).
|
|
492
535
|
|
|
493
536
|
`needs` vs `before`:
|
|
494
|
-
* `before {
|
|
537
|
+
* `before { hammer :env }` - fires for *every* command in a scope.
|
|
495
538
|
* `needs :env` - declared per command, deduped across the call chain.
|
|
496
539
|
|
|
497
540
|
## Command aliases (`alt`)
|
|
@@ -509,7 +552,7 @@ Then `hammer server`, `hammer s`, and `hammer srv` all dispatch to the
|
|
|
509
552
|
same command. Alts work inside namespaces too: `alt :m` on `db:migrate`
|
|
510
553
|
makes `db:m` resolve.
|
|
511
554
|
|
|
512
|
-
## Cross-invocation (`
|
|
555
|
+
## Cross-invocation (`hammer`)
|
|
513
556
|
|
|
514
557
|
From inside any command's proc - or from outside via the class - you can
|
|
515
558
|
invoke other commands without re-shelling out:
|
|
@@ -517,28 +560,54 @@ invoke other commands without re-shelling out:
|
|
|
517
560
|
```ruby
|
|
518
561
|
define :deploy do
|
|
519
562
|
proc do |opts|
|
|
520
|
-
|
|
521
|
-
|
|
563
|
+
hammer :build, env: 'prod', verbose: true
|
|
564
|
+
hammer 'db:migrate'
|
|
522
565
|
say.green 'deployed'
|
|
523
566
|
end
|
|
524
567
|
end
|
|
525
568
|
```
|
|
526
569
|
|
|
527
|
-
|
|
570
|
+
Signature: `hammer(name, *args, **opts)`. `name` is a symbol for a
|
|
571
|
+
single command or a colon-path string for namespaced commands. Trailing
|
|
572
|
+
positionals become positional ARGV; kwargs become CLI flags:
|
|
528
573
|
|
|
529
|
-
* `
|
|
530
|
-
|
|
531
|
-
*
|
|
574
|
+
* `hammer :foo` → `foo`
|
|
575
|
+
* `hammer 'db:users:list'` → `db:users:list`
|
|
576
|
+
* `hammer :evaluate, 'puts 42'` → `evaluate "puts 42"` (positional ARGV)
|
|
532
577
|
* `verbose: true` → `--verbose`
|
|
533
|
-
* `no_cache: true` → `--no-cache` (
|
|
534
|
-
the kwarg key become dashes)
|
|
578
|
+
* `no_cache: true` → `--no-cache` (underscores in the key become dashes)
|
|
535
579
|
* `dry_run: true` → `--dry-run`
|
|
536
580
|
* `env: 'prod'` → `--env=prod`
|
|
537
581
|
* `anything: false` → skipped (no-op; use `no_x: true` to negate)
|
|
538
582
|
|
|
539
|
-
`MyCli.
|
|
583
|
+
`MyCli.hammer 'db:users:list', verbose: true` also works at the
|
|
540
584
|
class level, useful for tests and scripting.
|
|
541
585
|
|
|
586
|
+
## Chained dispatch (`+`)
|
|
587
|
+
|
|
588
|
+
Run several commands in one shot, Rake-style, using a bare `+` token
|
|
589
|
+
as the separator:
|
|
590
|
+
|
|
591
|
+
```sh
|
|
592
|
+
hammer build + deploy + notify
|
|
593
|
+
hammer build prod -v + db:migrate --pretend + deploy --url=https://x.com
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Each segment is parsed and dispatched independently - its own opts,
|
|
597
|
+
its own positional args. `needs` prereqs dedupe across the whole
|
|
598
|
+
chain, so a shared `:env` step runs once even if every chained command
|
|
599
|
+
declares `needs :env`.
|
|
600
|
+
|
|
601
|
+
A `+` only acts as a separator when it arrives as its own argv token.
|
|
602
|
+
Quoting protects it: `--foo="a + b"` reaches the parser as a single
|
|
603
|
+
token and is left alone. To pass a literal `+` as a positional, double
|
|
604
|
+
it - `++` unescapes to a single `+`:
|
|
605
|
+
|
|
606
|
+
```sh
|
|
607
|
+
hammer echo ++ x # echo gets positional args ["+", "x"]
|
|
608
|
+
hammer echo + bar # runs echo, then runs bar
|
|
609
|
+
```
|
|
610
|
+
|
|
542
611
|
## Shell helpers
|
|
543
612
|
|
|
544
613
|
These are mixed into every handler (and also live on `Hammer::Shell` for
|
|
@@ -673,7 +742,7 @@ end
|
|
|
673
742
|
define :deploy do
|
|
674
743
|
desc 'Deploy to prod'
|
|
675
744
|
proc do |_|
|
|
676
|
-
|
|
745
|
+
hammer 'db:migrate' # cross-file invocation just works
|
|
677
746
|
say.cyan 'deployed'
|
|
678
747
|
end
|
|
679
748
|
end
|
|
@@ -798,7 +867,7 @@ define :deploy do
|
|
|
798
867
|
opt :force, type: :boolean
|
|
799
868
|
|
|
800
869
|
proc do |opts|
|
|
801
|
-
|
|
870
|
+
hammer :build, env: 'prod'
|
|
802
871
|
exit 0 unless yes? "deploy to #{opts[:url]}?" unless opts[:force]
|
|
803
872
|
say.yellow "deploying to #{opts[:url]}"
|
|
804
873
|
end
|
|
@@ -909,7 +978,7 @@ class MyCli < Hammer
|
|
|
909
978
|
end
|
|
910
979
|
|
|
911
980
|
MyCli.start(ARGV) # or:
|
|
912
|
-
MyCli.
|
|
981
|
+
MyCli.hammer :greet, loud: true
|
|
913
982
|
```
|
|
914
983
|
|
|
915
984
|
## Development
|
|
@@ -941,7 +1010,7 @@ few small things that have been bugging me about both for years.
|
|
|
941
1010
|
| Opts container | `Thor::CoreExt::HashWithIndifferentAccess` | plain `Hash` with symbol keys |
|
|
942
1011
|
| Positional args | method positional params + `method_option`, two parallel systems | declared-order opts fill from positional, single system |
|
|
943
1012
|
| Sub-namespaces | `register SubClass, 'name', '...'` (inheritance ceremony) | `namespace :name do ... end` (no classes needed) |
|
|
944
|
-
| Cross-invoke | `invoke 'name', [args], opts` | `
|
|
1013
|
+
| Cross-invoke | `invoke 'name', [args], opts` | `hammer :name, **opts` (looks like a method call) |
|
|
945
1014
|
| Inline CLI | class only | class DSL **or** `Hammer.run do ... end` block DSL **or** a `Hammerfile` |
|
|
946
1015
|
|
|
947
1016
|
**What hammer does better and why:**
|
|
@@ -955,7 +1024,7 @@ few small things that have been bugging me about both for years.
|
|
|
955
1024
|
you into method params (which then clash with options) or makes you read
|
|
956
1025
|
`ARGV` yourself. Hammer just says: opts you declared come first, leftover
|
|
957
1026
|
goes to `opts[:args]`.
|
|
958
|
-
* **Cross-invocation reads as Ruby.** `
|
|
1027
|
+
* **Cross-invocation reads as Ruby.** `hammer 'db:migrate', env: 'prod'`
|
|
959
1028
|
looks like a method call. Thor's `invoke('db:migrate', [], env: 'prod')`
|
|
960
1029
|
always feels like reflection.
|
|
961
1030
|
* **No generator complexity.** Thor's other half is file scaffolding and
|
|
@@ -971,8 +1040,8 @@ few small things that have been bugging me about both for years.
|
|
|
971
1040
|
| Namespacing | colon paths (`db:migrate`) | colon paths (`db:migrate`) - parity |
|
|
972
1041
|
| Per-task options | `task[a,b,c]` positional only | typed `opt`s with flags, aliases, defaults, required |
|
|
973
1042
|
| Help | `rake -T` (plain list) | bare `hammer` lists everything grouped by namespace; `hammer X -h` for per-command help with examples and defaults |
|
|
974
|
-
| Cross-invoke | `Rake::Task['db:migrate'].invoke` | `
|
|
975
|
-
| Prerequisites | `task :build => [:clean, :compile]` (declarative DAG) | explicit - call `
|
|
1043
|
+
| Cross-invoke | `Rake::Task['db:migrate'].invoke` | `hammer 'db:migrate'` |
|
|
1044
|
+
| Prerequisites | `task :build => [:clean, :compile]` (declarative DAG) | explicit - call `hammer :clean; hammer :compile` in the proc |
|
|
976
1045
|
| File tasks | yes (mtime-based) | no |
|
|
977
1046
|
| Aliases | none (workarounds via re-defined tasks) | `alt :short_name` |
|
|
978
1047
|
| Split across files | `import 'other.rake'` | `load auto: true` (or explicit paths/globs) |
|
data/lib/lux-hammer.rb
CHANGED
|
@@ -17,7 +17,7 @@ require_relative 'hammer/command_builder'
|
|
|
17
17
|
# opt :verbose, type: :boolean, alias: :v
|
|
18
18
|
# opt :env, type: :string, default: 'dev'
|
|
19
19
|
# proc do |opts|
|
|
20
|
-
# say "building #{opts[:env]} args=#{opts[:args].inspect}"
|
|
20
|
+
# say.green "building #{opts[:env]} args=#{opts[:args].inspect}"
|
|
21
21
|
# end
|
|
22
22
|
# end
|
|
23
23
|
# end
|
|
@@ -168,7 +168,7 @@ class Hammer
|
|
|
168
168
|
def namespace(name, &block)
|
|
169
169
|
sub = Class.new(Hammer)
|
|
170
170
|
# Track the top-level CLI class so cross-invocation
|
|
171
|
-
# (`
|
|
171
|
+
# (`hammer 'ns:cmd'`) from inside a namespaced command dispatches
|
|
172
172
|
# against the full tree, not just the current namespace.
|
|
173
173
|
sub.instance_variable_set(:@root, root)
|
|
174
174
|
# Parent link, so `before` hooks defined further up the namespace
|
|
@@ -188,7 +188,7 @@ class Hammer
|
|
|
188
188
|
#
|
|
189
189
|
# before { |opts| Dotenv.load }
|
|
190
190
|
# namespace :db do
|
|
191
|
-
# before {
|
|
191
|
+
# before { hammer :env }
|
|
192
192
|
# define :migrate do ... end
|
|
193
193
|
# end
|
|
194
194
|
def before(&block)
|
|
@@ -257,15 +257,41 @@ class Hammer
|
|
|
257
257
|
# Entry point. Parses ARGV, finds the right command, runs it.
|
|
258
258
|
# Command names are Rake-style colon paths: "build", "db:migrate",
|
|
259
259
|
# "db:users:list".
|
|
260
|
+
#
|
|
261
|
+
# Rake-style chained dispatch: `hammer build + deploy + notify`.
|
|
262
|
+
# A bare `+` argv token separates commands; `++` escapes to a literal
|
|
263
|
+
# `+` positional. Quoted shell args (`--foo="a + b"`) arrive as a
|
|
264
|
+
# single token and are not split.
|
|
260
265
|
def start(argv = ARGV)
|
|
261
266
|
# Track prereqs fired during this top-level invocation so a `needs`
|
|
262
267
|
# chain runs each prereq at most once. Nested `start` calls (e.g.
|
|
263
|
-
# `needs` -> `
|
|
264
|
-
# call owns its lifetime.
|
|
268
|
+
# `needs` -> `hammer` -> `start`, or a `+` chain) share the set;
|
|
269
|
+
# the outermost call owns its lifetime.
|
|
265
270
|
outer = Thread.current[:hammer_needs_ran].nil?
|
|
266
271
|
Thread.current[:hammer_needs_ran] ||= {}
|
|
267
272
|
Thread.current[:hammer_before_ran] ||= {}
|
|
268
273
|
|
|
274
|
+
split_chain(argv).each { |seg| dispatch(seg) }
|
|
275
|
+
ensure
|
|
276
|
+
Thread.current[:hammer_needs_ran] = nil if outer
|
|
277
|
+
Thread.current[:hammer_before_ran] = nil if outer
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private
|
|
281
|
+
|
|
282
|
+
# Split argv on bare `+` tokens; unescape `++` -> `+` within each
|
|
283
|
+
# segment. Returns [argv] when no chain operator is present.
|
|
284
|
+
def split_chain(argv)
|
|
285
|
+
return [argv] unless argv.include?('+') || argv.include?('++')
|
|
286
|
+
segs = argv.slice_when { |a, b| a == '+' || b == '+' }
|
|
287
|
+
.map { |seg| seg.reject { |t| t == '+' } }
|
|
288
|
+
.map { |seg| seg.map { |t| t == '++' ? '+' : t } }
|
|
289
|
+
.reject(&:empty?)
|
|
290
|
+
segs.empty? ? [argv] : segs
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Run a single (already-split) command segment.
|
|
294
|
+
def dispatch(argv)
|
|
269
295
|
argv = argv.dup
|
|
270
296
|
name = argv.shift
|
|
271
297
|
|
|
@@ -283,11 +309,10 @@ class Hammer
|
|
|
283
309
|
Shell.print_error("unknown command: #{name}")
|
|
284
310
|
print_help
|
|
285
311
|
exit 1
|
|
286
|
-
ensure
|
|
287
|
-
Thread.current[:hammer_needs_ran] = nil if outer
|
|
288
|
-
Thread.current[:hammer_before_ran] = nil if outer
|
|
289
312
|
end
|
|
290
313
|
|
|
314
|
+
public
|
|
315
|
+
|
|
291
316
|
# Find a command by canonical name or alt within this class.
|
|
292
317
|
def find_command(name)
|
|
293
318
|
commands[name.to_s] || commands.values.find { |c| c.matches?(name) }
|
|
@@ -313,23 +338,22 @@ class Hammer
|
|
|
313
338
|
klass
|
|
314
339
|
end
|
|
315
340
|
|
|
316
|
-
#
|
|
317
|
-
#
|
|
341
|
+
# Programmatic dispatch by name. Useful for scripting and tests.
|
|
342
|
+
#
|
|
343
|
+
# MyCli.hammer :build -> start(["build"])
|
|
344
|
+
# MyCli.hammer 'db:users:list' -> start(["db:users:list"])
|
|
345
|
+
# MyCli.hammer :eval, 'puts 42' -> start(["eval", "puts 42"])
|
|
346
|
+
# MyCli.hammer :build, env: 'prod' -> start(["build", "--env=prod"])
|
|
347
|
+
# MyCli.hammer :build, verbose: true -> start(["build", "--verbose"])
|
|
348
|
+
# MyCli.hammer :build, no_cache: true -> start(["build", "--no-cache"])
|
|
349
|
+
# MyCli.hammer :build, cache: false -> skipped (no-op)
|
|
318
350
|
#
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
path = str.sub(/^hammer_/, '').tr('_', ':')
|
|
326
|
-
argv = [path, *args.map(&:to_s)]
|
|
327
|
-
# kwarg key mirrors the CLI flag literally:
|
|
328
|
-
# verbose: true -> --verbose
|
|
329
|
-
# no_cache: true -> --no-cache (just the general rule)
|
|
330
|
-
# env: 'prod' -> --env=prod
|
|
331
|
-
# anything: false -> skipped (no-op; use `no_x: true` to negate)
|
|
332
|
-
kwargs.each do |k, v|
|
|
351
|
+
# Symbols are single-segment names; pass a string with colons for
|
|
352
|
+
# namespaced paths. Trailing positionals become positional ARGV.
|
|
353
|
+
# Underscores in option keys become dashes in flags.
|
|
354
|
+
def hammer(name, *args, **opts)
|
|
355
|
+
argv = [name.to_s, *args.map(&:to_s)]
|
|
356
|
+
opts.each do |k, v|
|
|
333
357
|
next if v == false
|
|
334
358
|
flag = "--#{k.to_s.tr('_', '-')}"
|
|
335
359
|
argv << (v == true ? flag : "#{flag}=#{v}")
|
|
@@ -337,10 +361,6 @@ class Hammer
|
|
|
337
361
|
start(argv)
|
|
338
362
|
end
|
|
339
363
|
|
|
340
|
-
def respond_to_missing?(name, include_private = false)
|
|
341
|
-
name.to_s.start_with?('hammer_') || super
|
|
342
|
-
end
|
|
343
|
-
|
|
344
364
|
# Yield [full_colon_path, Command] for every command in this class
|
|
345
365
|
# and all nested namespaces.
|
|
346
366
|
def each_command(prefix = nil, &block)
|
|
@@ -442,7 +462,7 @@ class Hammer
|
|
|
442
462
|
def print_command_list(klass, prefix = nil)
|
|
443
463
|
rows = []
|
|
444
464
|
# Commands without a `desc` are hidden from listings but still
|
|
445
|
-
# dispatchable + `
|
|
465
|
+
# dispatchable + `hammer`-callable - useful for private helpers
|
|
446
466
|
# invoked from `before` hooks or other commands (e.g. `:env`, `:app`).
|
|
447
467
|
klass.each_command(prefix) { |full, c| rows << [full, c] unless c.desc.empty? }
|
|
448
468
|
return if rows.empty?
|
|
@@ -530,20 +550,15 @@ class Hammer
|
|
|
530
550
|
#
|
|
531
551
|
# define :deploy do
|
|
532
552
|
# proc do |opts|
|
|
533
|
-
#
|
|
534
|
-
#
|
|
553
|
+
# hammer :build
|
|
554
|
+
# hammer 'db:migrate', pretend: true
|
|
535
555
|
# end
|
|
536
556
|
# end
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
self.class.root.send(name, *args, **kwargs, &block)
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
def respond_to_missing?(name, include_private = false)
|
|
546
|
-
name.to_s.start_with?('hammer_') || super
|
|
557
|
+
#
|
|
558
|
+
# Dispatches from the root class so colon paths resolve against the
|
|
559
|
+
# full tree even when called from inside a namespaced command.
|
|
560
|
+
def hammer(name, *args, **opts)
|
|
561
|
+
self.class.root.hammer(name, *args, **opts)
|
|
547
562
|
end
|
|
548
563
|
|
|
549
564
|
# ----- block DSL -----------------------------------------------------
|
|
@@ -575,11 +590,27 @@ class Hammer
|
|
|
575
590
|
path = find_hammerfile(Dir.pwd)
|
|
576
591
|
unless path
|
|
577
592
|
Shell.print_error "no Hammerfile found in #{Dir.pwd} or any parent directory"
|
|
593
|
+
|
|
594
|
+
# Heuristic: *.rb files referencing `Hammer.` are likely inline CLIs
|
|
595
|
+
# the user could promote into a Hammerfile.
|
|
596
|
+
excludes = %w[.git node_modules tmp vendor coverage dist build]
|
|
597
|
+
.map { |d| "--exclude-dir=#{d}" }.join(' ')
|
|
598
|
+
candidates = `grep -rl --include='*.rb' #{excludes} 'Hammer\\.' . 2>/dev/null`
|
|
599
|
+
.lines.map(&:strip).reject(&:empty?)
|
|
600
|
+
unless candidates.empty?
|
|
601
|
+
Shell.say "possible CLI implementation(s) - files referencing `Hammer.`:", :yellow
|
|
602
|
+
candidates.first(10).each { |f| Shell.say " #{f.sub(%r{\A\./}, '')}" }
|
|
603
|
+
Shell.say ''
|
|
604
|
+
end
|
|
605
|
+
|
|
578
606
|
Shell.say "create one - example:"
|
|
607
|
+
puts
|
|
579
608
|
Shell.say <<~RUBY
|
|
580
609
|
define :hello do
|
|
581
610
|
desc 'say hello'
|
|
582
|
-
proc
|
|
611
|
+
proc do |opts|
|
|
612
|
+
say.green "hello \#{opts[:args].first || 'world'}"
|
|
613
|
+
end
|
|
583
614
|
end
|
|
584
615
|
RUBY
|
|
585
616
|
exit 1
|