reline 0.3.9 → 0.6.1

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.
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.3.9'
2
+ VERSION = '0.6.1'
3
3
  end
data/lib/reline.rb CHANGED
@@ -6,45 +6,36 @@ require 'reline/key_actor'
6
6
  require 'reline/key_stroke'
7
7
  require 'reline/line_editor'
8
8
  require 'reline/history'
9
- require 'reline/terminfo'
9
+ require 'reline/io'
10
+ require 'reline/face'
10
11
  require 'rbconfig'
11
12
 
12
13
  module Reline
13
14
  # NOTE: For making compatible with the rb-readline gem
14
- FILENAME_COMPLETION_PROC = nil
15
- USERNAME_COMPLETION_PROC = nil
16
-
17
- class ConfigEncodingConversionError < StandardError; end
18
-
19
- Key = Struct.new(:char, :combined_char, :with_meta) do
20
- def match?(other)
21
- case other
22
- when Reline::Key
23
- (other.char.nil? or char.nil? or char == other.char) and
24
- (other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
25
- (other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
26
- when Integer, Symbol
27
- (combined_char and combined_char == other) or
28
- (combined_char.nil? and char and char == other)
29
- else
30
- false
31
- end
15
+ FILENAME_COMPLETION_PROC = nil # :nodoc:
16
+ USERNAME_COMPLETION_PROC = nil # :nodoc:
17
+
18
+ class ConfigEncodingConversionError < StandardError; end # :nodoc:
19
+
20
+ # EOF key: { char: nil, method_symbol: nil }
21
+ # Other key: { char: String, method_symbol: Symbol }
22
+ Key = Struct.new(:char, :method_symbol, :unused_boolean) do
23
+ # For dialog_proc `key.match?(dialog.name)`
24
+ def match?(sym)
25
+ method_symbol && method_symbol == sym
32
26
  end
33
- alias_method :==, :match?
34
- end
35
- CursorPos = Struct.new(:x, :y)
27
+ end # :nodoc:
28
+ CursorPos = Struct.new(:x, :y) # :nodoc:
36
29
  DialogRenderInfo = Struct.new(
37
30
  :pos,
38
31
  :contents,
39
- :bg_color,
40
- :pointer_bg_color,
41
- :fg_color,
42
- :pointer_fg_color,
32
+ :face,
33
+ :bg_color, # For the time being, this line should stay here for the compatibility with IRB.
43
34
  :width,
44
35
  :height,
45
36
  :scrollbar,
46
37
  keyword_init: true
47
- )
38
+ ) # :nodoc:
48
39
 
49
40
  class Core
50
41
  ATTR_READER_NAMES = %i(
@@ -76,10 +67,10 @@ module Reline
76
67
 
77
68
  def initialize
78
69
  self.output = STDOUT
70
+ @mutex = Mutex.new
79
71
  @dialog_proc_list = {}
80
72
  yield self
81
73
  @completion_quote_character = nil
82
- @bracketed_paste_finished = false
83
74
  end
84
75
 
85
76
  def io_gate
@@ -192,9 +183,7 @@ module Reline
192
183
  def output=(val)
193
184
  raise TypeError unless val.respond_to?(:write) or val.nil?
194
185
  @output = val
195
- if io_gate.respond_to?(:output=)
196
- io_gate.output = val
197
- end
186
+ io_gate.output = val
198
187
  end
199
188
 
200
189
  def vi_editing_mode
@@ -221,32 +210,25 @@ module Reline
221
210
 
222
211
  Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
223
212
  # autocomplete
224
- return nil unless config.autocompletion
225
- if just_cursor_moving and completion_journey_data.nil?
226
- # Auto complete starts only when edited
227
- return nil
228
- end
229
- pre, target, post = retrieve_completion_block(true)
230
- if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
231
- return nil
232
- end
233
- if completion_journey_data and completion_journey_data.list
234
- result = completion_journey_data.list.dup
235
- result.shift
236
- pointer = completion_journey_data.pointer - 1
237
- else
238
- result = call_completion_proc_with_checking_args(pre, target, post)
239
- pointer = nil
240
- end
241
- if result and result.size == 1 and result[0] == target and pointer != 0
242
- result = nil
243
- end
213
+ return unless config.autocompletion
214
+
215
+ journey_data = completion_journey_data
216
+ return unless journey_data
217
+
218
+ target = journey_data.list.first
219
+ completed = journey_data.list[journey_data.pointer]
220
+ result = journey_data.list.drop(1)
221
+ pointer = journey_data.pointer - 1
222
+ return if completed.empty? || (result == [completed] && pointer < 0)
223
+
244
224
  target_width = Reline::Unicode.calculate_width(target)
245
- x = cursor_pos.x - target_width
246
- if x < 0
247
- x = screen_width + x
225
+ completed_width = Reline::Unicode.calculate_width(completed)
226
+ if cursor_pos.x <= completed_width - target_width
227
+ # When target is rendered on the line above cursor position
228
+ x = screen_width - completed_width
248
229
  y = -1
249
230
  else
231
+ x = [cursor_pos.x - completed_width, 0].max
250
232
  y = 0
251
233
  end
252
234
  cursor_pos_to_render = Reline::CursorPos.new(x, y)
@@ -260,21 +242,20 @@ module Reline
260
242
  contents: result,
261
243
  scrollbar: true,
262
244
  height: [15, preferred_dialog_height].min,
263
- bg_color: 46,
264
- pointer_bg_color: 45,
265
- fg_color: 37,
266
- pointer_fg_color: 37
245
+ face: :completion_dialog
267
246
  )
268
- }
269
- Reline::DEFAULT_DIALOG_CONTEXT = Array.new
247
+ } # :nodoc:
248
+ Reline::DEFAULT_DIALOG_CONTEXT = Array.new # :nodoc:
270
249
 
