tabry 0.1.3 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b1cb912ae500805787b62500d1c4995215bc9e3c8a4ccf32b7224bc4fa227c6
4
- data.tar.gz: 68244bb100b3a262527b723b02fd6e73e9158f756bf44df960f8e5bd3d7e4679
3
+ metadata.gz: fe7c5c94f54f4d6fb603a73a8ec6b8364c63b6247a9dc3f095d4359fe96b5018
4
+ data.tar.gz: 4a29c24624360099c44dfd5c8c1420ef701bb21ee6535dc1e7a3540315002811
5
5
  SHA512:
6
- metadata.gz: 0d6e9694ca66c39e5627eed4e8159ad95510ae1f146221529d068ca92c32d3e9d3d3208d7da59909467f2833fcf0a064735e49c75816e3b11741fe659bf8d92f
7
- data.tar.gz: 0572051e1da3fa757609b2c5e956f745f434487e089268db318c1410f72e32a5bf906d4a4a4277cc23a40b77ed64ac8d41b1ebcb9fa0a78c0d0cff3c382c81f9
6
+ metadata.gz: b9b58e549801135afe35686048bd3b84b76c9e41cb5ecd5dc49bff3a1c3ea3ac26f10a2187eca5775ce4aa2d02d46157c559cefc2d376267ff62ebcf630b721c
7
+ data.tar.gz: a21e775881c48a511363cce6690e99cd13ccb32a3b829670bf049f3de63ab6e590b351a591717bb8137f112eb0e81514f548cefb8e3a0f4a79e5339118fafb33
data/bin/tabry-bash CHANGED
@@ -8,27 +8,12 @@ require "shellwords"
8
8
  require "yaml"
9
9
  require_relative "../lib/tabry/util"
10
10
  require_relative "../lib/tabry/runner"
11
+ require_relative "../lib/tabry/shell_splitter"
11
12
 
12
13
  # Bash-specific entrypoint, taking COMP_WORDS and COMP_CWORDS and returning possible options
13
14
 
14
- COMP_POINT_SENTINEL = "\uFFFF"
15
-
16
- # TODO: in weird scenarios this acts weird: namely, special shell operators like <(ls), $$((1 + 1))
17
- # Also it crashed on unbalanced quotes, like: foo "bar<TAB>
18
- # however, this will handle the common scenarios of escaping with quotes, single quotes, and backslashes
19
- # Split up args and put the argument that comp_point is in in the `last_arg` variable.
20
- # Just cutting off everything after comp_point might have worked, although
21
- # maybe we wanted the whole arg? Not sure this is the best.
22
15
  cmd_line, comp_point = ARGV
23
- cmd_line = cmd_line.dup
24
- cmd_line[comp_point.to_i...comp_point.to_i] = COMP_POINT_SENTINEL
25
- cmd, *all_args = Shellwords.split(cmd_line)
26
- last_arg_index = all_args.index { |arg| arg.include?(COMP_POINT_SENTINEL) }
27
- args = all_args[0..last_arg_index]
28
- last_arg = args.pop.gsub! COMP_POINT_SENTINEL, ""
29
-
30
- cmd_name = cmd.gsub(%r{.*/}, "")
31
-
16
+ cmd_name, args, last_arg = Tabry::ShellSplitter.split(cmd_line, comp_point)
32
17
  opts = Tabry::Runner.new(config_name: cmd_name).options(args, last_arg)
33
18
 
34
19
  if Tabry::Util.debug?
@@ -14,8 +14,11 @@ module Tabry
14
14
  @internals = internals
15
15
  end
16
16
 
17
- def self.sub_route(prefix, cli_class)
18
- (@sub_route_clis ||= {})[prefix.to_s] = cli_class
17
+ def self.sub_route(*prefixes, to:, **opts)
18
+ prefixes.map(&:to_s).each do |prefix|
19
+ (@sub_route_clis ||= {})[prefix] = to
20
+ (@sub_route_clis_opts ||= {})[prefix] = opts
21
+ end
19
22
  end
20
23
 
21
24
  def self.after_action(*method_names, only: nil, except: nil, &blk)
@@ -72,8 +72,12 @@ module Tabry
72
72
  sub_name, rest = met.split("__", 2)
73
73
 
74
74
  if sub_route_clis&.dig(sub_name)
75
+ # Instantiate CLI if it hasn't already (and store instantiation)
75
76
  sub_route_clis[sub_name] = instantiate_cli(sub_route_clis[sub_name], internals)
76
- get_cli_object_and_met(sub_route_clis[sub_name], rest, internals)
77
+ opts = cli.class.instance_variable_get(:@sub_route_clis_opts)[sub_name]
78
+
79
+ new_met_name = opts[:full_method_name] ? met : rest
80
+ get_cli_object_and_met(sub_route_clis[sub_name], new_met_name, internals)
77
81
  else
