tabry 0.2.2 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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