271
250
  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
272
- Reline.update_iogate
273
- io_gate.with_raw_input do
251
+ @mutex.synchronize do
274
252
  unless confirm_multiline_termination
275
253
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
276
254
  end
277
- inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
255
+
256
+ io_gate.with_raw_input do
257
+ inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
258
+ end
278
259
 
279
260
  whole_buffer = line_editor.whole_buffer.dup
280
261
  whole_buffer.taint if RUBY_VERSION < '2.7'
@@ -282,23 +263,31 @@ module Reline
282
263
  Reline::HISTORY << whole_buffer
283
264
  end
284
265
 
285
- line_editor.reset_line if line_editor.whole_buffer.nil?
286
- whole_buffer
266
+ if line_editor.eof?
267
+ line_editor.reset_line
268
+ # Return nil if the input is aborted by C-d.
269
+ nil
270
+ else
271
+ whole_buffer
272
+ end
287
273
  end
288
274
  end
289
275
 
290
276
  def readline(prompt = '', add_hist = false)
291
- Reline.update_iogate
292
- inner_readline(prompt, add_hist, false)
277
+ @mutex.synchronize do
278
+ io_gate.with_raw_input do
279
+ inner_readline(prompt, add_hist, false)
280
+ end
293
281
 
294
- line = line_editor.line.dup
295
- line.taint if RUBY_VERSION < '2.7'
296
- if add_hist and line and line.chomp("\n").size > 0
297
- Reline::HISTORY << line.chomp("\n")
298
- end
282
+ line = line_editor.line.dup
283
+ line.taint if RUBY_VERSION < '2.7'
284
+ if add_hist and line and line.chomp("\n").size > 0
285
+ Reline::HISTORY << line.chomp("\n")
286
+ end
299
287
 
300
- line_editor.reset_line if line_editor.line.nil?
301
- line
288
+ line_editor.reset_line if line_editor.line.nil?
289
+ line
290
+ end
302
291
  end
303
292
 
304
293
  private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
@@ -311,10 +300,15 @@ module Reline
311
300
  $stderr.sync = true
312
301
  $stderr.puts "Reline is used by #{Process.pid}"
313
302
  end
303
+ unless config.test_mode or config.loaded?
304
+ config.read
305
+ io_gate.set_default_key_bindings(config)
306
+ end
314
307
  otio = io_gate.prep
315
308
 
316
309
  may_req_ambiguous_char_width
317
- line_editor.reset(prompt, encoding: encoding)
310
+ key_stroke.encoding = encoding
311
+ line_editor.reset(prompt)
318
312
  if multiline
319
313
  line_editor.multiline_on
320
314
  if block_given?
@@ -323,154 +317,91 @@ module Reline
323
317
  else
324
318
  line_editor.multiline_off
325
319
  end
326
- line_editor.output = output
327
320
  line_editor.completion_proc = completion_proc
328
321
  line_editor.completion_append_character = completion_append_character
329
322
  line_editor.output_modifier_proc = output_modifier_proc
330
323
  line_editor.prompt_proc = prompt_proc
331
324
  line_editor.auto_indent_proc = auto_indent_proc
332
325
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
333
- line_editor.pre_input_hook = pre_input_hook
334
- @dialog_proc_list.each_pair do |name_sym, d|
335
- line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
336
- end
337
326
 