78
82
  [cli, met]
79
83
  end
@@ -9,8 +9,19 @@ module Tabry
9
9
  module_function
10
10
 
11
11
  def make_cmdline(cmdline, *args, echo: false, echo_only: false)
12
- args = Array(args).flatten
13
- cmdline = cmdline % args.map { |a| Shellwords.escape(a) }
12
+ # Allow to pass in an array, or varargs:
13
+ args = args.first if args.length == 1 && args.first.is_a?(Array)
14
+
15
+ args = args.map do |arg|
16
+ if arg.is_a?(Array)
17
+ # array of arguments get escaped separately and joined with strings (see specs)
18
+ arg.map { |a| Shellwords.escape(a) }.join(" ")
19
+ else
20
+ Shellwords.escape(arg)
21
+ end
22
+ end
23
+ cmdline = cmdline % args
24
+
14
25
  warn cmdline if echo || echo_only
15
26
  return nil if echo_only
16
27
 
@@ -18,14 +18,18 @@ module Tabry
18
18
  def options(token)
19
19
  # Bit of a hack: send this down to autocomplete shell commands
20
20
  # TODO: set this only in ShellOption -- would require passing state down on thru
21
+ before_env = ENV.fetch("TABRY_AUTOCOMPLETE_STATE", nil)
21
22
  ENV["TABRY_AUTOCOMPLETE_STATE"] = {
22
23
  cmd: result.config.cmd,
23
24
  flags: result.state.flags,
24
25
  args: result.state.args,
26
+ current_token: token,
25
27
  current_flag: result.state.current_flag
26
28
  }.to_json
27
29
 
28
30
  send(:"options_#{state.mode}", token || "")
31
+ ensure
32
+ ENV["TABRY_AUTOCOMPLETE_STATE"] = before_env
29
33
  end
30
34
 
31
35
  private
data/lib/tabry/result.rb CHANGED
@@ -35,7 +35,11 @@ module Tabry
35
35
 
36
36
  def wrong_number_of_args?
37
37
  n_args = state.args.length
38
- if n_args == 0 && current_sub.subs.any?
38
+ # This could use some tweaking. I'm not at which point a subcommand
39
+ # should be considered to be invalid with no arguments... if it has a
40
+ # subcommand it's a good sign, but if it also has an optional argument
41
+ # that means it's OK. Probably need another "no_args" construct.
42
+ if n_args == 0 && current_sub.subs.any? && !(current_sub.args.any? && current_sub.min_args == 0)
39
43
  if current_sub.args.any?
40
44
  "missing subcommand or arg(s)"
