tabry 0.1.3 → 0.1.5

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