yap-shell 0.6.0 → 0.7.0

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