tabry 0.1.4 → 0.2.1
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 +4 -4
- data/bin/tabry-bash +2 -29
- data/bin/tabry-generate-bash-complete +31 -0
- data/bin/tabry-help +1 -1
- data/bin/tabry-test-options +1 -1
- data/bin/tabry-test-parse +1 -1
- data/lib/tabry/cli/all_in_one.rb +98 -0
- data/lib/tabry/cli/arg_proxy.rb +4 -4
- data/lib/tabry/cli/base.rb +5 -2
- data/lib/tabry/cli/builder.rb +14 -8
- data/lib/tabry/cli/internals.rb +1 -1
- data/lib/tabry/cli/util.rb +23 -10
- data/lib/tabry/config_builder/arg_or_flag_builder.rb +39 -0
- data/lib/tabry/config_builder/flagarg_builder.rb +14 -0
- data/lib/tabry/config_builder/generic_builder.rb +103 -0
- data/lib/tabry/config_builder/sub_builder.rb +27 -0
- data/lib/tabry/config_builder/top_level_builder.rb +31 -0
- data/lib/tabry/config_builder.rb +16 -0
- data/lib/tabry/models/config_list.rb +2 -2
- data/lib/tabry/models/config_string_hash.rb +2 -2
- data/lib/tabry/result.rb +5 -1
- data/lib/tabry/runner.rb +6 -2
- data/lib/tabry/shell_splitter.rb +2 -0
- data/lib/tabry/shells/bash/wrapper.rb +38 -0
- data/lib/tabry/shells/bash.rb +46 -9
- data/sh/bash/tabry_bash.sh +1 -1
- data/sh/bash/tabry_bash_core.sh +3 -2
- data/spec/fixtures/config_builder/args.rb +49 -0
- data/spec/fixtures/config_builder/args.tabry +32 -0
- data/spec/fixtures/config_builder/args.yml +63 -0
- data/spec/fixtures/config_builder/defs.rb +33 -0
- data/spec/fixtures/config_builder/defs.tabry +21 -0
- data/spec/fixtures/config_builder/defs.yml +33 -0
- data/spec/fixtures/config_builder/flags.rb +26 -0
- data/spec/fixtures/config_builder/flags.tabry +13 -0
- data/spec/fixtures/config_builder/flags.yml +27 -0
- data/spec/fixtures/config_builder/subs.rb +34 -0
- data/spec/fixtures/config_builder/subs.tabry +25 -0
- data/spec/fixtures/config_builder/subs.yml +46 -0
- data/spec/fixtures/config_builder/underscoresdashes.rb +30 -0
- data/spec/fixtures/config_builder/underscoresdashes.tabry +18 -0
- data/spec/fixtures/config_builder/underscoresdashes.yml +39 -0
- data/spec/fixtures/vehicles.tabry +5 -0
- data/spec/fixtures/vehicles.yaml +5 -0
- data/spec/tabry/cli/all_in_one_spec.rb +141 -0
- data/spec/tabry/cli/arg_proxy_spec.rb +2 -2
- data/spec/tabry/cli/builder_spec.rb +51 -20
- data/spec/tabry/cli/util_spec.rb +125 -0
- data/spec/tabry/config_builder_spec.rb +64 -0
- data/spec/tabry/config_loader_spec.rb +2 -2
- data/spec/tabry/options_finder_spec.rb +1 -1
- data/spec/tabry/result_spec.rb +4 -0
- data/spec/tabry/runner_spec.rb +1 -1
- data/spec/tabry/shell_splitter_spec.rb +10 -8
- data/spec/tabry/shells/bash_spec.rb +44 -0
- data/tabry.gemspec +1 -1
- metadata +32 -4
- data/spec/lib/tabry/cli/util_spec.rb +0 -58
data/spec/fixtures/vehicles.yaml
CHANGED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../lib/tabry/cli/all_in_one"
|
|
4
|
+
require_relative "../../../lib/tabry/shells/bash"
|
|
5
|
+
require_relative "../../../lib/tabry/shells/bash/wrapper"
|
|
6
|
+
|
|
7
|
+
module Tabry::Spec
|
|
8
|
+
module Cli
|
|
9
|
+
module AllInOneSpec
|
|
10
|
+
class << self
|
|
11
|
+
attr_accessor :log
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe Tabry::CLI::AllInOne do
|
|
18
|
+
before do
|
|
19
|
+
Tabry::Spec::Cli::AllInOneSpec.log = []
|
|
20
|
+
allow(Kernel).to receive(:exit).and_raise(StandardError.new("exit called"))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe ".build" do
|
|
24
|
+
let(:cli) do
|
|
25
|
+
described_class.build do
|
|
26
|
+
config do
|
|
27
|
+
completion
|
|
28
|
+
|
|
29
|
+
cmd :abc
|
|
30
|
+
|
|
31
|
+
sub :foo do
|
|
32
|
+
arg :bar
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sub :foo2 do
|
|
36
|
+
sub :bar2
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Tabry::Spec::Cli::AllInOneSpec.log << :after_config
|
|
41
|
+
|
|
42
|
+
def foo
|
|
43
|
+
Tabry::Spec::Cli::AllInOneSpec.log << [:foo, args.bar]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def foo2__bar2
|
|
47
|
+
Tabry::Spec::Cli::AllInOneSpec.log << [:foo2__bar2]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "creates a builder with CLI" do
|
|
53
|
+
expect(cli).to be_a(Tabry::CLI::Builder)
|
|
54
|
+
expect(cli.cli_class).to be_a(Class)
|
|
55
|
+
expect(cli.cli_class).to be < Tabry::CLI::Base
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "makes the config using the config block" do
|
|
59
|
+
conf = cli.runner.config
|
|
60
|
+
expect(conf).to be_a(Tabry::Models::Config)
|
|
61
|
+
expect(conf.main.subs.by_name.keys).to include("foo2")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "makes a CLI with the methods in the block" do
|
|
65
|
+
cli.run(["foo", "bararg"])
|
|
66
|
+
expect(Tabry::Spec::Cli::AllInOneSpec.log).to eq [
|
|
67
|
+
:after_config,
|
|
68
|
+
[:foo, "bararg"],
|
|
69
|
+
]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "creates a #completion__bash method which generates completion" do
|
|
73
|
+
expect(Tabry::Shells::Bash).to receive(:generate_self).and_return("bash completion stuff")
|
|
74
|
+
expect_any_instance_of(Kernel).to receive(:puts).with("bash completion stuff")
|
|
75
|
+
cli.run(["completion", "bash"])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "creates a #completion method which generates options" do
|
|
79
|
+
expect(Tabry::Bash::Wrapper).to receive(:run).with("cmd line", "6", config: instance_of(Tabry::Models::Config))
|
|
80
|
+
cli.run(["completion", "cmd line", "6"])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "quits the block early after loading config and the subcommand to be run is #complete" do
|
|
84
|
+
allow(Tabry::Bash::Wrapper).to receive(:run)
|
|
85
|
+
expect(Tabry::Spec::Cli::AllInOneSpec.log).to eq([])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "can take a config object passed in to config" do
|
|
89
|
+
conf = Tabry::ConfigBuilder.build { sub :bar }
|
|
90
|
+
cli = described_class.build do
|
|
91
|
+
config conf
|
|
92
|
+
|
|
93
|
+
def bar; end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
expect(cli.runner.config).to eq(conf)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#run" do
|
|
101
|
+
it "runs build and then runs the builder with ARGV" do
|
|
102
|
+
stub_const("ARGV", ["abc", "def"])
|
|
103
|
+
test_blk = proc { completion }
|
|
104
|
+
test_builder = instance_double(Tabry::CLI::Builder)
|
|
105
|
+
expect(described_class).to receive(:build) do |**kwargs, &blk|
|
|
106
|
+
expect(blk).to eql(test_blk)
|
|
107
|
+
expect(kwargs).to eq({ cli: "fake cli", config: "fake config" })
|
|
108
|
+
test_builder
|
|
109
|
+
end
|
|
110
|
+
expect(test_builder).to receive(:run).with(%w[abc def])
|
|
111
|
+
described_class.run(cli: "fake cli", config: "fake config", &test_blk)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "#completion_only" do
|
|
116
|
+
it "creates a #completion__bash method which generates completion" do
|
|
117
|
+
stub_const("ARGV", %w[completion bash])
|
|
118
|
+
expect(Tabry::Shells::Bash).to receive(:generate_self).with(cmd_name: "foo").and_return("bash completion stuff")
|
|
119
|
+
expect_any_instance_of(Kernel).to receive(:puts).with("bash completion stuff")
|
|
120
|
+
|
|
121
|
+
described_class.completion_only do
|
|
122
|
+
cmd :foo
|
|
123
|
+
sub :bar
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "creates a #completion method which generates options" do
|
|
128
|
+
stub_const("ARGV", ["completion", "cmd line", "6"])
|
|
129
|
+
expect(Tabry::Bash::Wrapper).to receive(:run) do |cmd_line, comp_point, config:|
|
|
130
|
+
expect(cmd_line).to eq("cmd line")
|
|
131
|
+
expect(comp_point).to eq("6")
|
|
132
|
+
expect(config.cmd).to eq("foo")
|
|
133
|
+
expect(config.main.subs.by_name.keys).to eq(["bar"])
|
|
134
|
+
end
|
|
135
|
+
described_class.completion_only do
|
|
136
|
+
cmd :foo
|
|
137
|
+
sub :bar
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -49,14 +49,14 @@ describe Tabry::CLI::ArgProxy do
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
it "prints an error and exits if read thru reqd and the argument (name) is not found" do
|
|
52
|
-
expect(
|
|
52
|
+
expect(Kernel).to receive(:exit).with(1)
|
|
53
53
|
expect do
|
|
54
54
|
args.reqd.foo
|
|
55
55
|
end.to output(/FATAL: Missing required argument foo/).to_stderr
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
it "prints an error and exits if read thru reqd and the argument (index) is not found" do
|
|
59
|
-
expect(
|
|
59
|
+
expect(Kernel).to receive(:exit).with(1)
|
|
60
60
|
expect do
|
|
61
61
|
args.reqd[3]
|
|
62
62
|
end.to output(/FATAL: Missing required argument number 4/).to_stderr
|
|
@@ -28,7 +28,33 @@ class Tabry::CLI::Builder
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
class TestCliFoo < Tabry::CLI::Base
|
|
31
|
-
sub_route :bar, TestCliFooBar
|
|
31
|
+
sub_route :bar, to: TestCliFooBar
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class TestCli2 < Tabry::CLI::Base
|
|
35
|
+
class << self
|
|
36
|
+
attr_accessor :actions_run, :cli_object
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
after_action :my_after_action, except: :build2
|
|
40
|
+
|
|
41
|
+
def build2
|
|
42
|
+
self.class.actions_run << :build2
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sub__sub_sub
|
|
46
|
+
self.class.actions_run << :sub__sub_sub
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def my_before_action
|
|
50
|
+
self.class.actions_run << :before_action
|
|
51
|
+
self.class.cli_object = self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def my_after_action
|
|
55
|
+
self.class.actions_run << :after_action
|
|
56
|
+
self.class.cli_object = self
|
|
57
|
+
end
|
|
32
58
|
end
|
|
33
59
|
|
|
34
60
|
class TestCli < Tabry::CLI::Base
|
|
@@ -37,9 +63,10 @@ class Tabry::CLI::Builder
|
|
|
37
63
|
end
|
|
38
64
|
|
|
39
65
|
before_action :my_before_action, only: :build
|
|
40
|
-
after_action :my_after_action
|
|
66
|
+
after_action :my_after_action
|
|
41
67
|
|
|
42
|
-
sub_route :foo, TestCliFoo
|
|
68
|
+
sub_route :foo, to: TestCliFoo
|
|
69
|
+
sub_route :sub, :build2, to: TestCli2, full_method_name: true
|
|
43
70
|
|
|
44
71
|
def main
|
|
45
72
|
self.class.actions_run << :main
|
|
@@ -49,18 +76,10 @@ class Tabry::CLI::Builder
|
|
|
49
76
|
self.class.actions_run << :build
|
|
50
77
|
end
|
|
51
78
|
|
|
52
|
-
def build2
|
|
53
|
-
self.class.actions_run << :build2
|
|
54
|
-
end
|
|
55
|
-
|
|
56
79
|
def my_action
|
|
57
80
|
self.class.actions_run << :my_action
|
|
58
81
|
end
|
|
59
82
|
|
|
60
|
-
def sub__sub_sub
|
|
61
|
-
self.class.actions_run << :sub__sub_sub
|
|
62
|
-
end
|
|
63
|
-
|
|
64
83
|
def my_before_action
|
|
65
84
|
self.class.actions_run << :before_action
|
|
66
85
|
self.class.cli_object = self
|
|
@@ -78,7 +97,7 @@ describe Tabry::CLI::Builder do
|
|
|
78
97
|
|
|
79
98
|
before do
|
|
80
99
|
runner = instance_double(Tabry::Runner)
|
|
81
|
-
allow(Tabry::Runner).to receive(:new).with(
|
|
100
|
+
allow(Tabry::Runner).to receive(:new).with(config: "theconfigname").and_return runner
|
|
82
101
|
allow(runner).to receive(:parse).with(%w[the raw args]).and_return result
|
|
83
102
|
end
|
|
84
103
|
|
|
@@ -99,7 +118,8 @@ describe Tabry::CLI::Builder do
|
|
|
99
118
|
let(:builder) { described_class.new("theconfigname", Tabry::CLI::Builder::TestCli) }
|
|
100
119
|
|
|
101
120
|
let(:cli_class) { Tabry::CLI::Builder::TestCli }
|
|
102
|
-
let(:cli_class2) { Tabry::CLI::Builder::
|
|
121
|
+
let(:cli_class2) { Tabry::CLI::Builder::TestCli2 }
|
|
122
|
+
let(:cli_class_foo_bar) { Tabry::CLI::Builder::TestCliFooBar }
|
|
103
123
|
let(:actions_run) { cli_class.actions_run }
|
|
104
124
|
let(:cli_object) { cli_class.cli_object }
|
|
105
125
|
|
|
@@ -108,6 +128,8 @@ describe Tabry::CLI::Builder do
|
|
|
108
128
|
cli_class.cli_object = nil
|
|
109
129
|
cli_class2.actions_run = []
|
|
110
130
|
cli_class2.cli_object = nil
|
|
131
|
+
cli_class_foo_bar.actions_run = []
|
|
132
|
+
cli_class_foo_bar.cli_object = nil
|
|
111
133
|
end
|
|
112
134
|
|
|
113
135
|
describe "successful runs" do
|
|
@@ -132,7 +154,7 @@ describe Tabry::CLI::Builder do
|
|
|
132
154
|
describe "except on actions" do
|
|
133
155
|
let(:state) { { subcommand_stack: %w[build2] } }
|
|
134
156
|
|
|
135
|
-
it { expect(actions_run).to eq %i[build2] }
|
|
157
|
+
it { expect(cli_class2.actions_run).to eq %i[build2] }
|
|
136
158
|
end
|
|
137
159
|
|
|
138
160
|
describe "handling multiple levels of subcommand routing" do
|
|
@@ -143,14 +165,23 @@ describe Tabry::CLI::Builder do
|
|
|
143
165
|
end
|
|
144
166
|
|
|
145
167
|
it "maps to the sub-CLI" do
|
|
146
|
-
expect(
|
|
168
|
+
expect(cli_class_foo_bar.actions_run).to eq(%i[before_action waz])
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
describe "subcommand routing with full_method_name: true" do
|
|
173
|
+
let(:state) { { subcommand_stack: %i[sub sub_sub] } }
|
|
174
|
+
|
|
175
|
+
it "preserves the full method name" do
|
|
176
|
+
expect(cli_class.actions_run).to eq(%i[])
|
|
177
|
+
expect(cli_class2.actions_run).to eq(%i[sub__sub_sub after_action])
|
|
147
178
|
end
|
|
148
179
|
end
|
|
149
180
|
|
|
150
181
|
describe "handling multiple levels of subcommand routing (main method)" do
|
|
151
182
|
let(:state) { { subcommand_stack: %i[foo bar] } }
|
|
152
183
|
|
|
153
|
-
it { expect(
|
|
184
|
+
it { expect(cli_class_foo_bar.actions_run).to eq(%i[before_action main]) }
|
|
154
185
|
end
|
|
155
186
|
|
|
156
187
|
describe "providing an ArgProxy in TestCLI#args" do
|
|
@@ -184,7 +215,7 @@ describe Tabry::CLI::Builder do
|
|
|
184
215
|
|
|
185
216
|
it "quits and shows an error and usage info if there is incorrect usage" do
|
|
186
217
|
expect(result).to receive(:usage).and_return "example command usage"
|
|
187
|
-
expect(
|
|
218
|
+
expect(Kernel).to receive(:exit).with(1)
|
|
188
219
|
expect do
|
|
189
220
|
subject
|
|
190
221
|
end.to output(
|
|
@@ -198,7 +229,7 @@ describe Tabry::CLI::Builder do
|
|
|
198
229
|
|
|
199
230
|
it "quits and shows usage if help is passed in" do
|
|
200
231
|
expect(result).to receive(:usage).and_return "example command usage"
|
|
201
|
-
expect(
|
|
232
|
+
expect(Kernel).to receive(:exit).with(0)
|
|
202
233
|
expect { subject }.to output(/example command usage/).to_stdout
|
|
203
234
|
end
|
|
204
235
|
end
|
|
@@ -208,7 +239,7 @@ describe Tabry::CLI::Builder do
|
|
|
208
239
|
let(:state) { { subcommand_stack: %w[wombat] } }
|
|
209
240
|
|
|
210
241
|
it "quits and shows an error if there is the command is valid but the CLI class doesn't implement it" do
|
|
211
|
-
expect(
|
|
242
|
+
expect(Kernel).to receive(:exit).with(1)
|
|
212
243
|
expect { subject }.to output(/FATAL: CLI does not support command wombat/).to_stderr
|
|
213
244
|
end
|
|
214
245
|
end
|
|
@@ -217,7 +248,7 @@ describe Tabry::CLI::Builder do
|
|
|
217
248
|
let(:state) { { subcommand_stack: %w[foo] } }
|
|
218
249
|
|
|
219
250
|
it "quits and shows an error" do
|
|
220
|
-
expect(
|
|
251
|
+
expect(Kernel).to receive(:exit).with(1)
|
|
221
252
|
expect { subject }.to output(/FATAL: CLI does not support command main/).to_stderr
|
|
222
253
|
end
|
|
223
254
|
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../lib/tabry/cli/util"
|
|
4
|
+
|
|
5
|
+
describe Tabry::CLI::Util do
|
|
6
|
+
describe ".make_cmdline" do
|
|
7
|
+
it "escapes a single string" do
|
|
8
|
+
expect(described_class.make_cmdline(
|
|
9
|
+
"hello %s", "world;abc"
|
|
10
|
+
)).to eq(
|
|
11
|
+
"hello world\\;abc"
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "escapes multiple string arguments" do
|
|
16
|
+
expect(described_class.make_cmdline(
|
|
17
|
+
"hello %s hi %s", "world;abc", "def;ghi"
|
|
18
|
+
)).to eq(
|
|
19
|
+
"hello world\\;abc hi def\\;ghi"
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "escapes array arguments" do
|
|
24
|
+
# args is an array of strings and arrays
|
|
25
|
+
expect(described_class.make_cmdline(
|
|
26
|
+
"hello %s hi %s", "world;abc", ["foo;bar", "waz;ok"]
|
|
27
|
+
)).to eq(
|
|
28
|
+
"hello world\\;abc hi foo\\;bar waz\\;ok"
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "treats a single array of strings as a list of args, not as one array arg" do
|
|
33
|
+
# args is an array with one array of strings
|
|
34
|
+
# an improvement might be to figure out how
|
|
35
|
+
# many '%s's there are in the cmdline
|
|
36
|
+
expect(described_class.make_cmdline(
|
|
37
|
+
"hello %s hi %s", ["world;abc", "foo;bar"]
|
|
38
|
+
)).to eq(
|
|
39
|
+
"hello world\\;abc hi foo\\;bar"
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "escapes arrays if given one array of mixed strings and arrays" do
|
|
44
|
+
expect(described_class.make_cmdline(
|
|
45
|
+
"hello %s hi %s", [["world;abc", "foo;bar"], "waz;ok"]
|
|
46
|
+
)).to eq(
|
|
47
|
+
"hello world\\;abc foo\\;bar hi waz\\;ok"
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "can send a single array argument by wrapping in an array" do
|
|
52
|
+
# args is an array with one array of one array
|
|
53
|
+
expect(described_class.make_cmdline(
|
|
54
|
+
"hello %s hi", [["foo;bar", "waz;ok"]]
|
|
55
|
+
)).to eq(
|
|
56
|
+
"hello foo\\;bar waz\\;ok hi"
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "can merge_stderr" do
|
|
61
|
+
expect(described_class.make_cmdline(
|
|
62
|
+
"echo %s | cat", "a", merge_stderr: true
|
|
63
|
+
)).to eq(
|
|
64
|
+
"{ echo a | cat ;} 2>&1"
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "open_web_page" do
|
|
70
|
+
it 'uses "xdg-open" when on Linux' do
|
|
71
|
+
stub_const("RUBY_PLATFORM", "x86_64-linux")
|
|
72
|
+
expect(Kernel).to receive("system") do |cmdline|
|
|
73
|
+
expect(cmdline).to include("(xdg-open ")
|
|
74
|
+
end
|
|
75
|
+
described_class.open_web_page("http://example.com")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'uses "open" when on Darwin' do
|
|
79
|
+
stub_const("RUBY_PLATFORM", "x86_64-darwin")
|
|
80
|
+
expect(Kernel).to receive("system") do |cmdline|
|
|
81
|
+
expect(cmdline).to include("(open ")
|
|
82
|
+
end
|
|
83
|
+
described_class.open_web_page("http://example.com")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "backtick_or_die" do
|
|
88
|
+
before do
|
|
89
|
+
allow(Kernel).to receive(:exit)
|
|
90
|
+
allow(Kernel).to receive(:warn)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context "when the command returns a non-zero status code" do
|
|
94
|
+
before do
|
|
95
|
+
described_class.backtick_or_die("echo abc && echo def && echo ghi && false")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "prints out the output" do
|
|
99
|
+
expect(Kernel).to have_received(:warn).with(
|
|
100
|
+
a_string_including("\ndef\n")
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "exits with a non-zero status code" do
|
|
105
|
+
expect(Kernel).to have_received(:exit).with(1)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context "when the command does not exist" do
|
|
110
|
+
before do
|
|
111
|
+
described_class.backtick_or_die("theresnowayanyonehasthiscommandinstalled-randomstuff-2390832190832jk4398190fdlksjhfdsalkjlkfdsaj43098432908fd")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "exits with a non-zero status code" do
|
|
115
|
+
expect(Kernel).to have_received(:exit).with(1)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "says that the command does not exist" do
|
|
119
|
+
expect(Kernel).to have_received(:warn).with(
|
|
120
|
+
a_string_including("command does not exist")
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative "../../lib/tabry/config_builder"
|
|
6
|
+
|
|
7
|
+
describe Tabry::ConfigBuilder do
|
|
8
|
+
def sort_nested_hashes(object)
|
|
9
|
+
case object
|
|
10
|
+
when Hash
|
|
11
|
+
object.sort_by { |key, _val| key }.to_h.transform_values { |val| sort_nested_hashes(val) }
|
|
12
|
+
when Array
|
|
13
|
+
object.map { |val| sort_nested_hashes(val) }
|
|
14
|
+
else
|
|
15
|
+
object
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
%w[Args Defs Flags Subs Underscoresdashes].each do |fixture|
|
|
20
|
+
context "#{fixture} test fixture" do
|
|
21
|
+
it "matches what would be produced by the equivalent (old tabry language) tabry file" do
|
|
22
|
+
require_relative "../fixtures/config_builder/#{fixture.downcase}"
|
|
23
|
+
actual = Tabry::Spec::Fixtures::ConfigBuilder.const_get(fixture.to_sym)._raw
|
|
24
|
+
expected = YAML.load_file(File.expand_path("../fixtures/config_builder/#{fixture.downcase}.yml", __dir__))
|
|
25
|
+
|
|
26
|
+
actual = JSON.pretty_generate(sort_nested_hashes(actual))
|
|
27
|
+
expected = JSON.pretty_generate(sort_nested_hashes(expected))
|
|
28
|
+
|
|
29
|
+
expect(actual).to eql(expected)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "defines a 'completion' shortcut with 'completion' and 'completion__bash' subs" do
|
|
35
|
+
config = described_class.build do
|
|
36
|
+
completion
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
expect(config._raw.dig("main", "subs")).to eq(
|
|
40
|
+
[
|
|
41
|
+
{
|
|
42
|
+
"description" => "Get tab completion shell config",
|
|
43
|
+
"name" => "completion",
|
|
44
|
+
"args" => [
|
|
45
|
+
{
|
|
46
|
+
"description" => "(for internal usage, when used instead of subcommand) full command line for getting completion options",
|
|
47
|
+
"name" => "cmd_line"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"description" => "(for internal usage, when used instead of subcommand) comp point",
|
|
51
|
+
"name" => "comp_point"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"subs" => [
|
|
55
|
+
{
|
|
56
|
+
"description" => "Get tab completion for bash or zsh",
|
|
57
|
+
"name" => "bash"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -17,7 +17,7 @@ describe Tabry::ConfigLoader do
|
|
|
17
17
|
config = described_class.load(name: "#{__dir__}/../fixtures/vehicles.yaml")
|
|
18
18
|
expect(config).to be_a(Tabry::Models::Config)
|
|
19
19
|
expect(config.main.subs.map(&:name)).to eq(
|
|
20
|
-
%w[build list-vehicles move sub-with-sub-or-arg sub-with-mandatory-flag]
|
|
20
|
+
%w[build list-vehicles move sub-with-sub-or-arg sub-with-sub-or-opt-arg sub-with-mandatory-flag]
|
|
21
21
|
)
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ describe Tabry::ConfigLoader do
|
|
|
27
27
|
it "looks for yaml files in directories in TABRY_IMPORTS_PATH" do
|
|
28
28
|
config = described_class.load(name: "vehicles")
|
|
29
29
|
expect(config.main.subs.map(&:name)).to eq(
|
|
30
|
-
%w[build list-vehicles move sub-with-sub-or-arg sub-with-mandatory-flag]
|
|
30
|
+
%w[build list-vehicles move sub-with-sub-or-arg sub-with-sub-or-opt-arg sub-with-mandatory-flag]
|
|
31
31
|
)
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -12,7 +12,7 @@ describe Tabry::OptionsFinder do
|
|
|
12
12
|
|
|
13
13
|
examples = {
|
|
14
14
|
"lists possible subcommands of the main command" => [
|
|
15
|
-
%w[build list-vehicles move sub-with-sub-or-arg sub-with-mandatory-flag],
|
|
15
|
+
%w[build list-vehicles move sub-with-sub-or-arg sub-with-sub-or-opt-arg sub-with-mandatory-flag],
|
|
16
16
|
{}
|
|
17
17
|
],
|
|
18
18
|
"lists possible subcommands of a subcommand" => [
|
data/spec/tabry/result_spec.rb
CHANGED
|
@@ -70,6 +70,10 @@ describe Tabry::Result do
|
|
|
70
70
|
)).to be_nil
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
it "doesn't complain if given no args to a subcommand with subcommands and optional args" do
|
|
74
|
+
expect(reason(subcommand_stack: %w[sub-with-sub-or-opt-arg])).to be_nil
|
|
75
|
+
end
|
|
76
|
+
|
|
73
77
|
it "complains if mandatory varargs are not given" do
|
|
74
78
|
expect(reason(
|
|
75
79
|
subcommand_stack: %w[build]
|
data/spec/tabry/runner_spec.rb
CHANGED
|
@@ -3,18 +3,20 @@
|
|
|
3
3
|
require_relative "../../lib/tabry/shell_splitter"
|
|
4
4
|
|
|
5
5
|
describe Tabry::ShellSplitter do
|
|
6
|
-
describe
|
|
6
|
+
describe ".split" do
|
|
7
7
|
it "returns the command basename, argument, and last argument" do
|
|
8
|
-
str =
|
|
8
|
+
str = "foo/bar abc def ghi"
|
|
9
9
|
expect(described_class.split(str, 13)).to eq(["bar", ["abc"], "def"])
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
it "
|
|
13
|
-
expect(described_class.split('"/foo bar/waz" a\'b \'c\\ d "ef g" "hi jk" lmn', 38)).to eq(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
it "handles quotes and backslashes like a shell" do
|
|
13
|
+
expect(described_class.split('"/foo bar/waz" a\'b \'c\\ d "ef g" "hi jk" lmn', 38)).to eq(
|
|
14
|
+
[
|
|
15
|
+
"waz",
|
|
16
|
+
["ab c d", "ef g"],
|
|
17
|
+
"hi jk"
|
|
18
|
+
]
|
|
19
|
+
)
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../lib/tabry/shells/bash"
|
|
4
|
+
|
|
5
|
+
describe Tabry::Shells::Bash do
|
|
6
|
+
describe ".generate_self" do
|
|
7
|
+
before { @backup, $0 = $0, "/bla/waz/abc" }
|
|
8
|
+
after { $0 = @backup }
|
|
9
|
+
|
|
10
|
+
it "tells bash to use the currently running command plus 'completion' to get completion options" do
|
|
11
|
+
expect(described_class.generate_self).to match(
|
|
12
|
+
%r{^ *TABRY_IMPORTS_PATH='' _tabry_ABC_completions_internal /bla/waz/abc completion$}
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "uses the cmd_name given for the command name for bash's 'complete'" do
|
|
17
|
+
result = described_class.generate_self
|
|
18
|
+
expect(result).to include("\ncomplete -F _tabry_ABC_completions abc\n")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "defaults cmd_name to the basename of the currently running command" do
|
|
22
|
+
result = described_class.generate_self(cmd_name: 'wombat')
|
|
23
|
+
expect(result).to match(
|
|
24
|
+
%r{^ *TABRY_IMPORTS_PATH='' _tabry_WOMBAT_completions_internal /bla/waz/abc completion$}
|
|
25
|
+
)
|
|
26
|
+
expect(result).to include("\ncomplete -F _tabry_WOMBAT_completions wombat\n")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '.generate' do
|
|
31
|
+
it 'tells bash to use tabry-bash with a import path to get completion options' do
|
|
32
|
+
result = described_class.generate("my-cmd", "/path/to/mycmd.tabry")
|
|
33
|
+
expect(result).to include("TABRY_IMPORTS_PATH=/path/to/mycmd.tabry _tabry_MY_CMD_completions_internal /home/evan/dev/tabry/bin/tabry-bash \n")
|
|
34
|
+
expect(result).to include("complete -F _tabry_MY_CMD_completions my-cmd\n")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "takes a uniq_fn_id parameter to override the default function names" do
|
|
38
|
+
result = described_class.generate("my-cmd", "/path/to/mycmd.tabry", uniq_fn_id: "my cmd tabryv0.2.0")
|
|
39
|
+
expect(result).to include("TABRY_IMPORTS_PATH=/path/to/mycmd.tabry _tabry_MY_CMD_TABRYV0_2_0_completions_internal /home/evan/dev/tabry/bin/tabry-bash \n")
|
|
40
|
+
expect(result).to include("complete -F _tabry_MY_CMD_TABRYV0_2_0_completions my-cmd\n")
|
|
41
|
+
expect(result).to include("_tabry_MY_CMD_TABRYV0_2_0_completions_internal()")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/tabry.gemspec
CHANGED
|
@@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |s|
|
|
8
8
|
s.name = "tabry"
|
|
9
|
-
s.version = "0.1
|
|
9
|
+
s.version = "0.2.1"
|
|
10
10
|
s.summary = "Tab completion and CLIs extraordinaire"
|
|
11
11
|
s.authors = ["Evan Battaglia"]
|
|
12
12
|
s.email = "battaglia.evan@gmail.com"
|