tabry 0.2.2 → 0.2.3

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: 4a76bb8b85fa94e61bf625e435711d8e3385f5aa50a87a7c410e4f252947ffea
4
- data.tar.gz: '0632896bf92ca8b57a08db3b153e32aed0da12c71d9604d7260de2fba6fc9707'
3
+ metadata.gz: 53cb186e419ee3ddbecddaf09ac32b39d93d5668c58fd81bc8d08ff16e1b168d
4
+ data.tar.gz: c5d7fa4e944df15ff727eb0a9d38e2550da6988bdcf83f2dbaa743252f3c9669
5
5
  SHA512:
6
- metadata.gz: 8420bd16657ebbe6e2f13c55a14e62fffab982b5d81f71d76c36ccdd97d36ee4a09ce7ccf1386c5b0b553681b48d7b3fdd90336fb72574d55ba426ca22200ed9
7
- data.tar.gz: 1dcd973c7608319c73ae59ab8c516e0ccb54ec9c6bf909f13235e3be238e1059e93d2ba067d99a66b8de8fb5cca9c69e2cf861b1393aecbd9076580fbbdef37c
6
+ metadata.gz: 1da3914992fd9ca409800f0183992fe6a742f0d55f04573e20c119cace1b00538294113c675a8281ff881703cbb13fb5a1802d4d164d9343dd4d5543cd9f2ca5
7
+ data.tar.gz: 6a9797aaf76219131bcb3a6faa780ebedd0cd7971ad98f91e0d9f56d3d3302f4c29b62c9c4db0025fea4a1fea9047a40e54bf55400d2de1a7837915ddb7edc06
@@ -21,6 +21,11 @@ module Tabry
21
21
  [{ "type" => "shell", "value" => cmd.to_s }]
22
22
  end
23
23
 
24
+ # maybe I should rename this ... and include... ?
25
+ def method(met_name)
26
+ [{ "type" => "method", "value" => met_name.to_s }]
27
+ end
28
+
24
29
  def file
25
30
  [{ "type" => "file" }]
26
31
  end
@@ -10,11 +10,13 @@ module Tabry
10
10
 
11
11
  def defargs(name, &blk)
12
12
  name = name.to_s.gsub(/^@/, "")
13
+ name = name.gsub("_", "-") if _opts[:names_underscores_to_dashes]
13
14
  _set_hash :arg_includes, name, _build(SubBuilder, &blk)
14
15
  end
15
16
 
16
17
  def defopts(name, &blk)
17
18
  name = name.to_s.gsub(/^@/, "")
19
+ name = name.gsub("_", "-") if _opts[:names_underscores_to_dashes]
18
20
  _set_hash :option_includes, name, _build(ArgOrFlagBuilder, &blk)["options"]
19
21
  end
20
22
 
data/lib/tabry/machine.rb CHANGED
@@ -20,7 +20,7 @@ module Tabry
20
20
 
21
21
  def initialize(config)
22
22
  @config = config
23
- @state = State.new(mode: :subcommand, subcommand_stack: [], args: [], flags: {})
23
+ @state = State.new(mode: :subcommand, subcommand_stack: [], args: [], flags: {}, help: false, dashdash: false)
24
24
  end
25
25
 
26
26
  def run(tokens)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "option_base"
4
+
5
+ module Tabry
6
+ module Models
7
+ class MethodOption < OptionBase
8
+ # TODO: Handled upstream for now, could change later.
9
+ def options(_token)
10
+ [value.to_sym]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "config_error"
4
- require_relative "const_option"
5
- require_relative "shell_option"
6
- require_relative "include_option"
7
- require_relative "file_option"
8
- require_relative "dir_option"
4
+ %w[const shell include file dir method].each do |type|
5
+ require_relative "#{type}_option"
6
+ end
9
7
 
10
8
  module Tabry
11
9
  module Models
@@ -18,6 +16,8 @@ module Tabry
18
16
  ConstOption.new(**args)
19
17
  when "shell"
20
18
  ShellOption.new(**args)
19
+ when "method"
20
+ MethodOption.new(**args)
21
21
  when "include"
22
22
  IncludeOption.new(**args)
23
23
  when "file"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../cli/arg_proxy" # TODO: shared code now, probably move out of cli dir
