rufio 0.91.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c50fc72fd43046f6fb861b05ff378c5122ebfa1bba180463340a5f1e906ad3f
4
- data.tar.gz: 7e5fcc725fafc5ceeeb90433a5cb767e3d0c98431a0b5bdb554f727a99fb50f5
3
+ metadata.gz: 6e51cc4afc1f8340d7340288d7e20677fcd174d6dd32029714516827375d1d61
4
+ data.tar.gz: 3d777468ecee811d01cd303891d6c50aaae1b38e298e8ec2b48f818b6b24ad8c
5
5
  SHA512:
6
- metadata.gz: 5eb8c2b7955f69c822d58aaf976be1669aa446d12eacbac4fe24c0e152ffd6d79631488e6c0debdb7e232b477321ee4d5617873e06be5da1999704b28fe4554f
7
- data.tar.gz: fd71f1119f3ae724ecd8c220c6c9401a7dfd4d4fa40108aa2d5320afd23a2c181589224cbd157a28f8cbbcad847f48285bc4108cd583cec93230e74466cdafd0
6
+ metadata.gz: 4f9b4801d4d3f7bb0406e6f688d5e4de20ccf24d936924f714472d9841284804ebddc7d211a5c0b72cb72df09ea607ea47bd9bc02b0178550c86d9af791e8dfa
7
+ data.tar.gz: ffdb0429b787320d57fe28dd68bc67b7278dccf94723d30e52c619e51d60e954d3ed7fd4a1ca8eeaa1de0df914701784f08a74edafaa86a831bc928e3fad93ba
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to rufio will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.0] - 2026-04-28
9
+
10
+ ### Added
11
+ - **Job mode: View log with Space key**: Press Space on a selected job to display its full execution log. Footer switches to `[ESC] Close Log` while in log view; press ESC to return to the job list
12
+ - **Job mode: Disable Tab key**: Tab (bookmark cycling) is now suppressed in job mode to prevent accidental navigation. Removed `[Tab] Switch Mode` from the job mode footer
13
+
14
+ ### Fixed
15
+ - **Windows: ESC key not working**: `STDIN.raw` + `getbyte` silently drops the ESC byte (0x1B) under Windows ConPTY. The blocking input thread now uses `getch` instead, pushing each received byte onto the queue
16
+ - **Windows: Preview flickering**: Redundant `print "\e[2J\e[H"` calls before `renderer.clear` caused the screen to blank momentarily on every redraw. Removed the duplicate clears in `refresh_display`, `set_job_mode`, and `exit_job_mode`. Also unified the async highlight-updated flag into `UIRenderer` (it was maintained separately in both `TerminalUI` and `UIRenderer`, causing missed redraws)
17
+ - **Windows: Cursor visible as blinking vertical bar in bottom-right corner**: `tput civis` is a no-op on Windows, leaving the cursor visible. Replaced `tput civis/cnorm/smcup/rmcup` with ANSI sequences `\e[?25l`/`\e[?25h`/`\e[?1049h`/`\e[?1049l` which work on Windows Terminal. Cursor is shown only during command mode input and hidden otherwise
18
+ - **Windows: `bundle install` fails with "cannot load git ls-files"**: `rufio.gemspec` now falls back to `Dir.glob` when `git` is not on PATH
19
+
8
20
  ## [0.91.0] - 2026-04-12
9
21
 
10
22
  ### Changed
@@ -669,7 +669,6 @@ module Rufio
669
669
  exit_job_mode
670
670
  true
671
671
  when :show_log
672
- # ログ表示は将来実装
673
672
  @terminal_ui&.trigger_job_mode_redraw if @terminal_ui
674
673
  true
675
674
  when true, false
@@ -118,7 +118,8 @@ module Rufio
118
118
 
119
119
  begin
120
120
  Timeout.timeout(timeout_sec) do
121
- stdin, stdout_io, stderr_io, wait_thread = Open3.popen3(env, *command, **options)
121
+ args = env.empty? ? [*command] : [env, *command]
122
+ stdin, stdout_io, stderr_io, wait_thread = Open3.popen3(*args, **options)
122
123
  pid = wait_thread.pid
