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.
- checksums.yaml +4 -4
- data/CHANGES.md +9 -0
- data/README.md +45 -1
- data/examples/gitdown +1 -0
- data/lib/clamp/completion/bash_generator.rb +141 -0
- data/lib/clamp/completion/fish_generator.rb +75 -0
- data/lib/clamp/completion/zsh_generator.rb +126 -0
- data/lib/clamp/completion.rb +163 -0
- data/lib/clamp/version.rb +1 -1
- metadata +7 -23
- data/.autotest +0 -11
- data/.editorconfig +0 -10
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.rubocop.yml +0 -71
- data/.travis.yml +0 -6
- 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
data/.editorconfig
DELETED
data/.gitignore
DELETED
data/.rspec
DELETED
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
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
|