yap-shell 0.1.1 → 0.3.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/Gemfile +5 -0
  4. data/WISHLIST.md +14 -0
  5. data/addons/history/Gemfile +2 -0
  6. data/addons/history/history.rb +101 -0
  7. data/addons/history/lib/history/buffer.rb +204 -0
  8. data/addons/history/lib/history/events.rb +13 -0
  9. data/addons/keyboard_macros/keyboard_macros.rb +295 -0
  10. data/addons/prompt/Gemfile +1 -0
  11. data/addons/prompt/right_prompt.rb +17 -0
  12. data/addons/prompt_updates/prompt_updates.rb +28 -0
  13. data/addons/tab_completion/Gemfile +0 -0
  14. data/addons/tab_completion/lib/tab_completion/completer.rb +62 -0
  15. data/addons/tab_completion/lib/tab_completion/custom_completion.rb +33 -0
  16. data/addons/tab_completion/lib/tab_completion/dsl_methods.rb +7 -0
  17. data/addons/tab_completion/lib/tab_completion/file_completion.rb +75 -0
  18. data/addons/tab_completion/tab_completion.rb +157 -0
  19. data/bin/yap +13 -4
  20. data/lib/tasks/addons.rake +51 -0
  21. data/lib/yap.rb +4 -55
  22. data/lib/yap/shell.rb +51 -10
  23. data/lib/yap/shell/builtins.rb +2 -2
  24. data/lib/yap/shell/builtins/alias.rb +2 -2
  25. data/lib/yap/shell/builtins/cd.rb +9 -11
  26. data/lib/yap/shell/builtins/env.rb +11 -0
  27. data/lib/yap/shell/commands.rb +29 -18
  28. data/lib/yap/shell/evaluation.rb +185 -68
  29. data/lib/yap/shell/evaluation/shell_expansions.rb +85 -0
  30. data/lib/yap/shell/event_emitter.rb +18 -0
  31. data/lib/yap/shell/execution/builtin_command_execution.rb +1 -1
  32. data/lib/yap/shell/execution/command_execution.rb +3 -3
  33. data/lib/yap/shell/execution/context.rb +32 -9
  34. data/lib/yap/shell/execution/file_system_command_execution.rb +12 -7
  35. data/lib/yap/shell/execution/ruby_command_execution.rb +6 -6
  36. data/lib/yap/shell/execution/shell_command_execution.rb +17 -2
  37. data/lib/yap/shell/prompt.rb +21 -0
  38. data/lib/yap/shell/repl.rb +179 -18
  39. data/lib/yap/shell/version.rb +1 -1
  40. data/lib/yap/world.rb +149 -15
  41. data/lib/yap/world/addons.rb +135 -0
  42. data/rcfiles/.yaprc +240 -10
  43. data/test.rb +206 -0
  44. data/update-rawline.sh +6 -0
  45. data/yap-shell.gemspec +11 -3
  46. metadata +101 -10
  47. data/addons/history.rb +0 -171
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c4e3c7118e864187750ddf674697554b760e3853
4
- data.tar.gz: 8dc3b7e693b4cd35bd48935135cb9b8c37b13bd8
3
+ metadata.gz: 246c3bc588c78cfde392f379b5ca086e6aac79e0
4
+ data.tar.gz: f1c54403abc8e95a96f255582b6b3af4fcb511ba
5
5
  SHA512:
6
- metadata.gz: cfa9336a5d7756b646fe4de4f75830d9162a729351f2f710d4f18a74bde8c3a5708785becb519e32a9513724ebdf5591ffa4f65872dee3a52c02426fd685487d
7
- data.tar.gz: dce637e76f850540d3aaad4352bad5254abe9d8a7c4ec38b4ef819bb1babd39f9304b3d54e06999717c85b33e94cfd4a79e99eeb8932a8508efad0d0ddc4e0ce
6
+ metadata.gz: 61414d9cc1b15a58661bca3aac9911798fe6b8aabc99e4d03732109c29d4c2fa2d9928f2c43d0cadfb736a45f4ca056c41657226d654c3b04d2dc9ce14f1ae35
7
+ data.tar.gz: a3999e8f5fcbcaab03450309ecfc1a7942d3bf2f072a75862e96f203a5b9c3b89ee31ffde11ba3eedd59780a1f75a519107f04b9aa816b48a16cceef6b510eaa
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in yap.gemspec
4
4
  gemspec
