Skip to content

Commit 97a2e6b

Browse files
committed
improve Command#usage_string and tighten specs
1 parent 512338a commit 97a2e6b

File tree

6 files changed

+231
-31
lines changed

6 files changed

+231
-31
lines changed

lib/bashly/config_validator.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ def assert_command(key, value)
112112
assert_hash key, value, Script::Command.option_keys
113113

114114
refute value['commands'] && value['args'], "#{key} cannot have both commands and args"
115-
# refute value['commands'] && value['flags'], "#{key} cannot have both commands and flags"
116115

117116
assert_string "#{key}.name", value['name']
118117
assert_optional_string "#{key}.help", value['help']
@@ -151,6 +150,10 @@ def assert_command(key, value)
151150
refute repeatable_arg, "#{key}.catch_all makes no sense with repeatable arg (#{repeatable_arg})"
152151
end
153152

153+
if value['catch_all']
154+
refute value['commands'], "#{key}.catch_all makes no sense with commands"
155+
end
156+
154157
if value['expose']
155158
assert value['commands'], "#{key}.expose makes no sense without commands"
156159
end

lib/bashly/script/command.rb

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,19 @@ def group_string
192192
end
193193
end
194194

195+
# Returns a mode identifier
196+
def mode
197+
@mode ||= begin
198+
if global_flags? then :global_flags
199+
elsif commands.any? then :commands
200+
elsif args.any? and flags.any? then :args_and_flags
201+
elsif args.any? then :args
202+
elsif flags.any? then :flags
203+
else :empty
204+
end
205+
end
206+
end
207+
195208
# Returns an array of all parents. For example, the command
196209
# "docker container run" will have [docker, container] as its parents
197210
def parents
@@ -245,14 +258,23 @@ def summary_string
245258
# Returns a constructed string suitable for Usage pattern
246259
def usage_string
247260
result = [full_name]
248-
result << "[OPTIONS]" if global_flags?
249-
result << "COMMAND" if commands.any?
250-
args.each do |arg|
251-
result << arg.usage_string
261+
262+
result.push case mode
263+
when :global_flags then ["[OPTIONS]", "COMMAND"]
264+
when :commands then ["COMMAND"]
265+
when :args_and_flags then usage_string_args + ["[OPTIONS]"]
266+
when :args then usage_string_args
267+
when :flags then ["[OPTIONS]"]
268+
else nil
252269
end
253-
result << "[OPTIONS]" unless flags.empty? || global_flags?
254-
result << catch_all.usage_string if catch_all.enabled?
255-
result.join " "
270+
271+
result.push catch_all.usage_string if catch_all.enabled? && commands.empty?
272+
result.compact.join " "
273+
end
274+
275+
# Returns an array of args usage_string for the command's usage_string
276+
def usage_string_args
277+
args.map { |arg| arg.usage_string }
256278
end
257279

258280
# Returns an array of files to include as is inside the script
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#<Bashly::ConfigurationError: root.catch_all makes no sense with commands>

spec/bashly/script/command_spec.rb

Lines changed: 125 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140
it "returns a Command object of the first default command" do
141141
expect(subject.default_command).to be_a Script::Command
142142
expect(subject.default_command.name).to eq 'get'
143-
end
143+
end
144144
end
145145

146146
describe '#default_flags' do
@@ -238,6 +238,32 @@
238238
end
239239
end
240240

241+
describe '#global_flags?' do
242+
context "when a command has flags and commands" do
243+
let(:fixture) { :mode_global_flags }
244+
245+
it "returns true" do
246+
expect(subject.global_flags?).to be_truthy
247+
end
248+
end
249+
250+
context "when a command has flags but no commands" do
251+
let(:fixture) { :mode_flags }
252+
253+
it "returns false" do
254+
expect(subject.global_flags?).to be_falsy
255+
end
256+
end
257+
258+
context "when a command has commands but no flags" do
259+
let(:fixture) { :mode_commands }
260+
261+
it "returns false" do
262+
expect(subject.global_flags?).to be_falsy
263+
end
264+
end
265+
end
266+
241267
describe '#group_string' do
242268
it "returns a string suitable for showing the group in usage" do
243269
expect(subject.group_string).to eq "Commands:"
@@ -352,42 +378,118 @@
352378
end
353379
end
354380

381+
describe '#mode' do
382+
context "when flags and commands are defined" do
383+
let(:fixture) { :mode_global_flags }
384+
385+
it "returns :global_flags" do
386+
expect(subject.mode).to eq :global_flags
387+
end
388+
end
389+
390+
context "when only commands are defined" do
391+
let(:fixture) { :mode_commands }
392+
393+
it "returns :commands" do
394+
expect(subject.mode).to eq :commands
395+
end
396+
end
397+
398+
context "when args and flags are defined" do
399+
let(:fixture) { :mode_args_and_flags }
400+
401+
it "returns :args_and_flags" do
402+
expect(subject.mode).to eq :args_and_flags
403+
end
404+
end
405+
406+
context "when only args are defined" do
407+
let(:fixture) { :mode_args }
408+
409+
it "returns :args" do
410+
expect(subject.mode).to eq :args
411+
end
412+
end
413+
414+
context "when only flags are defined" do
415+
let(:fixture) { :mode_flags }
416+
417+
it "returns :flags" do
418+
expect(subject.mode).to eq :flags
419+
end
420+
end
421+
422+
context "when nothing is defined" do
423+
let(:fixture) { :mode_empty }
424+
425+
it "returns :empty" do
426+
expect(subject.mode).to eq :empty
427+
end
428+
end
429+
end
430+
355431
describe '#usage_string' do
356-
context "when no args and no commands are defined" do
357-
let(:fixture) { :git_status }
432+
context "when flags and commands are defined" do
433+
let(:fixture) { :mode_global_flags }
358434

