yap-shell 0.6.0 → 0.7.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.travis.lock +104 -0
  3. data/bin/yap +6 -0
  4. data/bin/yap-dev +37 -0
  5. data/lib/yap.rb +29 -39
  6. data/lib/yap/addon.rb +24 -0
  7. data/lib/yap/addon/base.rb +52 -0
  8. data/lib/yap/addon/export_as.rb +12 -0
  9. data/lib/yap/addon/loader.rb +84 -0
  10. data/lib/yap/addon/path.rb +56 -0
  11. data/lib/yap/addon/rc_file.rb +21 -0
  12. data/lib/yap/addon/reference.rb +22 -0
  13. data/lib/yap/cli.rb +4 -0
  14. data/lib/yap/cli/commands.rb +6 -0
  15. data/lib/yap/cli/commands/addon.rb +14 -0
  16. data/lib/yap/cli/commands/addon/disable.rb +35 -0
  17. data/lib/yap/cli/commands/addon/enable.rb +35 -0
  18. data/lib/yap/cli/commands/addon/list.rb +37 -0
  19. data/lib/yap/cli/commands/addon/search.rb +99 -0
  20. data/lib/yap/cli/commands/generate.rb +13 -0
  21. data/lib/yap/cli/commands/generate/addon.rb +258 -0
  22. data/lib/yap/cli/commands/generate/addonrb.template +22 -0
  23. data/lib/yap/cli/commands/generate/gemspec.template +25 -0
  24. data/lib/yap/cli/commands/generate/license.template +21 -0
  25. data/lib/yap/cli/commands/generate/rakefile.template +6 -0
  26. data/lib/yap/cli/commands/generate/readme.template +40 -0
  27. data/lib/yap/cli/options.rb +162 -0
  28. data/lib/yap/cli/options/addon.rb +64 -0
  29. data/lib/yap/cli/options/addon/disable.rb +62 -0
  30. data/lib/yap/cli/options/addon/enable.rb +63 -0
  31. data/lib/yap/cli/options/addon/list.rb +65 -0
  32. data/lib/yap/cli/options/addon/search.rb +76 -0
  33. data/lib/yap/cli/options/generate.rb +59 -0
  34. data/lib/yap/cli/options/generate/addon.rb +63 -0
  35. data/lib/yap/configuration.rb +10 -3
  36. data/lib/yap/gem_helper.rb +195 -0
  37. data/lib/yap/gem_tasks.rb +6 -0
  38. data/lib/yap/shell.rb +1 -1
  39. data/lib/yap/shell/repl.rb +1 -1
  40. data/lib/yap/shell/version.rb +1 -1
  41. data/lib/yap/world.rb +45 -7
  42. data/rcfiles/yaprc +90 -10
  43. data/spec/features/addons/generating_an_addon_spec.rb +55 -0
  44. data/spec/features/addons/using_an_addon_spec.rb +182 -0
  45. data/spec/features/aliases_spec.rb +6 -6
  46. data/spec/features/grouping_spec.rb +18 -18
  47. data/spec/features/line_editing_spec.rb +9 -1
  48. data/spec/features/redirection_spec.rb +12 -3
  49. data/spec/spec_helper.rb +21 -11
  50. data/spec/support/matchers/have_printed.rb +38 -0
  51. data/spec/support/yap_spec_dsl.rb +24 -6
  52. data/yap-shell.gemspec +6 -11
  53. metadata +51 -45
  54. data/addons/history/README.md +0 -16
  55. data/addons/history/history.rb +0 -58
  56. data/addons/history_search/history_search.rb +0 -197
  57. data/addons/keyboard_macros/keyboard_macros.rb +0 -425
  58. data/addons/keyboard_macros/lib/keyboard_macros/cycle.rb +0 -38
  59. data/addons/prompt/Gemfile +0 -1
  60. data/addons/prompt/right_prompt.rb +0 -17
  61. data/addons/prompt_updates/prompt_updates.rb +0 -28
  62. data/addons/tab_completion/Gemfile +0 -0
  63. data/addons/tab_completion/lib/tab_completion/basic_completion.rb +0 -151
  64. data/addons/tab_completion/lib/tab_completion/completer.rb +0 -62
  65. data/addons/tab_completion/lib/tab_completion/custom_completion.rb +0 -33
  66. data/addons/tab_completion/lib/tab_completion/dsl_methods.rb +0 -7
  67. data/addons/tab_completion/tab_completion.rb +0 -174
  68. data/lib/tasks/addons.rake +0 -97
  69. data/lib/yap/world/addons.rb +0 -181
