tabry 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/tabry-bash +48 -0
- data/bin/tabry-help +17 -0
- data/bin/tabry-test-options +9 -0
- data/bin/tabry-test-parse +20 -0
- data/lib/tabry/cli/arg_proxy.rb +71 -0
- data/lib/tabry/cli/base.rb +34 -0
- data/lib/tabry/cli/builder.rb +104 -0
- data/lib/tabry/cli/flag_proxy.rb +45 -0
- data/lib/tabry/cli/internals.rb +10 -0
- data/lib/tabry/cli/util/config.rb +48 -0
- data/lib/tabry/cli/util.rb +51 -0
- data/lib/tabry/config_loader.rb +55 -0
- data/lib/tabry/core_ext/string/colors.rb +91 -0
- data/lib/tabry/machine.rb +124 -0
- data/lib/tabry/models/arg.rb +43 -0
- data/lib/tabry/models/arg_base.rb +10 -0
- data/lib/tabry/models/arg_include.rb +9 -0
- data/lib/tabry/models/arg_includes.rb +14 -0
- data/lib/tabry/models/args_list.rb +31 -0
- data/lib/tabry/models/config.rb +44 -0
- data/lib/tabry/models/config_error.rb +8 -0
- data/lib/tabry/models/config_list.rb +48 -0
- data/lib/tabry/models/config_object.rb +78 -0
- data/lib/tabry/models/config_string_hash.rb +44 -0
- data/lib/tabry/models/const_option.rb +17 -0
- data/lib/tabry/models/dir_option.rb +25 -0
- data/lib/tabry/models/file_option.rb +25 -0
- data/lib/tabry/models/flag.rb +55 -0
- data/lib/tabry/models/flags_list.rb +47 -0
- data/lib/tabry/models/include_arg.rb +18 -0
- data/lib/tabry/models/include_flag.rb +18 -0
- data/lib/tabry/models/include_option.rb +22 -0
- data/lib/tabry/models/include_sub.rb +18 -0
- data/lib/tabry/models/option.rb +33 -0
- data/lib/tabry/models/option_base.rb +20 -0
- data/lib/tabry/models/option_includes.rb +14 -0
- data/lib/tabry/models/options_list.rb +18 -0
- data/lib/tabry/models/shell_option.rb +13 -0
- data/lib/tabry/models/sub.rb +59 -0
- data/lib/tabry/models/subs_list.rb +28 -0
- data/lib/tabry/options_finder.rb +87 -0
- data/lib/tabry/result.rb +110 -0
- data/lib/tabry/runner.rb +27 -0
- data/lib/tabry/state.rb +14 -0
- data/lib/tabry/usage_generator.rb +137 -0
- data/lib/tabry/util.rb +15 -0
- data/sh/tabry_bash.sh +61 -0
- data/sh/tabry_bash_help.sh +7 -0
- data/spec/fixtures/basiccli.json +1 -0
- data/spec/fixtures/basiccli.tabry +5 -0
- data/spec/fixtures/basiccli2.tabry +5 -0
- data/spec/fixtures/basiccli2.yml +7 -0
- data/spec/fixtures/vehicles.tabry +60 -0
- data/spec/fixtures/vehicles.yaml +135 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/tabry/cli/arg_proxy_spec.rb +76 -0
- data/spec/tabry/cli/builder_spec.rb +226 -0
- data/spec/tabry/config_loader_spec.rb +69 -0
- data/spec/tabry/machine_spec.rb +109 -0
- data/spec/tabry/models/args_list_spec.rb +36 -0
- data/spec/tabry/models/config_spec.rb +62 -0
- data/spec/tabry/models/const_option_spec.rb +17 -0
- data/spec/tabry/models/dir_option_spec.rb +16 -0
- data/spec/tabry/models/file_option_spec.rb +16 -0
- data/spec/tabry/models/options_list_spec.rb +47 -0
- data/spec/tabry/models/shell_option_spec.rb +19 -0
- data/spec/tabry/models/subs_list_spec.rb +24 -0
- data/spec/tabry/options_finder_spec.rb +91 -0
- data/spec/tabry/result_spec.rb +236 -0
- data/spec/tabry/runner_spec.rb +35 -0
- data/spec/tabry/usage_generator_spec.rb +67 -0
- data/tabry.gemspec +27 -0
- metadata +189 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# Silly example to test various things
|
2
|
+
# Compile with:
|
3
|
+
# treesitter/tabry-compile.js spec/fixtures/vehicles.tabry > spec/fixtures/vehicles.yaml
|
4
|
+
|
5
|
+
cmd vehicles
|
6
|
+
desc "Build and control vehicles"
|
7
|
+
|
8
|
+
flag verbose,v "Give more details in output"
|
9
|
+
|
10
|
+
sub build {
|
11
|
+
varargs vehicle-types @vehicle-type-arg
|
12
|
+
}
|
13
|
+
|
14
|
+
sub list-vehicles
|
15
|
+
|
16
|
+
sub move {
|
17
|
+
sub go,g @vehicle-type-arg
|
18
|
+
sub stop,s @vehicle-type-arg
|
19
|
+
sub crash @vehicle-type-arg {
|
20
|
+
opt arg crash-into-vehicle @vehicle-type-arg {
|
21
|
+
desc "Crash into another vehicle, default is to crash into a fire hydrant"
|
22
|
+
}
|
23
|
+
flagarg speed {
|
24
|
+
# ^ could be done with opts const, but this is just to test opts shell
|
25
|
+
opts shell "echo fast && echo slow"
|
26
|
+
}
|
27
|
+
flag dry-run "Don't actually crash, just simulate it"
|
28
|
+
flagarg output-to-file,f {
|
29
|
+
opts file
|
30
|
+
opts const "-"
|
31
|
+
}
|
32
|
+
flagarg output-to-directory,dir,d { opts dir }
|
33
|
+
}
|
34
|
+
sub freeway-crash,pileup,p @vehicle-type-arg {
|
35
|
+
desc "Crash on the freeway (AKA a 'pile up')"
|
36
|
+
opt varargs crash-into-vehicles @vehicle-type-arg {
|
37
|
+
title "vehicle to crash into"
|
38
|
+
desc "List of vehicles to crash into. Optional, leave out for a '1 car pileup' -- just crashing into center divider"
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
sub sub-with-sub-or-arg {
|
44
|
+
arg { opts const (x y z) }
|
45
|
+
sub subsub
|
46
|
+
}
|
47
|
+
|
48
|
+
sub sub-with-mandatory-flag {
|
49
|
+
opt arg { opts const (a b c) }
|
50
|
+
reqd flagarg mandatory
|
51
|
+
flagarg verbose,v # to test overriding a flag
|
52
|
+
}
|
53
|
+
|
54
|
+
defargs @vehicle-type-arg {
|
55
|
+
arg vehicle-type @vehicle-type
|
56
|
+
}
|
57
|
+
|
58
|
+
defopts @vehicle-type {
|
59
|
+
opts const (car bike)
|
60
|
+
}
|
@@ -0,0 +1,135 @@
|
|
1
|
+
cmd: vehicles
|
2
|
+
main:
|
3
|
+
description: Build and control vehicles
|
4
|
+
flags:
|
5
|
+
- name: verbose
|
6
|
+
aliases:
|
7
|
+
- v
|
8
|
+
description: Give more details in output
|
9
|
+
subs:
|
10
|
+
- name: build
|
11
|
+
args:
|
12
|
+
- name: vehicle-types
|
13
|
+
options:
|
14
|
+
- type: include
|
15
|
+
value: vehicle-type-arg
|
16
|
+
varargs: true
|
17
|
+
- name: list-vehicles
|
18
|
+
- name: move
|
19
|
+
subs:
|
20
|
+
- name: go
|
21
|
+
aliases:
|
22
|
+
- g
|
23
|
+
args:
|
24
|
+
- include: vehicle-type-arg
|
25
|
+
flags:
|
26
|
+
- include: vehicle-type-arg
|
27
|
+
subs:
|
28
|
+
- include: vehicle-type-arg
|
29
|
+
- name: stop
|
30
|
+
aliases:
|
31
|
+
- s
|
32
|
+
args:
|
33
|
+
- include: vehicle-type-arg
|
34
|
+
flags:
|
35
|
+
- include: vehicle-type-arg
|
36
|
+
subs:
|
37
|
+
- include: vehicle-type-arg
|
38
|
+
- name: crash
|
39
|
+
args:
|
40
|
+
- include: vehicle-type-arg
|
41
|
+
- name: crash-into-vehicle
|
42
|
+
optional: true
|
43
|
+
options:
|
44
|
+
- type: include
|
45
|
+
value: vehicle-type-arg
|
46
|
+
description: Crash into another vehicle, default is to crash into a fire hydrant
|
47
|
+
flags:
|
48
|
+
- include: vehicle-type-arg
|
49
|
+
- name: speed
|
50
|
+
arg: true
|
51
|
+
options:
|
52
|
+
- type: shell
|
53
|
+
value: echo fast && echo slow
|
54
|
+
- name: dry-run
|
55
|
+
description: Don't actually crash, just simulate it
|
56
|
+
- name: output-to-file
|
57
|
+
aliases:
|
58
|
+
- f
|
59
|
+
arg: true
|
60
|
+
options:
|
61
|
+
- type: file
|
62
|
+
- type: const
|
63
|
+
value: "-"
|
64
|
+
- name: output-to-directory
|
65
|
+
aliases:
|
66
|
+
- dir
|
67
|
+
- d
|
68
|
+
arg: true
|
69
|
+
options:
|
70
|
+
- type: dir
|
71
|
+
subs:
|
72
|
+
- include: vehicle-type-arg
|
73
|
+
- name: freeway-crash
|
74
|
+
aliases:
|
75
|
+
- pileup
|
76
|
+
- p
|
77
|
+
args:
|
78
|
+
- include: vehicle-type-arg
|
79
|
+
- name: crash-into-vehicles
|
80
|
+
optional: true
|
81
|
+
options:
|
82
|
+
- type: include
|
83
|
+
value: vehicle-type-arg
|
84
|
+
varargs: true
|
85
|
+
title: vehicle to crash into
|
86
|
+
description: List of vehicles to crash into. Optional, leave out for a '1 car
|
87
|
+
pileup' -- just crashing into center divider
|
88
|
+
flags:
|
89
|
+
- include: vehicle-type-arg
|
90
|
+
subs:
|
91
|
+
- include: vehicle-type-arg
|
92
|
+
description: Crash on the freeway (AKA a 'pile up')
|
93
|
+
- name: sub-with-sub-or-arg
|
94
|
+
args:
|
95
|
+
- options:
|
96
|
+
- type: const
|
97
|
+
value: x
|
98
|
+
- type: const
|
99
|
+
value: "y"
|
100
|
+
- type: const
|
101
|
+
value: z
|
102
|
+
subs:
|
103
|
+
- name: subsub
|
104
|
+
- name: sub-with-mandatory-flag
|
105
|
+
args:
|
106
|
+
- optional: true
|
107
|
+
options:
|
108
|
+
- type: const
|
109
|
+
value: a
|
110
|
+
- type: const
|
111
|
+
value: b
|
112
|
+
- type: const
|
113
|
+
value: c
|
114
|
+
flags:
|
115
|
+
- name: mandatory
|
116
|
+
required: true
|
117
|
+
arg: true
|
118
|
+
- name: verbose
|
119
|
+
aliases:
|
120
|
+
- v
|
121
|
+
arg: true
|
122
|
+
arg_includes:
|
123
|
+
vehicle-type-arg:
|
124
|
+
args:
|
125
|
+
- name: vehicle-type
|
126
|
+
options:
|
127
|
+
- type: include
|
128
|
+
value: vehicle-type
|
129
|
+
option_includes:
|
130
|
+
vehicle-type:
|
131
|
+
- type: const
|
132
|
+
value: car
|
133
|
+
- type: const
|
134
|
+
value: bike
|
135
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../lib/tabry/cli/arg_proxy"
|
4
|
+
|
5
|
+
describe Tabry::CLI::ArgProxy do
|
6
|
+
let(:named_args) do
|
7
|
+
{
|
8
|
+
"abc" => "123",
|
9
|
+
"def" => "456",
|
10
|
+
"ghi" => ["789", "000"],
|
11
|
+
}
|
12
|
+
end
|
13
|
+
let(:args) { described_class.new(named_args.values, named_args) }
|
14
|
+
|
15
|
+
describe "#[]" do
|
16
|
+
it "makes args accessible by name as a hash key (string)" do
|
17
|
+
expect(args["def"]).to eq("456")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "makes args accessible by name as a hash key (symbol)" do
|
21
|
+
expect(args[:ghi]).to eq(%w[789 000])
|
22
|
+
end
|
23
|
+
|
24
|
+
it "makes args accessible by index" do
|
25
|
+
expect(args[0]).to eq("123")
|
26
|
+
expect(args[1]).to eq("456")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns ungiven args as nil" do
|
30
|
+
expect(args["foo"]).to be_nil
|
31
|
+
expect(args[:bar]).to be_nil
|
32
|
+
expect(args[3]).to be_nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#accessing as a method" do
|
37
|
+
it "makes args accessible by name as a method" do
|
38
|
+
expect(args.def).to eq("456")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns nil if not found" do
|
42
|
+
expect(args.foo).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#reqd" do
|
47
|
+
it "provides access to arguments through reqd" do
|
48
|
+
expect(args.reqd.def).to eq("456")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "prints an error and exits if read thru reqd and the argument (name) is not found" do
|
52
|
+
expect(args.reqd).to receive(:exit).with(1)
|
53
|
+
expect do
|
54
|
+
args.reqd.foo
|
55
|
+
end.to output(/FATAL: Missing required argument foo/).to_stderr
|
56
|
+
end
|
57
|
+
|
58
|
+
it "prints an error and exits if read thru reqd and the argument (index) is not found" do
|
59
|
+
expect(args.reqd).to receive(:exit).with(1)
|
60
|
+
expect do
|
61
|
+
args.reqd[3]
|
62
|
+
end.to output(/FATAL: Missing required argument number 4/).to_stderr
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#slice" do
|
67
|
+
it "returns a hash with just the keys given" do
|
68
|
+
expect(args.slice(:abc, :ghi)).to eq(abc: "123", ghi: %w[789 000])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "is enumerable like an array" do
|
73
|
+
proxy = described_class.new(%w[a b c], {})
|
74
|
+
expect(proxy.map { |arg| "ok" + arg }).to eq(%w[oka okb okc])
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../lib/tabry/cli/builder"
|
4
|
+
require_relative "../../../lib/tabry/cli/base"
|
5
|
+
|
6
|
+
# probably test base & builder together in this file. and runner
|
7
|
+
|
8
|
+
class Tabry::CLI::Builder
|
9
|
+
class TestCliFooBar < Tabry::CLI::Base
|
10
|
+
class << self
|
11
|
+
attr_accessor :actions_run, :cli_object
|
12
|
+
end
|
13
|
+
|
14
|
+
before_action :my_before_action
|
15
|
+
|
16
|
+
def main
|
17
|
+
self.class.actions_run << :main
|
18
|
+
end
|
19
|
+
|
20
|
+
def waz
|
21
|
+
self.class.actions_run << :waz
|
22
|
+
end
|
23
|
+
|
24
|
+
def my_before_action
|
25
|
+
self.class.actions_run << :before_action
|
26
|
+
self.class.cli_object = self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class TestCliFoo < Tabry::CLI::Base
|
31
|
+
sub_route :bar, TestCliFooBar
|
32
|
+
end
|
33
|
+
|
34
|
+
class TestCli < Tabry::CLI::Base
|
35
|
+
class << self
|
36
|
+
attr_accessor :actions_run, :cli_object
|
37
|
+
end
|
38
|
+
|
39
|
+
before_action :my_before_action, only: :build
|
40
|
+
after_action :my_after_action, except: :build2
|
41
|
+
|
42
|
+
sub_route :foo, TestCliFoo
|
43
|
+
|
44
|
+
def main
|
45
|
+
self.class.actions_run << :main
|
46
|
+
end
|
47
|
+
|
48
|
+
def build
|
49
|
+
self.class.actions_run << :build
|
50
|
+
end
|
51
|
+
|
52
|
+
def build2
|
53
|
+
self.class.actions_run << :build2
|
54
|
+
end
|
55
|
+
|
56
|
+
def my_action
|
57
|
+
self.class.actions_run << :my_action
|
58
|
+
end
|
59
|
+
|
60
|
+
def sub__sub_sub
|
61
|
+
self.class.actions_run << :sub__sub_sub
|
62
|
+
end
|
63
|
+
|
64
|
+
def my_before_action
|
65
|
+
self.class.actions_run << :before_action
|
66
|
+
self.class.cli_object = self
|
67
|
+
end
|
68
|
+
|
69
|
+
def my_after_action
|
70
|
+
self.class.actions_run << :after_action
|
71
|
+
self.class.cli_object = self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe Tabry::CLI::Builder do
|
77
|
+
subject { builder.run(%w[the raw args]) }
|
78
|
+
|
79
|
+
before do
|
80
|
+
runner = instance_double(Tabry::Runner)
|
81
|
+
allow(Tabry::Runner).to receive(:new).with(config_name: "theconfigname").and_return runner
|
82
|
+
allow(runner).to receive(:parse).with(%w[the raw args]).and_return result
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:result) do
|
86
|
+
instance_double(
|
87
|
+
Tabry::Result,
|
88
|
+
state: Tabry::State.new({ args: [], subcommand_stack: [] }.merge(state)),
|
89
|
+
help?: help?,
|
90
|
+
invalid_usage_reason: invalid_usage_reason,
|
91
|
+
named_args: named_args
|
92
|
+
)
|
93
|
+
end
|
94
|
+
let(:help?) { nil }
|
95
|
+
let(:invalid_usage_reason) { nil }
|
96
|
+
let(:named_args) { {} }
|
97
|
+
let(:state) { {} }
|
98
|
+
|
99
|
+
let(:builder) { described_class.new("theconfigname", Tabry::CLI::Builder::TestCli) }
|
100
|
+
|
101
|
+
let(:cli_class) { Tabry::CLI::Builder::TestCli }
|
102
|
+
let(:cli_class2) { Tabry::CLI::Builder::TestCliFooBar }
|
103
|
+
let(:actions_run) { cli_class.actions_run }
|
104
|
+
let(:cli_object) { cli_class.cli_object }
|
105
|
+
|
106
|
+
before do
|
107
|
+
cli_class.actions_run = []
|
108
|
+
cli_class.cli_object = nil
|
109
|
+
cli_class2.actions_run = []
|
110
|
+
cli_class2.cli_object = nil
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "successful runs" do
|
114
|
+
before { subject }
|
115
|
+
|
116
|
+
describe "runs the method name, and before and after actions" do
|
117
|
+
let(:state) { { subcommand_stack: %w[build] } }
|
118
|
+
|
119
|
+
it { expect(actions_run).to eq %i[before_action build after_action] }
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "changing - into _ in subcommand names, and :only on actions" do
|
123
|
+
let(:state) { { subcommand_stack: %w[my-action] } }
|
124
|
+
|
125
|
+
it { expect(actions_run).to eq %i[my_action after_action] }
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "calling main as the main subcommand" do
|
129
|
+
it { expect(actions_run).to eq %i[main after_action] }
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "except on actions" do
|
133
|
+
let(:state) { { subcommand_stack: %w[build2] } }
|
134
|
+
|
135
|
+
it { expect(actions_run).to eq %i[build2] }
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "handling multiple levels of subcommand routing" do
|
139
|
+
let(:state) { { subcommand_stack: %i[foo bar waz] } }
|
140
|
+
|
141
|
+
it "doesn't run before/after actions" do
|
142
|
+
expect(actions_run).to eq(%i[])
|
143
|
+
end
|
144
|
+
|
145
|
+
it "maps to the sub-CLI" do
|
146
|
+
expect(cli_class2.actions_run).to eq(%i[before_action waz])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "handling multiple levels of subcommand routing (main method)" do
|
151
|
+
let(:state) { { subcommand_stack: %i[foo bar] } }
|
152
|
+
|
153
|
+
it { expect(cli_class2.actions_run).to eq(%i[before_action main]) }
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "providing an ArgProxy in TestCLI#args" do
|
157
|
+
let(:state) { { args: %w[123 bar] } }
|
158
|
+
let(:named_args) { { "a" => "123" } }
|
159
|
+
|
160
|
+
it do
|
161
|
+
expect(cli_object.args).to be_a(Tabry::CLI::ArgProxy)
|
162
|
+
expect(cli_object.args.a).to eq("123")
|
163
|
+
expect(cli_object.args.waz).to be_nil
|
164
|
+
expect(cli_object.args[1]).to eq("bar")
|
165
|
+
expect(cli_object.args[2]).to be_nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "providing access to flags in TestCLI#flags" do
|
170
|
+
let(:state) { { flags: { "foo" => "123", "bar" => true } } }
|
171
|
+
|
172
|
+
it do
|
173
|
+
expect(cli_object.args).to be_a(Tabry::CLI::ArgProxy)
|
174
|
+
expect(cli_object.flags.foo).to eq("123")
|
175
|
+
expect(cli_object.flags.bar).to be(true)
|
176
|
+
expect(cli_object.flags.waz).to be_nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "unsuccessful or help runs" do
|
182
|
+
describe "invalid usage" do
|
183
|
+
let(:invalid_usage_reason) { "Bla bla not enough arguments or something" }
|
184
|
+
|
185
|
+
it "quits and shows an error and usage info if there is incorrect usage" do
|
186
|
+
expect(result).to receive(:usage).and_return "example command usage"
|
187
|
+
expect(builder).to receive(:exit).with(1)
|
188
|
+
expect do
|
189
|
+
subject
|
190
|
+
end.to output(
|
191
|
+
/Invalid usage: Bla bla not enough arguments or something.*example command usage/m
|
192
|
+
).to_stdout
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe "help" do
|
197
|
+
let(:help?) { true }
|
198
|
+
|
199
|
+
it "quits and shows usage if help is passed in" do
|
200
|
+
expect(result).to receive(:usage).and_return "example command usage"
|
201
|
+
expect(builder).to receive(:exit).with(0)
|
202
|
+
expect { subject }.to output(/example command usage/).to_stdout
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "when the CLI class that doesn't implement a method" do
|
207
|
+
context "when the command is valid but the CLI class doesn't implement it" do
|
208
|
+
let(:state) { { subcommand_stack: %w[wombat] } }
|
209
|
+
|
210
|
+
it "quits and shows an error if there is the command is valid but the CLI class doesn't implement it" do
|
211
|
+
expect(builder).to receive(:exit).with(1)
|
212
|
+
expect { subject }.to output(/FATAL: CLI does not support command wombat/).to_stderr
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context "when the sub-route CLI doesn't implement \"main\" and that is what is requested" do
|
217
|
+
let(:state) { { subcommand_stack: %w[foo] } }
|
218
|
+
|
219
|
+
it "quits and shows an error" do
|
220
|
+
expect(builder).to receive(:exit).with(1)
|
221
|
+
expect { subject }.to output(/FATAL: CLI does not support command main/).to_stderr
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../lib/tabry/config_loader"
|
4
|
+
|
5
|
+
describe Tabry::ConfigLoader do
|
6
|
+
before do
|
7
|
+
@old_home = Dir.home
|
8
|
+
@old_tabry_imports_path = ENV.fetch("TABRY_IMPORTS_PATH", nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
ENV["HOME"] = @old_home
|
13
|
+
ENV["TABRY_IMPORTS_PATH"] = @old_tabry_imports_path
|
14
|
+
end
|
15
|
+
|
16
|
+
it "loads from absolute paths" do
|
17
|
+
config = described_class.load(name: "#{__dir__}/../fixtures/vehicles.yaml")
|
18
|
+
expect(config).to be_a(Tabry::Models::Config)
|
19
|
+
expect(config.main.subs.map(&:name)).to eq(
|
20
|
+
%w[build list-vehicles move sub-with-sub-or-arg sub-with-mandatory-flag]
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "use of $TABRY_IMPORTS_PATH" do
|
25
|
+
before { ENV["TABRY_IMPORTS_PATH"] = "#{__dir__}/../fixtures/" }
|
26
|
+
|
27
|
+
it "looks for yaml files in TABRY_IMPORTS_PATH" do
|
28
|
+
config = described_class.load(name: "vehicles")
|
29
|
+
expect(config.main.subs.map(&:name)).to eq(
|
30
|
+
%w[build list-vehicles move sub-with-sub-or-arg sub-with-mandatory-flag]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "looks for json files in TABRY_IMPORTS_PATH" do
|
35
|
+
config = described_class.load(name: "basiccli")
|
36
|
+
expect(config.main.subs.map(&:name)).to eq(%w[foo])
|
37
|
+
end
|
38
|
+
|
39
|
+
it "looks for yml files in TABRY_IMPORTS_PATH" do
|
40
|
+
config = described_class.load(name: "basiccli2")
|
41
|
+
expect(config.main.subs.map(&:name)).to eq(%w[waz])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "use of $HOME" do
|
46
|
+
before do
|
47
|
+
ENV["HOME"] = "#{__dir__}/../fixtures/dir_with_dot_tabry/"
|
48
|
+
|
49
|
+
# TABRY_IMPORTS_PATH is set first, so we have to null it out in case it is already set:
|
50
|
+
ENV["TABRY_IMPORTS_PATH"] = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "looks for json files in HOME/.tabry" do
|
54
|
+
config = described_class.load(name: "basiccli")
|
55
|
+
expect(config.main.subs.map(&:name)).to eq(%w[foo])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when it can't find a config" do
|
60
|
+
it "raises an error" do
|
61
|
+
expect do
|
62
|
+
described_class.load(name: "fj093jf04309fj3490fj3490f43j90kldfsjghkljasdghklshfsdkhjagfklj")
|
63
|
+
end.to raise_error(
|
64
|
+
described_class::ConfigNotFound,
|
65
|
+
/Could not find Tabry config fj093jf04309fj3490fj3490f43j90kldfsjghkljasdghklshfsdkhjagfklj\.\(json, yml, yaml\)/
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../lib/tabry/machine"
|
4
|
+
require_relative "../../lib/tabry/config_loader"
|
5
|
+
|
6
|
+
describe Tabry::Machine do
|
7
|
+
let(:config_fixture) { "#{__dir__}/../fixtures/vehicles.yaml" }
|
8
|
+
let(:config) { Tabry::ConfigLoader.load(name: config_fixture) }
|
9
|
+
|
10
|
+
def expect_sub_stack_and_args(arr)
|
11
|
+
expect(subject.subcommand_stack).to eq(arr)
|
12
|
+
end
|
13
|
+
|
14
|
+
def expect_args(arr)
|
15
|
+
expect(subject.args).to eq(arr)
|
16
|
+
end
|
17
|
+
|
18
|
+
examples = {
|
19
|
+
"handles subcommands" => [
|
20
|
+
%w[build],
|
21
|
+
subs: %w[build], mode: :subcommand
|
22
|
+
],
|
23
|
+
"handles bogus subcommands" => [
|
24
|
+
%w[bogus],
|
25
|
+
subs: [], args: %w[bogus]
|
26
|
+
],
|
27
|
+
"handles bogus subcommands of subcommands are treated as args" => [
|
28
|
+
%w[move bogus bogus2],
|
29
|
+
subs: %w[move], args: %w[bogus bogus2], mode: :subcommand
|
30
|
+
],
|
31
|
+
"handles subcommands of subcommands" => [
|
32
|
+
%w[move go],
|
33
|
+
subs: %w[move go],
|
34
|
+
],
|
35
|
+
"handles subcommands aliases" => [
|
36
|
+
%w[move g],
|
37
|
+
subs: %w[move go],
|
38
|
+
],
|
39
|
+
"handles argsuments" => [
|
40
|
+
%w[build arg1 arg2],
|
41
|
+
subs: %w[build], args: %w[arg1 arg2]
|
42
|
+
],
|
43
|
+
"handles flags" => [
|
44
|
+
%w[build -v],
|
45
|
+
subs: %w[build], flags: { "verbose" => true }, args: [], mode: :subcommand
|
46
|
+
],
|
47
|
+
"handles long flags" => [
|
48
|
+
%w[--verbose build],
|
49
|
+
subs: %w[build], flags: { "verbose" => true }, args: [], mode: :subcommand
|
50
|
+
],
|
51
|
+
"handles flags interspersed with arguments" => [
|
52
|
+
%w[move -v crash arg1 --dry-run arg2 arg3],
|
53
|
+
subs: %w[move crash], args: %w[arg1 arg2 arg3],
|
54
|
+
flags: { "dry-run" => true, "verbose" => true }, mode: :subcommand
|
55
|
+
],
|
56
|
+
"handles flags with an argument" => [
|
57
|
+
%w[move crash --dry-run arg1 -f file arg2 arg3],
|
58
|
+
subs: %w[move crash], args: %w[arg1 arg2 arg3],
|
59
|
+
flags: { "dry-run" => true, "output-to-file" => "file" }, mode: :subcommand
|
60
|
+
],
|
61
|
+
"handles double-dash to stop parsing of flags" => [
|
62
|
+
%w[move crash -- --dry-run arg1 -f file arg2 arg3],
|
63
|
+
subs: %w[move crash], args: %w[--dry-run arg1 -f file arg2 arg3], mode: :subcommand
|
64
|
+
],
|
65
|
+
"handles unknown long flags as args" => [
|
66
|
+
%w[move crash --notaflag arg2],
|
67
|
+
subs: %w[move crash], args: %w[--notaflag arg2], mode: :subcommand
|
68
|
+
],
|
69
|
+
"handles unknown short flags as args" => [
|
70
|
+
%w[move crash -x arg2 --dry-run],
|
71
|
+
subs: %w[move crash], args: %w[-x arg2], flags: { "dry-run" => true }, mode: :subcommand
|
72
|
+
],
|
73
|
+
"handles --help" => [
|
74
|
+
%w[move --help],
|
75
|
+
subs: %w[move], flags: {}, args: [], help: true, mode: :subcommand
|
76
|
+
],
|
77
|
+
"handles -?" => [
|
78
|
+
%w[move foo bar --help waz],
|
79
|
+
subs: %w[move], flags: {}, args: %w[foo bar waz], help: true
|
80
|
+
],
|
81
|
+
"ignores -?/--help after double-dash" => [
|
82
|
+
%w[move -- --help -? -- -],
|
83
|
+
subs: %w[move], flags: {}, args: %w[--help -? -- -], help: nil
|
84
|
+
],
|
85
|
+
"sets mode to flagarg when waiting for a flag argument" => [
|
86
|
+
%w[move crash --dry-run arg1 -f],
|
87
|
+
subs: %w[move crash], args: %w[arg1], flags: { "dry-run" => true },
|
88
|
+
mode: :flagarg, current_flag: "output-to-file"
|
89
|
+
],
|
90
|
+
# TODO: might want behavior to be different; currently I think if --verbose is before the
|
91
|
+
# subcommand it use's the main command's --verbose flag
|
92
|
+
"most specific sub's flag takes precedence in case of multiple matching" => [
|
93
|
+
%w[sub-with-mandatory-flag --verbose foo --mandatory abc],
|
94
|
+
subs: %w[sub-with-mandatory-flag],
|
95
|
+
flags: { "verbose" => "foo", "mandatory" => "abc" }
|
96
|
+
]
|
97
|
+
}
|
98
|
+
|
99
|
+
examples.each do |test_name, (tokens, expectation)|
|
100
|
+
it test_name do
|
101
|
+
pending unless expectation
|
102
|
+
result = described_class.run(config, tokens)
|
103
|
+
expectation.each do |key, val|
|
104
|
+
key = :subcommand_stack if key == :subs
|
105
|
+
expect(result.send(key)).to eq(val)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|