tabry 0.2.2 → 0.2.3

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