@@ -1,16 +0,0 @@
1
- # History Addon
2
-
3
- The History addon is provides primitive history capabilities:
4
-
5
- * Loads history from ~/.yap/history when yap is loaded
6
- * Saves history to ~/.yap/history when yap exits
7
- * Saving is an append-only operation
8
- * ~/.yap/history is in YAML format
9
-
10
- ## Shell Functions
11
-
12
- The history addon provides the `history` shell function that prints the contents of the history.
13
-
14
- ## Limitations
15
-
16
- The history addon currently does not support any kind of advanced history operations.
@@ -1,58 +0,0 @@
1
- class History < Addon
2
- attr_reader :file
3
- attr_reader :position
4
-
5
- def initialize_world(world)
6
- return unless world.configuration.use_history?
7
- @world = world
8
-
9
- @file = world.configuration.path_for('history')
10
- @position = 0
11
-
12
- load_history
13
-
14
- world.func(:history) do |args:, stdin:, stdout:, stderr:|
15
- history_length = @world.editor.history.length
16
- first_arg = args.first.to_i
17
- size = first_arg > 0 ? first_arg + 1 : history_length
18
-
19
- # start from -2 since we don't want to include the current history
20
- # command being run.
21
- stdout.puts @world.editor.history[history_length-size..-2]
22
- end
23
- end
24
-
25
- def save
26
- debug_log "saving history file=#{file.inspect}"
27
- File.open(file, "a") do |file|
28
- # Don't write the YAML header because we're going to append to the
29
- # history file, not overwrite. YAML works fine without it.
30
- unwritten_history = @world.editor.history.to_a[@position..-1]
31
- if unwritten_history.any?
32
- contents = unwritten_history
33
- .each_with_object([]) { |line, arr| arr << line unless line == arr.last }
34
- .map { |str| str.respond_to?(:without_ansi) ? str.without_ansi : str }
35
- .to_yaml
36
- .gsub(/^---.*?^/m, '')
37
- file.write contents
38
- end
39
- end
40
- end
41
-
42
- private
43
-
44
- def load_history
45
- debug_log "loading history file=#{file.inspect}"
46
- at_exit { save }
47
-
48
- if File.exists?(file) && File.readable?(file)
49
- history = YAML.load_file(file) || []
50
-
51
- # History starts at the end of the history loaded from file.
52
- @position = history.length
53
-
54
- # Rely on the builtin history for now.
55
- @world.editor.history.replace(history)
56
- end
57
- end
58
- end
@@ -1,197 +0,0 @@
1
- class HistorySearch < Addon
2
- attr_reader :editor
3
-
4
- def initialize_world(world)
5
- @editor = world.editor
6
- end
7
-
8
- def prompt_user_to_search
9
- label_text = "(reverse-search): "
10
- search_label = ::TerminalLayout::Box.new(
11
- content: label_text,
12
- style: {
13
- display: :inline,
14
- height: 1
15
- }
16
- )
17
- search_box = ::TerminalLayout::InputBox.new(
18
- content: "",
19
- style: {
20
- display: :inline,
21
- height: 1
22
- }
23
- )
24
- search_box.name = "focused-input-box"
25
-
26
- Treefell['shell'].puts "editor.content_box.children: #{editor.content_box.children.inspect}"
27
- history_search_env = editor.new_env
28
- history_search = Search.new(editor.history,
29
- line: ::RawLine::LineEditor.new(::RawLine::Line.new, sync_with: -> { search_box }),
30
- keys: history_search_env.keys,
31
- search: -> (term:, result:){
32
- # when there is no match, result will be nil, #to_s clears out the content
33
- editor.input_box.content = result.to_s
34
- },
35
- done: -> (execute:, result:){
36
- editor.content_box.children = []
37
- editor.pop_env
38
- editor.focus_input_box(editor.input_box)
39
- editor.overwrite_line(result.to_s)
40
- editor.process_line if execute
41
- }
42
- )
43
-
44
- editor.push_env history_search_env
45
- editor.push_keyboard_input_processor(history_search)
46
-
47
- editor.content_box.children = [search_label, search_box]
48
- editor.focus_input_box(search_box)
49
- end
50
-
51
- class Search
52
- attr_reader :keys
53
-
54
- def initialize(history, keys:, line:, search:, done:)
55
- @history = history
56
- @keys = keys
57
- @line = line
58
- @search_proc = search || -> {}
59
- @done_proc = done || -> {}
60
-
61
- initialize_key_bindings
62
- end
63
-
64
- def initialize_key_bindings
65
- @keys.bind(:return){ execute }
66
- @keys.bind(:ctrl_j){ accept }
67
- @keys.bind(:ctrl_r){ search_again_backward }
68
- @keys.bind(:ctrl_n){ search_again_forward }
69
- @keys.bind(:ctrl_a){ @line.move_to_beginning_of_input }
70
- @keys.bind(:ctrl_e){ @line.move_to_end_of_input }
71
- @keys.bind(:backspace) do
72
- @line.delete_left_character
73
- perform_search(type: @last_search_was)
74
- end
75
- @keys.bind(:escape){ cancel }
76
- @keys.bind(:ctrl_c){ cancel }
77
- end
78
-
79
- def read_bytes(bytes)
80
- if bytes.any?
81
- Treefell['shell'].puts "history search found bytes: #{bytes.inspect}"
82
-
83
- search_bytes = process_bytes(bytes)
84
-
85
- if search_bytes.any?
86
- Treefell['shell'].puts "history searching with bytes=#{bytes.inspect}"
87
- search_with_bytes(bytes)
88
- end
89
- end
90
- end
91
-
92
- private
93
-
94
- # given [1,2,3] first try [1,2,3]. If no key binding
95
- # matches, then try [1,2]. If no keybinding matches try [1].
96
- # Execution should flow in left to right, positional order.
97
- # This returns an array of left over bytes in the order they
98
- # appeared that did not match any keybinding
99
- def process_bytes bytes, leftover=[]
100
- if bytes.empty?
101
- leftover
102
- elsif @keys[bytes]
103
- @keys[bytes].call
104
- leftover
105
- else
106
- process_bytes(
107
- bytes[0..-2],
108
- [bytes[-1]].concat(leftover)
109
- )
110
- end
111
- end
112
-
113
- def accept
114
- @done_proc.call(execute: false, result: result)
115
- end
116
-
117
- def cancel
118
- @done_proc.call(execute: false, result: nil)
119
- end
120
-
121
- def execute
122
- @done_proc.call(execute: true, result: result)
123
- end
124
-
125
- def found_match(result:)
126
- @search_proc.call(term: @line.text, result: result)
127
- end
128
-
129
- def no_match_found
130
- @last_match_index = nil
131
- @search_proc.call(term: @line.text, result: result)
132
- end
133
-
134
- def result
135
- @history2search[@last_match_index] if @last_match_index
136
- end
137
-
138
- def search_again_backward
139
- Treefell['shell'].puts "history searching again backward"
140
- if @last_search_was == :forward
141
- @last_match_index = @history.length - @last_match_index - 1
142
- @history2search = @history.reverse
143
- end
144
- perform_search(starting_index: @last_match_index, type: :backward)
145
- end
146
-
147
- def search_again_forward
148
- Treefell['shell'].puts "history searching again forward"
149
- if @last_search_was == :backward
150
- @last_match_index = @history.length - @last_match_index - 1
151
- @history2search = @history
152
- end
153
- perform_search(starting_index: @last_match_index, type: :forward)
154
- end
155
-
156
- def search_with_bytes(bytes)
157
- part = bytes.map(&:chr).join
158
- @line.write(part.scan(/[[:print:]]/).join)
159
- @history2search = @history.reverse
160
- perform_search(type: :backward)
161
- end
162
-
163
- def perform_search(starting_index: -1, type:)
164
- if @line.text.empty?
165
- no_match_found
166
- return
167
- end
168
-
169
- # fuzzy search
170
- characters = @line.text.split('').map { |ch| Regexp.escape(ch) }
171
- fuzzy_search_regex = /#{characters.join('.*?')}/
172
-
173
- # non-fuzzy-search
174
- # fuzzy_search_regex = /#{Regexp.escape(@line.text)}/
175
-
176
- Treefell['shell'].puts "history search matching on regex=#{fuzzy_search_regex.inspect} starting_index=#{starting_index}"
177
- @last_search_was = type
178
-
179
- match = @history2search.detect.with_index do |item, i|
180
- next if i <= starting_index
181
-
182
- # Treefell['shell'].puts "history search matching #{(item + @line.text).inspect} =~ #{fuzzy_search_regex}"
183
- md = (item + @line.text).match(fuzzy_search_regex)
184
- if md && md.end(0) <= item.length
185
- # Treefell['shell'].puts "history search match #{item} at #{i}"
186
- @last_match_index = i
187
- end
188
- end
189
-
190
- if match
191
- found_match(result: match)
192
- else
193
- no_match_found
194
- end
195
- end
196
- end
197
- end
@@ -1,425 +0,0 @@
1
- class KeyboardMacros < Addon
2
- require 'keyboard_macros/cycle'
3
-
4
- module PrettyPrintKey
5
- # ppk means "pretty print key". For example, it returns \C-g if the given
6
- # byte is 7.
7
- def ppk(byte)
8
- if byte && byte.ord <= 26
9
- '\C-' + ('a'..'z').to_a[byte.ord - 1]
10
- else
11
- byte.inspect
12
- end
13
- end
14
- end
15
-
16
- include PrettyPrintKey
17
-
18
- DEFAULT_TRIGGER_KEY = :ctrl_g
19
- DEFAULT_CANCEL_KEY = " "
20
- DEFAULT_TIMEOUT_IN_MS = 500
21
-
22
- def self.load_addon
23
- @instance ||= new
24
- end
25
-
26
- attr_reader :world
27
- attr_accessor :timeout_in_ms
28
- attr_accessor :cancel_key, :trigger_key
29
- attr_accessor :cancel_on_unknown_sequences
30
-
31
- def initialize_world(world)
32
- @world = world
33
- @configurations = []
34
- @stack = []
35
- @timeout_in_ms = DEFAULT_TIMEOUT_IN_MS
36
- @cancel_key = DEFAULT_CANCEL_KEY
37
- @trigger_key = DEFAULT_TRIGGER_KEY
38
- @cancel_on_unknown_sequences = false
39
- end
40
-
41
- def cancel_key=(key)
42
- debug_log "setting default cancel_key key=#{ppk(key)}"
43
- @cancel_key = key
44
- end
45
-
46
- def cancel_on_unknown_sequences=(true_or_false)
47
- debug_log "setting default cancel_on_unknown_sequences=#{true_or_false}"
48
- @cancel_on_unknown_sequences = true_or_false
49
- end
50
-
51
- def timeout_in_ms=(milliseconds)
52
- debug_log "setting default timeout_in_ms milliseconds=#{milliseconds.inspect}"
53
- @timeout_in_ms = milliseconds
54
- end
55
-
56
- def trigger_key=(key)
57
- debug_log "setting default trigger_key key=#{ppk(key)}"
58
- @trigger_key = key
59
- end
60
-
61
- def configure(cancel_key: nil, trigger_key: nil, &blk)
62
- debug_log "configure cancel_key=#{ppk(cancel_key)} trigger_key=#{ppk(trigger_key)} block_given?=#{block_given?}"
63
-
64
- cancel_key ||= @cancel_key
65
- trigger_key ||= @trigger_key
66
-
67
- cancel_blk = lambda do
68
- world.editor.event_loop.clear @event_id
69
- cancel_processing
70
- nil
71
- end
72
-
73
- configuration = Configuration.new(
74
- keymap: world.editor.terminal.keys,
75
- trigger_key: trigger_key,
76
- cancellation: Cancellation.new(cancel_key: cancel_key, &cancel_blk),
77
- editor: world.editor,
78
- )
79
-
80
- blk.call configuration if blk
81
-
82
- world.unbind(trigger_key)
83
- world.bind(trigger_key) do
84
- debug_log "macro triggered key=#{ppk(trigger_key)}"
85
-
86
- begin
87
- @previous_result = nil
88
- @stack << OpenStruct.new(configuration: configuration)
89
- configuration.start.call if configuration.start
90
-
91
- debug_log "taking over keyboard input processing from editor"
92
- world.editor.push_keyboard_input_processor(self)
93
-
94
- wait_timeout_in_seconds = 0.1
95
- world.editor.input.wait_timeout_in_seconds = wait_timeout_in_seconds
96
- ensure
97
- queue_up_remove_input_processor(&configuration.stop)
98
- end
99
- end
100
-
101
- @configurations << configuration
102
- end
103
-
104
- def cycle(name, &cycle_thru_blk)
105
- debug_log "defining cycle name=#{name.inspect}"
106
-
107
- @cycles ||= {}
108
- if block_given?
109
- cycle = KeyboardMacros::Cycle.new(
110
- cycle_proc: cycle_thru_blk,
111
- on_cycle_proc: -> (old_value, new_value) {
112
- @world.editor.delete_n_characters(old_value.to_s.length)
113
- process_result(new_value)
114
- }
115
- )
116
- @cycles[name] = cycle
117
- else
118
- @cycles.fetch(name)
119
- end
120
- end
121
-
122
- #
123
- # InputProcessor Methods
124
- #
125
-
126
- def read_bytes(bytes)
127
- if @stack.last
128
- current_definition = @stack.last
129
- configuration = current_definition.configuration
130
- end
131
-
132
- bytes.each_with_index do |byte, i|
133
- definition = configuration[byte]
134
- if !definition
135
- cancel_processing if cancel_on_unknown_sequences
136
- break
137
- end
138
-
139
- configuration = definition.configuration
140
- configuration.start.call if configuration.start
141
- @stack << definition
142
-
143
- result = definition.process
144
-
145
- if result =~ /\n$/
146
- world.editor.write result.chomp, add_to_line_history: false
147
- world.editor.event_loop.clear @event_id if @event_id
148
- cancel_processing
149
- world.editor.newline # add_to_history
150
- world.editor.process_line
151
- break
152
- end
153
-
154
- if i == bytes.length - 1
155
- while @stack.last && @stack.last.fragment?
156
- @stack.pop
157
- end
158
- end
159
-
160
- if @event_id
161
- world.editor.event_loop.clear @event_id
162
- @event_id = queue_up_remove_input_processor
163
- end
164
-
165
- process_result(result)
166
- end
167
- end
168
-
169
- private
170
-
171
- def process_result(result)
172
- if result.is_a?(String)
173
- @world.editor.write result, add_to_line_history: false
174
- @previous_result = result
175
- end
176
- end
177
-
178
- def queue_up_remove_input_processor(&blk)
179
- return unless @timeout_in_ms
180
-
181
- event_args = {
182
- name: 'remove_input_processor',
183
- source: self,
184
- interval_in_ms: @timeout_in_ms,
185
- }
186
- @event_id = world.editor.event_loop.once(event_args) do
187
- cancel_processing
188
- end
189
- end
190
-
191
- def cancel_processing
192
- debug_log "cancel_processing"
193
- @event_id = nil
194
- @stack.reverse.each do |definition|
195
- definition.configuration.stop.call if definition.configuration.stop
196
- end
197
- @stack.clear
198
- if world.editor.keyboard_input_processor == self
199
- debug_log "giving keyboard input processing control back"
200
- world.editor.pop_keyboard_input_processor
201
-
202
- debug_log "restoring default editor input timeout"
203
- world.editor.input.restore_default_timeout
204
- end
205
- end
206
-
207
- class Cancellation
208
- attr_reader :cancel_key
209
-
210
- def initialize(cancel_key: , &blk)
211
- @cancel_key = cancel_key
212
- @blk = blk
213
- end
214
-
215
- def call
216
- @blk.call
217
- end
218
- end
219
-
220
- class Configuration
221
- include PrettyPrintKey
222
-
223
- attr_reader :cancellation, :trigger_key, :keymap
224
-
225
- def debug_log(*args)
226
- KeyboardMacros.debug_log(*args)
227
- end
228
-
229
- def initialize(cancellation: nil, editor:, keymap: {}, trigger_key: nil)
230
- @cancellation = cancellation
231
- @editor = editor
232
- @keymap = keymap
233
- @trigger_key = trigger_key
234
- @storage = {}
235
- @on_start_blk = nil
236
- @on_stop_blk = nil
237
- @cycles = {}
238
-
239
- debug_log "configuring a macro trigger_key=#{ppk(trigger_key)}"
240
-
241
- if @cancellation
242
- define @cancellation.cancel_key, -> { @cancellation.call }
243
- end
244
- end
245
-
246
- def start(&blk)
247
- @on_start_blk = blk if blk
248
- @on_start_blk
249
- end
250
-
251
- def stop(&blk)
252
- @on_stop_blk = blk if blk
253
- @on_stop_blk
254
- end
255
-
256
- def cycle(name, &cycle_thru_blk)
257
- debug_log "defining a cycle on macro name=#{name.inspect}"
258
-
259
- if block_given?
260
- cycle = KeyboardMacros::Cycle.new(
261
- cycle_proc: cycle_thru_blk,
262
- on_cycle_proc: -> (old_value, new_value) {
263
- @editor.delete_n_characters(old_value.to_s.length)
264
- }
265
- )
266
- @cycles[name] = cycle
267
- else
268
- @cycles.fetch(name)
269
- end
270
- end
271
-
272
- def fragment(sequence, result)
273
- define(sequence, result, fragment: true)
274
- end
275
-
276
- def define(sequence, result=nil, fragment: false, &blk)
277
- debug_log "defining macro sequence=#{sequence.inspect} result=#{result.inspect} fragment=#{fragment.inspect} under macro #{ppk(trigger_key)}"
278
- unless result.respond_to?(:call)
279
- string_result = result
280
- result = -> { string_result }
281
- end
282
-
283
- case sequence
284
- when String
285
- recursively_define_sequence_for_bytes(
286
- self,
287
- sequence.bytes,
288
- result,
289
- fragment: fragment,
290
- &blk
291
- )
292
- when Symbol
293
- recursively_define_sequence_for_bytes(
294
- self,
295
- @keymap.fetch(sequence){
296
- fail "Cannot bind unknown sequence #{sequence.inspect}"
297
- },
298
- result,
299
- fragment: fragment,
300
- &blk
301
- )
302
- when Regexp
303
- define_sequence_for_regex(sequence, result, fragment: fragment, &blk)
304
- else
305
- raise NotImplementedError, <<-EOT.gsub(/^\s*/, '')
306
- Don't know how to define macro for sequence: #{sequence.inspect}
307
- EOT
308
- end
309
- end
310
-
311
- def [](byte)
312
- @storage.values.detect { |definition| definition.matches?(byte) }
313
- end
314
-
315
- def []=(key, definition)
316
- @storage[key] = definition
317
- end
318
-
319
- def inspect
320
- str = @storage.map{ |k,v| "#{k}=#{v.inspect}" }.join("\n ")
321
- num_items = @storage.reduce(0) { |s, arr| s + arr.length }
322
- "<Configuration num_items=#{num_items} stored_keys=#{str}>"
323
- end
324
-
325
- private
326
-
327
- def define_sequence_for_regex(regex, result, fragment: false, &blk)
328
- @storage[regex] = Definition.new(
329
- configuration: Configuration.new(
330
- cancellation: @cancellation,
331
- keymap: @keymap,
332
- editor: @editor
333
- ),
334
- fragment: fragment,
335
- sequence: regex,
336
- result: result,
337
- &blk
338
- )
339
- end
340
-
341
- def recursively_define_sequence_for_bytes(configuration, bytes, result, fragment: false, &blk)
342
- byte, rest = bytes[0], bytes[1..-1]
343
- if rest.any?
344
- definition = if configuration[byte]
345
- configuration[byte]
346
- else
347
- Definition.new(
348
- configuration: Configuration.new(
349
- cancellation: @cancellation,
350
- keymap: @keymap,
351
- editor: @editor
352
- ),
353
- fragment: fragment,
354
- sequence: byte,
355
- result: nil
356
- )
357
- end
358
- blk.call(definition.configuration) if blk
359
- configuration[byte] = definition
360
- recursively_define_sequence_for_bytes(
361
- definition.configuration,
362
- rest,
363
- result,
364
- fragment: fragment,
365
- &blk
366
- )
367
- else
368
- definition = Definition.new(
369
- configuration: Configuration.new(
370
- keymap: @keymap,
371
- editor: @editor
372
- ),
373
- fragment: fragment,
374
- sequence: byte,
375
- result: result
376
- )
377
- configuration[byte] = definition
378
- blk.call(definition.configuration) if blk
379
- definition
380
- end
381
- end
382
- end
383
-
384
- class Definition
385
- attr_reader :configuration, :result, :sequence
386
-
387
- def initialize(configuration: nil, fragment: false, sequence:, result: nil)
388
- @fragment = fragment
389
- @configuration = configuration
390
- @sequence = sequence
391
- @result = result
392
- end
393
-
394
- def inspect
395
- "<Definition fragment=#{@fragment.inspect} sequence=#{@sequence.inspect} result=#{@result.inspect} configuration=#{@configuration.inspect}>"
396
- end
397
-
398
- def fragment?
399
- @fragment
400
- end
401
-
402
- def matches?(byte)
403
- if @sequence.is_a?(Regexp)
404
- @match_data = @sequence.match(byte.chr)
405
- else
406
- @sequence == byte
407
- end
408
- end
409
-
410
- def process
411
- if @result
412
- if @match_data
413
- if @match_data.captures.empty?
414
- @result.call(@match_data[0])
415
- else
416
- @result.call(*@match_data.captures)
417
- end
418
- else
419
- @result.call
420
- end
421
- end
422
- end
423
- end
424
-
425
- end