tabry 0.1.2 → 0.1.4

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