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 +4 -4
- data/bin/tabry-bash +2 -17
- data/lib/tabry/cli/base.rb +5 -2
- data/lib/tabry/cli/builder.rb +5 -1
- data/lib/tabry/cli/util.rb +13 -2
- data/lib/tabry/options_finder.rb +4 -0
- data/lib/tabry/result.rb +5 -1
- data/lib/tabry/shell_splitter.rb +34 -0
- data/sh/fish/README.md +16 -0
- data/sh/fish/tabry_fish.fish +95 -0
- data/spec/fixtures/vehicles.tabry +5 -0
- data/spec/fixtures/vehicles.yaml +5 -0
- data/spec/lib/tabry/cli/util_spec.rb +60 -0
- data/spec/tabry/cli/builder_spec.rb +46 -15
- data/spec/tabry/config_loader_spec.rb +2 -2
- data/spec/tabry/options_finder_spec.rb +37 -1
- data/spec/tabry/result_spec.rb +4 -0
- data/spec/tabry/shell_splitter_spec.rb +20 -0
- data/tabry.gemspec +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe7c5c94f54f4d6fb603a73a8ec6b8364c63b6247a9dc3f095d4359fe96b5018
|
4
|
+
data.tar.gz: 4a29c24624360099c44dfd5c8c1420ef701bb21ee6535dc1e7a3540315002811
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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?
|
data/lib/tabry/cli/base.rb
CHANGED
@@ -14,8 +14,11 @@ module Tabry
|
|
14
14
|
@internals = internals
|
15
15
|
end
|
16
16
|
|
17
|
-
def self.sub_route(
|
18
|
-
(
|
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)
|
data/lib/tabry/cli/builder.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/tabry/cli/util.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
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
|
|
data/lib/tabry/options_finder.rb
CHANGED
@@ -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
|
-
|
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
|
data/spec/fixtures/vehicles.yaml
CHANGED
@@ -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
|
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::
|
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
|
@@ -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
|
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]
|
@@ -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.
|
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.
|
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-
|
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.
|
228
|
+
rubygems_version: 3.2.22
|
224
229
|
signing_key:
|
225
230
|
specification_version: 4
|
226
231
|
summary: Tab completion and CLIs extraordinaire
|