clamp 1.3.3 → 1.5.0

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.
data/.editorconfig DELETED
@@ -1,10 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = space
5
- indent_size = 2
6
- end_of_line = lf
7
- charset = utf-8
8
- trim_trailing_whitespace = true
9
- insert_final_newline = true
10
- max_line_length = 120
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- *.gem
2
- .bundle
3
- .markdownlint*
4
- .rvmrc
5
- .ruby-version
6
- .yardoc
7
- doc
8
- pkg/*
9
- Gemfile.lock
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --color
2
- --warnings
data/.rubocop.yml DELETED
@@ -1,71 +0,0 @@
1
- plugins:
2
- - rubocop-rake
3
- - rubocop-rspec
4
-
5
- AllCops:
6
- TargetRubyVersion: 2.5
7
- NewCops: enable
8
-
9
- Layout/LineLength:
10
- Max: 120
11
-
12
- Layout/EmptyLinesAroundBlockBody:
13
- Enabled: false
14
-
15
- Layout/EmptyLinesAroundClassBody:
16
- EnforcedStyle: empty_lines
17
-
18
- Layout/EmptyLinesAroundModuleBody:
19
- Enabled: false
20
-
21
- Metrics/AbcSize:
22
- Enabled: false
23
-
24
- Metrics/BlockLength:
25
- Exclude:
26
- - "spec/**/*"
27
-
28
- Metrics/MethodLength:
29
- Max: 30
30
-
31
- Naming/AccessorMethodName:
32
- Enabled: false
33
-
34
- Naming/FileName:
35
- Exclude:
36
- - "bin/*"
37
-
38
- Naming/PredicatePrefix:
39
- Enabled: false
40
-
41
- Style/ClassAndModuleChildren:
42
- EnforcedStyle: nested
43
- Exclude:
44
- - "spec/**/*"
45
-
46
- Style/Documentation:
47
- Exclude:
48
- - "lib/**/version.rb"
49
- - "examples/*"
50
- - "spec/**/*"
51
-
52
- Style/Encoding:
53
- Enabled: true
54
-
55
- Style/Lambda:
56
- Enabled: false
57
-
58
- Style/NumericLiterals:
59
- Enabled: false
60
-
61
- Style/StderrPuts:
62
- Enabled: false
63
-
64
- Style/StringLiterals:
65
- EnforcedStyle: double_quotes
66
-
67
- Style/WordArray:
68
- Enabled: false
69
-
70
- RSpec/NestedGroups:
71
- Enabled: false
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.5
4
- - 2.6
5
- - 2.7
6
- - 3.0
data/CODEOWNERS DELETED
@@ -1 +0,0 @@
1
- * @mdub
data/Gemfile DELETED
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec
6
-
7
- group :development do
8
- gem "guard-rspec", "~> 4.7", require: false
9
- gem "highline"
10
- gem "listen", "~> 3.9"
11
- gem "pry-byebug", "~> 3.11"
12
- gem "rake", "~> 13.3"
13
- gem "rubocop", "~> 1.79.0", require: false
14
- gem "rubocop-rake", "~> 0.7.1", require: false
15
- gem "rubocop-rspec", "~> 3.6.0", require: false
16
- end
17
-
18
- group :test do
19
- gem "rspec", "~> 3.13"
20
- end
data/Guardfile DELETED
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # A sample Guardfile
4
- # More info at https://github.com/guard/guard#readme
5
-
6
- ## Uncomment and set this to only include directories you want to watch
7
- # directories %w(app lib config test spec features) \
8
- # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
-
10
- ## NOTE: if you are using the `directories` clause above and you are not
11
- ## watching the project directory ('.'), then you will want to move
12
- ## the Guardfile to a watched dir and symlink it back, e.g.
13
- #
14
- # $ mkdir config
15
- # $ mv Guardfile config/
16
- # $ ln -s config/Guardfile .
17
- #
18
- # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
-
20
- # NOTE: The cmd option is now required due to the increasing number of ways
21
- # rspec may be run, below are examples of the most common uses.
22
- # * bundler: 'bundle exec rspec'
23
- # * bundler binstubs: 'bin/rspec'
24
- # * spring: 'bin/rspec' (This will use spring if running and you have
25
- # installed the spring binstubs per the docs)
26
- # * zeus: 'zeus rspec' (requires the server to be started separately)
27
- # * 'just' rspec: 'rspec'
28
-
29
- guard :rspec, cmd: "bundle exec rspec" do
30
- require "guard/rspec/dsl"
31
- dsl = Guard::RSpec::Dsl.new(self)
32
-
33
- # Feel free to open issues for suggestions and improvements
34
-
35
- # RSpec files
36
- rspec = dsl.rspec
37
- watch(rspec.spec_helper) { rspec.spec_dir }
38
- watch(rspec.spec_support) { rspec.spec_dir }
39
- watch(rspec.spec_files)
40
-
41
- # Ruby files
42
- ruby = dsl.ruby
43
- dsl.watch_spec_files_for(ruby.lib_files)
44
-
45
- end
data/Rakefile DELETED
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler"
4
-
5
- Bundler::GemHelper.install_tasks
6
-
7
- require "rspec/core/rake_task"
8
-
9
- RSpec::Core::RakeTask.new do |t|
10
- t.pattern = "spec/**/*_spec.rb"
11
- t.rspec_opts = ["--colour", "--format", "documentation"]
12
- end
13
-
14
- require "rubocop/rake_task"
15
-
16
- RuboCop::RakeTask.new
17
-
18
- task "default" => ["spec", "rubocop"]
data/clamp.gemspec DELETED
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $LOAD_PATH.push File.expand_path("lib", __dir__)
4
- require "clamp/version"
5
-
6
- Gem::Specification.new do |s|
7
-
8
- s.name = "clamp"
9
- s.version = Clamp::VERSION.dup
10
- s.platform = Gem::Platform::RUBY
11
- s.authors = ["Mike Williams"]
12
- s.email = "mdub@dogbiscuit.org"
13
- s.homepage = "https://github.com/mdub/clamp"
14
-
15
- s.license = "MIT"
16
-
17
- s.summary = "a minimal framework for command-line utilities"
18
- s.description = <<-TEXT.gsub(/^\s+/, "")
19
- Clamp provides an object-model for command-line utilities.
20
- It handles parsing of command-line options, and generation of usage help.
21
- TEXT
22
-
23
- s.files = `git ls-files`.split("\n")
24
- s.require_paths = ["lib"]
25
-
26
- s.required_ruby_version = ">= 2.5", "< 4"
27
- s.metadata["rubygems_mfa_required"] = "true"
28
- end
@@ -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