tabry 0.2.2 → 0.2.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/bin/tabry-fish +9 -0
  3. data/bin/tabry-generate-fish-complete +31 -0
  4. data/lib/tabry/cli/all_in_one.rb +27 -6
  5. data/lib/tabry/config_builder/arg_or_flag_builder.rb +5 -0
  6. data/lib/tabry/config_builder/top_level_builder.rb +3 -0
  7. data/lib/tabry/machine.rb +1 -1
  8. data/lib/tabry/models/const_option.rb +1 -1
  9. data/lib/tabry/models/flags_list.rb +6 -2
  10. data/lib/tabry/models/include_option.rb +2 -2
  11. data/lib/tabry/models/method_option.rb +14 -0
  12. data/lib/tabry/models/option.rb +5 -5
  13. data/lib/tabry/models/options_list.rb +2 -2
  14. data/lib/tabry/models/shell_option.rb +1 -1
  15. data/lib/tabry/models/subs_list.rb +13 -2
  16. data/lib/tabry/options_finder.rb +13 -8
  17. data/lib/tabry/replty/base.rb +23 -0
  18. data/lib/tabry/replty/builder.rb +100 -0
  19. data/lib/tabry/runner.rb +2 -2
  20. data/lib/tabry/shell_tokenizer.rb +50 -0
  21. data/lib/tabry/shells/bash/wrapper.rb +2 -2
  22. data/lib/tabry/shells/fish/wrapper.rb +51 -0
  23. data/lib/tabry/shells/fish.rb +82 -0
  24. data/lib/tabry/version.rb +3 -0
  25. data/sh/fish/README.md +2 -1
  26. data/sh/fish/tabry_fish.fish +17 -11
  27. data/spec/fixtures/config_builder/underscoresdashes.yml +1 -1
  28. data/spec/fixtures/vehicles-expectations.json +351 -0
  29. data/spec/tabry/cli/all_in_one_spec.rb +12 -0
  30. data/spec/tabry/machine_spec.rb +5 -82
  31. data/spec/tabry/shell_tokenizer_spec.rb +34 -0
  32. data/tabry.gemspec +2 -4
  33. data/treesitter/corpus/opts.txt +2 -0
  34. data/treesitter/grammar.js +7 -0
  35. data/treesitter/src/grammar.json +21 -0
  36. data/treesitter/src/node-types.json +23 -0
  37. data/treesitter/src/parser.c +959 -893
  38. data/treesitter/tabry-compile.js +6 -0
  39. metadata +16 -5
  40. data/lib/tabry/shell_splitter.rb +0 -34
  41. data/spec/tabry/shell_splitter_spec.rb +0 -22
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Used to generate a tab-completion function for a Tabry CLI using absolute
4
+ # paths to the tabry-bash script in this repo and to the Tabry JSON/YML file.
5
+ # Using uniquely-named tab-completion functions and absolute paths means you can
6
+ # have different Tabry-based CLIs using different versions of Tabry without any
7
+ # conflicts.
8
+ # See sh/bash/README.md and "Adding Tab Completion to your CLI" in main README
9
+
10
+ require "shellwords"
11
+
12
+ module Tabry
13
+ module Shells
14
+ module Fish
15
+ # NOTE! This code uses sh/fish/tabry_fish.fish and is described in
16
+ # sh/fish/README.md; see that README for more info
17
+ def self.generate(cmd_name, tabry_file_path, uniq_fn_id: nil)
18
+ generate_internal(
19
+ cmd_name: cmd_name,
20
+ import_path: File.expand_path(tabry_file_path),
21
+ tabry_fish_executable: File.expand_path("#{path_to_tabry}/bin/tabry-fish"),
22
+ tabry_fish_arg: nil,
23
+ uniq_fn_id: uniq_fn_id
24
+ )
25
+ end
26
+
27
+ # Generate fish completion code that will run the currently running
28
+ # command ($0) with "completion" to get completion options. "cmd_name"
29
+ # is used to tell fish which command to make options for
30
+ def self.generate_self(cmd_name: nil)
31
+ cmd_name ||= File.basename($0)
32
+ generate_internal(
33
+ cmd_name: cmd_name,
34
+ import_path: "",
35
+ tabry_fish_executable: File.expand_path($0),
36
+ tabry_fish_arg: "completion",
37
+ )
38
+ end
39
+
40
+ def self.path_to_tabry
41
+ File.expand_path("#{__dir__}/../../../")
42
+ end
43
+
44
+ def self.esc(s)
45
+ Shellwords.escape(s)
46
+ end
47
+
48
+ def self.add_uniq_id(str, pattern, uniq_id)
49
+ str.gsub! pattern, "#{pattern}_#{uniq_id}"
50
+ end
51
+
52
+ def self.generate_internal(cmd_name:, import_path:, tabry_fish_executable:, tabry_fish_arg:, uniq_fn_id: nil)
53
+ # uniq_fn_id is added to the bash functions to ensure they are unique
54
+ # -- by default this is the capitalized command name
55
+ uniq_fn_id ||= cmd_name
56
+ uniq_fn_id = uniq_fn_id.upcase.gsub(/[^A-Z0-9_]/, "_")
57
+ script = File.read("#{__dir__}/../../../sh/fish/tabry_fish.fish")
58
+
59
+ add_uniq_id(script, "tabry_completion_init", uniq_fn_id)
60
+ add_uniq_id(script, "__fish_tabry_internal_invoke", uniq_fn_id)
61
+ add_uniq_id(script, "__fish_tabry_check_only_args", uniq_fn_id)
62
+ add_uniq_id(script, "__fish_tabry_check_only_file", uniq_fn_id)
63
+ add_uniq_id(script, "__fish_tabry_check_args_and_file", uniq_fn_id)
64
+ add_uniq_id(script, "__fish_tabry_completions", uniq_fn_id)
65
+
66
+ script.gsub! "# TABRY_IMPORT_PATH_REPLACE (DO NOT REMOVE)", "set TABRY_IMPORTS_PATH #{esc import_path}"
67
+ script.gsub! "# TABRY_EXECUTABLE_REPLACE (DO NOT REMOVE)", "set TABRY_EXECUTABLE #{esc tabry_fish_executable}"
68
+ if !tabry_fish_arg.nil?
69
+ script.gsub! "# TABRY_ARG_REPLACE (DO NOT REMOVE)", "set TABRY_ARG #{esc tabry_fish_arg}"
70
+ end
71
+
72
+ <<~END_FISH_CODE_TEMPLATE
73
+ # The following Autocomplete is for a Tabry-powered command. It was
74
+ # generated by the command itself. See the documentation located in
75
+ # #{esc path_to_tabry}/sh/fish/README.md
76
+ #{script}
77
+ tabry_completion_init_#{uniq_fn_id} #{esc cmd_name}
78
+ END_FISH_CODE_TEMPLATE
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module Tabry
2
+ VERSION = "0.2.4"
3
+ end
data/sh/fish/README.md CHANGED
@@ -2,8 +2,9 @@ This directory includes fish completions for Tabry
2
2
 
