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.
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