lux-hammer 0.3.5 → 0.3.7
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 +68 -11
- data/README.md +98 -18
- data/lib/hammer/builtins.rb +143 -55
- data/lib/hammer/command.rb +1 -1
- data/lib/hammer/recipe.rb +2 -2
- data/lib/lux-hammer.rb +146 -93
- data/recipes/llm.rb +434 -0
- 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: 7e19b144891467fd0209c27265352a153f340820d14384d8deacb46e8ef58532
|
|
4
|
+
data.tar.gz: 279c7ac83d6f597d28e42b799e5cfdc917b0ce10f88d2950d9f0fe562ed674de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9baea7eb17ad8b98ec3f7778431c603a03283e029a95e9140f83026aa88f587e01a4e87bf7dc09b6538a95f463f067293daf147df02e3454f161d737c9e6bad
|
|
7
|
+
data.tar.gz: 4b41c8875111734dfbe6402b97eaa03be209081f3086096cde94343eacbb1c5fd18d2933575ad1818ca08e6ffae363fd695cb107d5ddb7068610580bd04b7289
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.7
|
data/AGENTS.md
CHANGED
|
@@ -45,7 +45,7 @@ lib/hammer/loader.rb # `*_hammer.rb` fragment loader (auto/glob/file)
|
|
|
45
45
|
lib/hammer/builder.rb # Block-DSL context (Hammerfile / Hammer.run)
|
|
46
46
|
lib/hammer/command_builder.rb # `task :name do ... end` context
|
|
47
47
|
lib/hammer/recipe.rb # Recipe discovery (gem + user dir, desc, stub)
|
|
48
|
-
lib/hammer/builtins.rb #
|
|
48
|
+
lib/hammer/builtins.rb # Built-in tasks (recipes, update, agents, version, init, ...)
|
|
49
49
|
recipes/ # Bundled recipes (each `<name>.rb` -> a bin via stub)
|
|
50
50
|
test/dsl_test.rb # DSL surface, dispatch, help formatting
|
|
51
51
|
test/load_test.rb # `load` / `*_hammer.rb` fragment loader
|
|
@@ -143,11 +143,20 @@ Runtime cross-invocation:
|
|
|
143
143
|
(`bin/hammer`).
|
|
144
144
|
* `Hammer.cli(argv = ARGV)` - internal entry for `bin/hammer` only:
|
|
145
145
|
walks up from `Dir.pwd` for a `Hammerfile`, chdirs into its dir,
|
|
146
|
-
errors if none found anywhere up the tree
|
|
147
|
-
|
|
148
|
-
`
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
errors if none found anywhere up the tree (unless the invocation
|
|
147
|
+
routes to a built-in - see `dispatches_to_builtin?` /
|
|
148
|
+
`looks_like_builtin?`, true for bare invocation / leading flag /
|
|
149
|
+
explicit help / a built-in task name). After evaluating the
|
|
150
|
+
Hammerfile, registers the always-on core built-ins (`:default`,
|
|
151
|
+
`:help`, `:update`, `:agents`, `:version`) via
|
|
152
|
+
`Hammer::Builtins.register_core` - each guarded by
|
|
153
|
+
`unless commands.key?(...)` so a user-defined task wins silently.
|
|
154
|
+
No-project-only built-ins (`:recipes`, `:init`) are NOT registered
|
|
155
|
+
when a Hammerfile loaded - reach them with `--system`. The
|
|
156
|
+
`--system` flag is peeled off argv at the top and forces the
|
|
157
|
+
no-Hammerfile branch. Not part of the user-facing surface - don't
|
|
158
|
+
recommend it in examples; `Hammer.run` is what library users should
|
|
159
|
+
reach for.
|
|
151
160
|
* `Hammer.recipe(name, argv = ARGV)` - entry for recipe stubs in PATH.
|
|
152
161
|
Loads `<gem>/recipes/<name>.rb` (or its user-dir override), runs as
|
|
153
162
|
a standalone CLI with `program_name = name`. No Hammerfile lookup,
|
|
@@ -186,7 +195,9 @@ explicit ADR-level discussion. Keys:
|
|
|
186
195
|
actually picked when fuzzy matching kicks in. Only options that
|
|
187
196
|
differ from their default are listed; booleans render as `--flag`
|
|
188
197
|
/ `--no-flag`. Help paths (`-h`, bare namespace) short-circuit
|
|
189
|
-
before the banner.
|
|
198
|
+
before the banner. Set `HAMMER_QUIET=1` to suppress the banner
|
|
199
|
+
globally - useful when a task writes machine-readable output to
|
|
200
|
+
stdout (e.g. a JSON-emitting Claude Code / Codex hook).
|
|
190
201
|
* There is **no per-level dispatch**. A namespace is a container, not a
|
|
191
202
|
CLI of its own. Do not reintroduce `subclass.start(remaining_argv)`.
|
|
192
203
|
* `start(argv)` is a two-step pipeline: `split_chain(argv)` (private)
|
|
@@ -206,11 +217,57 @@ explicit ADR-level discussion. Keys:
|
|
|
206
217
|
is the whole API. `Hammer.cli` warms the cache before chdir-ing into
|
|
207
218
|
the Hammerfile's directory so the resolved name stays relative to the
|
|
208
219
|
cwd the user invoked from.
|
|
209
|
-
* `hammer` (no args)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
220
|
+
* `hammer` (no args) routes to the `:default` built-in task, which
|
|
221
|
+
falls through to `print_help(extended: false)` - the brief command
|
|
222
|
+
listing with a top gray `lux-hammer VERSION - <homepage>` banner.
|
|
223
|
+
* `hammer -h` / `--help` / `help [TARGET]` routes to the `:help`
|
|
224
|
+
built-in task, which calls `print_help(target, extended: true)` -
|
|
225
|
+
adds global flags (rendered from `:default`'s declared opts),
|
|
226
|
+
`Recipes:` section (hammer binary only), a small Hammerfile example,
|
|
227
|
+
and the footer link.
|
|
213
228
|
* `hammer COMMAND -h` / `--help` prints per-command help (reserved on every command).
|
|
229
|
+
|
|
230
|
+
## Built-in tasks (user-overridable)
|
|
231
|
+
|
|
232
|
+
`Hammer::Builtins` defines two registration entry points; both register
|
|
233
|
+
**after** Hammerfile evaluation and skip each task when the user
|
|
234
|
+
already defined it (`unless klass.commands.key?(name)` - no
|
|
235
|
+
redefinition warning).
|
|
236
|
+
|
|
237
|
+
* `register_core(klass)` - always called. Registers `:default`,
|
|
238
|
+
`:help`, `:update`, `:agents`, `:version`. These coexist with
|
|
239
|
+
Hammerfile tasks.
|
|
240
|
+
* `register_no_project(klass)` - called only on the no-Hammerfile
|
|
241
|
+
branch of `Hammer.cli` (which `--system` also routes through).
|
|
242
|
+
Registers `:recipes` and `:init` - tasks that would collide too
|
|
243
|
+
easily with user tasks inside a project.
|
|
244
|
+
|
|
245
|
+
No reserved namespace. Names like `:self`, `:recipes`, `:update` can
|
|
246
|
+
all be defined freely in a Hammerfile; the built-ins yield.
|
|
247
|
+
|
|
248
|
+
Task contracts:
|
|
249
|
+
|
|
250
|
+
* `:default` - hidden (no desc). Just prints `self.class.root.print_help`
|
|
251
|
+
(brief). Fires for bare `hammer` and leading-flag invocations where
|
|
252
|
+
the first token starts with `-` and isn't `-h` / `--help` (see
|
|
253
|
+
`Hammer.dispatch`). User overrides typically declare flag opts and
|
|
254
|
+
fall through to `hammer :help` when none matched.
|
|
255
|
+
* `:help` - has a desc, shows in listings. Calls
|
|
256
|
+
`self.class.root.print_help(opts[:args].first, extended: true)`.
|
|
257
|
+
Fires for `help` / `-h` / `--help` at the top level.
|
|
258
|
+
* `:update` / `:agents` / `:version` - thin wrappers around
|
|
259
|
+
`Hammer.self_update` / `Hammer.print_ai_help` / `puts Hammer::VERSION`.
|
|
260
|
+
* `:recipes` - all recipe-management actions on one task, picked via
|
|
261
|
+
boolean opts: `--install [NAME [TARGET]]`, `--show NAME`,
|
|
262
|
+
`--path NAME`, `--edit NAME`, `--run NAME [ARGS]` (use `--` to
|
|
263
|
+
forward flags through). Bare invocation lists.
|
|
264
|
+
* `:init` - writes `Hammer::STARTER_HAMMERFILE` to `./Hammerfile`;
|
|
265
|
+
refuses if one exists.
|
|
266
|
+
|
|
267
|
+
Both `:default` and `:help` are invoked via `run_command(cmd, argv,
|
|
268
|
+
full: name, quiet: true)` - the gray `> prog cmd ...` banner is
|
|
269
|
+
suppressed because the user didn't type `hammer default` /
|
|
270
|
+
`hammer help` literally.
|
|
214
271
|
* Commands listed flat with colon paths, grouped by top-level namespace.
|
|
215
272
|
* Bare namespace (`hammer db`) prints the same listing scoped to that
|
|
216
273
|
namespace.
|
data/README.md
CHANGED
|
@@ -73,7 +73,7 @@ Clones into `~/.local/share/lux-hammer` (override with `LUX_HAMMER_DIR=`),
|
|
|
73
73
|
builds the gem, and installs it. Re-run any time, or use:
|
|
74
74
|
|
|
75
75
|
```sh
|
|
76
|
-
hammer
|
|
76
|
+
hammer update # git pull main + rebuild + reinstall
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
## Quick start
|
|
@@ -483,10 +483,6 @@ hammer : # trailing colon at root: full help for every command
|
|
|
483
483
|
Namespaces nest to any depth. There is no per-level dispatch - the root
|
|
484
484
|
parses the whole colon path and walks the namespace tree.
|
|
485
485
|
|
|
486
|
-
The `self:` namespace is reserved for hammer's own built-in commands
|
|
487
|
-
(see [Recipes](#recipes-shareable-mini-clis-shipped-with-hammer)
|
|
488
|
-
below). Defining `namespace :self` in a Hammerfile raises an error.
|
|
489
|
-
|
|
490
486
|
## Pre-hooks (`before`)
|
|
491
487
|
|
|
492
488
|
A `before do ... end` block at the root scope or inside a `namespace`
|
|
@@ -541,6 +537,78 @@ end
|
|
|
541
537
|
`hammer` and `hammer db` won't list `env`, but `hammer env`,
|
|
542
538
|
`hammer :env` from another proc, and `before { hammer :env }` all work.
|
|
543
539
|
|
|
540
|
+
## Built-in tasks (all overridable)
|
|
541
|
+
|
|
542
|
+
The `hammer` binary auto-registers a small set of built-in tasks at
|
|
543
|
+
the root of your CLI. Each one is guarded by `unless commands.key?` -
|
|
544
|
+
defining your own `task :name` in a Hammerfile silently replaces the
|
|
545
|
+
built-in.
|
|
546
|
+
|
|
547
|
+
Always available (with or without a Hammerfile):
|
|
548
|
+
|
|
549
|
+
* `:default` - fires on bare `hammer` and on leading-flag invocations.
|
|
550
|
+
Hidden from listings. Just prints the brief help by default; override
|
|
551
|
+
to wire up your own global flags.
|
|
552
|
+
* `:help` - `hammer help [TARGET]`, `hammer -h`, `hammer --help`. Prints
|
|
553
|
+
the extended help view; `TARGET` accepts a command path or a `ns:`
|
|
554
|
+
prefix.
|
|
555
|
+
* `:update` - rebuild + reinstall lux-hammer from main.
|
|
556
|
+
* `:agents` - dump AGENTS.md (AI-friendly Hammerfile authoring docs).
|
|
557
|
+
* `:version` - print the lux-hammer version.
|
|
558
|
+
|
|
559
|
+
Only when no Hammerfile is loaded (or `--system` is passed - see below):
|
|
560
|
+
|
|
561
|
+
* `:recipes` - list / install / show / edit recipes.
|
|
562
|
+
* `:init` - write a starter Hammerfile in cwd (refuses if one exists).
|
|
563
|
+
|
|
564
|
+
These two are skipped inside a project so they don't shadow user tasks.
|
|
565
|
+
To reach them from inside a project, pass `--system`:
|
|
566
|
+
|
|
567
|
+
```sh
|
|
568
|
+
hammer --system recipes # list recipes from anywhere
|
|
569
|
+
hammer --system recipes --install srt ~/bin/srt
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
`--system` forces the no-Hammerfile branch - the Hammerfile in the
|
|
573
|
+
current tree (if any) isn't loaded for that invocation.
|
|
574
|
+
|
|
575
|
+
Customize bare `hammer` by replacing `:default`:
|
|
576
|
+
|
|
577
|
+
```ruby
|
|
578
|
+
# Hammerfile
|
|
579
|
+
|
|
580
|
+
task :default do
|
|
581
|
+
opt :version, type: :boolean, alias: :v
|
|
582
|
+
opt :status, type: :boolean, alias: :s
|
|
583
|
+
|
|
584
|
+
proc do |opts|
|
|
585
|
+
if opts[:version]
|
|
586
|
+
say "myapp #{MyApp::VERSION}"
|
|
587
|
+
elsif opts[:status]
|
|
588
|
+
sh 'bin/myapp status'
|
|
589
|
+
else
|
|
590
|
+
hammer :help # fall through to help
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Or replace `:help` to add a banner:
|
|
597
|
+
|
|
598
|
+
```ruby
|
|
599
|
+
task :help do
|
|
600
|
+
desc 'Show help (with a banner)'
|
|
601
|
+
proc do |opts|
|
|
602
|
+
say.cyan "MyApp #{MyApp::VERSION} - https://internal/docs"
|
|
603
|
+
say ''
|
|
604
|
+
self.class.root.print_help(opts[:args].first, extended: true)
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
`-h` / `--help` stay reserved on every command - you can't shadow them
|
|
610
|
+
with an `opt`.
|
|
611
|
+
|
|
544
612
|
## Prereqs (`needs`)
|
|
545
613
|
|
|
546
614
|
Declare commands that must run before this one (Rake-style task deps):
|
|
@@ -1003,19 +1071,29 @@ A **recipe** is a standalone Hammerfile-style script bundled inside the
|
|
|
1003
1071
|
top-level binary in your `PATH` - so `srt` becomes a real command, not
|
|
1004
1072
|
`hammer srt:shift`.
|
|
1005
1073
|
|
|
1074
|
+
Recipe management lives under the `recipes` task. From inside a
|
|
1075
|
+
project the task isn't registered (so it can't shadow user tasks);
|
|
1076
|
+
pass `--system` to reach it from anywhere.
|
|
1077
|
+
|
|
1006
1078
|
Listing what's available:
|
|
1007
1079
|
|
|
1008
1080
|
```sh
|
|
1009
|
-
$ hammer
|
|
1081
|
+
$ hammer recipes # from any non-project dir
|
|
1082
|
+
$ hammer --system recipes # from inside a project
|
|
1010
1083
|
gem:
|
|
1011
1084
|
srt # Subtitle (.srt) toolkit - shift timestamps, show stats
|
|
1012
|
-
[install: hammer
|
|
1085
|
+
[install: hammer recipes --install srt]
|
|
1086
|
+
llm # personal LLM utility CLI (memory store, prompt-token expander, ...)
|
|
1087
|
+
[install: hammer recipes --install llm]
|
|
1013
1088
|
```
|
|
1014
1089
|
|
|
1015
|
-
Installing one
|
|
1090
|
+
Installing one. With no TARGET, the stub is printed to stdout (you
|
|
1091
|
+
redirect it yourself); with a TARGET path, lux-hammer writes the file
|
|
1092
|
+
and chmods +x in one step:
|
|
1016
1093
|
|
|
1017
1094
|
```sh
|
|
1018
|
-
$ hammer
|
|
1095
|
+
$ hammer recipes --install srt ~/bin/srt # write + chmod
|
|
1096
|
+
$ hammer recipes --install srt > ~/bin/srt && chmod +x ~/bin/srt
|
|
1019
1097
|
$ srt --help
|
|
1020
1098
|
Usage: srt COMMAND [ARGS]
|
|
1021
1099
|
|
|
@@ -1032,8 +1110,8 @@ so the recipe always runs the version currently in the gem.
|
|
|
1032
1110
|
### Authoring your own
|
|
1033
1111
|
|
|
1034
1112
|
Drop a plain `.rb` file in `~/.config/hammer/recipes/`. The first
|
|
1035
|
-
`# desc: ...` comment is what shows in `hammer
|
|
1036
|
-
|
|
1113
|
+
`# desc: ...` comment is what shows in `hammer recipes`. The file body
|
|
1114
|
+
uses the same DSL as a Hammerfile - `task`, `namespace`, `before`,
|
|
1037
1115
|
`load`. Example `~/.config/hammer/recipes/json.rb`:
|
|
1038
1116
|
|
|
1039
1117
|
```ruby
|
|
@@ -1052,20 +1130,22 @@ end
|
|
|
1052
1130
|
Install it the same way:
|
|
1053
1131
|
|
|
1054
1132
|
```sh
|
|
1055
|
-
$ hammer
|
|
1133
|
+
$ hammer recipes --install json ~/bin/json
|
|
1056
1134
|
```
|
|
1057
1135
|
|
|
1058
1136
|
User-dir recipes override gem recipes with the same name, so you can
|
|
1059
1137
|
fork without forking.
|
|
1060
1138
|
|
|
1061
|
-
### Other `
|
|
1139
|
+
### Other `recipes` actions
|
|
1062
1140
|
|
|
1063
1141
|
```sh
|
|
1064
|
-
hammer
|
|
1065
|
-
hammer
|
|
1066
|
-
hammer
|
|
1067
|
-
hammer
|
|
1068
|
-
hammer
|
|
1142
|
+
hammer recipes # list all
|
|
1143
|
+
hammer recipes --install # interactive picker, then prints stub
|
|
1144
|
+
hammer recipes --show NAME # cat the recipe source
|
|
1145
|
+
hammer recipes --path NAME # absolute path
|
|
1146
|
+
hammer recipes --edit NAME # open in $EDITOR (copies gem -> user dir first)
|
|
1147
|
+
hammer recipes --run NAME [ARGS] # run without installing its bin
|
|
1148
|
+
hammer recipes --run NAME -- --help # `--` forwards flags to the recipe
|
|
1069
1149
|
```
|
|
1070
1150
|
|
|
1071
1151
|
## Programmatic use
|
data/lib/hammer/builtins.rb
CHANGED
|
@@ -1,78 +1,166 @@
|
|
|
1
1
|
class Hammer
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
2
|
+
# Built-in tasks of the `hammer` binary - all live at the root level
|
|
3
|
+
# (no reserved namespace). Two registration entry points:
|
|
4
|
+
#
|
|
5
|
+
# * register_core - tasks always available (subject to user override
|
|
6
|
+
# via the `unless commands.key?(...)` guard): :default, :help,
|
|
7
|
+
# :update, :agents, :version. These coexist with Hammerfile tasks.
|
|
8
|
+
#
|
|
9
|
+
# * register_no_project - tasks meaningful only when no Hammerfile is
|
|
10
|
+
# loaded (or `--system` was passed): :recipes, :init. These would
|
|
11
|
+
# collide too easily with user tasks if always registered, so the
|
|
12
|
+
# Hammerfile branch skips them.
|
|
7
13
|
module Builtins
|
|
8
14
|
module_function
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
klass.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
def register_core(klass)
|
|
17
|
+
register_help(klass) unless klass.commands.key?('help')
|
|
18
|
+
register_default(klass) unless klass.commands.key?('default')
|
|
19
|
+
register_update(klass) unless klass.commands.key?('update')
|
|
20
|
+
register_agents(klass) unless klass.commands.key?('agents')
|
|
21
|
+
register_version(klass) unless klass.commands.key?('version')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def register_no_project(klass)
|
|
25
|
+
register_recipes(klass) unless klass.commands.key?('recipes')
|
|
26
|
+
register_init(klass) unless klass.commands.key?('init')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def register_help(klass)
|
|
30
|
+
klass.class_eval do
|
|
31
|
+
task :help do
|
|
32
|
+
desc <<~TXT
|
|
33
|
+
Show help. Optional TARGET = command name or namespace (`ns:`).
|
|
34
|
+
|
|
35
|
+
Without TARGET prints the extended top-level help (commands,
|
|
36
|
+
recipes, global flags, examples). With a command path prints
|
|
37
|
+
per-command help; with a namespace prefix prints that
|
|
38
|
+
namespace's command listing.
|
|
39
|
+
TXT
|
|
40
|
+
example 'help'
|
|
41
|
+
example 'help build'
|
|
42
|
+
example 'help db:'
|
|
43
|
+
proc do |opts|
|
|
44
|
+
self.class.root.print_help(opts[:args].first, extended: true)
|
|
45
|
+
end
|
|
18
46
|
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# `:default` fires on bare `hammer` and on leading-flag invocations
|
|
51
|
+
# (other than -h/--help). Hidden from listings (no desc). All it
|
|
52
|
+
# does now is print help - the old flag opts (--update, --ai, ...)
|
|
53
|
+
# moved to dedicated tasks (:update, :agents, ...).
|
|
54
|
+
def register_default(klass)
|
|
55
|
+
klass.class_eval do
|
|
56
|
+
task :default do
|
|
57
|
+
proc { self.class.root.print_help }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
19
61
|
|
|
62
|
+
def register_update(klass)
|
|
63
|
+
klass.class_eval do
|
|
20
64
|
task :update do
|
|
21
|
-
desc '
|
|
65
|
+
desc 'Rebuild + reinstall lux-hammer from main'
|
|
22
66
|
proc { Hammer.self_update }
|
|
23
67
|
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
24
70
|
|
|
25
|
-
|
|
71
|
+
def register_agents(klass)
|
|
72
|
+
klass.class_eval do
|
|
73
|
+
task :agents do
|
|
74
|
+
desc 'Print AGENTS.md (Hammerfile authoring docs for AI assistants)'
|
|
75
|
+
proc { Hammer.print_ai_help }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def register_version(klass)
|
|
81
|
+
klass.class_eval do
|
|
82
|
+
task :version do
|
|
83
|
+
desc 'Print lux-hammer version'
|
|
84
|
+
proc { puts Hammer::VERSION }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def register_init(klass)
|
|
90
|
+
klass.class_eval do
|
|
91
|
+
task :init do
|
|
92
|
+
desc 'Write a starter Hammerfile in the current directory'
|
|
93
|
+
proc { Hammer::Builtins.write_starter_hammerfile }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# `:recipes` rolls all recipe-management actions into one task. Bare
|
|
99
|
+
# invocation lists; opts pick the action and positional args carry
|
|
100
|
+
# the recipe name (and optional target path for --install). Run via
|
|
101
|
+
# `--run NAME [ARGS]` - use `--` to forward flags to the recipe
|
|
102
|
+
# itself (e.g. `hammer recipes --run srt -- --help`).
|
|
103
|
+
def register_recipes(klass)
|
|
104
|
+
klass.class_eval do
|
|
105
|
+
task :recipes do
|
|
26
106
|
desc <<~TXT
|
|
27
|
-
Manage recipes
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
install [NAME] [PATH] install stub. No PATH: print to stdout. With PATH: write + chmod +x.
|
|
31
|
-
show NAME cat recipe source
|
|
32
|
-
path NAME print recipe abs path
|
|
33
|
-
edit NAME open recipe in $EDITOR
|
|
34
|
-
run NAME [ARGS] run a recipe without installing its bin
|
|
107
|
+
Manage recipes - the standalone Hammerfile-style scripts
|
|
108
|
+
bundled with the gem (and under ~/.config/hammer/recipes).
|
|
109
|
+
Bare invocation lists; flags pick the action.
|
|
35
110
|
TXT
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
example '
|
|
111
|
+
opt :install, type: :boolean, desc: 'install recipe stub (picker if no NAME). With TARGET path: write + chmod.'
|
|
112
|
+
opt :show, type: :boolean, desc: 'cat recipe source'
|
|
113
|
+
opt :path, type: :boolean, desc: 'print recipe abs path'
|
|
114
|
+
opt :edit, type: :boolean, desc: 'open recipe in $EDITOR (copies gem -> user dir first)'
|
|
115
|
+
opt :run, type: :boolean, desc: 'run a recipe without installing its bin (forwards remaining args)'
|
|
116
|
+
example 'recipes'
|
|
117
|
+
example 'recipes --install srt ~/bin/srt # write + chmod in one shot'
|
|
118
|
+
example 'recipes --install srt > ~/bin/srt && chmod +x $_'
|
|
119
|
+
example 'recipes --show srt'
|
|
120
|
+
example 'recipes --run srt extract movie.mp4'
|
|
121
|
+
example 'recipes --run srt -- --help # -- forwards flags to the recipe'
|
|
42
122
|
proc do |opts|
|
|
43
|
-
|
|
44
|
-
|
|
123
|
+
args = opts[:args]
|
|
124
|
+
if opts[:install]
|
|
125
|
+
Hammer::Builtins::RecipesActions.install(args[0], args[1])
|
|
126
|
+
elsif opts[:show]
|
|
127
|
+
Hammer::Builtins::RecipesActions.show(Hammer::Builtins::RecipesActions.require_name!(args[0], 'show'))
|
|
128
|
+
elsif opts[:path]
|
|
129
|
+
Hammer::Builtins::RecipesActions.path(Hammer::Builtins::RecipesActions.require_name!(args[0], 'path'))
|
|
130
|
+
elsif opts[:edit]
|
|
131
|
+
Hammer::Builtins::RecipesActions.edit(Hammer::Builtins::RecipesActions.require_name!(args[0], 'edit'))
|
|
132
|
+
elsif opts[:run]
|
|
133
|
+
name = Hammer::Builtins::RecipesActions.require_name!(args[0], 'run')
|
|
134
|
+
Hammer.recipe(name, args[1..])
|
|
135
|
+
else
|
|
136
|
+
Hammer::Builtins::RecipesActions.list
|
|
137
|
+
end
|
|
45
138
|
end
|
|
46
139
|
end
|
|
47
140
|
end
|
|
48
|
-
ensure
|
|
49
|
-
Thread.current[:hammer_builtins_loading] = nil
|
|
50
141
|
end
|
|
51
142
|
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
case action
|
|
60
|
-
when nil then list
|
|
61
|
-
when 'install' then install(name, rest.first)
|
|
62
|
-
when 'show' then show(require_name!(name, 'show'))
|
|
63
|
-
when 'path' then path(require_name!(name, 'path'))
|
|
64
|
-
when 'edit' then edit(require_name!(name, 'edit'))
|
|
65
|
-
when 'run' then Hammer.recipe(require_name!(name, 'run'), rest)
|
|
66
|
-
else
|
|
67
|
-
Shell.print_error "unknown action: #{action}"
|
|
68
|
-
Shell.say 'valid: install, show, path, edit, run (or omit for list)', :yellow
|
|
69
|
-
exit 1
|
|
70
|
-
end
|
|
143
|
+
# Writes ./Hammerfile with the canonical starter template. Refuses
|
|
144
|
+
# if one already exists - `init` must not clobber.
|
|
145
|
+
def write_starter_hammerfile
|
|
146
|
+
target = File.join(Dir.pwd, 'Hammerfile')
|
|
147
|
+
if File.exist?(target)
|
|
148
|
+
Shell.print_error "Hammerfile already exists at #{target}"
|
|
149
|
+
exit 1
|
|
71
150
|
end
|
|
151
|
+
File.write(target, Hammer::STARTER_HAMMERFILE)
|
|
152
|
+
Shell.say "created #{target}", :green
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Implementations of the `recipes` task's action flags, plus the
|
|
156
|
+
# no-flag listing view. Separate module so the task definition above
|
|
157
|
+
# stays small.
|
|
158
|
+
module RecipesActions
|
|
159
|
+
module_function
|
|
72
160
|
|
|
73
161
|
def require_name!(name, action)
|
|
74
162
|
return name if name
|
|
75
|
-
Shell.print_error "missing recipe name (usage:
|
|
163
|
+
Shell.print_error "missing recipe name (usage: recipes --#{action} NAME)"
|
|
76
164
|
exit 1
|
|
77
165
|
end
|
|
78
166
|
|
|
@@ -94,7 +182,7 @@ class Hammer
|
|
|
94
182
|
Shell.say "#{source}:", :yellow
|
|
95
183
|
items.each_key do |name|
|
|
96
184
|
_n, desc, installed = rows.find { |r| r.first == name }
|
|
97
|
-
status = installed ? "(installed: #{installed})" : "[install: hammer
|
|
185
|
+
status = installed ? "(installed: #{installed})" : "[install: hammer recipes --install #{name}]"
|
|
98
186
|
line = " #{name.ljust(width)} # #{desc}"
|
|
99
187
|
Shell.say line
|
|
100
188
|
Shell.say " #{' ' * width} #{status}", :gray
|
|
@@ -144,7 +232,7 @@ class Hammer
|
|
|
144
232
|
end
|
|
145
233
|
|
|
146
234
|
# For a gem recipe, offer to copy to user dir first so edits
|
|
147
|
-
# survive `hammer
|
|
235
|
+
# survive `hammer update`. Then exec $EDITOR on the file.
|
|
148
236
|
def edit(name)
|
|
149
237
|
path = Hammer::Recipe.path(name) or fail_unknown(name)
|
|
150
238
|
editor = ENV['EDITOR'] || ENV['VISUAL']
|
data/lib/hammer/command.rb
CHANGED
|
@@ -2,7 +2,7 @@ class Hammer
|
|
|
2
2
|
# A single registered command on a Hammer class.
|
|
3
3
|
class Command
|
|
4
4
|
attr_reader :name, :desc, :options, :examples, :alts, :needs
|
|
5
|
-
attr_accessor :handler
|
|
5
|
+
attr_accessor :handler, :location, :prev_location
|
|
6
6
|
|
|
7
7
|
def initialize(name:, desc: '', handler: nil)
|
|
8
8
|
@name = name.to_s
|
data/lib/hammer/recipe.rb
CHANGED
|
@@ -49,14 +49,14 @@ class Hammer
|
|
|
49
49
|
''
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
# Ruby wrapper text printed by `hammer
|
|
52
|
+
# Ruby wrapper text printed by `hammer recipes --install`. User
|
|
53
53
|
# redirects it to a file in PATH and chmods +x. The leading comment
|
|
54
54
|
# documents the canonical install command. Name is passed as a
|
|
55
55
|
# string literal so hyphenated names (`git-helper`) work too.
|
|
56
56
|
def stub(name)
|
|
57
57
|
<<~RUBY
|
|
58
58
|
#!/usr/bin/env ruby
|
|
59
|
-
# install: hammer
|
|
59
|
+
# install: hammer recipes --install #{name} > ~/bin/#{name} && chmod +x $_
|
|
60
60
|
require 'lux-hammer'
|
|
61
61
|
Hammer.recipe('#{name}', ARGV)
|
|
62
62
|
RUBY
|