123
124
  stdin.close
124
125
  stdout = stdout_io.read
@@ -162,7 +163,9 @@ module Rufio
162
163
 
163
164
  # タイムアウトなしで実行
164
165
  def execute_without_timeout(command, env, options)
165
- stdout, stderr, status = Open3.capture3(env, *command, **options)
166
+ # 空の env ハッシュを渡すと Windows SystemRoot 等が引き継がれないため省略する
167
+ args = env.empty? ? [*command] : [env, *command]
168
+ stdout, stderr, status = Open3.capture3(*args, **options)
166
169
 
167
170
  {
168
171
  success: status.success?,
@@ -183,7 +186,8 @@ module Rufio
183
186
 
184
187
  begin
185
188
  Timeout.timeout(timeout_sec) do
186
- stdin, stdout_io, stderr_io, wait_thread = Open3.popen3(env, shell_command, **options)
189
+ args = env.empty? ? [shell_command] : [env, shell_command]
190
+ stdin, stdout_io, stderr_io, wait_thread = Open3.popen3(*args, **options)
187
191
  pid = wait_thread.pid
188
192
  stdin.close
189
193
  stdout = stdout_io.read
@@ -226,7 +230,8 @@ module Rufio
226
230
 
227
231
  # シェルコマンドをタイムアウトなしで実行
228
232
  def execute_shell_without_timeout(shell_command, env, options)
229
- stdout, stderr, status = Open3.capture3(env, shell_command, **options)
233
+ args = env.empty? ? [shell_command] : [env, shell_command]
234
+ stdout, stderr, status = Open3.capture3(*args, **options)
230
235
 
231
236
  {
232
237
  success: status.success?,
@@ -48,6 +48,8 @@ module Rufio
48
48
  @running = false
49
49
  @test_mode = test_mode
50
50
  @multibyte_reader = MultibyteInputReader.new(STDIN)
51
+ @input_queue = nil
52
+ @input_thread = nil
51
53
  @command_mode_active = false
52
54
  @command_input = ""
53
55
  @command_mode = CommandMode.new
@@ -73,8 +75,6 @@ module Rufio
73
75
 
74
76
  # シンタックスハイライター(bat が利用可能な場合のみ動作)
75
77
  @syntax_highlighter = SyntaxHighlighter.new
76
- # 非同期ハイライト完了フラグ(Thread → メインループへの通知)
77
- @highlight_updated = false
78
78
 
79
79
 
80
80
  # Tab mode manager
@@ -132,7 +132,6 @@ module Rufio
132
132
  def refresh_display
133
133
  # ウィンドウサイズを更新してから画面をクリアして再描画
134
134
  update_screen_size
135
- print "\e[2J\e[H" # clear screen, cursor to home
136
135
 
137
136
  # プレビューキャッシュをクリア(ディレクトリ変更やリフレッシュ時)
138
137
  @preview_cache.clear
@@ -176,16 +175,36 @@ module Rufio
176
175
  # ブックマークハイライトが期限切れかどうか
177
176
  # @return [Boolean] true=期限切れ or ハイライト中でない, false=ハイライト中
178
177
  def setup_terminal
179
- # terminal setup
180
- system('tput smcup') # alternate screen
181
- system('tput civis') # cursor invisible
182
- print "\e[2J\e[H" # clear screen, cursor to home (first time only)
178
+ # terminal setup(ANSI エスケープで統一 — tput は Windows で動作しない)
179
+ print "\e[?1049h" # alternate screen buffer
180
+ print "\e[?25l" # cursor invisible
181
+ print "\e[2J\e[H" # clear screen, cursor to home (first time only)
183
182
 
184
183
  # rawモードに設定(ゲームループのノンブロッキング入力用)
185
184
  if STDIN.tty?
186
185
  STDIN.raw!
187
186
  end
188
187
 
188
+ # Windows: ブロッキング読み取りスレッドを起動。
189
+ # ConPTY パイプでは IO.select のシグナル遅延で ESC を取りこぼすため、
190
+ # 常にブロッキングで読み取りキューに積む。
191
+ # getbyte は raw モード下で ESC (0x1B) を取りこぼす ConPTY バグがあるため
192
+ # getch を使用し、受信した各バイトをキューに積む。
193
+ if windows?
194
+ @input_queue = Queue.new
195
+ @stop_input_thread = false
196
+ @input_thread = Thread.new do
197
+ loop do
198
+ break if @stop_input_thread
199
+ ch = STDIN.getch(min: 1)
200
+ break if ch.nil? || @stop_input_thread
201
+ ch.bytes.each { |b| @input_queue << b }
202
+ end
203
+ rescue
204
+ # スレッド終了
205
+ end
206
+ end
207
+
189
208
  # SGR拡張マウスレポートを有効化
190
209
  # Unix: \e[?1003h (any-event) + \e[?1006h (SGR座標) → クリック・スクロール・移動
191
210
  # Windows: \e[?1003h は Windows Terminal / conhost 非対応。
@@ -215,35 +234,66 @@ module Rufio
215
234
  end
216
235
 
217
236
  # エスケープシーケンスの後続バイトを読み取る(矢印キー等の短いシーケンス用)。
218
- # Windows: IO.select(timeout=0) がESCを取りこぼすため 5ms タイムアウトを使用。
237
+ # Windows: IO.select を使わずキューから取得(5ms タイムアウト)。
219
238
  def read_next_input_byte
220
239
  if windows?
221
- return nil unless IO.select([STDIN], nil, nil, 0.005)
222
- STDIN.read_nonblock(1) rescue nil
240
+ b = windows_queue_pop_with_timeout(0.005)
241
+ b.nil? ? nil : b.chr(Encoding::BINARY)
223
242
  else
224
243
  STDIN.read_nonblock(1) rescue nil
225
244
  end
226
245
  end
227
246
 
228
247
  # SGRマウスシーケンスの後続バイトを読み取る。
229
- # \e[<Btn;Col;RowM/m は最大 15 バイト程度になるため、
230
- # Windows Console のイベントキューにバイトが積まれるまで 20ms 待つ。
248
+ # Windows: キューから取得(20ms タイムアウト)。
231
249
  def read_next_mouse_byte
232
250
  if windows?
233
- return nil unless IO.select([STDIN], nil, nil, 0.020)
234
- STDIN.read_nonblock(1) rescue nil
251
+ b = windows_queue_pop_with_timeout(0.020)
252
+ b.nil? ? nil : b.chr(Encoding::BINARY)
235
253
  else
236
254
  STDIN.read_nonblock(1) rescue nil
237
255
  end
238
256
  end
239
257
 
240
- def cleanup_terminal
241
- # rawモードを解除
242
- if STDIN.tty?
243
- STDIN.cooked!
258
+ # Windows 専用: キューから1文字(マルチバイト対応)を組み立てる。
259
+ # first_byte_int は getbyte が返す Integer。
260
+ def windows_build_char(first_byte_int)
261
+ remaining = case first_byte_int
262
+ when 0x00..0x7F then 0
263
+ when 0xC0..0xDF then 1
264
+ when 0xE0..0xEF then 2
265
+ when 0xF0..0xF7 then 3
266
+ else 0
267
+ end
268
+ if remaining == 0
269
+ first_byte_int.chr(Encoding::UTF_8)
270
+ else
271
+ buf = first_byte_int.chr(Encoding::BINARY)
272
+ remaining.times do
273
+ b = windows_queue_pop_with_timeout(0.005)
274
+ return nil if b.nil?
275
+ buf << b.chr(Encoding::BINARY)
276
+ end
277
+ result = buf.force_encoding(Encoding::UTF_8)
278
+ result.valid_encoding? ? result : nil
279
+ end
280
+ end
281
+
282
+ # Windows 専用: @input_queue からタイムアウト付きで Integer バイトを取り出す。
283
+ def windows_queue_pop_with_timeout(timeout_sec)
284
+ deadline = Time.now + timeout_sec
285
+ loop do
286
+ begin
287
+ return @input_queue.pop(true)
288
+ rescue ThreadError
289
+ return nil if Time.now >= deadline
290
+ sleep 0.001
291
+ end
244
292
  end
293
+ end
245
294
 
246
- # マウスレポートを無効化(setup_terminal と対称)
295
+ def cleanup_terminal
296
+ # マウスレポートを先に無効化して新しいイベントの流入を止める
247
297
  if windows?
248
298
  print "\e[?1000l\e[?1006l"
249
299
  else
@@ -251,11 +301,46 @@ module Rufio
251
301
  end
252
302
  STDOUT.flush
253
303
 
254
- system('tput rmcup') # normal screen
255
- system('tput cnorm') # cursor normal
304
+ # Windows 入力スレッドを停止
305
+ if windows? && @input_thread
306
+ @stop_input_thread = true
307
+ @input_thread.kill rescue nil
308
+ @input_thread.join(0.5) rescue nil
309
+ @input_thread = nil
310
+ # コンソール入力バッファをフラッシュして残留イベントによる不正出力を防ぐ
311
+ windows_flush_console_input_buffer
312
+ end
313
+
314
+ # rawモードを解除
315
+ if STDIN.tty?
316
+ STDIN.cooked!
317
+ end
318
+
319
+ print "\e[?25h" # cursor visible
320
+ print "\e[?1049l" # restore normal screen buffer
321
+ STDOUT.flush
256
322
  puts ConfigLoader.message('app.terminated')
257
323
  end
258
324
 
325
+ def windows_flush_console_input_buffer
326
+ require 'fiddle'
327
+ kernel32 = Fiddle.dlopen('kernel32')
328
+ get_std_handle = Fiddle::Function.new(
329
+ kernel32['GetStdHandle'],
330
+ [Fiddle::TYPE_LONG],
331
+ Fiddle::TYPE_VOIDP
332
+ )
333
+ flush = Fiddle::Function.new(
334
+ kernel32['FlushConsoleInputBuffer'],
335
+ [Fiddle::TYPE_VOIDP],
336
+ Fiddle::TYPE_INT
337
+ )
338
+ handle = get_std_handle.call(-10) # STD_INPUT_HANDLE = -10
339
+ flush.call(handle)
340
+ rescue
341
+ # Windows API が使えない場合は無視して続行
342
+ end
343
+
259
344
  # ゲームループパターンのmain_loop(CPU最適化版:フレームスキップ対応)
260
345
  # UPDATE → DRAW → RENDER → SLEEP のサイクル
261
346
  # 変更がない場合は描画をスキップしてCPU使用率を削減
@@ -369,9 +454,9 @@ module Rufio
369
454
  needs_redraw = true
370
455
  end
371
456
 
372
- # 非同期シンタックスハイライト完了チェック(バックグラウンドスレッドからの通知)
373
- if @highlight_updated
374
- @highlight_updated = false
457
+ # 非同期シンタックスハイライト完了チェック(UIRenderer側のフラグを参照)
458
+ if @ui_renderer.highlight_updated?
459
+ @ui_renderer.reset_highlight_updated
375
460
  needs_redraw = true
376
461
  end
377
462
 
@@ -432,25 +517,28 @@ module Rufio
432
517
  private
433
518
 
434
519
  # ノンブロッキング入力処理(ゲームループ用)
435
- # Windows: IO.select(timeout=1ms) で入力確認後 read_nonblock
436
- # timeout=0 だとESCキーを取りこぼす(コンソールの処理タイミング競合)ため
437
- # 1ms の正のタイムアウトを使う。コンソールハンドルでも ConPTY パイプでも動作する。
520
+ # Windows: ブロッキング入力スレッドのキューからノンブロッキングで取得
521
+ # (IO.select の ConPTY シグナル遅延問題を回避)
438
522
  # Unix: IO.select(timeout=0) で入力確認後 read_nonblock
439
523
  def handle_input_nonblocking
440
- # 入力バイトを1つ読み取る
441
524
  if windows?
442
- # Windows: timeout=0 ではESCキーを取りこぼすため 1ms タイムアウトを使用
443
- return false unless IO.select([STDIN], nil, nil, 0.001)
525
+ # Windows: ブロッキング入力スレッドが積んだキューから取得
526
+ raw_byte = begin
527
+ @input_queue.pop(true)
528
+ rescue ThreadError
529
+ return false
530
+ end
531
+ input = windows_build_char(raw_byte)
532
+ return false unless input
444
533
  else
445
534
  # Unix: 0msタイムアウトで即座にチェック(30FPS = 33.33ms/frame)
446
535
  return false unless IO.select([STDIN], nil, nil, 0)
447
- end
448
-
449
- begin
450
- input = @multibyte_reader.read_char
451
- return false if input.nil?
452
- rescue Errno::ENOTTY, Errno::ENODEV
453
- return false
536
+ begin
537
+ input = @multibyte_reader.read_char
538
+ return false if input.nil?
539
+ rescue Errno::ENOTTY, Errno::ENODEV
540
+ return false
541
+ end
454
542
  end
455
543
 
456
544
  # コマンドモードがアクティブな場合は、エスケープシーケンス処理をスキップ
@@ -492,15 +580,19 @@ module Rufio
492
580
  when 'C' then 'l' # Right arrow
493
581
  when 'D' then 'h' # Left arrow
494
582
  when 'Z' then handle_shift_tab; return true # Shift+Tab
495
- else "\e" # ESCキー(そのまま保持)
583
+ else
584
+ # 未知の CSI シーケンス(\e[27;1u 等): 残りバイトを読み捨てて
585
+ # パイプを汚染しないようにする
586
+ drain_csi_sequence(third_char)
587
+ "\e"
496
588
  end
497
589
  else
498
590
  input = "\e" # ESCキー(そのまま保持)
499
591
  end
500
592
  end
501
593
 
502
- # TabキーはFilesモードの時のみブックマーク循環移動
503
- if input == "\t" && @tab_mode_manager.current_mode == :files
594
+ # TabキーはFilesモードの時のみブックマーク循環移動(ジョブモード中は無効)
595
+ if input == "\t" && @tab_mode_manager.current_mode == :files && !@in_job_mode
504
596
  handle_tab_key
505
597
  return true
506
598
  end
@@ -703,6 +795,8 @@ module Rufio
703
795
  def activate_command_mode
704
796
  @command_mode_active = true
705
797
  @command_input = ""
798
+ print "\e[?25h" # カーソルを表示(テキスト入力中)
799
+ STDOUT.flush
706
800
  # 閲覧中ディレクトリをコマンドモードに通知(ローカルスクリプト・Rakefileの検出用)
707
801
  browsing_dir = @directory_listing&.current_path || Dir.pwd
708
802
  @command_mode.update_browsing_directory(browsing_dir)
@@ -712,6 +806,8 @@ module Rufio
712
806
  def deactivate_command_mode
713
807
  @command_mode_active = false
714
808
  @command_input = ""
809
+ print "\e[?25l" # カーソルを非表示(通常のファイラー表示に戻る)
810
+ STDOUT.flush
715
811
  # オーバーレイをクリア
716
812
  @screen&.clear_overlay if @screen&.overlay_enabled?
717
813
  end
@@ -923,8 +1019,7 @@ module Rufio
923
1019
  @job_manager = job_manager
924
1020
  @notification_manager = notification_manager
925
1021
  @in_job_mode = true
926
- # 画面を一度クリアしてレンダラーをリセット
927
- print "\e[2J\e[H"
1022
+ # レンダラーをリセット(print "\e[2J\e[H" は renderer.clear に含まれる)
928
1023
  @renderer.clear if @renderer
929
1024
  # 再描画フラグを立てる
930
1025
  @job_mode_needs_redraw = true
@@ -937,7 +1032,6 @@ module Rufio
937
1032
  @job_manager = nil
938
1033
  # バッファベースの全画面再描画を使用
939
1034
  update_screen_size
940
- print "\e[2J\e[H"
941
1035
  if @screen && @renderer
942
1036
  @renderer.clear
943
1037
  @screen.clear
@@ -1135,6 +1229,16 @@ module Rufio
1135
1229
  })
