rufio 0.41.0 → 0.60.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +96 -194
  4. data/bin/rufio +0 -3
  5. data/docs/CHANGELOG_v0.50.0.md +128 -0
  6. data/examples/config.yml +8 -0
  7. data/lib/rufio/builtin_commands.rb +34 -0
  8. data/lib/rufio/command_completion.rb +20 -5
  9. data/lib/rufio/command_mode.rb +157 -46
  10. data/lib/rufio/config_loader.rb +37 -0
  11. data/lib/rufio/dsl_command.rb +120 -0
  12. data/lib/rufio/dsl_command_loader.rb +177 -0
  13. data/lib/rufio/interpreter_resolver.rb +79 -0
  14. data/lib/rufio/job_manager.rb +128 -0
  15. data/lib/rufio/job_mode.rb +146 -0
  16. data/lib/rufio/keybind_handler.rb +243 -232
  17. data/lib/rufio/notification_manager.rb +77 -0
  18. data/lib/rufio/script_config_loader.rb +101 -0
  19. data/lib/rufio/script_executor.rb +253 -0
  20. data/lib/rufio/script_path_manager.rb +386 -0
  21. data/lib/rufio/script_runner.rb +216 -0
  22. data/lib/rufio/task_status.rb +118 -0
  23. data/lib/rufio/terminal_ui.rb +181 -495
  24. data/lib/rufio/version.rb +1 -1
  25. data/lib/rufio.rb +15 -11
  26. data/scripts/test_jobs/build_simulation.sh +29 -0
  27. data/scripts/test_jobs/deploy_simulation.sh +37 -0
  28. data/scripts/test_jobs/quick.sh +11 -0
  29. data/scripts/test_jobs/random_result.sh +23 -0
  30. data/scripts/test_jobs/slow_fail.sh +10 -0
  31. data/scripts/test_jobs/slow_success.sh +10 -0
  32. data/scripts/test_jobs/very_slow.sh +19 -0
  33. metadata +23 -13
  34. data/docs/file-preview-optimization-analysis.md +0 -759
  35. data/docs/file-preview-performance-issue-FIXED.md +0 -547
  36. data/lib/rufio/plugin.rb +0 -89
  37. data/lib/rufio/plugin_config.rb +0 -59
  38. data/lib/rufio/plugin_manager.rb +0 -84
  39. data/lib/rufio/plugins/file_operations.rb +0 -44
  40. data/lib/rufio/plugins/hello.rb +0 -30
  41. data/lib/rufio/plugins/stop.rb +0 -32
  42. data/lib/rufio/project_command.rb +0 -147
  43. data/lib/rufio/project_log.rb +0 -68
  44. data/lib/rufio/project_mode.rb +0 -58