5
+ # gem 'yap-shell-parser', path: "../yap-shell-parser"
6
+ # gem 'yap-shell-parser', git: 'git@github.com:zdennis/yap-shell-parser.git'
7
+ # gem "rawline", git: 'git@github.com:zdennis/rawline.git', branch: 'issues/input-issues'
8
+ # gem "rawline", path: '/Users/zdennis/source/opensource_projects/rawline'
9
+ # gem "pry-byebug"
data/WISHLIST.md CHANGED
@@ -1,3 +1,7 @@
1
+ CLI
2
+
3
+ * yap install right-prompt
4
+
1
5
 
2
6
  * need to add custom tab completion for aliases, builtins, shell commands, and file system commands
3
7
  * support user-specified gems to install (so they're available for rc files)
@@ -38,3 +42,13 @@ Others requests.
38
42
  * @dylanized: themeable
39
43
  * @dylanized: browser-based
40
44
  * @dylanized: bookmarks
45
+
46
+
47
+ ### Tab completion
48
+ * sort completions for printing
49
+ * print completion in groups?
50
+ * match on command, command with arguments, arguments, env var args
51
+ * supply the completion text
52
+ * supply the descriptive text
53
+ * alias one completion for another
54
+ * cache completions? invalidate cached completions
@@ -0,0 +1,2 @@
1
+ gem "chronic", "~> 0.10.2"
2
+ gem "term-ansicolor", "~> 1.3.2"
@@ -0,0 +1,101 @@
1
+ require 'forwardable'
2
+ require 'term/ansicolor'
3
+ require 'ostruct'
4
+
5
+ class History < Addon
6
+ require 'history/buffer'
7
+ require 'history/events'
8
+
9
+ Color = Object.extend Term::ANSIColor
10
+
11
+ class << self
12
+ attr_accessor :history_item_formatter, :ignore_history_item
13
+ end
14
+
15
+ self.ignore_history_item = ->(item:) do
16
+ item.command == "exit"
17
+ end
18
+
19
+ self.history_item_formatter = ->(item:, options:{}) do
20
+ if item.duration
21
+ sprintf(
22
+ "%#{options[:max_position_width]}d %-s %s",
23
+ item.position,
24
+ item.command,
25
+ Color.negative(Color.intense_black(item.duration))
26
+ )
27
+ else
28
+ sprintf("%#{options[:max_position_width]}d %-s", item.position, item.command)
29
+ end
30
+ end
31
+
32
+ def initialize_world(world)
33
+ @world = world
34
+ load_history
35
+
36
+ world.editor.bind(:ctrl_h) { show_history(@world.editor) }
37
+
38
+ world.func(:history) do |args:, stdin:, stdout:, stderr:|
39
+ first_arg = args.first
40
+ case first_arg
41
+ when String
42
+ if first_arg.start_with?("/")
43
+ regex = first_arg.gsub(/(^\|\/$)/, '')
44
+ ignore_history_item = ->(item:, options:{}) do
45
+ item.command !~ /#{regex}/
46
+ end
47
+ else
48
+ ignore_history_item = ->(item:, options:{}) do
49
+ item.command !~ /#{first_arg}/
50
+ end
51
+ end
52
+ show_history(world.editor, redraw_prompt:false, ignore_history_item:ignore_history_item)
53
+ else
54
+ show_history(world.editor, redraw_prompt:false)
55
+ end
56
+ end
57
+ end
58
+
59
+ def show_history(editor, redraw_prompt:true, ignore_history_item:nil, history_item_formatter:nil)
60
+ editor.puts @history
61
+ end
62
+
63
+ def executing(command:, started_at:)
64
+ # raise "Cannot acknowledge execution beginning of a command when no group has been started!" unless history.last
65
+ end
66
+
67
+ def executed(command:, stopped_at:)
68
+ # raise "Cannot complete execution of a command when no command has been started!" unless history.last
69
+ end
70
+
71
+ def save
72
+ File.open(history_file, "a") do |file|
73
+ # Don't write the YAML header because we're going to append to the
74
+ # history file, not overwrite. YAML works fine without it.
75
+ file.write @world.editor.history.to_yaml(@history_start_position..-1).gsub(/^---.*/, '')
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def history
82
+ @history
83
+ end
84
+
85
+ def history_file
86
+ @history_file ||= File.expand_path('~') + '/.yap-history'
87
+ end
88
+
89
+ def load_history
90
+ @world.editor.history = @history = History::Buffer.new(Float::INFINITY)
91
+ @history_start_position = 0
92
+
93
+ at_exit { save }
94
+
95
+ return unless File.exists?(history_file) && File.readable?(history_file)
96
+
97
+ history_elements = YAML.load_file(history_file) || []
98
+ @history_start_position = history_elements.length
99
+ @world.editor.history.replace(history_elements)
100
+ end
101
+ end
@@ -0,0 +1,204 @@
1
+ class History
2
+ class Buffer < Array
3
+ attr_reader :position, :size
4
+ attr_accessor :duplicates, :exclude, :cycle
5
+
6
+ #
7
+ # Create an instance of History::Buffer.
8
+ # This method takes an optional block used to override the
9
+ # following instance attributes:
10
+ # * <tt>@duplicates</tt> - whether or not duplicate items will be stored in the buffer.
11
+ # * <tt>@exclude</tt> - a Proc object defining exclusion rules to prevent items from being added to the buffer.
12
+ # * <tt>@cycle</tt> - Whether or not the buffer is cyclic.
13
+ #
14
+ def initialize(size)
15
+ @duplicates = true
16
+ @exclude = lambda{|a|}
17
+ @cycle = false
18
+ yield self if block_given?
19
+ @size = size
20
+ @position = nil
21
+ end
22
+
23
+ def supports_partial_text_matching?
24
+ true
25
+ end
26
+
27
+ def supports_matching_text?
28
+ true
29
+ end
30
+
31
+ #
32
+ # Clears the current position on the history object. Useful when deciding
33
+ # to cancel/reset history navigation.
34
+ #
35
+ def clear_position
36
+ @position = nil
37
+ end
38
+
39
+ def searching?
40
+ !!@position
41
+ end
42
+
43
+ #
44
+ # Resize the buffer, resetting <tt>@position</tt> to nil.
45
+ #
46
+ def resize(new_size)
47
+ if new_size < @size
48
+ @size-new_size.times { pop }
49
+ end
50
+ @size = new_size
51
+ @position = nil
52
+ end
53
+
54
+ #
55
+ # Clear the content of the buffer and reset <tt>@position</tt> to nil.
56
+ #
57
+ def empty
58
+ @position = nil
59
+ clear
60
+ end
61
+
62
+ #
63
+ # Retrieve a copy of the element at <tt>@position</tt>.
64
+ #
65
+ def get
66
+ return nil unless length > 0
67
+ return nil unless @position
68
+ at(@position).dup
69
+ end
70
+
71
+ #
72
+ # Return true if <tt>@position</tt> is at the end of the buffer.
73
+ #
74
+ def end?
75
+ @position == length-1
76
+ end
77
+
78
+ #
79
+ # Return true if <tt>@position</tt> is at the start of the buffer.
80
+ #
81
+ def start?
82
+ @position == 0
83
+ end
84
+
85
+ #
86
+ # Decrement <tt>@position</tt>. By default the history will become
87
+ # positioned at the previous item.
88
+ #
89
+ def back(matching_text: nil)
90
+ return nil unless length > 0
91
+ @position = search_back(matching_text: matching_text) || @position
92
+ end
93
+
94
+ #
95
+ # Increment <tt>@position</tt>. By default the history will become
96
+ # positioned at the next item.
97
+ #
98
+ def forward(matching_text: nil)
99
+ return nil unless length > 0
100
+ @position = search_forward(matching_text: matching_text) || @position
101
+ end
102
+
103
+ #
104
+ # Add a new item to the buffer.
105
+ #
106
+ def push(item)
107
+ item = item.without_ansi if item.respond_to?(:without_ansi)
108
+
109
+ if !@duplicates && self[-1] == item
110
+ # skip adding this line
111
+ return
112
+ end
113
+
114
+ unless @exclude.call(item)
115
+ # Remove the oldest element if size is exceeded
116
+ if @size <= length
117
+ reverse!.pop
118
+ reverse!
119
+ end
120
+ # Add the new item and reset the position
121
+ super(item)
122
+ @position = nil
123
+ end
124
+ end
125
+
126
+ alias << push
127
+
128
+ def to_yaml(range=(0..-1))
129
+ self[range].map do |element|
130
+ element.kind_of?(String) ? element : element.to_h
131
+ end.to_yaml
132
+ end
133
+
134
+ private
135
+
136
+ def search_back(matching_text:)
137
+ if !matching_text
138
+ matching_text = ""
139
+ elsif matching_text.respond_to?(:without_ansi)
140
+ matching_text = matching_text.without_ansi
141
+ end
142
+
143
+ command_history = self
144
+ upto_index = (position || length) - 1
145
+ current = get
146
+
147
+ # $z.puts
148
+ # $z.puts <<-EOS.gsub(/^\s*\|/, '')
149
+ # |Search backward:
150
+ # | current:#{current.inspect}
151
+ # | history: #{command_history.inspect}
152
+ # | history position: #{position}
153
+ # | matching_text: #{matching_text.inspect}
154
+ # | upto_index: #{upto_index}
155
+ # | snapshot: #{command_history[0..upto_index].reverse.inspect}
156
+ # EOS
157
+
158
+ return position unless upto_index >= 0
159
+
160
+ snapshot = command_history[0..upto_index].reverse
161
+ no_match = nil
162
+
163
+ position = snapshot.each_with_index.reduce(no_match) do |no_match, (text, i)|
164
+ # $z.print " - matching #{text.inspect} =~ /^#{matching_text.to_s}/ && #{current} != #{text} : "
165
+ if text =~ /^#{Regexp.escape(matching_text)}/ && current != text
166
+ # $z.puts " match #{i}, returning position #{snapshot.length - (i + 1)}"
167
+
168
+ # convert to non-reversed indexing
169
+ position = snapshot.length - (i + 1)
170
+ break position
171
+ else
172
+ # $z.puts " no match."
173
+ no_match
174
+ end
175
+ end
176
+ end
177
+
178
+ def search_forward(matching_text:)
179
+ if !matching_text
180
+ matching_text = ""
181
+ elsif matching_text.respond_to?(:without_ansi)
182
+ matching_text = matching_text.without_ansi
183
+ end
184
+
185
+ command_history = self
186
+ return nil unless position
187
+
188
+ start_index = position + 1
189
+ snapshot = command_history[start_index..-1].dup
190
+ no_match = nil
191
+ current = get
192
+
193
+ position = snapshot.each_with_index.reduce(no_match) do |no_match, (text, i)|
194
+ if text =~ /^#{Regexp.escape(matching_text.to_s)}/ && current != text
195
+ position = start_index + i
196
+ break position
197
+ else
198
+ no_match
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ end
@@ -0,0 +1,13 @@
1
+ Yap::Shell::Execution::Context.on(:before_statements_execute) do |world|
2
+ end
3
+
4
+ Yap::Shell::Execution::Context.on(:after_statements_execute) do |world|
5
+ end
6
+
7
+ Yap::Shell::Execution::Context.on(:before_execute) do |world, command:|
8
+ # world[:history].executing command:command.str, started_at:Time.now
9
+ end
10
+
11
+ Yap::Shell::Execution::Context.on(:after_execute) do |world, command:, result:|
12
+ # world[:history].executed command:command.str, stopped_at:Time.now
13
+ end
@@ -0,0 +1,295 @@
1
+ class KeyboardMacros < Addon
2
+ DEFAULT_TRIGGER_KEY = :ctrl_g
3
+ DEFAULT_CANCEL_KEY = " "
4
+ DEFAULT_TIMEOUT_IN_MS = 500
5
+
6
+ def self.load_addon
7
+ @instance ||= new
8
+ end
9
+
10
+ attr_reader :world
11
+ attr_accessor :timeout_in_ms
12
+ attr_accessor :cancel_key, :trigger_key
13
+ attr_accessor :cancel_on_unknown_sequences
14
+
15
+ def initialize_world(world)
16
+ @world = world
17
+ @configurations = []
18
+ @stack = []
19
+ @timeout_in_ms = DEFAULT_TIMEOUT_IN_MS
20
+ @cancel_key = DEFAULT_CANCEL_KEY
21
+ @trigger_key = DEFAULT_TRIGGER_KEY
22
+ @cancel_on_unknown_sequences = false
23
+ end
24
+
25
+ def configure(cancel_key: nil, trigger_key: nil, &blk)
26
+ cancel_key ||= @cancel_key
27
+ trigger_key ||= @trigger_key
28
+
29
+ cancel_blk = lambda do
30
+ world.editor.event_loop.clear @event_id
31
+ cancel_processing
32
+ nil
33
+ end
34
+
35
+ configuration = Configuration.new(
36
+ keymap: world.editor.terminal.keys,
37
+ trigger_key: trigger_key,
38
+ cancellation: Cancellation.new(cancel_key: cancel_key, &cancel_blk)
39
+ )
40
+
41
+ blk.call configuration if blk
42
+
43
+ world.unbind(trigger_key)
44
+ world.bind(trigger_key) do
45
+ begin
46
+ @stack << configuration
47
+ configuration.start.call if configuration.start
48
+ world.editor.keyboard_input_processors.push(self)
49
+ world.editor.input.wait_timeout_in_seconds = 0.1
50
+ ensure
51
+ queue_up_remove_input_processor(&configuration.stop)
52
+ end
53
+ end
54
+
55
+ @configurations << configuration
56
+ end
57
+
58
+ #
59
+ # InputProcessor Methods
60
+ #
61
+
62
+ def read_bytes(bytes)
63
+ configuration = @stack.last
64
+ bytes.each do |byte|
65
+ definition = configuration[byte]
66
+ if !definition
67
+ cancel_processing if cancel_on_unknown_sequences
68
+ break
69
+ end
70
+ configuration = definition.configuration
71
+ if configuration
72
+ configuration.start.call if configuration.start
73
+ @stack << configuration
74
+ end
75
+
76
+ result = definition.process
77
+
78
+ if result =~ /\n$/
79
+ world.editor.write result.chomp
80
+ world.editor.event_loop.clear @event_id if @event_id
81
+ cancel_processing
82
+ world.editor.newline # add_to_history
83
+ world.editor.process_line
84
+ break
85
+ end
86
+
87
+ @stack.pop if definition.fragment?
88
+
89
+ if @event_id
90
+ world.editor.event_loop.clear @event_id
91
+ @event_id = queue_up_remove_input_processor
92
+ end
93
+ world.editor.write result if result.is_a?(String)
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def queue_up_remove_input_processor(&blk)
100
+ return unless @timeout_in_ms
101
+
102
+ event_args = {
103
+ name: 'remove_input_processor',
104
+ source: self,
105
+ interval_in_ms: @timeout_in_ms,
106
+ }
107
+ @event_id = world.editor.event_loop.once(event_args) do
108
+ cancel_processing
109
+ end
110
+ end
111
+
112
+ def cancel_processing
113
+ @event_id = nil
114
+ @stack.reverse.each do |configuration|
115
+ configuration.stop.call if configuration.stop
116
+ end
117
+ @stack.clear
118
+ if world.editor.keyboard_input_processors.last == self
119
+ world.editor.keyboard_input_processors.pop
120
+ world.editor.input.restore_default_timeout
121
+ end
122
+ end
123
+
124
+ class Cancellation
125
+ attr_reader :cancel_key
126
+
127
+ def initialize(cancel_key: , &blk)
128
+ @cancel_key = cancel_key
129
+ @blk = blk
130
+ end
131
+
132
+ def call
133
+ @blk.call
134
+ end
135
+ end
136
+
137
+ class Configuration
138
+ attr_reader :cancellation, :trigger_key, :keymap
139
+
140
+ def initialize(cancellation: nil, keymap: {}, trigger_key: nil)
141
+ @cancellation = cancellation
142
+ @keymap = keymap
143
+ @trigger_key = trigger_key
144
+ @storage = {}
145
+ @on_start_blk = nil
146
+ @on_stop_blk = nil
147
+
148
+ if @cancellation
149
+ define @cancellation.cancel_key, -> { @cancellation.call }
150
+ end
151
+ end
152
+
153
+ def start(&blk)
154
+ @on_start_blk = blk if blk
155
+ @on_start_blk
156
+ end
157
+
158
+ def stop(&blk)
159
+ @on_stop_blk = blk if blk
160
+ @on_stop_blk
161
+ end
162
+
163
+ def fragment(sequence, result)
164
+ define(sequence, result).tap do |definition|
165
+ definition.fragment!
166
+ end
167
+ end
168
+
169
+ def define(sequence, result, &blk)
170
+ unless result.respond_to?(:call)
171
+ string_result = result
172
+ result = -> { string_result }
173
+ end
174
+
175
+ case sequence
176
+ when String
177
+ recursively_define_sequence_for_bytes(
178
+ self,
179
+ sequence.bytes,
180
+ result,
181
+ &blk
182
+ )
183
+ when Symbol
184
+ recursively_define_sequence_for_bytes(
185
+ self,
186
+ @keymap.fetch(sequence){
187
+ fail "Cannot bind unknown sequence #{sequence.inspect}"
188
+ },
189
+ result,
190
+ &blk
191
+ )
192
+ when Regexp
193
+ define_sequence_for_regex(sequence, result, &blk)
194
+ else
195
+ raise NotImplementedError, <<-EOT.gsub(/^\s*/, '')
196
+ Don't know how to define macro for sequence: #{sequence.inspect}
197
+ EOT
198
+ end
199
+ end
200
+
201
+ def [](byte)
202
+ @storage.values.detect { |definition| definition.matches?(byte) }
203
+ end
204
+
205
+ def []=(key, definition)
206
+ @storage[key] = definition
207
+ end
208
+
209
+ private
210
+
211
+ def define_sequence_for_regex(regex, result, &blk)
212
+ @storage[regex] = Definition.new(
213
+ configuration: Configuration.new(
214
+ cancellation: @cancellation,
215
+ keymap: @keymap
216
+ ),
217
+ sequence: regex,
218
+ result: result,
219
+ &blk
220
+ )
221
+ end
222
+
223
+ def recursively_define_sequence_for_bytes(configuration, bytes, result, &blk)
224
+ byte, rest = bytes[0], bytes[1..-1]
225
+ if rest.any?
226
+ definition = Definition.new(
227
+ configuration: Configuration.new(
228
+ cancellation: @cancellation,
229
+ keymap: @keymap
230
+ ),
231
+ sequence: byte,
232
+ result: nil,
233
+ &blk
234
+ )
235
+ configuration[byte] = definition
236
+ recursively_define_sequence_for_bytes(
237
+ definition.configuration,
238
+ rest,
239
+ result,
240
+ &blk
241
+ )
242
+ else
243
+ configuration[byte] = Definition.new(
244
+ configuration: Configuration.new(keymap: @keymap),
245
+ sequence: byte,
246
+ result: result,
247
+ &blk
248
+ )
249
+ end
250
+ end
251
+ end
252
+
253
+ class Definition
254
+ attr_reader :bytes, :configuration, :result, :sequence
255
+
256
+ def initialize(configuration: nil, sequence:, result: nil, &blk)
257
+ @fragment = false
258
+ @configuration = configuration
259
+ @sequence = sequence
260
+ @result = result
261
+ blk.call(@configuration) if blk
262
+ end
263
+
264
+ def fragment?
265
+ @fragment
266
+ end
267
+
268
+ def fragment!
269
+ @fragment = true
270
+ end
271
+
272
+ def matches?(byte)
273
+ if @sequence.is_a?(Regexp)
274
+ @match_data = @sequence.match(byte.chr)
275
+ else
276
+ @sequence == byte
277
+ end
278
+ end
279
+
280
+ def process
281
+ if @result
282
+ if @match_data
283
+ if @match_data.captures.empty?
284
+ @result.call(@match_data[0])
285
+ else
286
+ @result.call(*@match_data.captures)
287
+ end
288
+ else
289
+ @result.call
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ end