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