tabry 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|