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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/bin/tabry-bash +2 -29
  3. data/bin/tabry-generate-bash-complete +31 -0
  4. data/bin/tabry-help +1 -1
  5. data/bin/tabry-test-options +1 -1
  6. data/bin/tabry-test-parse +1 -1
  7. data/lib/tabry/cli/all_in_one.rb +98 -0
  8. data/lib/tabry/cli/arg_proxy.rb +4 -4
  9. data/lib/tabry/cli/base.rb +5 -2
  10. data/lib/tabry/cli/builder.rb +14 -8
  11. data/lib/tabry/cli/internals.rb +1 -1
  12. data/lib/tabry/cli/util.rb +23 -10
  13. data/lib/tabry/config_builder/arg_or_flag_builder.rb +39 -0
  14. data/lib/tabry/config_builder/flagarg_builder.rb +14 -0
  15. data/lib/tabry/config_builder/generic_builder.rb +103 -0
  16. data/lib/tabry/config_builder/sub_builder.rb +27 -0
  17. data/lib/tabry/config_builder/top_level_builder.rb +31 -0
  18. data/lib/tabry/config_builder.rb +16 -0
  19. data/lib/tabry/models/config_list.rb +2 -2
  20. data/lib/tabry/models/config_string_hash.rb +2 -2
  21. data/lib/tabry/result.rb +5 -1
  22. data/lib/tabry/runner.rb +6 -2
  23. data/lib/tabry/shell_splitter.rb +2 -0
  24. data/lib/tabry/shells/bash/wrapper.rb +38 -0
  25. data/lib/tabry/shells/bash.rb +46 -9
  26. data/sh/bash/tabry_bash.sh +1 -1
  27. data/sh/bash/tabry_bash_core.sh +3 -2
  28. data/spec/fixtures/config_builder/args.rb +49 -0
  29. data/spec/fixtures/config_builder/args.tabry +32 -0
  30. data/spec/fixtures/config_builder/args.yml +63 -0
  31. data/spec/fixtures/config_builder/defs.rb +33 -0
  32. data/spec/fixtures/config_builder/defs.tabry +21 -0
  33. data/spec/fixtures/config_builder/defs.yml +33 -0
  34. data/spec/fixtures/config_builder/flags.rb +26 -0
  35. data/spec/fixtures/config_builder/flags.tabry +13 -0
  36. data/spec/fixtures/config_builder/flags.yml +27 -0
  37. data/spec/fixtures/config_builder/subs.rb +34 -0
  38. data/spec/fixtures/config_builder/subs.tabry +25 -0
  39. data/spec/fixtures/config_builder/subs.yml +46 -0
  40. data/spec/fixtures/config_builder/underscoresdashes.rb +30 -0
  41. data/spec/fixtures/config_builder/underscoresdashes.tabry +18 -0
  42. data/spec/fixtures/config_builder/underscoresdashes.yml +39 -0
  43. data/spec/fixtures/vehicles.tabry +5 -0
  44. data/spec/fixtures/vehicles.yaml +5 -0
  45. data/spec/tabry/cli/all_in_one_spec.rb +141 -0
  46. data/spec/tabry/cli/arg_proxy_spec.rb +2 -2
  47. data/spec/tabry/cli/builder_spec.rb +51 -20
  48. data/spec/tabry/cli/util_spec.rb +125 -0
  49. data/spec/tabry/config_builder_spec.rb +64 -0
  50. data/spec/tabry/config_loader_spec.rb +2 -2
  51. data/spec/tabry/options_finder_spec.rb +1 -1
  52. data/spec/tabry/result_spec.rb +4 -0
  53. data/spec/tabry/runner_spec.rb +1 -1
  54. data/spec/tabry/shell_splitter_spec.rb +10 -8
  55. data/spec/tabry/shells/bash_spec.rb +44 -0
  56. data/tabry.gemspec +1 -1
  57. metadata +32 -4
  58. data/spec/lib/tabry/cli/util_spec.rb +0 -58
@@ -101,6 +101,11 @@ main:
101
101
  value: z
102
102
  subs:
103
103
  - name: subsub
104
+ - name: sub-with-sub-or-opt-arg
105
+ subs:
106
+ - name: subsub
107
+ args:
108
+ - optional: true
104
109
  - name: sub-with-mandatory-flag
105
110
  args:
106
111
  - optional: true
@@ -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(args.reqd).to receive(:exit).with(1)
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(args.reqd).to receive(:exit).with(1)
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, except: :build2
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(config_name: "theconfigname").and_return runner
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::TestCliFooBar }
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(cli_class2.actions_run).to eq(%i[before_action waz])
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(cli_class2.actions_run).to eq(%i[before_action main]) }
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(builder).to receive(:exit).with(1)
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(builder).to receive(:exit).with(0)
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(builder).to receive(:exit).with(1)
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(builder).to receive(:exit).with(1)
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" => [
@@ -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]
@@ -4,7 +4,7 @@ require_relative "../../lib/tabry/runner"
4
4
 
5
5
  describe Tabry::Runner do
6
6
  subject do
7
- described_class.new(config_name: "configname")
7
+ described_class.new(config: "configname")
8
8
  end
9
9
 
10
10
  let(:config) { instance_double(Tabry::Models::Config) }
@@ -3,18 +3,20 @@
3
3
  require_relative "../../lib/tabry/shell_splitter"
4
4
 
5
5
  describe Tabry::ShellSplitter do
6
- describe '.split' do
6
+ describe ".split" do
7
7
  it "returns the command basename, argument, and last argument" do
8
- str = 'foo/bar abc def ghi'
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 "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
- "waz",
15
- ["ab c d", "ef g"],
16
- "hi jk"
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.4"
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"