tabry 0.1.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 +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
|