tabry 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/bin/tabry-bash +48 -0
  3. data/bin/tabry-help +17 -0
  4. data/bin/tabry-test-options +9 -0
  5. data/bin/tabry-test-parse +20 -0
  6. data/lib/tabry/cli/arg_proxy.rb +71 -0
  7. data/lib/tabry/cli/base.rb +34 -0
  8. data/lib/tabry/cli/builder.rb +104 -0
  9. data/lib/tabry/cli/flag_proxy.rb +45 -0
  10. data/lib/tabry/cli/internals.rb +10 -0
  11. data/lib/tabry/cli/util/config.rb +48 -0
  12. data/lib/tabry/cli/util.rb +51 -0
  13. data/lib/tabry/config_loader.rb +55 -0
  14. data/lib/tabry/core_ext/string/colors.rb +91 -0
  15. data/lib/tabry/machine.rb +124 -0
  16. data/lib/tabry/models/arg.rb +43 -0
  17. data/lib/tabry/models/arg_base.rb +10 -0
  18. data/lib/tabry/models/arg_include.rb +9 -0
  19. data/lib/tabry/models/arg_includes.rb +14 -0
  20. data/lib/tabry/models/args_list.rb +31 -0
  21. data/lib/tabry/models/config.rb +44 -0
  22. data/lib/tabry/models/config_error.rb +8 -0
  23. data/lib/tabry/models/config_list.rb +48 -0
  24. data/lib/tabry/models/config_object.rb +78 -0
  25. data/lib/tabry/models/config_string_hash.rb +44 -0
  26. data/lib/tabry/models/const_option.rb +17 -0
  27. data/lib/tabry/models/dir_option.rb +25 -0
  28. data/lib/tabry/models/file_option.rb +25 -0
  29. data/lib/tabry/models/flag.rb +55 -0
  30. data/lib/tabry/models/flags_list.rb +47 -0
  31. data/lib/tabry/models/include_arg.rb +18 -0
  32. data/lib/tabry/models/include_flag.rb +18 -0
  33. data/lib/tabry/models/include_option.rb +22 -0
  34. data/lib/tabry/models/include_sub.rb +18 -0
  35. data/lib/tabry/models/option.rb +33 -0
  36. data/lib/tabry/models/option_base.rb +20 -0
  37. data/lib/tabry/models/option_includes.rb +14 -0
  38. data/lib/tabry/models/options_list.rb +18 -0
  39. data/lib/tabry/models/shell_option.rb +13 -0
  40. data/lib/tabry/models/sub.rb +59 -0
  41. data/lib/tabry/models/subs_list.rb +28 -0
  42. data/lib/tabry/options_finder.rb +87 -0
  43. data/lib/tabry/result.rb +110 -0
  44. data/lib/tabry/runner.rb +27 -0
  45. data/lib/tabry/state.rb +14 -0
  46. data/lib/tabry/usage_generator.rb +137 -0
  47. data/lib/tabry/util.rb +15 -0
  48. data/sh/tabry_bash.sh +61 -0
  49. data/sh/tabry_bash_help.sh +7 -0
  50. data/spec/fixtures/basiccli.json +1 -0
  51. data/spec/fixtures/basiccli.tabry +5 -0
  52. data/spec/fixtures/basiccli2.tabry +5 -0
  53. data/spec/fixtures/basiccli2.yml +7 -0
  54. data/spec/fixtures/vehicles.tabry +60 -0
  55. data/spec/fixtures/vehicles.yaml +135 -0
  56. data/spec/spec_helper.rb +10 -0
  57. data/spec/tabry/cli/arg_proxy_spec.rb +76 -0
  58. data/spec/tabry/cli/builder_spec.rb +226 -0
  59. data/spec/tabry/config_loader_spec.rb +69 -0
  60. data/spec/tabry/machine_spec.rb +109 -0
  61. data/spec/tabry/models/args_list_spec.rb +36 -0
  62. data/spec/tabry/models/config_spec.rb +62 -0
  63. data/spec/tabry/models/const_option_spec.rb +17 -0
  64. data/spec/tabry/models/dir_option_spec.rb +16 -0
  65. data/spec/tabry/models/file_option_spec.rb +16 -0
  66. data/spec/tabry/models/options_list_spec.rb +47 -0
  67. data/spec/tabry/models/shell_option_spec.rb +19 -0
  68. data/spec/tabry/models/subs_list_spec.rb +24 -0
  69. data/spec/tabry/options_finder_spec.rb +91 -0
  70. data/spec/tabry/result_spec.rb +236 -0
  71. data/spec/tabry/runner_spec.rb +35 -0
  72. data/spec/tabry/usage_generator_spec.rb +67 -0
  73. data/tabry.gemspec +27 -0
  74. metadata +189 -0
@@ -0,0 +1,7 @@
1
+ cmd: basiccli2
2
+ main:
3
+ subs:
4
+ - name: waz
5
+ args:
6
+ - name: bar
7
+
@@ -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
+
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["RAILS_ENV"] ||= "test"
4
+
5
+ require "simplecov"
6
+ SimpleCov.start do
7
+ add_filter "spec"
8
+ track_files "lib/**/*.rb"
9
+ end
10
+ SimpleCov.minimum_coverage(80)
@@ -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