338
- unless config.test_mode
339
- config.read
340
- config.reset_default_key_bindings
341
- io_gate.set_default_key_bindings(config)
327
+ # Readline calls pre_input_hook just after printing the first prompt.
328
+ line_editor.print_nomultiline_prompt
329
+ pre_input_hook&.call
330
+
331
+ unless Reline::IOGate.dumb?
332
+ @dialog_proc_list.each_pair do |name_sym, d|
333
+ line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
334
+ end
342
335
  end
343
336
 
337
+ line_editor.update_dialogs
344
338
  line_editor.rerender
345
339
 
346
340
  begin
347
341
  line_editor.set_signal_handlers
348
- prev_pasting_state = false
349
342
  loop do
350
- prev_pasting_state = io_gate.in_pasting?
351
343
  read_io(config.keyseq_timeout) { |inputs|
352
- line_editor.set_pasting_state(io_gate.in_pasting?)
353
- inputs.each { |c|
354
- line_editor.input_key(c)
355
- line_editor.rerender
356
- }
357
- if @bracketed_paste_finished
358
- line_editor.rerender_all
359
- @bracketed_paste_finished = false
344
+ inputs.each do |key|
345
+ case key.method_symbol
346
+ when :bracketed_paste_start
347
+ # io_gate is Reline::ANSI because the key :bracketed_paste_start is only assigned in Reline::ANSI
348
+ key = Reline::Key.new(io_gate.read_bracketed_paste, :insert_multiline_text)
349
+ when :quoted_insert, :ed_quoted_insert
350
+ key = Reline::Key.new(io_gate.read_single_char(config.keyseq_timeout), :insert_raw_char)
351
+ end
352
+ line_editor.set_pasting_state(io_gate.in_pasting?)
353
+ line_editor.update(key)
360
354
  end
361
355
  }
362
- if prev_pasting_state == true and not io_gate.in_pasting? and not line_editor.finished?
363
- line_editor.set_pasting_state(false)
364
- prev_pasting_state = false
365
- line_editor.rerender_all
356
+ if line_editor.finished?
357
+ line_editor.render_finished
358
+ break
359
+ else
360
+ line_editor.rerender
366
361
  end
367
- break if line_editor.finished?
368
362
  end
369
363
  io_gate.move_cursor_column(0)
370
364
  rescue Errno::EIO
371
365
  # Maybe the I/O has been closed.
372
- rescue StandardError => e
373
- line_editor.finalize
374
- io_gate.deprep(otio)
375
- raise e
376
- rescue Exception
377
- # Including Interrupt
366
+ ensure
378
367
  line_editor.finalize
379
368
  io_gate.deprep(otio)
380
- raise
381
369
  end
382
-
383
- line_editor.finalize
384
- io_gate.deprep(otio)
385
370
  end
386
371
 
387
- # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
388
- # is followed by a character, and times out and treats it as a standalone
389
- # ESC if the second character does not arrive. If the second character
390
- # comes before timed out, it is treated as a modifier key with the
391
- # meta-property of meta-key, so that it can be distinguished from
392
- # multibyte characters with the 8th bit turned on.
393
- #
394
- # GNU Readline will wait for the 2nd character with "keyseq-timeout"
395
- # milli-seconds but wait forever after 3rd characters.
372
+ # GNU Readline watis for "keyseq-timeout" milliseconds when the input is
373
+ # ambiguous whether it is matching or matched.
374
+ # If the next character does not arrive within the specified timeout, input
375
+ # is considered as matched.
376
+ # `ESC` is ambiguous because it can be a standalone ESC (matched) or part of
377
+ # `ESC char` or part of CSI sequence (matching).
396
378
  private def read_io(keyseq_timeout, &block)
397
379
  buffer = []
380
+ status = KeyStroke::MATCHING
398
381
  loop do
399
- c = io_gate.getc(Float::INFINITY)
400
- if c == -1
401
- result = :unmatched
402
- @bracketed_paste_finished = true
403
- else
404
- buffer << c
405
- result = key_stroke.match_status(buffer)
406
- end
407
- case result
408
- when :matched
409
- expanded = key_stroke.expand(buffer).map{ |expanded_c|
410
- Reline::Key.new(expanded_c, expanded_c, false)
411
- }
412
- block.(expanded)
413
- break
414
- when :matching
415
- if buffer.size == 1
416
- case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
417
- when :break then break
418
- when :next then next
419
- end
420
- end
421
- when :unmatched
422
- if buffer.size == 1 and c == "\e".ord
423
- read_escaped_key(keyseq_timeout, c, block)
382
+ timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY
383
+ c = io_gate.getc(timeout)
384
+ if c.nil? || c == -1
385
+ if status == KeyStroke::MATCHING_MATCHED
386
+ status = KeyStroke::MATCHED
387
+ elsif buffer.empty?
388
+ # io_gate is closed and reached EOF
389
+ block.call([Key.new(nil, nil, false)])
390
+ return
424
391
  else