4
+ require_relative "../cli/flag_proxy" # TODO: shared code now, probably move out of cli dir
5
+
6
+ module Tabry
7
+ module Replty
8
+ class Base
9
+ attr_reader :internals, :args, :flags
10
+
11
+ def internals=(internals)
12
+ @internals = internals
13
+ @args = ::Tabry::CLI::ArgProxy.new(internals.state.args, internals.result.named_args)
14
+ @flags = ::Tabry::CLI::FlagProxy.new(internals.state.flags)
15
+ end
16
+
17
+ # NOTE: internal functions (besides internal/args/flags) start with "tabry_"; reconsider scheme
18
+ def tabry_prompt
19
+ "> "
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "readline"
4
+ require_relative "../cli/internals" # TODO: bring out of cli if shared code now, probably
5
+ require_relative "../shell_tokenizer"
6
+ require_relative "../runner"
7
+
8
+ module Tabry
9
+ module Replty
10
+ class Builder
11
+ attr_reader :runner, :repl
12
+
13
+ # TODO: implement before_action, after_action, sub-REPLs; share code in run_result with CLI::Builder
14
+
15
+ def initialize(config, repl)
16
+ @runner = Tabry::Runner.new(config: config)
17
+ @repl = repl
18
+ end
19
+
20
+ def readline
21
+ Readline.readline(repl.tabry_prompt, true)
22
+ rescue Interrupt
23
+ puts "^C"
24
+ retry
25
+ end
26
+
27
+ def load_history(file)
28
+ return unless file
29
+
30
+ File.open(file) do |f|
31
+ f.each do |l|
32
+ Readline::HISTORY.push l.chomp
33
+ end
34
+ end
35
+ rescue Errno::ENOENT # rubocop:disable Lint/SuppressedException
36
+ end
37
+
38
+ def save_history(file, max_size = 10000)
39
+ return unless file
40
+
41
+ history = Readline::HISTORY.each
42
+
43
+ # Skip over excess elements:
44
+ (Readline::HISTORY.length - max_size).times do
45
+ history.next
46
+ end
47
+
48
+ File.open(file, "w") do |f|
49
+ loop { f.puts history.next }
50
+ end
51
+ end
52
+
53
+ def run(history_file: nil, tokenizer: ShellTokenizer)
54
+ history_file = history_file&.gsub(%r{^~/}, "#{Dir.home}/")
55
+ load_history(history_file)
56
+
57
+ Readline.completion_proc = proc do
58
+ cmd, args, last_arg = tokenizer.split_with_comppoint(Readline.line_buffer, Readline.point)
59
+ options = runner.options([cmd, *args].compact, last_arg)
60
+ options.map do |opt|
61
+ # if opt is a symbol, it's a "opts method" option type -- where a REPL method returns the options.
62
+ # TODO: a bit weird since the REPL's "args" and "flags" are wrong/old when that completion method is run
63
+ # also filter to start_with? is more appropriate in tabry, not here, but whatever
64
+ opt.is_a?(Symbol) ? repl.send(opt)&.select { |x| x.start_with?(last_arg) } : opt
65
+ end.flatten
66
+ end
67
+
68
+ while (cmdline = readline)
69
+ raw_args = tokenizer.split(cmdline)
70
+ next if raw_args.empty?
71
+
72
+ result = runner.parse(raw_args)
73
+ # TODO: usage has "myrepl" (command name) in; also, "got 1 args" is confusing when command doesn't exist
74
+ if result.help?
75
+ puts result.usage
76
+ elsif result.invalid_usage_reason
77
+ puts "Invalid usage: #{result.invalid_usage_reason}"
78
+ puts
79
+ puts result.usage
80
+ else
81
+ run_result(raw_args, result)
82
+ end
83
+ end
84
+
85
+ save_history(history_file)
86
+ end
87
+
88
+ def run_result(raw_args, result)
89
+ met = result.state.subcommand_stack.join("__").gsub("-", "_")
90
+ repl.internals = ::Tabry::CLI::Internals.new(
91
+ runner: runner, raw_args: raw_args,
92
+ state: result.state, met: met, result: result
93
+ )
94
+ catch(:tabry_replty_command_exit) do
95
+ repl.send(met.to_sym)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+
5
+ # Code to tokenize whole command lines into command and argument
6
+ # This may be adjustable in some cases
7
+ module Tabry
8
+ module ShellTokenizer
9
+ module_function
10
+
11
+ COMP_POINT_SENTINEL = "\uFFFF"
12
+
13
+ # Use Shellwords.split() to split a command line + comp point (index of the
14
+ # cursor in the command line) into the args up to the current token plus
15
+ # current token
16
+ #
17
+ # Returns [cmd_name, args, last_arg]
18
+ # cmd_name is the basename of the command run
19
+ #
20
+ def split_with_comppoint(cmd_line, comp_point)
21
+ # TODO: in weird scenarios this acts weird: namely, special shell operators like <(ls), $$((1 + 1))
22
+ # Also it crashed on unbalanced quotes, like: foo "bar<TAB>
23
+ # however, this will handle the common scenarios of escaping with quotes, single quotes, and backslashes
24
+ # Split up args and put the argument that comp_point is in in the `last_arg` variable.
25
+ # Just cutting off everything after comp_point might have worked, although
26
+ # maybe we wanted the whole arg? Not sure this is the best.
27
+ cmd_line = cmd_line.dup
28
+ cmd_line[comp_point.to_i...comp_point.to_i] = COMP_POINT_SENTINEL
29
+ all_tokens = Shellwords.split(cmd_line)
30
+
31
+ # ignore all tokens after the one with the sentinel, then replace the COMP_POINT_SENTINEL.
32
+ # the last token is now the token which the cursor is on (the entire token, not just before the cursor)
33
+ last_arg_index = all_tokens.index { |arg| arg.include?(COMP_POINT_SENTINEL) }
34
+ all_tokens = all_tokens[0..last_arg_index]
35
+ all_tokens.last.gsub! COMP_POINT_SENTINEL, ""
36
+
37
+ # take last_arg first -- will always be non-null (it will be empty if input string is empty).
38
+ last_arg = all_tokens.pop
39
+ cmd = all_tokens.shift
40
+
41
+ cmd_name = cmd&.gsub(%r{.*/}, "")
42
+
43
+ [cmd_name, all_tokens, last_arg]
44
+ end
45
+
46
+ def split(str)
47
+ Shellwords.split(str)
48
+ end
49
+ end
50
+ end
@@ -4,14 +4,14 @@ require "shellwords"
4
4
  require "yaml"
5
5
  require_relative "../../util"
6
6
  require_relative "../../runner"
7
- require_relative "../../shell_splitter"
7
+ require_relative "../../shell_tokenizer"
8
8
 
9
9
  # Bash-specific entrypoint, taking COMP_WORDS and COMP_CWORDS and returning possible options
10
10
  module Tabry
11
11
  module Bash
12
12
  module Wrapper
13
13
  def self.run(cmd_line, comp_point, config: nil)
14
- cmd_name, args, last_arg = Tabry::ShellSplitter.split(cmd_line, comp_point)
14
+ cmd_name, args, last_arg = Tabry::ShellTokenizer.split_with_comppoint(cmd_line, comp_point)
15
15
  opts = Tabry::Runner.new(config: config || cmd_name).options(args, last_arg)
16
16
 
17
17
  if Tabry::Util.debug?
@@ -29,7 +29,7 @@ function __fish_tabry_check_only_args
29
29
  set args (echo "$result"|sed 's/ .*//')
30
30
  set specials (echo "$result"|grep -o ' file')
31
31
 
32
- # https://github.com/fish-shell/fish-shell/issues/5186#issuecomment-421244106
32
+ # https://github.com/fish-shell/fish-shell/issues/5186#issuecomment-421244106 (the random "x")
33
33
  if test "x$args" != "x" -a "$specials" != " file"
34
34
  # echo "confirming only args: [$result,$args,$specials]" 1>&2
35
35
  return 0;
@@ -45,7 +45,8 @@ function __fish_tabry_check_only_file
45
45
 
46
46
  set args (echo "$result"|sed 's/ .*//')
47
47
 
48
- if test "x$args" = "x" -a (string match -ra ' file' $result)
48
+ # The '--' arg is needed in case $result contains flag-like strings
49
+ if test "x$args" = "x" -a (string match -ra -- ' file' $result)
49
50
  # echo "confirming only file" 1>&2
50
51
  return 0;
51
52
  else
@@ -59,7 +60,8 @@ function __fish_tabry_check_args_and_file
59
60
 
60
61
  set args (echo "$result"|sed 's/ .*//')
61
62
 
62
- if test "x$args" != "x" -a (string match -ra ' file' $result)
63
+ # The '--' arg is needed in case $result contains flag-like strings
64
+ if test "x$args" != "x" -a (string match -ra -- ' file' $result)
63
65
  # echo "confirming args and file" 1>&2
64
66
  return 0;
65
67
  else
@@ -73,7 +75,8 @@ function __fish_tabry_completions
73
75
 
74
76
  set args (echo "$result"|sed 's/ .*//')
75
77
 
76
- set args_parsed (string split ' ' $args)
78
+ # The '--' arg is needed in case $result contains flag-like strings
79
+ set args_parsed (string split -- ' ' $args)
77
80
 
78
81
  if test "x$args" = "x"
79
82
  # Don't offer anything if we don't have any completions
@@ -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
+ }