clamp 1.4.0 → 1.5.1
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/CHANGES.md +8 -0
- data/README.md +44 -0
- data/examples/gitdown +1 -0
- data/lib/clamp/completion/bash_generator.rb +207 -0
- data/lib/clamp/completion/fish_generator.rb +176 -0
- data/lib/clamp/completion/zsh_generator.rb +123 -0
- data/lib/clamp/completion.rb +187 -0
- data/lib/clamp/version.rb +1 -1
- metadata +5 -21
- data/.autotest +0 -11
- data/.editorconfig +0 -10
- data/.github/workflows/ci.yml +0 -31
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.rubocop.yml +0 -74
- data/CODEOWNERS +0 -1
- data/Gemfile +0 -20
- data/Guardfile +0 -45
- data/Rakefile +0 -18
- data/clamp.gemspec +0 -28
- data/spec/clamp/command_group_spec.rb +0 -438
- data/spec/clamp/command_option_module_spec.rb +0 -40
- data/spec/clamp/command_option_reordering_spec.rb +0 -58
- data/spec/clamp/command_spec.rb +0 -1280
- data/spec/clamp/help/builder_spec.rb +0 -81
- data/spec/clamp/messages_spec.rb +0 -50
- data/spec/clamp/option/definition_spec.rb +0 -343
- data/spec/clamp/parameter/definition_spec.rb +0 -314
- data/spec/spec_helper.rb +0 -65
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
|
-
describe Clamp::Command do
|
|
6
|
-
|
|
7
|
-
extend CommandFactory
|
|
8
|
-
include OutputCapture
|
|
9
|
-
|
|
10
|
-
context "with subcommands" do
|
|
11
|
-
|
|
12
|
-
given_command "flipflop" do
|
|
13
|
-
|
|
14
|
-
def execute
|
|
15
|
-
puts message
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
subcommand "flip", "flip it" do
|
|
19
|
-
def message
|
|
20
|
-
"FLIPPED"
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
subcommand "flop", "flop it\nfor extra flop" do
|
|
25
|
-
def message
|
|
26
|
-
"FLOPPED"
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
describe "flip command" do
|
|
33
|
-
before do
|
|
34
|
-
command.run(["flip"])
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it "delegates to sub-commands" do
|
|
38
|
-
expect(stdout).to match(/FLIPPED/)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
describe "flop command" do
|
|
43
|
-
before do
|
|
44
|
-
command.run(["flop"])
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "delegates to sub-commands" do
|
|
48
|
-
expect(stdout).to match(/FLOPPED/)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
context "when executed with no subcommand" do
|
|
53
|
-
|
|
54
|
-
it "triggers help" do
|
|
55
|
-
expect do
|
|
56
|
-
command.run([])
|
|
57
|
-
end.to raise_error(Clamp::HelpWanted)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
describe "#help" do
|
|
63
|
-
|
|
64
|
-
it "shows subcommand parameters in usage" do
|
|
65
|
-
expect(command.help).to include("flipflop [OPTIONS] SUBCOMMAND [ARG] ...")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
it "lists subcommands" do
|
|
69
|
-
expect(command.help).to match(/Subcommands:\n +flip +flip it\n +flop +flop it/)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
it "handles new lines in subcommand descriptions" do
|
|
73
|
-
expect(command.help).to match(/flop +flop it\n +for extra flop/)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
describe ".find_subcommand_class" do
|
|
79
|
-
|
|
80
|
-
it "finds subcommand classes" do
|
|
81
|
-
flip_class = command_class.find_subcommand_class("flip")
|
|
82
|
-
expect(flip_class.new("xx").message).to eq("FLIPPED")
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
context "with an aliased subcommand" do
|
|
90
|
-
|
|
91
|
-
given_command "blah" do
|
|
92
|
-
|
|
93
|
-
subcommand ["say", "talk"], "Say something" do
|
|
94
|
-
|
|
95
|
-
parameter "WORD ...", "stuff to say"
|
|
96
|
-
|
|
97
|
-
def execute
|
|
98
|
-
puts word_list
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
describe "the first alias" do
|
|
106
|
-
|
|
107
|
-
before do
|
|
108
|
-
command.run(["say", "boo"])
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
it "responds to it" do
|
|
112
|
-
expect(stdout).to match(/boo/)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
describe "the second alias" do
|
|
118
|
-
|
|
119
|
-
before do
|
|
120
|
-
command.run(["talk", "jive"])
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
it "responds to it" do
|
|
124
|
-
expect(stdout).to match(/jive/)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
describe "#help" do
|
|
130
|
-
|
|
131
|
-
it "lists all aliases" do
|
|
132
|
-
help = command.help
|
|
133
|
-
expect(help).to match(/say, talk .* Say something/)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
context "with nested subcommands" do
|
|
141
|
-
|
|
142
|
-
given_command "fubar" do
|
|
143
|
-
|
|
144
|
-
subcommand "foo", "Foo!" do
|
|
145
|
-
|
|
146
|
-
subcommand "bar", "Baaaa!" do
|
|
147
|
-
|
|
148
|
-
def self.this_is_bar; end
|
|
149
|
-
|
|
150
|
-
def execute
|
|
151
|
-
puts "FUBAR"
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
it "delegates multiple levels" do
|
|
161
|
-
command.run(["foo", "bar"])
|
|
162
|
-
expect(stdout).to match(/FUBAR/)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
describe ".find_subcommand_class" do
|
|
166
|
-
|
|
167
|
-
it "finds nested subcommands" do
|
|
168
|
-
expect(command_class.find_subcommand_class("foo", "bar")).to respond_to(:this_is_bar)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
context "with a default subcommand" do
|
|
176
|
-
|
|
177
|
-
given_command "admin" do
|
|
178
|
-
|
|
179
|
-
self.default_subcommand = "status"
|
|
180
|
-
|
|
181
|
-
subcommand "status", "Show status" do
|
|
182
|
-
|
|
183
|
-
def execute
|
|
184
|
-
puts "All good!"
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
context "when executed with no subcommand" do
|
|
192
|
-
|
|
193
|
-
it "invokes the default subcommand" do
|
|
194
|
-
command.run([])
|
|
195
|
-
expect(stdout).to match(/All good/)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
context "with a default subcommand, declared the old way" do
|
|
203
|
-
|
|
204
|
-
given_command "admin" do
|
|
205
|
-
|
|
206
|
-
default_subcommand "status", "Show status" do
|
|
207
|
-
|
|
208
|
-
def execute
|
|
209
|
-
puts "All good!"
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
context "when executed with no subcommand" do
|
|
217
|
-
|
|
218
|
-
it "invokes the default subcommand" do
|
|
219
|
-
command.run([])
|
|
220
|
-
expect(stdout).to match(/All good/)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
context "when declaring a default subcommand after subcommands" do
|
|
228
|
-
|
|
229
|
-
let(:command) do
|
|
230
|
-
Class.new(Clamp::Command) do
|
|
231
|
-
|
|
232
|
-
subcommand "status", "Show status" do
|
|
233
|
-
|
|
234
|
-
def execute
|
|
235
|
-
puts "All good!"
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
it "is not supported" do
|
|
244
|
-
|
|
245
|
-
expect do
|
|
246
|
-
command.default_subcommand = "status"
|
|
247
|
-
end.to raise_error(/default_subcommand must be defined before subcommands/)
|
|
248
|
-
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
context "with subcommands, declared after a parameter" do
|
|
254
|
-
|
|
255
|
-
given_command "with" do
|
|
256
|
-
|
|
257
|
-
parameter "THING", "the thing"
|
|
258
|
-
|
|
259
|
-
subcommand "spit", "spit it" do
|
|
260
|
-
def execute
|
|
261
|
-
puts "spat the #{thing}"
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
subcommand "say", "say it" do
|
|
266
|
-
subcommand "loud", "yell it" do
|
|
267
|
-
def execute
|
|
268
|
-
puts thing.upcase
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
it "allows the parameter to be specified first" do
|
|
276
|
-
command.run(["dummy", "spit"])
|
|
277
|
-
expect(stdout.strip).to eq "spat the dummy"
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
it "passes the parameter down the stack" do
|
|
281
|
-
command.run(["money", "say", "loud"])
|
|
282
|
-
expect(stdout.strip).to eq "MONEY"
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
it "shows parameter in usage help" do
|
|
286
|
-
command.run(["stuff", "say", "loud", "--help"])
|
|
287
|
-
rescue Clamp::HelpWanted => e
|
|
288
|
-
expect(e.command.invocation_path).to eq "with THING say loud"
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
describe "each subcommand" do
|
|
294
|
-
|
|
295
|
-
let(:command_class) do
|
|
296
|
-
|
|
297
|
-
speed_options = Module.new do
|
|
298
|
-
extend Clamp::Option::Declaration
|
|
299
|
-
|
|
300
|
-
option "--speed", "SPEED", "how fast", default: "slowly"
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
Class.new(Clamp::Command) do
|
|
304
|
-
|
|
305
|
-
option "--direction", "DIR", "which way", default: "home"
|
|
306
|
-
|
|
307
|
-
include speed_options
|
|
308
|
-
|
|
309
|
-
subcommand "move", "move in the appointed direction" do
|
|
310
|
-
|
|
311
|
-
def execute
|
|
312
|
-
motion = context[:motion] || "walking"
|
|
313
|
-
puts "#{motion} #{direction} #{speed}"
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
let(:command) do
|
|
322
|
-
command_class.new("go")
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
it "accepts options defined in superclass (specified after the subcommand)" do
|
|
326
|
-
command.run(["move", "--direction", "north"])
|
|
327
|
-
expect(stdout).to match(/walking north/)
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
it "accepts options defined in superclass (specified before the subcommand)" do
|
|
331
|
-
command.run(["--direction", "north", "move"])
|
|
332
|
-
expect(stdout).to match(/walking north/)
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
it "accepts options defined in included modules" do
|
|
336
|
-
command.run(["move", "--speed", "very quickly"])
|
|
337
|
-
expect(stdout).to match(/walking home very quickly/)
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
it "has access to command context" do
|
|
341
|
-
command = command_class.new("go", motion: "wandering")
|
|
342
|
-
command.run(["move"])
|
|
343
|
-
expect(stdout).to match(/wandering home/)
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
context "with a subcommand, with options" do
|
|
349
|
-
|
|
350
|
-
given_command "weeheehee" do
|
|
351
|
-
option "--json", "JSON", "a json blob" do |option|
|
|
352
|
-
print "parsing!"
|
|
353
|
-
option
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
subcommand "woohoohoo", "like weeheehee but with more o" do
|
|
357
|
-
def execute; end
|
|
358
|
-
end
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
it "only parses options once" do
|
|
362
|
-
command.run(["--json", '{"a":"b"}', "woohoohoo"])
|
|
363
|
-
expect(stdout).to eq "parsing!"
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
context "with an unknown subcommand" do
|
|
369
|
-
|
|
370
|
-
let(:subcommand_missing) do
|
|
371
|
-
Module.new do
|
|
372
|
-
def subcommand_missing(_name)
|
|
373
|
-
abort "there is no such thing"
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
let(:subcommand_missing_with_return) do
|
|
379
|
-
Module.new do
|
|
380
|
-
def subcommand_missing(_name)
|
|
381
|
-
self.class.recognised_subcommands.first.subcommand_class
|
|
382
|
-
end
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
let(:command_class) do
|
|
387
|
-
|
|
388
|
-
Class.new(Clamp::Command) do
|
|
389
|
-
subcommand "test", "test subcommand" do
|
|
390
|
-
def execute
|
|
391
|
-
puts "known subcommand"
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
def execute; end
|
|
396
|
-
end
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
let(:command) do
|
|
400
|
-
command_class.new("foo")
|
|
401
|
-
end
|
|
402
|
-
|
|
403
|
-
it "signals no such subcommand usage error" do
|
|
404
|
-
expect { command.run(["foo"]) }.to raise_error(Clamp::UsageError, "No such sub-command 'foo'")
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
it "executes the subcommand missing method" do
|
|
408
|
-
command.extend subcommand_missing
|
|
409
|
-
expect { command.run(["foo"]) }.to raise_error(SystemExit, /there is no such thing/)
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
it "uses the subcommand class returned from subcommand_missing" do
|
|
413
|
-
command.extend subcommand_missing_with_return
|
|
414
|
-
command.run(["foo"])
|
|
415
|
-
expect(stdout).to match(/known subcommand/)
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
context "with a subcommand and required options" do
|
|
421
|
-
|
|
422
|
-
given_command "movements" do
|
|
423
|
-
option "--direction", "N|S|E|W", "bearing", required: true
|
|
424
|
-
subcommand "hop", "Hop" do
|
|
425
|
-
def execute
|
|
426
|
-
puts "Hopping #{direction}"
|
|
427
|
-
end
|
|
428
|
-
end
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
it "allows options after the subcommand" do
|
|
432
|
-
command.run(%w[hop --direction south])
|
|
433
|
-
expect(stdout).to eq "Hopping south\n"
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
|
-
describe Clamp::Command do
|
|
6
|
-
|
|
7
|
-
include OutputCapture
|
|
8
|
-
|
|
9
|
-
context "with included module" do
|
|
10
|
-
|
|
11
|
-
let(:command) do
|
|
12
|
-
|
|
13
|
-
shared_options = Module.new do
|
|
14
|
-
extend Clamp::Option::Declaration
|
|
15
|
-
|
|
16
|
-
option "--size", "SIZE", default: 4
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
command_class = Class.new(Clamp::Command) do
|
|
20
|
-
|
|
21
|
-
include shared_options
|
|
22
|
-
|
|
23
|
-
def execute
|
|
24
|
-
puts "size = #{size}"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
command_class.new("foo")
|
|
30
|
-
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it "accepts options from included module" do
|
|
34
|
-
command.run(["--size", "42"])
|
|
35
|
-
expect(stdout).to eq "size = 42\n"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
|
-
describe Clamp::Command do
|
|
6
|
-
|
|
7
|
-
extend CommandFactory
|
|
8
|
-
include OutputCapture
|
|
9
|
-
|
|
10
|
-
context "with allow_options_after_parameters enabled" do
|
|
11
|
-
|
|
12
|
-
before do
|
|
13
|
-
Clamp.allow_options_after_parameters = true
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
after do
|
|
17
|
-
Clamp.allow_options_after_parameters = false
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
given_command("cmd") do
|
|
21
|
-
|
|
22
|
-
option ["-v", "--verbose"], :flag, "Be noisy"
|
|
23
|
-
|
|
24
|
-
subcommand "say", "Say something" do
|
|
25
|
-
|
|
26
|
-
option "--loud", :flag, "say it loud"
|
|
27
|
-
|
|
28
|
-
parameter "WORDS ...", "the thing to say", attribute_name: :words
|
|
29
|
-
|
|
30
|
-
def execute
|
|
31
|
-
message = words.join(" ")
|
|
32
|
-
message = message.upcase if loud?
|
|
33
|
-
message *= 3 if verbose?
|
|
34
|
-
$stdout.puts message
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it "still works" do
|
|
42
|
-
command.run(%w[say foo])
|
|
43
|
-
expect(stdout).to eq "foo\n"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it "honours options after positional arguments" do
|
|
47
|
-
command.run(%w[say blah --verbose])
|
|
48
|
-
expect(stdout).to eq "blahblahblah\n"
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it "honours options declared on subcommands" do
|
|
52
|
-
command.run(%w[say --loud blah])
|
|
53
|
-
expect(stdout).to eq "BLAH\n"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
end
|