359-
it "returns a string suitable to be used as a usage pattern" do
360-
expect(subject.usage_string).to eq "git status"
435+
it "returns the correct string" do
436+
expect(subject.usage_string).to eq "get [OPTIONS] COMMAND"
361437
end
362438
end
363439

364-
context "when flags are defined" do
365-
let(:fixture) { :flags_only_command }
440+
context "when only commands are defined" do
441+
let(:fixture) { :mode_commands }
366442

367-
it "adds [OPTIONS] to the usage string" do
368-
expect(subject.usage_string).to eq "git status [OPTIONS]"
369-
end
443+
it "returns the correct string" do
444+
expect(subject.usage_string).to eq "get COMMAND"
445+
end
370446
end
371447

372-
context "when args are defined" do
373-
it "includes them in the usage string" do
374-
expect(subject.usage_string).to eq "get SOURCE [TARGET] [OPTIONS]"
375-
end
448+
context "when args and flags are defined" do
449+
let(:fixture) { :mode_args_and_flags }
450+
451+
it "returns the correct string" do
452+
expect(subject.usage_string).to eq "get SOURCE [TARGET] [OPTIONS] [PARAMS...]"
453+
end
454+
end
455+
456+
context "when only args are defined" do
457+
let(:fixture) { :mode_args }
458+
459+
it "returns the correct string" do
460+
expect(subject.usage_string).to eq "get SOURCE [TARGET] [PARAMS...]"
461+
end
376462
end
377463

378-
context "when commands are defined" do
379-
let(:fixture) { :docker }
464+
context "when only flags are defined" do
465+
let(:fixture) { :mode_flags }
380466

381-
it "includes [command] in the usage string" do
382-
expect(subject.usage_string).to eq "docker COMMAND"
383-
end
467+
it "returns the correct string" do
468+
expect(subject.usage_string).to eq "get [OPTIONS] [PARAMS...]"
469+
end
470+
471+
context "when it has a parent" do
472+
let(:fixture) { :flags_only_command }
473+
474+
it "returns the correct string" do
475+
expect(subject.usage_string).to eq "git status [OPTIONS]"
476+
end
477+
end
384478
end
385479

386-
context "when catch_all is enabled" do
387-
let(:fixture) { :catch_all }
480+
context "when nothing is defined" do
481+
let(:fixture) { :mode_empty }
482+
483+
it "returns the correct string" do
484+
expect(subject.usage_string).to eq "get [PARAMS...]"
485+
end
486+
487+
context "when it has a parent" do
488+
let(:fixture) { :git_status }
388489

389-
it "includes the catch_all usage string" do
390-
expect(subject.usage_string).to eq "get [...]"
490+
it "returns the correct string" do
491+
expect(subject.usage_string).to eq "git status"
492+
end
391493
end
392494
end
393495
end

spec/fixtures/script/commands.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,70 @@
217217
name: get
218218
help: get something from somewhere
219219

220+
:mode_global_flags:
221+
name: get
222+
help: get something from somewhere
223+
224+
flags:
225+
- long: --force
226+
- long: --verbose
227+
228+
commands:
229+
- name: download
230+
flags:
231+
- long: --http
232+
- name: upload
233+
234+
:mode_commands:
235+
name: get
236+
help: get something from somewhere
237+
238+
commands:
239+
- name: download
240+
flags:
241+
- long: --http
242+
- name: upload
243+
244+
:mode_args_and_flags:
245+
name: get
246+
help: get something from somewhere
247+
catch_all: params
248+
249+
args:
250+
- name: source
251+
required: true
252+
- name: target
253+
254+
flags:
255+
- long: --force
256+
required: true
257+
- long: --verbose
258+
259+
:mode_args:
260+
name: get
261+
help: get something from somewhere
262+
catch_all: params
263+
264+
args:
265+
- name: source
266+
required: true
267+
- name: target
268+
269+
:mode_flags:
270+
name: get
271+
help: get something from somewhere
272+
catch_all: params
273+
274+
flags:
275+
- long: --force
276+
required: true
277+
- long: --verbose
278+
279+
:mode_empty:
280+
name: get
281+
help: get something from somewhere
282+
catch_all: params
283+
220284
:nested_aliases:
221285
name: cli
222286
commands:

spec/fixtures/script/validations.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@
3636
help: catch_all.required should be boolean
3737
required: 1
3838

39+
:command_catch_all_commands:
40+
name: invalid
41+
catch_all:
42+
label: params
43+
help: catch_all makes no sense with commands
44+
commands:
45+
- name: config
46+
3947
:command_expose_invalid_type:
4048
name: invalid
4149
help: expose should be boolean or 'always'

0 commit comments

Comments
 (0)