1136
1230
  end
1137
1231
 
1232
+ # 未知の CSI シーケンス(\e[X...)の残りバイトを終端アルファベットまで読み捨てる。
1233
+ # Windows Terminal が \e[27;1u 等を送った場合のパイプ汚染を防ぐ。
1234
+ def drain_csi_sequence(first_char)
1235
+ return if first_char.nil? || first_char =~ /[A-Za-z]/
1236
+ 10.times do
1237
+ ch = read_next_input_byte
1238
+ break if ch.nil? || ch =~ /[A-Za-z]/
1239
+ end
1240
+ end
1241
+
1138
1242
  end
1139
1243
  end
1140
1244
 
@@ -128,7 +128,8 @@ module Rufio
128
128
 
129
129
  if in_job_mode
130
130
  # ジョブモード: フッタ y=0(上部)、コンテンツ y=1〜h-2、統合行 y=h-1(下部)
131
- draw_job_footer_to_buffer(screen, 0, job_manager)
131
+ log_mode = job_mode_instance&.log_mode? || false
132
+ draw_job_footer_to_buffer(screen, 0, job_manager, log_mode: log_mode)
132
133
  draw_job_list_to_buffer(screen, content_height, job_manager, job_mode_instance)
133
134
  draw_mode_tabs_to_buffer(screen, @screen_height - 1)