425
- expanded = buffer.map{ |expanded_c|
426
- Reline::Key.new(expanded_c, expanded_c, false)
427
- }
428
- block.(expanded)
392
+ status = KeyStroke::UNMATCHED
429
393
  end
430
- break
394
+ else
395
+ buffer << c
396
+ status = key_stroke.match_status(buffer)
431
397
  end
432
- end
433
- end
434
398
 
435
- private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
436
- succ_c = io_gate.getc(keyseq_timeout.fdiv(1000))
437
- if succ_c
438
- case key_stroke.match_status(buffer.dup.push(succ_c))
439
- when :unmatched
440
- if c == "\e".ord
441
- block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
442
- else
443
- block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
444
- end
445
- return :break
446
- when :matching
447
- io_gate.ungetc(succ_c)
448
- return :next
449
- when :matched
450
- buffer << succ_c
451
- expanded = key_stroke.expand(buffer).map{ |expanded_c|
452
- Reline::Key.new(expanded_c, expanded_c, false)
453
- }
454
- block.(expanded)
455
- return :break
399
+ if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED
400
+ expanded, rest_bytes = key_stroke.expand(buffer)
401
+ rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
402
+ block.call(expanded)
403
+ return
456
404
  end
457
- else
458
- block.([Reline::Key.new(c, c, false)])
459
- return :break
460
- end
461
- end
462
-
463
- private def read_escaped_key(keyseq_timeout, c, block)
464
- escaped_c = io_gate.getc(keyseq_timeout.fdiv(1000))
465
-
466
- if escaped_c.nil?
467
- block.([Reline::Key.new(c, c, false)])
468
- elsif escaped_c >= 128 # maybe, first byte of multi byte
469
- block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
470
- elsif escaped_c == "\e".ord # escape twice
471
- block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
472
- else
473
- block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
474
405
  end
475
406
  end
476
407
 
@@ -480,7 +411,7 @@ module Reline
480
411
  end
481
412
 
482
413
  private def may_req_ambiguous_char_width
483
- @ambiguous_width = 2 if io_gate == Reline::GeneralIO or !STDOUT.tty?
414
+ @ambiguous_width = 1 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
484
415
  return if defined? @ambiguous_width
485
416
  io_gate.move_cursor_column(0)
486
417
  begin
@@ -489,7 +420,7 @@ module Reline
489
420
  # LANG=C
490
421
  @ambiguous_width = 1
491
422
  else
492
- @ambiguous_width = io_gate.cursor_pos.x
423
+ @ambiguous_width = io_gate.cursor_pos.x == 2 ? 2 : 1
493
424
  end
494
425
  io_gate.move_cursor_column(0)
495
426
  io_gate.erase_after_cursor
@@ -508,6 +439,17 @@ module Reline
508
439
  }
509
440
  def_single_delegators :core, :input=, :output=
510
441
  def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
442
+
443
+ ##
444
+ # :singleton-method: readmultiline
445
+ # :call-seq:
446
+ # readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination) -> string or nil
447
+ def_single_delegators :core, :readmultiline
448
+
449
+ ##
450
+ # :singleton-method: readline
451
+ # :call-seq:
452
+ # readline(prompt = '', add_hist = false) -> string or nil
511
453
  def_single_delegators :core, :readline
512
454
  def_single_delegators :core, :completion_case_fold, :completion_case_fold=
513
455
  def_single_delegators :core, :completion_quote_character
@@ -528,8 +470,8 @@ module Reline
528
470
  def_single_delegator :line_editor, :byte_pointer, :point
529
471
  def_single_delegator :line_editor, :byte_pointer=, :point=
530
472
 
531
- def self.insert_text(*args, &block)
532
- line_editor.insert_text(*args, &block)
473
+ def self.insert_text(text)
474
+ line_editor.insert_multiline_text(text)
533
475
  self
534
476
  end
535
477
 
@@ -543,19 +485,18 @@ module Reline
543
485
  def_single_delegators :core, :dialog_proc
544
486
  def_single_delegators :core, :autocompletion, :autocompletion=
545
487
 
546
- def_single_delegators :core, :readmultiline
547
488
  def_instance_delegators self, :readmultiline