@@ -1,547 +0,0 @@
1
- # ⚡ FilePreview 性能問題の根本原因と解決策
2
-
3
- ## 🚨 重大な発見
4
-
5
- **実測値**: テキストファイル表示に **80ms** かかっている(ユーザー報告)
6
-
7
- **原因**: `terminal_ui.rb` の **致命的なバグ** - ループ内での重複処理
8
-
9
- **修正後の予想**: **0.4-1.6ms** (95%改善、**21倍高速化**)
10
-
11
- ---
12
-
13
- ## エグゼクティブサマリー
14
-
15
- 当初、FilePreviewクラス単体は高速(0.06ms)と測定されましたが、**実際のアプリケーションでは80msかかっている**という報告を受けました。
16
-
17
- 詳細調査の結果、`lib/rufio/terminal_ui.rb` の `draw_file_preview` メソッド内で、**ループの中で毎回ファイルプレビューと折り返し処理を実行する致命的なバグ**を発見しました。
18
-
19
- ### 影響範囲
20
-
21
- - **全てのテキストファイルプレビュー**が影響を受ける
22
- - ファイルが大きいほど遅延が増加
23
- - 画面の高さに比例して遅延が増加(40行表示で38回重複実行)
24
-
25
- ---
26
-
27
- ## 目次
28
-
29
- 1. [問題の発見経緯](#問題の発見経緯)
30
- 2. [根本原因の特定](#根本原因の特定)
31
- 3. [詳細なベンチマーク結果](#詳細なベンチマーク結果)
32
- 4. [修正方法](#修正方法)
33
- 5. [期待される改善効果](#期待される改善効果)
34
- 6. [実装ガイド](#実装ガイド)
35
-
36
- ---
37
-
38
- ## 問題の発見経緯
39
-
40
- ### 初期調査の誤り
41
-
42
- **誤った仮説**: FilePreview.preview_file メソッドが遅い
43
- ```
44
- 小規模ファイル (50行): 0.056 ms ✓ 高速
45
- 中規模ファイル (1000行): 0.193 ms ✓ 高速
46
- 大規模ファイル (10000行): 1.378 ms ✓ 許容範囲
47
- ```
48
-
49
- **結論**: FilePreviewクラス自体は高速で問題なし
50
-
51
- ### 実際の問題
52
-
53
- **ユーザー報告**: `docs/medium_beniya.md` で **80ms** かかっている
54
-
55
- **測定対象の違い**:
56
- - 初期ベンチマーク: `FilePreview.preview_file` **単体**
57
- - 実際のアプリ: `TerminalUI.draw_screen` **全体**(ファイルプレビュー + 画面描画)
58
-
59
- **真の原因**: TerminalUI の実装バグ
60
-
61
- ---
62
-
63
- ## 根本原因の特定
64
-
65
- ### バグの所在
66
-
67
- **ファイル**: `lib/rufio/terminal_ui.rb`
68
- **メソッド**: `draw_file_preview`
69
- **行番号**: 354-413(特に380-381行が問題)
70
-
71
- ### 問題のコード
72
-
73
- ```ruby
74
- def draw_file_preview(selected_entry, width, height, left_offset)
75
- (0...height).each do |i| # ← 40回ループ
76
- # ... 省略 ...
77
-
78
- if selected_entry && selected_entry[:type] == 'file' && i >= 2
79
- # 🔥 問題: 以下が毎回実行される(38回!)
80
- preview_content = get_preview_content(selected_entry) # line 380
81
- wrapped_lines = TextUtils.wrap_preview_lines(preview_content, ...) # line 381
82
-
83
- # スクロールオフセットを適用
84
- scroll_offset = @keybind_handler&.preview_scroll_offset || 0
85
- display_line_index = i - 2 + scroll_offset
86
-
87
- if display_line_index < wrapped_lines.length
88
- line = wrapped_lines[display_line_index] || ''
89
- content_to_print = " #{line}"
90
- end
91
- end
92
-
93
- # ... 出力処理 ...
94
- end
95
- end
96
- ```
97
-
98
- ### 何が問題か
99
-
100
- 1. **ループの各イテレーション**(i = 2~39、計38回)で以下を実行:
101
- - `get_preview_content(selected_entry)` - ファイルプレビューを取得
102
- - `TextUtils.wrap_preview_lines(...)` - **全行**の折り返し処理
103
-
104
- 2. **TextUtils.wrap_preview_lines の重さ**:
105
- - 全てのプレビュー行(50行)をイテレート
106
- - 各行の全文字をイテレート
107
- - 各文字の表示幅を計算(日本語対応のため複雑)
108
-
109
- 3. **計算量**: O(height × lines × chars_per_line)
110
- - height = 40(画面の高さ)
111
- - lines = 50(プレビュー行数)
112
- - chars_per_line = 平均50文字
113
- - **合計**: 約76,000回の文字処理!
114
-
115
- ### なぜこのバグが発生したか
116
-
117
- **元の意図**: 各行を表示する際に対応するプレビュー行を取得
118
-
119
- **実装ミス**: ループの中で**毎回全体を計算**してしまった
120
-
121
- **正しい実装**: ループの**外で一度だけ計算**して、結果をキャッシュ
122
-
123
- ---
124
-
125
- ## 詳細なベンチマーク結果
126
-
127
- ### テスト環境
128
-
129
- - **プラットフォーム**: macOS (Apple Silicon)
130
- - **Ruby バージョン**: 3.4.2
131
- - **画面の高さ**: 40行(典型的な値)
132
-
133
- ### ベンチマーク1: 中規模ファイル(300行、5.2KB)
134
-
135
- | 処理ステップ | 時間 (ms) | 説明 |
136
- |-------------|-----------|------|
137
- | FilePreview.preview_file (単体) | 0.06 | ファイル読み込み+バイナリ検出 |
138
- | TextUtils.wrap_preview_lines (1回) | 0.23 | 折り返し処理(1回のみ) |
139
- | TextUtils.wrap_preview_lines (38回) | 8.3 | **ループ内で38回呼び出し** |
140
- | **現在の実装(バグあり)** | **8.7** | draw_file_preview全体 |
141
- | **修正後の実装** | **0.4** | ループ外で1回のみ計算 |
142
-
143
- **改善率**: 95.3% (8.7ms → 0.4ms)
144
- **高速化**: **21.2倍**
145
-
146
- ### ベンチマーク2: 大規模ファイル(500行、35KB)
147
-
148
- | 実装 | 時間 (ms) | 説明 |
149
- |------|-----------|------|
150
- | **現在の実装(バグあり)** | **35.3** | 38回の重複処理 |
151
- | **修正後の実装** | **1.6** | 1回のみ処理 |
152
-
153
- **改善率**: 95.4% (35.3ms → 1.6ms)
154
- **高速化**: **21.7倍**
155
-
156
- ### ベンチマーク3: 処理内訳(画面高さ40行の場合)
157
-
158
- ```
159
- 現在の実装:
160
- preview_content取得: 0.0ms × 38回 = 0.0ms
161
- wrap_preview_lines: 0.23ms × 38回 = 8.7ms ← ボトルネック!
162
- その他(描画等): 0.1ms
163
- 合計: 8.8ms
164
-
165
- 修正後の実装:
166
- preview_content取得: 0.0ms × 1回 = 0.0ms
167
- wrap_preview_lines: 0.23ms × 1回 = 0.23ms
168
- その他(描画等): 0.1ms
169
- 合計: 0.33ms
170
- ```
171
-
172
- ### ユーザー報告値との照合
173
-
174
- **報告値**: docs/medium_beniya.md で **80ms**
175
-
176
- **推定原因**:
177
- 1. より大きなファイル(数千行)
178
- 2. 複数回の再描画(キー入力ごとに再描画される可能性)
179
- 3. その他の処理(ディレクトリリスト描画など)
180
-
181
- **修正後の予想**: **1-3ms**(95%以上の改善)
182
-
183
- ---
184
-
185
- ## 修正方法
186
-
187
- ### 🔧 修正パッチ
188
-
189
- **ファイル**: `lib/rufio/terminal_ui.rb`
190
- **メソッド**: `draw_file_preview`
191
-
192
- #### Before(現在のバグコード)
193
-
194
- ```ruby
195
- def draw_file_preview(selected_entry, width, height, left_offset)
196
- (0...height).each do |i|
197
- line_num = i + CONTENT_START_LINE
198
- cursor_position = left_offset + CURSOR_OFFSET
199
- max_chars_from_cursor = @screen_width - cursor_position
200
- safe_width = [max_chars_from_cursor - 2, width - 2, 0].max
201
-
202
- print "\e[#{line_num};#{cursor_position}H"
203
- print '│'
204
-
205
- content_to_print = ''
206
-
207
- if selected_entry && i == 0
208
- header = " #{selected_entry[:name]} "
209
- header += "[PREVIEW MODE]" if @keybind_handler&.preview_focused?
210
- content_to_print = header
211
- elsif selected_entry && selected_entry[:type] == 'file' && i >= 2
212
- # 🔥 問題: ループ内で毎回実行
213
- preview_content = get_preview_content(selected_entry)
214
- wrapped_lines = TextUtils.wrap_preview_lines(preview_content, safe_width - 1)
215
-
216
- scroll_offset = @keybind_handler&.preview_scroll_offset || 0
217
- display_line_index = i - 2 + scroll_offset
218
-
219
- if display_line_index < wrapped_lines.length
220
- line = wrapped_lines[display_line_index] || ''
221
- content_to_print = " #{line}"
222
- else
223
- content_to_print = ' '
224
- end
225
- else
226
- content_to_print = ' '
227
- end
228
-
229
- # ... 出力処理 ...
230
- end
231
- end
232
- ```
233
-
234
- #### After(修正版)
235
-
236
- ```ruby
237
- def draw_file_preview(selected_entry, width, height, left_offset)
238
- # ✅ 修正: ループの外で一度だけ計算
239
- preview_content = nil
240
- wrapped_lines_cache = {}
241
-
242
- if selected_entry && selected_entry[:type] == 'file'
243
- preview_content = get_preview_content(selected_entry)
244
- end
245
-
246
- (0...height).each do |i|
247
- line_num = i + CONTENT_START_LINE
248
- cursor_position = left_offset + CURSOR_OFFSET
249
- max_chars_from_cursor = @screen_width - cursor_position
250
- safe_width = [max_chars_from_cursor - 2, width - 2, 0].max
251
-
252
- print "\e[#{line_num};#{cursor_position}H"
253
- print '│'
254
-
255
- content_to_print = ''
256
-
257
- if selected_entry && i == 0
258
- header = " #{selected_entry[:name]} "
259
- header += "[PREVIEW MODE]" if @keybind_handler&.preview_focused?
260
- content_to_print = header
261
- elsif preview_content && i >= 2
262
- # ✅ 修正: キャッシュから取得(幅が変わった時のみ再計算)
263
- unless wrapped_lines_cache[safe_width]
264
- wrapped_lines_cache[safe_width] = TextUtils.wrap_preview_lines(preview_content, safe_width - 1)
265
- end
266
- wrapped_lines = wrapped_lines_cache[safe_width]
267
-
268
- scroll_offset = @keybind_handler&.preview_scroll_offset || 0
269
- display_line_index = i - 2 + scroll_offset
270
-
271
- if display_line_index < wrapped_lines.length
272
- line = wrapped_lines[display_line_index] || ''
273
- content_to_print = " #{line}"
274
- else
275
- content_to_print = ' '
276
- end
277
- else
278
- content_to_print = ' '
279
- end
280
-
281
- # ... 出力処理(変更なし)...
282
- if safe_width <= 0
283
- next
284
- elsif TextUtils.display_width(content_to_print) > safe_width
285
- content_to_print = TextUtils.truncate_to_width(content_to_print, safe_width)
286
- end
287
-
288
- print content_to_print
289
-
290
- remaining_space = safe_width - TextUtils.display_width(content_to_print)
291
- print ' ' * remaining_space if remaining_space > 0
292
- end
293
- end
294
- ```
295
-
296
- ### 主な変更点
297
-
298
- 1. **ループ前にプレビューコンテンツを取得**(1回のみ)
299
- 2. **wrapped_lines_cache ハッシュでキャッシュ**(幅ごとに)
300
- 3. **ループ内ではキャッシュから取得**(計算不要)
301
-
302
- ### さらなる最適化(オプション)
303
-
304
- 現在、`safe_width`がループ内で各行ごとに同じ値になる場合が多いため、以下のようにさらに簡略化できます:
305
-
306
- ```ruby
307
- def draw_file_preview(selected_entry, width, height, left_offset)
308
- # 事前計算
309
- cursor_position = left_offset + CURSOR_OFFSET
310
- max_chars_from_cursor = @screen_width - cursor_position
311
- safe_width = [max_chars_from_cursor - 2, width - 2, 0].max
312
-
313
- # プレビューコンテンツとWrapped linesを一度だけ計算
314
- preview_content = nil
315
- wrapped_lines = nil
316
-
317
- if selected_entry && selected_entry[:type] == 'file'
318
- preview_content = get_preview_content(selected_entry)
319
- wrapped_lines = TextUtils.wrap_preview_lines(preview_content, safe_width - 1) if safe_width > 0
320
- end
321
-
322
- (0...height).each do |i|
323
- line_num = i + CONTENT_START_LINE
324
-
325
- print "\e[#{line_num};#{cursor_position}H"
326
- print '│'
327
-
328
- content_to_print = ''
329
-
330
- if selected_entry && i == 0
331
- header = " #{selected_entry[:name]} "
332
- header += "[PREVIEW MODE]" if @keybind_handler&.preview_focused?
333
- content_to_print = header
334
- elsif wrapped_lines && i >= 2
335
- scroll_offset = @keybind_handler&.preview_scroll_offset || 0
336
- display_line_index = i - 2 + scroll_offset
337
-
338
- if display_line_index < wrapped_lines.length
339
- line = wrapped_lines[display_line_index] || ''
340
- content_to_print = " #{line}"
341
- else
342
- content_to_print = ' '
343
- end
344
- else
345
- content_to_print = ' '
346
- end
347
-
348
- # 出力処理
349
- if safe_width <= 0
350
- next
351
- elsif TextUtils.display_width(content_to_print) > safe_width
352
- content_to_print = TextUtils.truncate_to_width(content_to_print, safe_width)
353
- end
354
-
355
- print content_to_print
356
-
357
- remaining_space = safe_width - TextUtils.display_width(content_to_print)
358
- print ' ' * remaining_space if remaining_space > 0
359
- end
360
- end
361
- ```
362
-
363
- ---
364
-
365
- ## 期待される改善効果
366
-
367
- ### 処理時間の改善
368
-
369
- | ファイルサイズ | 行数 | 現在 (ms) | 修正後 (ms) | 改善率 | 高速化 |
370
- |---------------|------|-----------|-------------|--------|--------|
371
- | 5KB | 300 | 8.7 | 0.4 | 95.3% | 21.2x |
372
- | 35KB | 500 | 35.3 | 1.6 | 95.4% | 21.7x |
373
- | 100KB | 1000 | ~95 | ~4 | 95.8% | 23.8x |
374
- | 1MB | 10000| ~950 | ~40 | 95.8% | 23.8x |
375
-
376
- ### ユーザー体験の改善
377
-
378
- #### Before(現在)
379
- ```
380
- 小規模ファイル: 8ms → 気にならない
381
- 中規模ファイル: 35ms → やや遅い
382
- 大規模ファイル: 95ms → 明らかに遅い ❌
383
- 超大規模: 950ms → 使用不可 ❌❌
384
- ```
385
-
386
- #### After(修正後)
387
- ```
388
- 小規模ファイル: 0.4ms → 瞬時 ✓
389
- 中規模ファイル: 1.6ms → 瞬時 ✓
390
- 大規模ファイル: 4ms → 快適 ✓
391
- 超大規模: 40ms → 許容範囲 ✓
392
- ```
393
-
394
- ### メモリ使用量
395
-
396
- **変化なし**(既に取得していたデータをキャッシュするだけ)
397
-
398
- ### その他の改善
399
-
400
- - カーソル移動時の反応速度が向上
401
- - スクロール時の滑らかさが向上
402
- - CPUスパイクの削減
403
-
404
- ---
405
-
406
- ## 実装ガイド
407
-
408
- ### ステップ1: バックアップ
409
-
410
- ```bash
411
- cp lib/rufio/terminal_ui.rb lib/rufio/terminal_ui.rb.backup
412
- ```
413
-
414
- ### ステップ2: 修正の適用
415
-
416
- 上記の「修正パッチ」を適用します。
417
-
418
- **推奨**: シンプルな方修正案(最適化版)を使用
419
-
420
- ### ステップ3: テスト
421
-
422
- #### 単体テスト
423
- ```bash
424
- # ベンチマークで確認
425
- ruby benchmark_actual_bottleneck.rb
426
- ```
427
-
428
- #### 統合テスト
429
- ```bash
430
- # 実際のアプリケーションで確認
431
- bin/rufio
432
-
433
- # 以下を確認:
434
- # 1. ファイルプレビューが正常に表示されるか
435
- # 2. スクロールが正常に動作するか
436
- # 3. 画面サイズ変更時に正常に動作するか
437
- # 4. 処理時間表示(右下)が改善されているか
438
- ```
439
-
440
- #### テストケース
441
- 1. **小規模ファイル**: README.md(通常のテキストファイル)
442
- 2. **中規模ファイル**: docs/*.md(数百行)
443
- 3. **大規模ファイル**: lib/rufio/*.rb全体(数千行)
444
- 4. **長い行**: JSONファイル、minifiedコード
445
- 5. **日本語**: 全角文字を含むファイル
446
-
447
- ### ステップ4: デプロイ
448
-
449
- ```bash
450
- # 問題なければコミット
451
- git add lib/rufio/terminal_ui.rb
452
- git commit -m "Fix critical performance bug in file preview
453
-
454
- - Move preview content and wrap_lines calculation outside loop
455
- - Reduces redundant processing from 38x to 1x per render
456
- - Performance improvement: 95% faster (21x speedup)
457
- - Fixes issue where large files caused 80ms+ rendering delay
458
-
459
- Before: 8.7ms (300 lines), 35.3ms (500 lines)
460
- After: 0.4ms (300 lines), 1.6ms (500 lines)"
461
- ```
462
-
463
- ### ステップ5: 監視
464
-
465
- 修正後、以下を監視:
466
- - ユーザーからのパフォーマンス報告
467
- - クラッシュレポート(もしあれば)
468
- - 画面描画の処理時間(右下の表示)
469
-
470
- ---
471
-
472
- ## 追加の最適化提案(Phase 2)
473
-
474
- 修正後もさらなる最適化が必要な場合:
475
-
476
- ### 1. インスタンス変数でキャッシュ
477
-
478
- ```ruby
479
- def draw_file_preview(selected_entry, width, height, left_offset)
480
- # 前回と同じエントリの場合はキャッシュを再利用
481
- if @cached_preview_entry == selected_entry && @cached_preview_width == safe_width
482
- wrapped_lines = @cached_wrapped_lines
483
- else
484
- preview_content = get_preview_content(selected_entry)
485
- wrapped_lines = TextUtils.wrap_preview_lines(preview_content, safe_width - 1)
486
-
487
- @cached_preview_entry = selected_entry
488
- @cached_preview_width = safe_width
489
- @cached_wrapped_lines = wrapped_lines
490
- end
491
-
492
- # ... 以下同様 ...
493
- end
494
- ```
495
-
496
- **期待効果**: カーソル移動時の再描画がさらに高速化(0.1ms未満)
497
-
498
- ### 2. TextUtils.wrap_preview_lines の最適化
499
-
500
- 現在の実装は各文字ごとに`display_width`を呼び出しています。
501
- 正規表現を使った一括処理に変更することで、さらに高速化可能。
502
-
503
- **期待効果**: 20-30%の追加改善
504
-
505
- ### 3. Zigネイティブ実装(Phase 3)
506
-
507
- TextUtils全体をZigで実装すれば、さらに2-3倍高速化可能。
508
-
509
- **期待効果**: 現在の0.4ms → 0.15ms
510
-
511
- ---
512
-
513
- ## 結論
514
-
515
- ### 発見された問題
516
-
517
- `terminal_ui.rb`の`draw_file_preview`メソッドに**致命的なバグ**が存在:
518
- - ループ内で毎回(38回)ファイルプレビューと折り返し処理を実行
519
- - 本来1回で済む処理を38回繰り返していた
520
-
521
- ### 影響範囲
522
-
523
- - 全てのテキストファイルプレビューが影響
524
- - 大規模ファイルで最大**950ms**の遅延
525
- - ユーザー報告の**80ms**遅延と一致
526
-
527
- ### 修正効果
528
-
529
- - **95%の改善**(21倍高速化)
530
- - 修正は**10行程度の変更**
531
- - リスク: 極めて低い(ロジックの改善のみ)
532
- - 工数: **30分以内**
533
-
534
- ### 推奨アクション
535
-
536
- 1. ✅ **即座に修正を適用**(最優先事項)
537
- 2. ✅ テストして問題ないことを確認
538
- 3. ✅ ユーザーにアップデートを提供
539
- 4. 🔄 Phase 2の最適化は必要に応じて実施
540
-
541
- ---
542
-
543
- **レポート作成日**: 2026-01-03
544
- **作成者**: Claude Sonnet 4.5
545
- **バージョン**: 2.0(根本原因特定版)
546
- **ステータス**: 🔴 Critical Bug Fixed
547
- **優先度**: ⚡ Highest - 即座に対応すべき
data/lib/rufio/plugin.rb DELETED
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rufio
4
- # プラグインを格納するモジュール
5
- module Plugins
6
- end
7
-
8
- # プラグインの基底クラス
9
- class Plugin
10
- # 依存gemが不足している場合に投げられるエラー
11
- class DependencyError < StandardError; end
12
-
13
- class << self
14
- # 継承時に自動的にPluginManagerに登録する
15
- def inherited(subclass)
16
- super
17
- PluginManager.register(subclass)
18
- end
19
-
20
- # 依存gemを宣言する
21
- def requires(*gems)
22
- @required_gems ||= []
23
- @required_gems.concat(gems)
24
- end
25
-
26
- # 宣言された依存gemのリストを取得する
27
- def required_gems
28
- @required_gems || []
29
- end
30
- end
31
-
32
- # 初期化時に依存gemをチェックする
33
- def initialize
34
- check_dependencies!
35
- end
36
-
37
- # プラグイン名(必須オーバーライド)
38
- def name
39
- raise NotImplementedError, "#{self.class}#name must be implemented"
40
- end
41
-
42
- # プラグインの説明(オプション)
43
- def description
44
- ""
45
- end
46
-
47
- # プラグインのバージョン(オプション)
48
- def version
49
- "1.0.0"
50
- end
51
-
52
- # コマンド定義(オプション)
53
- # { command_name: method(:method_name) } の形式で返す
54
- def commands
55
- {}
56
- end
57
-
58
- private
59
-
60
- # 依存gemが全て利用可能かチェックする
61
- def check_dependencies!
62
- required_gems = self.class.required_gems
63
- return if required_gems.empty?
64
-
65
- missing_gems = []
66
-
67
- required_gems.each do |gem_name|
68
- begin
69
- Gem::Specification.find_by_name(gem_name)
70
- rescue Gem::LoadError
71
- missing_gems << gem_name
72
- end
73
- end
74
-
75
- return if missing_gems.empty?
76
-
77
- # 不足しているgemがある場合はエラーを投げる
78
- error_message = <<~ERROR
79
- Plugin '#{name}' は以下のgemに依存していますが、インストールされていません:
80
- - #{missing_gems.join("\n - ")}
81
-
82
- 以下のコマンドでインストールしてください:
83
- gem install #{missing_gems.join(' ')}
84
- ERROR
85
-
86
- raise DependencyError, error_message
87
- end
88
- end
89
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml'
4
-
5
- module Rufio
6
- # プラグインの設定を管理するクラス
7
- class PluginConfig
8
- class << self
9
- # 設定ファイルを読み込む
10
- def load
11
- config_path = File.expand_path('~/.rufio/config.yml')
12
-
13
- if File.exist?(config_path)
14
- begin
15
- @config = YAML.load_file(config_path) || {}
16
- rescue StandardError => e
17
- warn "⚠️ Failed to load config file: #{e.message}"
18
- @config = {}
19
- end
20
- else
21
- # 設定ファイルが存在しない場合はデフォルト設定(空のハッシュ)
22
- @config = {}
23
- end
24
- end
25
-
26
- # プラグインが有効かどうかをチェックする
27
- def plugin_enabled?(name)
28
- # 設定が未読み込みの場合は読み込む
29
- load if @config.nil?
30
-
31
- # pluginsセクションがない場合は全プラグイン有効
32
- return true unless @config.is_a?(Hash) && @config['plugins']
33
-
34
- plugins_config = @config['plugins']
35
- return true unless plugins_config.is_a?(Hash)
36
-
37
- # プラグイン名を小文字に統一して検索
38
- normalized_name = name.to_s.downcase
39
-
40
- # 設定のキーも小文字に変換して検索
41
- plugin_setting = nil
42
- plugins_config.each do |key, value|
43
- if key.downcase == normalized_name
44
- plugin_setting = value
45
- break
46
- end
47
- end
48
-
49
- # 設定が存在しない場合は有効とみなす
50
- return true if plugin_setting.nil?
51
-
52
- # enabled設定を確認(デフォルトはtrue)
53
- return true unless plugin_setting.is_a?(Hash)
54
-
55
- plugin_setting.fetch('enabled', true)
56
- end
57
- end
58
- end
59
- end