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,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/args_list"
5
+
6
+ describe Tabry::Models::ArgsList do
7
+ subject do
8
+ described_class.new(root: double, raw: [
9
+ { "name" => "foo", "description" => "the foo arg" },
10
+ { "name" => "bar", "description" => "the bar arg" },
11
+ { "name" => "things", "description" => "the waz arg", "varargs" => true },
12
+ ])
13
+ end
14
+
15
+ describe "#[]" do
16
+ it "provides access to arguments by index" do
17
+ expect(subject[0].name).to eq("foo")
18
+ expect(subject[0]).to be_a(Tabry::Models::Arg)
19
+ expect(subject[1].name).to eq("bar")
20
+ expect(subject[2].name).to eq("things")
21
+ expect(subject[2]).to be_a(Tabry::Models::Arg)
22
+ expect(subject[2].varargs?).to be(true)
23
+ end
24
+ end
25
+
26
+ describe "#varargs_arg" do
27
+ it "returns the vararg if there is one" do
28
+ expect(subject.varargs_arg).to eq(subject[2])
29
+ end
30
+
31
+ it "returns nil if there is no varargs" do
32
+ args_list = described_class.new(root: double, raw: [{ "name" => "foo" }])
33
+ expect(args_list.varargs_arg).to be_nil
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/config"
5
+
6
+ describe Tabry::Models::Config do
7
+ subject { described_class.new(raw: JSON.parse(config_hash.to_json)) }
8
+
9
+ let(:config_hash) do
10
+ {
11
+ cmd: "foo",
12
+ main: {
13
+ subs: [
14
+ {
15
+ name: "sub1",
16
+ description: "first-level sub",
17
+ subs: [
18
+ {
19
+ name: "sub1.1",
20
+ description: "sub under a sub"
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ name: "sub2",
26
+ description: "first-level sub 2",
27
+ subs: [
28
+ { name: "sub21", subs: [{ name: "subs211" }] }
29
+ ],
30
+ }
31
+ ]
32
+ }
33
+ }
34
+ end
35
+
36
+ describe "#dig_sub_array" do
37
+ it "returns the array of subcommands leading up to the specified subcommand" do
38
+ res = subject.dig_sub_array(%w[sub1 sub1.1])
39
+ expect(res.map(&:name)).to eq([nil, "sub1", "sub1.1"])
40
+ expect(res).to eq([
41
+ subject.main,
42
+ subject.main.subs[0],
43
+ subject.main.subs[0].subs[0]
44
+ ])
45
+ end
46
+
47
+ it "returns an array with just the main subcommand if an empty array is passed in" do
48
+ res = subject.dig_sub_array(%w[])
49
+ expect(res).to eq([subject.main])
50
+ end
51
+ end
52
+
53
+ describe "#dig_sub" do
54
+ it "returns the sub referenced" do
55
+ expect(subject.dig_sub(%w[sub2])).to eq(subject.main.subs[1])
56
+ end
57
+
58
+ it "returns the main sub if an empty array is passed" do
59
+ expect(subject.dig_sub(%w[])).to eq(subject.main)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/const_option"
5
+
6
+ describe Tabry::Models::ConstOption do
7
+ subject do
8
+ described_class.new(root: double, raw: { "type" => "const", "value" => "foobar" })
9
+ end
10
+
11
+ it "filters by prefix" do
12
+ expect(subject.options("")).to eq(%w[foobar])
13
+ expect(subject.options("f")).to eq(%w[foobar])
14
+ expect(subject.options("foo")).to eq(%w[foobar])
15
+ expect(subject.options("w")).to eq(%w[])
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/dir_option"
5
+
6
+ describe Tabry::Models::DirOption do
7
+ subject do
8
+ described_class.new(root: double, raw: { "type" => "dir" })
9
+ end
10
+
11
+ # Handled by tabru=bash/tabry-bash.sh/shell, we just return a symbol to
12
+ # communicate to tabry-bash
13
+ it "returns a array with a symbol" do
14
+ expect(subject.options("whatever")).to eq([:directory])
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/file_option"
5
+
6
+ describe Tabry::Models::FileOption do
7
+ subject do
8
+ described_class.new(root: double, raw: { "type" => "file" })
9
+ end
10
+
11
+ # Handled by tabru=bash/tabry-bash.sh/shell, we just return a symbol to
12
+ # communicate to tabry-bash
13
+ it "returns a array with a symbol" do
14
+ expect(subject.options("whatever")).to eq([:file])
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/options_list"
5
+
6
+ describe Tabry::Models::OptionsList do
7
+ subject do
8
+ described_class.new(root: double, raw: JSON.parse(raw_array.to_json))
9
+ end
10
+
11
+ let(:raw_array) do
12
+ [
13
+ { type: "file" },
14
+ { type: "shell", value: "echo a && echo b" },
15
+ { type: "const", value: "foo" },
16
+ { type: "const", value: "bar" },
17
+ { type: "dir" },
18
+ ]
19
+ end
20
+
21
+ it "is enumerable over the options it creates" do
22
+ expect(subject.any? { |opt| opt.type == "dir" }).to be(true)
23
+ expect(subject.map(&:class)).to eq([
24
+ Tabry::Models::FileOption,
25
+ Tabry::Models::ShellOption,
26
+ Tabry::Models::ConstOption,
27
+ Tabry::Models::ConstOption,
28
+ Tabry::Models::DirOption,
29
+ ])
30
+ end
31
+
32
+ describe "options" do
33
+ it "aggregates options from all options" do
34
+ opts_results = [
35
+ %w[foo bar waz],
36
+ %w[a b c],
37
+ %w[1 a foo],
38
+ %w[ok ok ok],
39
+ %w[d c b a]
40
+ ]
41
+ subject.each_with_index do |opt, i|
42
+ expect(opt).to receive(:options).with("some-token").and_return(opts_results[i])
43
+ end
44
+ expect(subject.options("some-token")).to match_array(%w[a b c d foo bar waz ok 1])
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/shell_option"
5
+
6
+ describe Tabry::Models::ShellOption do
7
+ subject do
8
+ described_class.new(root: double, raw: {
9
+ "type" => "shell", "value" => "echo a && echo a1 && echo b"
10
+ })
11
+ end
12
+
13
+ it "filters by prefix" do
14
+ expect(subject.options("")).to match_array(%w[a a1 b])
15
+ expect(subject.options("a")).to match_array(%w[a a1])
16
+ expect(subject.options("a1")).to match_array(%w[a1])
17
+ expect(subject.options("a1 ")).to match_array(%w[])
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../lib/tabry/models/subs_list"
5
+
6
+ describe Tabry::Models::SubsList do
7
+ describe "#by_name" do
8
+ subject do
9
+ described_class.new(root: double, raw: [
10
+ { "name" => "foo", "description" => "the foo sub" },
11
+ { "name" => "bar", "description" => "the bar subcommand" },
12
+ { "name" => "waz", "description" => "the waz subcommand" },
13
+ ])
14
+ end
15
+
16
+ it "finds subcommands by name" do
17
+ bar = subject.by_name["bar"]
18
+ expect(bar).to be_a(Tabry::Models::Sub)
19
+ expect(bar.name).to eq("bar")
20
+ expect(bar.description).to eq("the bar subcommand")
21
+ expect(subject.by_name["waz"].name).to eq("waz")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/tabry/config_loader"
4
+ require_relative "../../lib/tabry/options_finder"
5
+ require_relative "../../lib/tabry/result"
6
+ require_relative "../../lib/tabry/state"
7
+
8
+ describe Tabry::OptionsFinder do
9
+ let(:config_fixture) { "#{__dir__}/../fixtures/vehicles.yaml" }
10
+ let(:config) { Tabry::ConfigLoader.load(name: config_fixture) }
11
+ let(:state) { {} }
12
+
13
+ examples = {
14
+ "lists possible subcommands of the main command" => [
15
+ %w[build list-vehicles move sub-with-sub-or-arg sub-with-mandatory-flag],
16
+ {}
17
+ ],
18
+ "lists possible subcommands of a subcommand" => [
19
+ %w[go stop crash freeway-crash],
20
+ subs: %w[move]
21
+ ],
22
+ "lists possible arguments (const)" => [
23
+ %w[car bike],
24
+ subs: %w[move go]
25
+ ],
26
+ "lists both possible args and subs if a subcommand can take either" => [
27
+ %w[x y z subsub],
28
+ subs: %w[sub-with-sub-or-arg]
29
+ ],
30
+ "lists possible flags if the last token starts with a dash" => [
31
+ %w[--verbose --speed --output-to-file --output-to-directory --dry-run],
32
+ subs: %w[move crash],
33
+ token: "-"
34
+ ],
35
+ "doesn't list a flag if it has already been given" => [
36
+ %w[--verbose --speed --output-to-file --output-to-directory],
37
+ flags: { "dry-run" => true },
38
+ subs: %w[move crash],
39
+ token: "-"
40
+ ],
41
+ "doesn't suggests flags if '--' has been used" => [
42
+ [],
43
+ dashdash: true,
44
+ subs: %w[move crash],
45
+ token: "-",
46
+ ],
47
+ "lists only a mandatory flag if it hasn't been given yet" => [
48
+ %w[--mandatory],
49
+ subs: %w[sub-with-mandatory-flag],
50
+ ],
51
+ "lists other args after a mandatory flag has been given" => [
52
+ %w[a b c],
53
+ subs: %w[sub-with-mandatory-flag],
54
+ flags: { "mandatory" => "foo" }
55
+ ],
56
+ "lists possibilities for a flag arguments (shell)" => [
57
+ %w[fast slow],
58
+ subs: %w[move crash],
59
+ mode: :flagarg,
60
+ current_flag: "speed",
61
+ ],
62
+ "lists possibilities for a flag arguments (file, const)" => [
63
+ [:file, "-"],
64
+ subs: %w[move crash],
65
+ mode: :flagarg,
66
+ current_flag: "output-to-file",
67
+ ],
68
+ "lists possibilities for a flag arguments (dir)" => [
69
+ [:directory],
70
+ subs: %w[move crash],
71
+ mode: :flagarg,
72
+ current_flag: "output-to-directory",
73
+ ],
74
+ "lists nothing if no options are defined" => [
75
+ [],
76
+ subs: %w[sub-with-sub-or-arg],
77
+ mode: :flagarg,
78
+ current_flag: "mandatory"
79
+ ],
80
+ }
81
+
82
+ examples.each do |name, (expected_options, hash)|
83
+ it name do
84
+ token = hash.delete(:token)
85
+ hash[:subcommand_stack] = hash.delete(:subs) || []
86
+ defaults = { mode: :subcommand, args: [], flags: {}, current_flag: nil, dashdash: nil }
87
+ result = Tabry::Result.new(config, Tabry::State.new(defaults.merge(hash)))
88
+ expect(described_class.options(result, token)).to match_array expected_options
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/tabry/result"
4
+ require_relative "../../lib/tabry/config_loader"
5
+ require_relative "../../lib/tabry/state"
6
+
7
+ describe Tabry::Result do
8
+ subject do
9
+ described_class.new(config, state)
10
+ end
11
+
12
+ let(:config_fixture) { "#{__dir__}/../fixtures/vehicles.yaml" }
13
+ let(:config) { Tabry::ConfigLoader.load(name: config_fixture) }
14
+
15
+ describe "#current_sub" do
16
+ let(:state) { Tabry::State.new(subcommand_stack: %w[move crash]) }
17
+
18
+ it "returns the last subcommand" do
19
+ expect(subject.current_sub).to be_a(Tabry::Models::Sub)
20
+ expect(subject.current_sub.name).to eq("crash")
21
+ end
22
+ end
23
+
24
+ describe "#invalid_usage_reason" do
25
+ def reason(state_hash)
26
+ state = { mode: :subcommand, args: [], flags: {} }.merge(state_hash)
27
+ described_class.new(config, Tabry::State.new(**state)).invalid_usage_reason
28
+ end
29
+
30
+ it "returns nil if everything is fine (subcommand with no args)" do
31
+ expect(reason(
32
+ subcommand_stack: %w[list-vehicles]
33
+ )).to be_nil
34
+ end
35
+
36
+ it "complains if mandatory args are not given" do
37
+ expect(reason(
38
+ subcommand_stack: %w[move go]
39
+ )).to eq('missing argument "vehicle-type"')
40
+ end
41
+
42
+ it "complains if a mandatory subcommand (of the main command) is not given" do
43
+ expect(reason(
44
+ subcommand_stack: %w[]
45
+ )).to eq("missing subcommand")
46
+ end
47
+
48
+ it "complains if a mandatory subcommand (of a subcommand) is not given" do
49
+ expect(reason(
50
+ subcommand_stack: %w[move]
51
+ )).to eq("missing subcommand")
52
+ end
53
+
54
+ it "complains if a mandatory subcommand or arg is not given" do
55
+ expect(reason(
56
+ subcommand_stack: %w[sub-with-sub-or-arg]
57
+ )).to eq("missing subcommand or arg(s)")
58
+ end
59
+
60
+ it "doesn't complain if mandatory args are given" do
61
+ expect(reason(
62
+ subcommand_stack: %w[move go],
63
+ args: %w[vehicle1],
64
+ flags: { "dry-run" => true, "output-to-file" => "some-file" }
65
+ )).to be_nil
66
+
67
+ expect(reason(
68
+ subcommand_stack: %w[sub-with-sub-or-arg],
69
+ args: %w[arg]
70
+ )).to be_nil
71
+ end
72
+
73
+ it "complains if mandatory varargs are not given" do
74
+ expect(reason(
75
+ subcommand_stack: %w[build]
76
+ )).to eq("missing one or more args")
77
+ end
78
+
79
+ it "doesn't complain if one vararg is given" do
80
+ expect(reason(
81
+ subcommand_stack: %w[build],
82
+ args: %w[vehicle1]
83
+ )).to be_nil
84
+
85
+ expect(reason(
86
+ subcommand_stack: %w[move freeway-crash],
87
+ args: %w[vehicle1 vehicle2]
88
+ )).to be_nil
89
+ end
90
+
91
+ it "doesn't complain if multiple varargs are given" do
92
+ expect(reason(
93
+ subcommand_stack: %w[build],
94
+ args: %w[vehicle1 vehicle2 vehicle3]
95
+ )).to be_nil
96
+
97
+ expect(reason(
98
+ subcommand_stack: %w[move freeway-crash],
99
+ args: %w[vehicle1 vehicle2 vehicle3 vehicle4]
100
+ )).to be_nil
101
+ end
102
+
103
+ it "doesn't complain if an optional arg is not given" do
104
+ expect(reason(
105
+ subcommand_stack: %w[move crash],
106
+ args: %w[vehicle1]
107
+ )).to be_nil
108
+ end
109
+
110
+ it "doesn't complain if an optional arg is given" do
111
+ expect(reason(
112
+ subcommand_stack: %w[move crash],
113
+ args: %w[vehicle1 vehicle2]
114
+ )).to be_nil
115
+ end
116
+
117
+ it "complains if too many args are given" do
118
+ expect(reason(
119
+ subcommand_stack: %w[move crash],
120
+ args: %w[vehicle1 vehicle2 vehicle3]
121
+ )).to eq("got 3 args, must be between 1 and 2")
122
+ end
123
+
124
+ # TODO: test also in machine
125
+ it "complains if a mandatory flag is not given" do
126
+ expect(reason(
127
+ subcommand_stack: %w[sub-with-mandatory-flag]
128
+ )).to eq("missing required flag mandatory")
129
+ end
130
+
131
+ it "doesn't complain if a mandatory flag is given" do
132
+ expect(reason(
133
+ subcommand_stack: %w[sub-with-mandatory-flag],
134
+ flags: { "mandatory" => "foo" }
135
+ )).to be_nil
136
+ end
137
+ end
138
+
139
+ describe "#usage" do
140
+ it "uses UsageGenerator" do
141
+ state = { mode: :subcommand, args: [], flags: {}, subcommand_stack: %w[move go] }
142
+ expect(Tabry::UsageGenerator).to receive(:new) do |sub_stack, cmd|
143
+ expect(sub_stack.map(&:class)).to eq([Tabry::Models::Sub, Tabry::Models::Sub, Tabry::Models::Sub])
144
+ expect(cmd).to eq("vehicles")
145
+ instance_double(Tabry::UsageGenerator, usage: "foobar")
146
+ end
147
+ usage = described_class.new(config, Tabry::State.new(**state)).usage
148
+ expect(usage).to eq("foobar")
149
+ end
150
+
151
+ it "passes in passed-in command to UsageGenerator" do
152
+ state = { mode: :subcommand, args: [], flags: {}, subcommand_stack: %w[] }
153
+ expect(Tabry::UsageGenerator).to receive(:new) do |_sub_stack, cmd|
154
+ expect(cmd).to eq("customcmd")
155
+ instance_double(Tabry::UsageGenerator, usage: "foobar")
156
+ end
157
+ usage = described_class.new(config, Tabry::State.new(**state)).usage("customcmd")
158
+ expect(usage).to eq("foobar")
159
+ end
160
+ end
161
+
162
+ describe "#top_level?" do
163
+ context "there is a subcommand" do
164
+ let(:state) { Tabry::State.new(subcommand_stack: %w[move crash]) }
165
+
166
+ it { expect(subject.top_level?).to be(false) }
167
+ end
168
+
169
+ context "if there is no subcommand" do
170
+ let(:state) { Tabry::State.new(subcommand_stack: %w[], args: %w[foo]) }
171
+
172
+ it { expect(subject.top_level?).to be(true) }
173
+ end
174
+ end
175
+
176
+ describe "#help?" do
177
+ context "if help is true in the state" do
178
+ let(:state) { Tabry::State.new(subcommand_stack: %w[move crash], help: true) }
179
+
180
+ it { expect(subject.help?).to be(true) }
181
+ end
182
+
183
+ context "if help is absent/nil from the state" do
184
+ let(:state) { Tabry::State.new(subcommand_stack: %w[move crash]) }
185
+
186
+ it { expect(subject.help?).to be_falsey }
187
+ end
188
+ end
189
+
190
+ describe "#named_args" do
191
+ let(:state) do
192
+ Tabry::State.new(subcommand_stack: %w[move crash], args: %w[car bicycle])
193
+ end
194
+
195
+ it "returns a hash of all the named args" do
196
+ expect(subject.named_args).to eq(
197
+ "vehicle-type" => "car",
198
+ "crash-into-vehicle" => "bicycle"
199
+ )
200
+ end
201
+
202
+ context "with varargs" do
203
+ let(:state) { Tabry::State.new(subcommand_stack: %w[build], args: %w[car jeep moped]) }
204
+
205
+ it "puts the varargs in the hash as an array" do
206
+ expect(subject.named_args).to eq("vehicle-types" => %w[car jeep moped])
207
+ end
208
+ end
209
+
210
+ context "with optional, non-empty varargs" do
211
+ let(:state) do
212
+ Tabry::State.new(subcommand_stack: %w[move freeway-crash], args: %w[car jeep moped])
213
+ end
214
+
215
+ it "puts the varargs in the hash as an array" do
216
+ expect(subject.named_args).to eq(
217
+ "vehicle-type" => "car",
218
+ "crash-into-vehicles" => %w[jeep moped]
219
+ )
220
+ end
221
+ end
222
+
223
+ context "with empty optional varargs" do
224
+ let(:state) do
225
+ Tabry::State.new(subcommand_stack: %w[move freeway-crash], args: %w[car])
226
+ end
227
+
228
+ it "puts the varargs in the hash as an array" do
229
+ expect(subject.named_args).to eq(
230
+ "vehicle-type" => "car",
231
+ "crash-into-vehicles" => %w[]
232
+ )
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/tabry/runner"
4
+
5
+ describe Tabry::Runner do
6
+ subject do
7
+ described_class.new(config_name: "configname")
8
+ end
9
+
10
+ let(:config) { instance_double(Tabry::Models::Config) }
11
+ let(:state) { instance_double(Tabry::State) }
12
+
13
+ before do
14
+ allow(Tabry::ConfigLoader).to receive(:load).with(name: "configname").and_return(config)
15
+ end
16
+
17
+ describe "#parse" do
18
+ it "calls the machine and makes a result" do
19
+ expect(Tabry::Machine).to receive(:run).with(config, %w[foo bar]).and_return state
20
+ res = subject.parse(%w[foo bar])
21
+ expect(res).to be_a(Tabry::Result)
22
+ expect(res.state).to eq(state)
23
+ expect(res.config).to eq(config)
24
+ end
25
+ end
26
+
27
+ describe "#options" do
28
+ it "runs OptionsFinder" do
29
+ res = instance_double(Tabry::Result)
30
+ expect(subject).to receive(:parse).with(%w[foo bar]).and_return res
31
+ expect(Tabry::OptionsFinder).to receive(:options).with(res, "waz").and_return %w[a b c]
32
+ expect(subject.options(%w[foo bar], "waz")).to eq(%w[a b c])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/tabry/usage_generator"
4
+ require_relative "../../lib/tabry/config_loader"
5
+
6
+ describe Tabry::UsageGenerator do
7
+ let(:config_fixture) { "#{__dir__}/../fixtures/vehicles.yaml" }
8
+ let(:config) { Tabry::ConfigLoader.load(name: config_fixture) }
9
+ let(:usage) do
10
+ str = described_class.new(config.dig_sub_array(sub_stack), config.cmd).usage
11
+ decolorified = str.gsub(/\e\[(\d+)m/, "")
12
+ decolorified
13
+ end
14
+
15
+ describe "args" do
16
+ let(:sub_stack) { %w[move crash] }
17
+
18
+ it "shows args" do
19
+ expect(usage).to match(/^ARGS.*^ *vehicle-type/m)
20
+ end
21
+
22
+ it "shows optional args and descriptions" do
23
+ expect(usage).to match(/^ *crash-into-vehicle \(optional\)\n^ *Crash into another vehicle/m)
24
+ end
25
+
26
+ it "shows args in the Usage line" do
27
+ expect(usage).to include "Usage: vehicles move crash <vehicle-type> [<crash-into-vehicle>]"
28
+ end
29
+ end
30
+
31
+ describe "varargs" do
32
+ let(:sub_stack) { %w[move freeway-crash] }
33
+
34
+ it "shows varargs with the title" do
35
+ expect(usage).to include \
36
+ "vehicles move freeway-crash <vehicle-type> [<vehicle to crash into> [<vehicle to crash into>...]]"
37
+ end
38
+ end
39
+
40
+ describe "subcommands" do
41
+ let(:sub_stack) { %w[move] }
42
+
43
+ it "lists subcommands" do
44
+ expect(usage).to match(/^SUBCOMMANDS.*^ *crash/m)
45
+ end
46
+
47
+ it "shows a subcommand alias" do
48
+ expect(usage).to include "go (alias: g)"
49
+ end
50
+
51
+ it "shows subcommand aliases" do
52
+ expect(usage).to include "freeway-crash (aliases: pileup, p)"
53
+ end
54
+ end
55
+
56
+ describe "flags" do
57
+ let(:sub_stack) { %w[move crash] }
58
+
59
+ it "shows flags with titles" do
60
+ expect(usage).to match(/^ *FLAGS.*^ *--dry-run.*Don't actually crash/m)
61
+ end
62
+
63
+ it "shows flags aliases and args" do
64
+ expect(usage).to include "--output-to-file, -f <arg>"
65
+ end
66
+ end
67
+ end