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 +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
|