3
3
  There are two steps to add to your ~/.config/fish/config.fish to use:
4
4
 
5
- 1. Source this file
5
+ 1. Source `tabry_fish.fish`
6
6
  2. Add a call to `tabry_completion_init`, for each command
7
+ 3. Ensure the `TABRY_IMPORTS_PATH` environment variable is defined and points to where your compiled tabry files are located.
7
8
 
8
9
  ```sh
9
10
  source tabry_fish.fish
@@ -12,14 +12,19 @@ end
12
12
 
13
13
  # return true if tabry only reports commands
14
14
  function __fish_tabry_internal_invoke
15
- set SCRIPT (status --current-filename)
16
- set SCRIPT_DIR (dirname $SCRIPT)
15
+ # TABRY_IMPORT_PATH_REPLACE (DO NOT REMOVE)
16
+ # TABRY_EXECUTABLE_REPLACE (DO NOT REMOVE)
17
+ # TABRY_ARG_REPLACE (DO NOT REMOVE)
18
+ set -x TABRY_FISH_MODE true
17
19
 
18
20
  # -C "Get cursor position"
19
21
  set cursor_position (commandline -C)
20
22
  set cmd (commandline)
21
- set result ($SCRIPT_DIR/../../bin/tabry-bash "$cmd" "$cursor_position")
22
- echo $result
23
+
24
+ set result ($TABRY_EXECUTABLE $TABRY_ARG "$cmd" "$cursor_position")
25
+ for arg in $result
26
+ echo $arg
27
+ end
23
28
  end
24
29
 
25
30
  # return true if tabry only reports file
@@ -29,7 +34,7 @@ function __fish_tabry_check_only_args
29
34
  set args (echo "$result"|sed 's/ .*//')
30
35
  set specials (echo "$result"|grep -o ' file')
31
36
 
32
- # https://github.com/fish-shell/fish-shell/issues/5186#issuecomment-421244106
37
+ # https://github.com/fish-shell/fish-shell/issues/5186#issuecomment-421244106 (the random "x")
33
38
  if test "x$args" != "x" -a "$specials" != " file"
34
39
  # echo "confirming only args: [$result,$args,$specials]" 1>&2
35
40
  return 0;
@@ -45,7 +50,8 @@ function __fish_tabry_check_only_file
45
50
 
46
51
  set args (echo "$result"|sed 's/ .*//')
47
52
 
48
- if test "x$args" = "x" -a (string match -ra ' file' $result)
53
+ # The '--' arg is needed in case $result contains flag-like strings
54
+ if test "x$args" = "x" -a (string match -ra -- ' file' $result)
49
55
  # echo "confirming only file" 1>&2
50
56
  return 0;
51
57
  else
@@ -59,7 +65,8 @@ function __fish_tabry_check_args_and_file
59
65
 
60
66
  set args (echo "$result"|sed 's/ .*//')
61
67
 
62
- if test "x$args" != "x" -a (string match -ra ' file' $result)
68
+ # The '--' arg is needed in case $result contains flag-like strings
69
+ if test "x$args" != "x" -a (string match -ra -- ' file' $result)
63
70
  # echo "confirming args and file" 1>&2
64
71
  return 0;
65
72
  else
@@ -73,15 +80,14 @@ function __fish_tabry_completions
73
80
 
74
81
  set args (echo "$result"|sed 's/ .*//')
75
82
 
76
- set args_parsed (string split ' ' $args)
77
-
78
83
  if test "x$args" = "x"
79
84
  # Don't offer anything if we don't have any completions
80
85
  return 1;
81
86
  else
82
- # $args_parsed will be something like: ["foo" "bar" "baz" "" "file"]
87
+ # $result will be something like: ["foo 'this is foo'" "bar" "baz" "" "file"]
83
88
  # where "file" is special, since it's after the space.
84
- for arg in $args_parsed
89
+ for arg in $result
90
+ set clean_arg (echo "$arg"|sed 's/ .*//')
85
91
  if test "x$arg" != "x"
86
92
  echo "$arg"
87
93
  else
@@ -33,7 +33,7 @@ main:
33
33
  - include: project-and-environment
34
34
  - include: project-and-environment
35
35
  option_includes:
36
- project_and_environment:
36
+ project-and-environment:
37
37
  - type: const
38
38
  value: a
39
39
 
@@ -0,0 +1,351 @@
1
+ {
2
+ "handles subcommands": [
3
+ [
4
+ "build"
5
+ ],
6
+ {
7
+ "subs": [
8
+ "build"
9
+ ],
10
+ "mode": "subcommand"
11
+ }
12
+ ],
13
+ "handles bogus subcommands": [
14
+ [
15
+ "bogus"
16
+ ],
17
+ {
18
+ "subs": [
19
+
20
+ ],
21
+ "args": [
22
+ "bogus"
23
+ ]
24
+ }
25
+ ],
26
+ "handles bogus subcommands of subcommands are treated as args": [
27
+ [
28
+ "move",
29
+ "bogus",
30
+ "bogus2"
31
+ ],
32
+ {
33
+ "subs": [
34
+ "move"
35
+ ],
36
+ "args": [
37
+ "bogus",
38
+ "bogus2"
39
+ ],
40
+ "mode": "subcommand"
41
+ }
42
+ ],
43
+ "handles subcommands of subcommands": [
44
+ [
45
+ "move",
46
+ "go"
47
+ ],
48
+ {
49
+ "subs": [
50
+ "move",
51
+ "go"
52
+ ]
53
+ }
54
+ ],
55
+ "handles subcommands aliases": [
56
+ [
57
+ "move",
58
+ "g"
59
+ ],
60
+ {
61
+ "subs": [
62
+ "move",
63
+ "go"
64
+ ]
65
+ }
66
+ ],
67
+ "handles argsuments": [
68
+ [
69
+ "build",
70
+ "arg1",
71
+ "arg2"
72
+ ],
73
+ {
74
+ "subs": [
75
+ "build"
76
+ ],
77
+ "args": [
78
+ "arg1",
79
+ "arg2"
80
+ ]
81
+ }
82
+ ],
83
+ "handles flags": [
84
+ [
85
+ "build",
86
+ "-v"
87
+ ],
88
+ {
89
+ "subs": [
90
+ "build"
91
+ ],
92
+ "flags": {
93
+ "verbose": true
94
+ },
95
+ "args": [
96
+
97
+ ],
98
+ "mode": "subcommand"
99
+ }
100
+ ],
101
+ "handles long flags": [
102
+ [
103
+ "--verbose",
104
+ "build"
105
+ ],
106
+ {
107
+ "subs": [
108
+ "build"
109
+ ],
110
+ "flags": {
111
+ "verbose": true
112
+ },
113
+ "args": [
114
+
115
+ ],
116
+ "mode": "subcommand"
117
+ }
118
+ ],
119
+ "handles flags interspersed with arguments": [
120
+ [
121
+ "move",
122
+ "-v",
123
+ "crash",
124
+ "arg1",
125
+ "--dry-run",
126
+ "arg2",
127
+ "arg3"
128
+ ],
129
+ {
130
+ "subs": [
131
+ "move",
132
+ "crash"
133
+ ],
134
+ "args": [
135
+ "arg1",
136
+ "arg2",
137
+ "arg3"
138
+ ],
139
+ "flags": {
140
+ "dry-run": true,
141
+ "verbose": true
142
+ },
143
+ "mode": "subcommand"
144
+ }
145
+ ],
146
+ "handles flags with an argument": [
147
+ [
148
+ "move",
149
+ "crash",
150
+ "--dry-run",
151
+ "arg1",
152
+ "-f",
153
+ "file",
154
+ "arg2",
155
+ "arg3"
156
+ ],
157
+ {
158
+ "subs": [
159
+ "move",
160
+ "crash"
161
+ ],
162
+ "args": [
163
+ "arg1",
164
+ "arg2",
165
+ "arg3"
166
+ ],
167
+ "flags": {
168
+ "dry-run": true,
169
+ "output-to-file": "file"
170
+ },
171
+ "mode": "subcommand"
172
+ }
173
+ ],
174
+ "handles double-dash to stop parsing of flags": [
175
+ [
176
+ "move",
177
+ "crash",
178
+ "--",
179
+ "--dry-run",
180
+ "arg1",
181
+ "-f",
182
+ "file",
183
+ "arg2",
184
+ "arg3"
185
+ ],
186
+ {
187
+ "subs": [
188
+ "move",
189
+ "crash"
190
+ ],
191
+ "args": [
192
+ "--dry-run",
193
+ "arg1",
194
+ "-f",
195
+ "file",
196
+ "arg2",
197
+ "arg3"
198
+ ],
199
+ "mode": "subcommand",
200
+ "dashdash": true
201
+ }
202
+ ],
203
+ "handles unknown long flags as args": [
204
+ [
205
+ "move",
206
+ "crash",
207
+ "--notaflag",
208
+ "arg2"
209
+ ],
210
+ {
211
+ "subs": [
212
+ "move",
213
+ "crash"
214
+ ],
215
+ "args": [
216
+ "--notaflag",
217
+ "arg2"
218
+ ],
219
+ "mode": "subcommand"
220
+ }
221
+ ],
222
+ "handles unknown short flags as args": [
223
+ [
224
+ "move",
225
+ "crash",
226
+ "-x",
227
+ "arg2",
228
+ "--dry-run"
229
+ ],
230
+ {
231
+ "subs": [
232
+ "move",
233
+ "crash"
234
+ ],
235
+ "args": [
236
+ "-x",
237
+ "arg2"
238
+ ],
239
+ "flags": {
240
+ "dry-run": true
241
+ },
242
+ "mode": "subcommand"
243
+ }
244
+ ],
245
+ "handles --help": [
246
+ [
247
+ "move",
248
+ "--help"
249
+ ],
250
+ {
251
+ "subs": [
252
+ "move"
253
+ ],
254
+ "flags": {
255
+ },
256
+ "args": [
257
+
258
+ ],
259
+ "help": true,
260
+ "mode": "subcommand"
261
+ }
262
+ ],
263
+ "handles -?": [
264
+ [
265
+ "move",
266
+ "foo",
267
+ "bar",
268
+ "--help",
269
+ "waz"
270
+ ],
271
+ {
272
+ "subs": [
273
+ "move"
274
+ ],
275
+ "flags": {
276
+ },
277
+ "args": [
278
+ "foo",
279
+ "bar",
280
+ "waz"
281
+ ],
282
+ "help": true
283
+ }
284
+ ],
285
+ "ignores -?/--help after double-dash": [
286
+ [
287
+ "move",
288
+ "--",
289
+ "--help",
290
+ "-?",
291
+ "--",
292
+ "-"
293
+ ],
294
+ {
295
+ "subs": [
296
+ "move"
297
+ ],
298
+ "flags": {
299
+ },
300
+ "args": [
301
+ "--help",
302
+ "-?",
303
+ "--",
304
+ "-"
305
+ ],
306
+ "help": false,
307
+ "dashdash": true
308
+ }
309
+ ],
310
+ "sets mode to flagarg when waiting for a flag argument": [
311
+ [
312
+ "move",
313
+ "crash",
314
+ "--dry-run",
315
+ "arg1",
316
+ "-f"
317
+ ],
318
+ {
319
+ "subs": [
320
+ "move",
321
+ "crash"
322
+ ],
323
+ "args": [
324
+ "arg1"
325
+ ],
326
+ "flags": {
327
+ "dry-run": true
328
+ },
329
+ "mode": "flagarg",
330
+ "current_flag": "output-to-file"
331
+ }
332
+ ],
333
+ "most specific sub's flag takes precedence in case of multiple matching": [
334
+ [
335
+ "sub-with-mandatory-flag",
336
+ "--verbose",
337
+ "foo",
338
+ "--mandatory",
339
+ "abc"
340
+ ],
341
+ {
342
+ "subs": [
343
+ "sub-with-mandatory-flag"
344
+ ],
345
+ "flags": {
346
+ "verbose": "foo",
347
+ "mandatory": "abc"
348
+ }
349
+ }
350
+ ]
351
+ }
@@ -3,6 +3,7 @@
3
3
  require_relative "../../../lib/tabry/cli/all_in_one"
4
4
  require_relative "../../../lib/tabry/shells/bash"
5
5
  require_relative "../../../lib/tabry/shells/bash/wrapper"
6
+ require_relative "../../../lib/tabry/shells/fish"
6
7
 
7
8
  module Tabry::Spec
8
9
  module Cli
@@ -124,6 +125,17 @@ describe Tabry::CLI::AllInOne do
124
125
  end
125
126
  end
126
127
 
128
+ it "creates a #completion__fish method which generates completion" do
129
+ stub_const("ARGV", %w[completion fish])
130
+ expect(Tabry::Shells::Fish).to receive(:generate_self).with(cmd_name: "foo").and_return("fish completion stuff")
131
+ expect_any_instance_of(Kernel).to receive(:puts).with("fish completion stuff")
132
+
133
+ described_class.completion_only do
134
+ cmd :foo
135
+ sub :bar
136
+ end
137
+ end
138
+
127
139
  it "creates a #completion method which generates options" do
128
140
  stub_const("ARGV", ["completion", "cmd line", "6"])
129
141
  expect(Tabry::Bash::Wrapper).to receive(:run) do |cmd_line, comp_point, config:|
@@ -15,94 +15,17 @@ describe Tabry::Machine do
15
15
  expect(subject.args).to eq(arr)
16
16
  end
17
17
 
18
- examples = {
19
- "handles subcommands" => [
20
- %w[build],
21
- subs: %w[build], mode: :subcommand
22
- ],
23
- "handles bogus subcommands" => [
24
- %w[bogus],
25
- subs: [], args: %w[bogus]
26
- ],
27
- "handles bogus subcommands of subcommands are treated as args" => [
28
- %w[move bogus bogus2],
29
- subs: %w[move], args: %w[bogus bogus2], mode: :subcommand
30
- ],
31
- "handles subcommands of subcommands" => [
32
- %w[move go],
33
- subs: %w[move go],
34
- ],
35
- "handles subcommands aliases" => [
36
- %w[move g],
37
- subs: %w[move go],
38
- ],
39
- "handles argsuments" => [
40
- %w[build arg1 arg2],
41
- subs: %w[build], args: %w[arg1 arg2]
42
- ],
43
- "handles flags" => [
44
- %w[build -v],
45
- subs: %w[build], flags: { "verbose" => true }, args: [], mode: :subcommand
46
- ],
47
- "handles long flags" => [
48
- %w[--verbose build],
49
- subs: %w[build], flags: { "verbose" => true }, args: [], mode: :subcommand
50
- ],
51
- "handles flags interspersed with arguments" => [
52
- %w[move -v crash arg1 --dry-run arg2 arg3],
53
- subs: %w[move crash], args: %w[arg1 arg2 arg3],
54
- flags: { "dry-run" => true, "verbose" => true }, mode: :subcommand
55
- ],
56
- "handles flags with an argument" => [
57
- %w[move crash --dry-run arg1 -f file arg2 arg3],
58
- subs: %w[move crash], args: %w[arg1 arg2 arg3],
59
- flags: { "dry-run" => true, "output-to-file" => "file" }, mode: :subcommand
60
- ],
61
- "handles double-dash to stop parsing of flags" => [
62
- %w[move crash -- --dry-run arg1 -f file arg2 arg3],
63
- subs: %w[move crash], args: %w[--dry-run arg1 -f file arg2 arg3], mode: :subcommand
64
- ],
65
- "handles unknown long flags as args" => [
66
- %w[move crash --notaflag arg2],
67
- subs: %w[move crash], args: %w[--notaflag arg2], mode: :subcommand
68
- ],
69
- "handles unknown short flags as args" => [
70
- %w[move crash -x arg2 --dry-run],
71
- subs: %w[move crash], args: %w[-x arg2], flags: { "dry-run" => true }, mode: :subcommand
72
- ],
73
- "handles --help" => [
74
- %w[move --help],
75
- subs: %w[move], flags: {}, args: [], help: true, mode: :subcommand
76
- ],
77
- "handles -?" => [
78
- %w[move foo bar --help waz],
79
- subs: %w[move], flags: {}, args: %w[foo bar waz], help: true
80
- ],
81
- "ignores -?/--help after double-dash" => [
82
- %w[move -- --help -? -- -],
83
- subs: %w[move], flags: {}, args: %w[--help -? -- -], help: nil
84
- ],
85
- "sets mode to flagarg when waiting for a flag argument" => [
86
- %w[move crash --dry-run arg1 -f],
87
- subs: %w[move crash], args: %w[arg1], flags: { "dry-run" => true },
88
- mode: :flagarg, current_flag: "output-to-file"
89
- ],
90
- # TODO: might want behavior to be different; currently I think if --verbose is before the
91
- # subcommand it use's the main command's --verbose flag
92
- "most specific sub's flag takes precedence in case of multiple matching" => [
93
- %w[sub-with-mandatory-flag --verbose foo --mandatory abc],
94
- subs: %w[sub-with-mandatory-flag],
95
- flags: { "verbose" => "foo", "mandatory" => "abc" }
96
- ]
97
- }
18
+ examples = JSON.parse(File.read("#{__dir__}/../fixtures/vehicles-expectations.json"))
98
19
 
99
20
  examples.each do |test_name, (tokens, expectation)|
100
21
  it test_name do
101
22
  pending unless expectation
102
23
  result = described_class.run(config, tokens)
103
24
  expectation.each do |key, val|
104
- key = :subcommand_stack if key == :subs
105
- expect(result.send(key)).to eq(val)
25
+ key = 'subcommand_stack' if key == 'subs'
26
+ res = result.send(key.to_sym)
27
+ res = res.to_s if res.is_a?(Symbol)
28
+ expect(res).to eq(val)
106
29
  end
107
30
  end
108
31
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/tabry/shell_tokenizer"
4
+
5
+ describe Tabry::ShellTokenizer do
6
+ describe ".split_with_comppoint" do
7
+ it "returns the command basename, argument, and last argument" do
8
+ str = "foo/bar abc def ghi"
9
+ expect(described_class.split_with_comppoint(str, 13)).to eq(["bar", ["abc"], "def"])
10
+ end
11
+
12
+ it "returns [] as arguments and the last argument as non-nil when there is only one arg" do
13
+ expect(described_class.split_with_comppoint("foo bar", 5)).to eq(["foo", [], "bar"])
14
+ end
15
+
16
+ it "handles quotes and backslashes like a shell" do
17
+ expect(described_class.split_with_comppoint('"/foo bar/waz" a\'b \'c\\ d "ef g" "hi jk" lmn', 38)).to eq(
18
+ [
19
+ "waz",
20
+ ["ab c d", "ef g"],
21
+ "hi jk"
22
+ ]
23
+ )
24
+ end
25
+
26
+ it "supports tokens with argument (tab on command), yielding the last arg" do
27
+ expect(described_class.split_with_comppoint("abc", 2)).to eq([nil, [], "abc"])
28
+ end
29
+
30
+ it "supports empty strings" do
31
+ expect(described_class.split_with_comppoint("", 0)).to eq([nil, [], ""])
32
+ end
33
+ end
34
+ end