41
45
  else
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Use Shellwords.split() to split a command line + comp point (index of the
4
+ # cursor in the command line) into the args up to the current token plus
5
+ # current token
6
+ module Tabry
7
+ module ShellSplitter
8
+ module_function
9
+
10
+ COMP_POINT_SENTINEL = "\uFFFF"
11
+
12
+ def split(cmd_line, comp_point)
13
+ # Returns [cmd_name, args, last_arg]
14
+ # cmd_name is the basename of the command run
15
+ #
16
+ # TODO: in weird scenarios this acts weird: namely, special shell operators like <(ls), $$((1 + 1))
17
+ # Also it crashed on unbalanced quotes, like: foo "bar<TAB>
18
+ # however, this will handle the common scenarios of escaping with quotes, single quotes, and backslashes
19
+ # Split up args and put the argument that comp_point is in in the `last_arg` variable.
20
+ # Just cutting off everything after comp_point might have worked, although
21
+ # maybe we wanted the whole arg? Not sure this is the best.
22
+ cmd_line = cmd_line.dup
23
+ cmd_line[comp_point.to_i...comp_point.to_i] = COMP_POINT_SENTINEL
24
+ cmd, *all_args = Shellwords.split(cmd_line)
25
+ last_arg_index = all_args.index { |arg| arg.include?(COMP_POINT_SENTINEL) }
26
+ args = all_args[0..last_arg_index]
27
+ last_arg = args.pop.gsub! COMP_POINT_SENTINEL, ""
28
+
29
+ cmd_name = cmd.gsub(%r{.*/}, "")
30
+
31
+ [cmd_name, args, last_arg]
32
+ end
33
+ end
34
+ end
data/sh/fish/README.md ADDED
@@ -0,0 +1,16 @@
1
+ This directory includes fish completions for Tabry
2
+
3
+ There are two steps to add to your ~/.config/fish/config.fish to use:
4
+
5
+ 1. Source this file
6
+ 2. Add a call to `tabry_completion_init`, for each command
7
+
8
+ ```sh
9
+ source tabry_fish.fish
10
+ tabry_completion_init "aws"
11
+ tabry_completion_init "rapture"
12
+ ```
13
+
14
+ Note: Currently, the fish completion support doesn't distinguish between directory completion and file completion. The native fish complete command doesn't seem to support it (https://fishshell.com/docs/current/completions.html), so something custom would have to be written.
15
+
16
+ Also note: the bash completion code can be used to create completely separate tabry bash functions for each tabry-based CLI, so multiple tabry-based CLIs can use distinct tabry versions. The fish completion does not yet support this (see sh/bash/README.md).
@@ -0,0 +1,95 @@
1
+
2
+ function tabry_completion_init
3
+ # Three separate complete calls here are needed to support files.
4
+ # One provides completions in the case where tabry offers _only_ arguments,
5
+ # another provides completions where tabry offers _only_ files,
6
+ # the third handles the case where tabry offers both files and arguments
7
+ set cmd $argv[1]
8
+ complete -f -c "$cmd" -n "__fish_tabry_check_only_args" -a "(__fish_tabry_completions)"
9
+ complete -c "$cmd" -n "__fish_tabry_check_only_file"
10
+ complete -c "$cmd" -n "__fish_tabry_check_args_and_file" -a "(__fish_tabry_completions)"
11
+ end
12
+
13
+ # return true if tabry only reports commands
14
+ function __fish_tabry_internal_invoke
15
+ set SCRIPT (status --current-filename)
16
+ set SCRIPT_DIR (dirname $SCRIPT)
17
+
18
+ # -C "Get cursor position"
19
+ set cursor_position (commandline -C)
20
+ set cmd (commandline)
21
+ set result ($SCRIPT_DIR/../../bin/tabry-bash "$cmd" "$cursor_position")
22
+ echo $result
23
+ end
24
+
25
+ # return true if tabry only reports file
26
+ function __fish_tabry_check_only_args
27
+ set result (__fish_tabry_internal_invoke)
28
+
29
+ set args (echo "$result"|sed 's/ .*//')
30
+ set specials (echo "$result"|grep -o ' file')
31
+
32
+ # https://github.com/fish-shell/fish-shell/issues/5186#issuecomment-421244106
33
+ if test "x$args" != "x" -a "$specials" != " file"
34
+ # echo "confirming only args: [$result,$args,$specials]" 1>&2
35
+ return 0;
36
+ else
37
+ # echo "rejecting only args: [$result,$args,$specials]" 1>&2
38
+ return 1;
39
+ end
40
+ end
41
+
42
+ # return true if tabry reports file _and_ commands
43
+ function __fish_tabry_check_only_file
44
+ set result (__fish_tabry_internal_invoke)
45
+
46
+ set args (echo "$result"|sed 's/ .*//')
47
+
48
+ if test "x$args" = "x" -a (string match -ra ' file' $result)
49
+ # echo "confirming only file" 1>&2
50
+ return 0;
51
+ else
52
+ # echo "rejecting only file: [$args,$specials]" 1>&2
53
+ return 1;
54
+ end
55
+ end
56
+
57
+ function __fish_tabry_check_args_and_file
58
+ set result (__fish_tabry_internal_invoke)
59
+
60
+ set args (echo "$result"|sed 's/ .*//')
61
+
62
+ if test "x$args" != "x" -a (string match -ra ' file' $result)
63
+ # echo "confirming args and file" 1>&2
64
+ return 0;
65
+ else
66
+ # echo "rejecting args and file: [$args,$specials]" 1>&2
67
+ return 1;
68
+ end
69
+ end
70
+
71
+ function __fish_tabry_completions
72
+ set result (__fish_tabry_internal_invoke)
73
+
74
+ set args (echo "$result"|sed 's/ .*//')
75
+
76
+ set args_parsed (string split ' ' $args)
77
+
78
+ if test "x$args" = "x"
79
+ # Don't offer anything if we don't have any completions
80
+ return 1;
81
+ else
82
+ # $args_parsed will be something like: ["foo" "bar" "baz" "" "file"]
83
+ # where "file" is special, since it's after the space.
84
+ for arg in $args_parsed
85
+ if test "x$arg" != "x"
86
+ echo "$arg"
87
+ else
88
+ break;
89
+ end
90
+ end
91
+
92
+ # echo $args
93
+ return 0;
94
+ end
95
+ end
@@ -45,6 +45,11 @@ sub sub-with-sub-or-arg {
45
45
  sub subsub
46
46
  }
47
47
 
48
+ sub sub-with-sub-or-opt-arg{
49
+ sub subsub
50
+ opt arg
51
+ }
52
+
48
53
  sub sub-with-mandatory-flag {
49
54
  opt arg { opts const (a b c) }
50
55
  reqd flagarg mandatory
@@ -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,60 @@
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
+ end
60
+ end
@@ -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
@@ -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
@@ -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" => [
@@ -88,4 +88,40 @@ describe Tabry::OptionsFinder do
88
88
  expect(described_class.options(result, token)).to match_array expected_options
89
89
  end
90
90
  end
91
+
92
+ describe "TABRY_AUTOCOMPLETE_STATE" do
93
+ before { @env_before = ENV.fetch("TABRY_AUTOCOMPLETE_STATE", nil) }
94
+
95
+ after { ENV["TABRY_AUTOCOMPLETE_STATE"] = @env_before }
96
+
97
+ let(:state) do
98
+ {
99
+ mode: :flagarg, current_flag: "speed",
100
+ subcommand_stack: %w[move crash],
101
+ flags: {}, args: []
102
+ }
103
+ end
104
+ let(:result) { Tabry::Result.new(config, Tabry::State.new(state)) }
105
+ let(:flag) { config.dig_sub(%w[move crash]).flags["speed"] }
106
+
107
+ it "sets TABRY_AUTOCOMPLETE_STATE with information about the state" do
108
+ expect(flag.options).to receive(:options) do
109
+ expect(JSON.parse(ENV.fetch("TABRY_AUTOCOMPLETE_STATE", nil), symbolize_names: true)).to eq(
110
+ args: [],
111
+ cmd: "vehicles",
112
+ current_flag: "speed",
113
+ current_token: "abcd",
114
+ flags: {}
115
+ )
116
+ end
117
+
118
+ described_class.options(result, "abcd")
119
+ end
120
+
121
+ it "sets TABRY_AUTOCOMPLETE_STATE back to what it was before" do
122
+ ENV["TABRY_AUTOCOMPLETE_STATE"] = "foobar1234"
123
+ described_class.options(result, "foo")
124
+ expect(ENV.fetch("TABRY_AUTOCOMPLETE_STATE", nil)).to eq("foobar1234")
125
+ end
126
+ end
91
127
  end
@@ -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]
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/tabry/shell_splitter"
4
+
5
+ describe Tabry::ShellSplitter do
6
+ describe ".split" do
7
+ it "returns the command basename, argument, and last argument" do
8
+ str = "foo/bar abc def ghi"
9
+ expect(described_class.split(str, 13)).to eq(["bar", ["abc"], "def"])
10
+ end
11
+
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
+ "waz",
15
+ ["ab c d", "ef g"],
16
+ "hi jk"
17
+ ])
18
+ end
19
+ end
20
+ 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.3"
9
+ s.version = "0.1.5"
10
10
  s.summary = "Tab completion and CLIs extraordinaire"