548
489
  private :readmultiline
549
490
 
550
- def self.encoding_system_needs
491
+ def self.encoding_system_needs # :nodoc:
551
492
  self.core.encoding
552
493
  end
553
494
 
554
495
  def self.core
555
496
  @core ||= Core.new { |core|
556
497
  core.config = Reline::Config.new
557
- core.key_stroke = Reline::KeyStroke.new(core.config)
558
- core.line_editor = Reline::LineEditor.new(core.config, core.encoding)
498
+ core.key_stroke = Reline::KeyStroke.new(core.config, core.encoding)
499
+ core.line_editor = Reline::LineEditor.new(core.config)
559
500
 
560
501
  core.basic_word_break_characters = " \t\n`><=;|&{("
561
502
  core.completer_word_break_characters = " \t\n`><=;|&{("
@@ -574,36 +515,14 @@ module Reline
574
515
  def self.line_editor
575
516
  core.line_editor
576
517
  end
518
+ end
577
519
 
578
- def self.update_iogate
579
- return if core.config.test_mode
580
520
 
581
- # Need to change IOGate when `$stdout.tty?` change from false to true by `$stdout.reopen`
582
- # Example: rails/spring boot the application in non-tty, then run console in tty.
583
- if ENV['TERM'] != 'dumb' && core.io_gate == Reline::GeneralIO && $stdout.tty?
584
- require 'reline/ansi'
585
- remove_const(:IOGate)
586
- const_set(:IOGate, Reline::ANSI)
587
- end
588
- end
589
- end
521
+ Reline::IOGate = Reline::IO.decide_io_gate
590
522
 
591
- require 'reline/general_io'
592
- io = Reline::GeneralIO
593
- unless ENV['TERM'] == 'dumb'
594
- case RbConfig::CONFIG['host_os']
595
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
596
- require 'reline/windows'
597
- tty = (io = Reline::Windows).msys_tty?
598
- else
599
- tty = $stdout.tty?
600
- end
601
- end
602
- Reline::IOGate = if tty
603
- require 'reline/ansi'
604
- Reline::ANSI
605
- else
606
- io
607
- end
523
+ # Deprecated
524
+ Reline::GeneralIO = Reline::Dumb.new # :nodoc:
525
+
526
+ Reline::Face.load_initial_configs
608
527
 
609
528
  Reline::HISTORY = Reline::History.new(Reline.core.config)
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-10-03 00:00:00.000000000 Z
10
+ date: 2025-04-04 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: io-console
@@ -35,29 +34,33 @@ files:
35
34
  - COPYING
36
35
  - README.md
37
36
  - lib/reline.rb
38
- - lib/reline/ansi.rb
39
37
  - lib/reline/config.rb
40
- - lib/reline/general_io.rb
38
+ - lib/reline/face.rb
41
39
  - lib/reline/history.rb
40
+ - lib/reline/io.rb
41
+ - lib/reline/io/ansi.rb
42
+ - lib/reline/io/dumb.rb
43
+ - lib/reline/io/windows.rb
42
44
  - lib/reline/key_actor.rb
43
45
  - lib/reline/key_actor/base.rb
46
+ - lib/reline/key_actor/composite.rb
44
47
  - lib/reline/key_actor/emacs.rb
45
48
  - lib/reline/key_actor/vi_command.rb
46
49
  - lib/reline/key_actor/vi_insert.rb
47
50
  - lib/reline/key_stroke.rb
48
51
  - lib/reline/kill_ring.rb
49
52
  - lib/reline/line_editor.rb
50
- - lib/reline/terminfo.rb
51
53
  - lib/reline/unicode.rb
52
54
  - lib/reline/unicode/east_asian_width.rb
53
55
  - lib/reline/version.rb
54
- - lib/reline/windows.rb
55
56
  - license_of_rb-readline
56
57
  homepage: https://github.com/ruby/reline
57
58
  licenses:
58
59
  - Ruby
59
- metadata: {}
60
- post_install_message:
60
+ metadata:
61
+ bug_tracker_uri: https://github.com/ruby/reline/issues
62
+ changelog_uri: https://github.com/ruby/reline/releases
63
+ source_code_uri: https://github.com/ruby/reline
61
64
  rdoc_options: []
62
65
  require_paths:
63
66
  - lib
@@ -72,8 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
75
  - !ruby/object:Gem::Version
73
76
  version: '0'
74
77
  requirements: []
75
- rubygems_version: 3.4.13
76
- signing_key:
78
+ rubygems_version: 3.6.3
77
79
  specification_version: 4
78
80
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.
79
81
  test_files: []