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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 851bca602512cce6e27c0b2ddba4b9282d0aab24fa86f24de0ec04aabc98a034
4
- data.tar.gz: 4176bbdc0763fea935d6f8758128c61f09262f029a36e161bd4f9d3cdbe523f4
3
+ metadata.gz: 7e19b144891467fd0209c27265352a153f340820d14384d8deacb46e8ef58532
4
+ data.tar.gz: 279c7ac83d6f597d28e42b799e5cfdc917b0ce10f88d2950d9f0fe562ed674de
5
5
  SHA512:
6
- metadata.gz: ae6d18d7c53d52c04cd783a745b420caa420dd6e440cd512afc6d80b5c9ec05ef806732c58aa7467266bf3f4bceb0f0b72cbd7f80b6cee5bf61e0c4e7b78aafb
7
- data.tar.gz: c02de0cdb479bf2a8d88ceb6d7306835426719ed735a2beb7e3c6b3fd881dda97f7d2d9dbc9d64e7c8fe2fb794cf91cdc6f6f844570d9ff8d99859dd0538a963
6
+ metadata.gz: e9baea7eb17ad8b98ec3f7778431c603a03283e029a95e9140f83026aa88f587e01a4e87bf7dc09b6538a95f463f067293daf147df02e3454f161d737c9e6bad
7
+ data.tar.gz: 4b41c8875111734dfbe6402b97eaa03be209081f3086096cde94343eacbb1c5fd18d2933575ad1818ca08e6ffae363fd695cb107d5ddb7068610580bd04b7289
data/.version CHANGED
@@ -1 +1 @@
1
- 0.3.5
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 # Lazy-loaded `self:` namespace (recipe, ai, update)
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. Also lazy-registers the
147
- `self:` namespace when argv shows the user wants help or a
148
- `self:`-prefixed command. Not part of the user-facing surface -
149
- don't recommend it in examples; `Hammer.run` is what library users
150
- should reach for.
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) prints just the command listing (with a top gray
210
- `lux-hammer VERSION - <homepage>` banner for the hammer binary).
211
- * `hammer -h` / `hammer --help` adds global flags, a small Hammerfile
212
- example, and the footer link on top of the same listing.
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 --update # git pull main + rebuild + reinstall
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 self:recipe
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 self:recipe install srt]
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 (you control the path; nothing is written for you):
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 self:recipe install srt > ~/bin/srt && chmod +x ~/bin/srt
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 self:recipe`. The file
1036
- body uses the same DSL as a Hammerfile - `task`, `namespace`, `before`,
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 self:recipe install json > ~/bin/json && chmod +x ~/bin/json
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 `self:recipe` actions
1139
+ ### Other `recipes` actions
1062
1140
 
1063
1141
  ```sh
1064
- hammer self:recipe # list all
1065
- hammer self:recipe install # interactive picker, then prints stub
1066
- hammer self:recipe show <NAME> # cat the recipe source
1067
- hammer self:recipe path <NAME> # absolute path
1068
- hammer self:recipe edit <NAME> # open in $EDITOR (copies gem -> user dir first)
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
@@ -1,78 +1,166 @@
1
1
  class Hammer
2
- # Lazy-loaded "built-ins" attached under the reserved `self:` namespace
3
- # of the `hammer` binary. Hosts management commands (recipes, AGENTS.md
4
- # dump, self-update) that should not appear on every user-command run.
5
- # `Hammer.cli` only calls `register` when argv shows the user is
6
- # asking for help or invoking a `self:`-prefixed command.
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
- # Wire the `self:` namespace into `klass`. Idempotent - safe to call
11
- # twice; the second call replaces the existing subclass.
12
- def register(klass)
13
- Thread.current[:hammer_builtins_loading] = true
14
- klass.namespace(:self) do
15
- task :ai do
16
- desc 'Print AGENTS.md (AI-friendly Hammerfile authoring docs)'
17
- proc { Hammer.print_ai_help }
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 'Update lux-hammer from github main (requires install.sh checkout)'
65
+ desc 'Rebuild + reinstall lux-hammer from main'
22
66
  proc { Hammer.self_update }
23
67
  end
68
+ end
69
+ end
24
70
 
25
- task :recipe do
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. First positional argument is the action.
28
-
29
- (no args) list all recipes
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
- example 'self:recipe'
37
- example 'self:recipe install srt ~/bin/srt # write + chmod in one shot'
38
- example 'self:recipe install srt > ~/bin/srt && chmod +x $_'
39
- example 'self:recipe show srt'
40
- example 'self:recipe run srt extract movie.mp4'
41
- example 'self:recipe run srt -- --help # -- forwards flags to the recipe'
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
- action, name, *rest = opts[:args]
44
- Hammer::Builtins::Recipes.dispatch(action, name, rest)
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
- # Implementations of the `self:recipe <action>` sub-commands, plus
53
- # the no-action listing view. Kept in its own module so the
54
- # namespace definition above stays small and skimmable.
55
- module Recipes
56
- module_function
57
-
58
- def dispatch(action, name, rest = [])
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: self:recipe #{action} NAME)"
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 self:recipe install #{name}]"
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 self:update`. Then exec $EDITOR on the file.
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']
@@ -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 self:recipe install`. User
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 self:recipe install #{name} > ~/bin/#{name} && chmod +x $_
59
+ # install: hammer recipes --install #{name} > ~/bin/#{name} && chmod +x $_
60
60
  require 'lux-hammer'
61
61
  Hammer.recipe('#{name}', ARGV)
62
62
  RUBY