tabry 0.1.2 → 0.1.4

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: 853b43dede9ba1a93e3581eefd4dbe883ec0cbef4cf493776183df0d363fed3e
4
- data.tar.gz: f8bb31ead64b56da0f0bbabe051219fca8e40ade773b730b4295d990b5fe03b5
3
+ metadata.gz: 238e670dd915a52c21ff0af689fd3885261deba3caee10dae8972f06cbf90ccb
4
+ data.tar.gz: ff161d2cda396309d243a8f1dd5f4a75a6b154f6bdacedb7ee7e59c4bae5d8f5
5
5
  SHA512:
6
- metadata.gz: 463f392cad04097474a34abd6bf78d9bf3dfb88a7af8867f3879bcce514ec07c5d8d9acddcb01e8cb6dbb286545085ccd72fdaeb2686d7e1f738eae2405752b4
7
- data.tar.gz: 1f877f562de0e3b3d08421b087cc66d00efc2875807da22b88fec70068aaa55b512840eb12e3414a9813e6932017a7737cb5ad5e91df19024627095536cb1275
6
+ metadata.gz: 66d5af76b745d7cb56c1905f095c7b137acfd3d816f4e8fb070c0ccf8c76c5be123c85ebc5b1fc29480e73485a7f9dacb438d98c0f514009830afabb5ec13426
7
+ data.tar.gz: 30545a229fc13939596285535d84a6e69cc73c08e248f0d3a3bf18cbba18a379dbc26a1756c9f9266081503d277da15738d0608e573a219715b8d46211eb8772
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?
@@ -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
@@ -0,0 +1,32 @@
1
+ # Use Shellwords.split() to split a command line + comp point (index of the
2
+ # cursor in the command line) into the args up to the current token plus
3
+ # current token
4
+ module Tabry
5
+ module ShellSplitter
6
+ module_function
7
+
8
+ COMP_POINT_SENTINEL = "\uFFFF"
9
+
10
+ def split(cmd_line, comp_point)
11
+ # Returns [cmd_name, args, last_arg]
12
+ # cmd_name is the basename of the command run
13
+ #
14
+ # TODO: in weird scenarios this acts weird: namely, special shell operators like <(ls), $$((1 + 1))
15
+ # Also it crashed on unbalanced quotes, like: foo "bar<TAB>
16
+ # however, this will handle the common scenarios of escaping with quotes, single quotes, and backslashes
17
+ # Split up args and put the argument that comp_point is in in the `last_arg` variable.
18
+ # Just cutting off everything after comp_point might have worked, although
19
+ # maybe we wanted the whole arg? Not sure this is the best.
20
+ cmd_line = cmd_line.dup
21
+ cmd_line[comp_point.to_i...comp_point.to_i] = COMP_POINT_SENTINEL
22
+ cmd, *all_args = Shellwords.split(cmd_line)
23
+ last_arg_index = all_args.index { |arg| arg.include?(COMP_POINT_SENTINEL) }
24
+ args = all_args[0..last_arg_index]
25
+ last_arg = args.pop.gsub! COMP_POINT_SENTINEL, ""
26
+
27
+ cmd_name = cmd.gsub(%r{.*/}, "")
28
+
29
+ [cmd_name, args, last_arg]
30
+ end
31
+ end
32
+ end
@@ -97,11 +97,11 @@ module Tabry
97
97
  lines << ""
98
98
  lines << "FLAGS".green.bold
99
99
  if sub != sub_stack.last
100
- lines.last << if full_sub_name == ""
101
- " (global)".green.bold
102
- else
103
- " (#{full_sub_name})".green.bold
104
- end
100
+ lines[-1] += if full_sub_name == ""
101
+ " (global)".green.bold
102
+ else
103
+ " (#{full_sub_name})".green.bold
104
+ end
105
105
  end
106
106
 
107
107
  sub.flags.map(&:name).sort.each do |name|
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
@@ -0,0 +1,58 @@
1
+ require_relative "../../../../lib/tabry/cli/util"
2
+
3
+ describe Tabry::CLI::Util do
4
+ describe ".make_cmdline" do
5
+ it "escapes a single string" do
6
+ expect(described_class.make_cmdline(
7
+ "hello %s", "world;abc"
8
+ )).to eq(
9
+ "hello world\\;abc"
10
+ )
11
+ end
12
+
13
+ it "escapes multiple string arguments" do
14
+ expect(described_class.make_cmdline(
15
+ "hello %s hi %s", "world;abc", "def;ghi"
16
+ )).to eq(
17
+ "hello world\\;abc hi def\\;ghi"
18
+ )
19
+ end
20
+
21
+ it "escapes array arguments" do
22
+ # args is an array of strings and arrays
23
+ expect(described_class.make_cmdline(
24
+ "hello %s hi %s", "world;abc", ["foo;bar", "waz;ok"]
25
+ )).to eq(
26
+ "hello world\\;abc hi foo\\;bar waz\\;ok"
27
+ )
28
+ end
29
+
30
+ it "treats a single array of strings as a list of args, not as one array arg" do
31
+ # args is an array with one array of strings
32
+ # an improvement might be to figure out how
33
+ # many '%s's there are in the cmdline
34
+ expect(described_class.make_cmdline(
35
+ "hello %s hi %s", ["world;abc", "foo;bar"]
36
+ )).to eq(
37
+ "hello world\\;abc hi foo\\;bar"
38
+ )
39
+ end
40
+
41
+ it "escapes arrays if given one array of mixed strings and arrays" do
42
+ expect(described_class.make_cmdline(
43
+ "hello %s hi %s", [["world;abc", "foo;bar"], "waz;ok"]
44
+ )).to eq(
45
+ "hello world\\;abc foo\\;bar hi waz\\;ok"
46
+ )
47
+ end
48
+
49
+ it "can send a single array argument by wrapping in an array" do
50
+ # args is an array with one array of one array
51
+ expect(described_class.make_cmdline(
52
+ "hello %s hi", [["foo;bar", "waz;ok"]]
53
+ )).to eq(
54
+ "hello foo\\;bar waz\\;ok hi"
55
+ )
56
+ end
57
+ end
58
+ end
@@ -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
@@ -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 "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.2"
9
+ s.version = "0.1.4"
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.2
4
+ version: 0.1.4
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-09-22 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