134
135
  else
@@ -659,6 +660,12 @@ module Rufio
659
660
  def draw_job_list_to_buffer(screen, height, job_manager, job_mode_instance)
660
661
  return unless job_manager
661
662
 
663
+ # ログモード中は選択ジョブのログを表示
664
+ if job_mode_instance&.log_mode?
665
+ draw_job_log_to_buffer(screen, height, job_mode_instance.selected_job)
666
+ return
667
+ end
668
+
662
669
  jobs = job_manager.jobs
663
670
  selected_index = job_mode_instance&.selected_index || 0
664
671
 
@@ -674,6 +681,24 @@ module Rufio
674
681
  end
675
682
  end
676
683
 
684
+ def draw_job_log_to_buffer(screen, height, job)
685
+ unless job
686
+ screen.put_string(0, CONTENT_START_LINE, 'No job selected'.ljust(@screen_width), fg: "\e[90m")
687
+ return
688
+ end
689
+
690
+ log_lines = job.logs || []
691
+ title = "=== Log: #{job.name} ==="
692
+ screen.put_string(0, CONTENT_START_LINE, title.ljust(@screen_width), fg: "\e[1;36m")
693
+
694
+ (0...height - 1).each do |i|
695
+ line_num = i + CONTENT_START_LINE + 1
696
+ line = log_lines[i] || ''
697
+ line = line[0...@screen_width].ljust(@screen_width)
698
+ screen.put_string(0, line_num, line, fg: "\e[37m")
699
+ end
700
+ end
701
+
677
702
  def draw_job_line_to_buffer(screen, job, is_selected, y)
678
703
  icon = job.status_icon
679
704
  name = job.name
@@ -712,9 +737,13 @@ module Rufio
712
737
  end
713
738
  end
714
739
 
715
- def draw_job_footer_to_buffer(screen, y, job_manager)
740
+ def draw_job_footer_to_buffer(screen, y, job_manager, log_mode: false)
716
741
  job_count = job_manager&.job_count || 0
717
- help_text = "[Space] View Log | [x] Cancel | [Tab] Switch Mode | Jobs: #{job_count}"
742
+ help_text = if log_mode
743
+ "[ESC] Close Log | Jobs: #{job_count}"
744
+ else
745
+ "[Space] View Log | [x] Cancel | Jobs: #{job_count}"
746
+ end
718
747
  footer_content = help_text.center(@screen_width)[0...@screen_width]
719
748
 
720
749
  footer_content.each_char.with_index do |char, x|
data/lib/rufio/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rufio
4
- VERSION = '0.91.0'
4
+ VERSION = '1.0.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rufio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.91.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - masisz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-12 00:00:00.000000000 Z
11
+ date: 2026-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console