11
11
  s.authors = ["Evan Battaglia"]
12
12
  s.email = "battaglia.evan@gmail.com"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Battaglia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-25 00:00:00.000000000 Z
11
+ date: 2022-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry-byebug
@@ -133,6 +133,7 @@ files:
133
133
  - lib/tabry/options_finder.rb
134
134
  - lib/tabry/result.rb
135
135
  - lib/tabry/runner.rb
136
+ - lib/tabry/shell_splitter.rb
136
137
  - lib/tabry/shells/bash.rb
137
138
  - lib/tabry/state.rb
138
139
  - lib/tabry/usage_generator.rb
@@ -141,12 +142,15 @@ files:
141
142
  - sh/bash/tabry_bash.sh
142
143
  - sh/bash/tabry_bash_core.sh
143
144
  - sh/bash/tabry_bash_help.sh
145
+ - sh/fish/README.md
146
+ - sh/fish/tabry_fish.fish
144
147
  - spec/fixtures/basiccli.json
145
148
  - spec/fixtures/basiccli.tabry
146
149
  - spec/fixtures/basiccli2.tabry
147
150
  - spec/fixtures/basiccli2.yml
148
151
  - spec/fixtures/vehicles.tabry
149
152
  - spec/fixtures/vehicles.yaml
153
+ - spec/lib/tabry/cli/util_spec.rb
150
154
  - spec/spec_helper.rb
151
155
  - spec/tabry/cli/arg_proxy_spec.rb
152
156
  - spec/tabry/cli/builder_spec.rb
@@ -163,6 +167,7 @@ files:
163
167
  - spec/tabry/options_finder_spec.rb
164
168
  - spec/tabry/result_spec.rb
165
169
  - spec/tabry/runner_spec.rb
170
+ - spec/tabry/shell_splitter_spec.rb
166
171
  - spec/tabry/usage_generator_spec.rb
167
172
  - tabry.gemspec
168
173
  - treesitter/Cargo.toml
@@ -220,7 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
225
  - !ruby/object:Gem::Version
221
226
  version: '0'
222
227
  requirements: []
223
- rubygems_version: 3.2.15
228
+ rubygems_version: 3.2.22
224
229
  signing_key:
225
230
  specification_version: 4
226
231
  summary: Tab completion and CLIs extraordinaire