rufio 1.1.0 → 1.4.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +26 -297
  4. data/README_ja.md +24 -261
  5. data/Rakefile +14 -1
  6. data/bin/rufio +31 -2
  7. data/docs/fast_zip_design_ja.md +167 -0
  8. data/docs/guide.md +410 -0
  9. data/docs/guide_ja.md +410 -0
  10. data/info/help.md +54 -17
  11. data/info/keybindings.md +50 -22
  12. data/info/welcome.md +14 -5
  13. data/lib/rufio/application.rb +12 -1
  14. data/lib/rufio/archive_controller.rb +211 -0
  15. data/lib/rufio/background_command_executor.rb +9 -0
  16. data/lib/rufio/background_status.rb +49 -0
  17. data/lib/rufio/bookmark_controller.rb +38 -121
  18. data/lib/rufio/bookmark_manager.rb +77 -331
  19. data/lib/rufio/command_completion.rb +28 -2
  20. data/lib/rufio/command_history.rb +5 -2
  21. data/lib/rufio/command_mode.rb +60 -134
  22. data/lib/rufio/command_mode_ui.rb +4 -42
  23. data/lib/rufio/config.rb +88 -136
  24. data/lib/rufio/config_loader.rb +4 -11
  25. data/lib/rufio/dialog_renderer.rb +90 -152
  26. data/lib/rufio/dialog_support.rb +69 -0
  27. data/lib/rufio/document_text_extractor.rb +167 -0
  28. data/lib/rufio/file_operation_controller.rb +49 -222
  29. data/lib/rufio/file_operations.rb +28 -6
  30. data/lib/rufio/file_preview.rb +46 -2
  31. data/lib/rufio/global_search.rb +125 -13
  32. data/lib/rufio/help_menu.rb +19 -7
  33. data/lib/rufio/interpreter_resolver.rb +6 -11
  34. data/lib/rufio/job_manager.rb +2 -5
  35. data/lib/rufio/key_input.rb +77 -0
  36. data/lib/rufio/keybind_handler.rb +83 -116
  37. data/lib/rufio/local_script_scanner.rb +6 -18
  38. data/lib/rufio/native/aarch64-linux-gnu/librufio_zig.so +0 -0
  39. data/lib/rufio/native/aarch64-linux-musl/librufio_zig.so +0 -0
  40. data/lib/rufio/native/aarch64-macos/librufio_zig.dylib +0 -0
  41. data/lib/rufio/native/aarch64-windows/rufio_zig.dll +0 -0
  42. data/lib/rufio/native/x86_64-linux-gnu/librufio_zig.so +0 -0
  43. data/lib/rufio/native/x86_64-linux-musl/librufio_zig.so +0 -0
  44. data/lib/rufio/native/x86_64-macos/librufio_zig.dylib +0 -0
  45. data/lib/rufio/native/x86_64-windows/rufio_zig.dll +0 -0
  46. data/lib/rufio/native_content_search.rb +153 -0
  47. data/lib/rufio/native_scanner.rb +13 -68
  48. data/lib/rufio/native_scanner_zig.rb +106 -56
  49. data/lib/rufio/native_zip.rb +121 -0
  50. data/lib/rufio/navigation_controller.rb +20 -26
  51. data/lib/rufio/path_jump.rb +55 -0
  52. data/lib/rufio/screen.rb +2 -12
  53. data/lib/rufio/script_executor.rb +6 -73
  54. data/lib/rufio/script_file.rb +44 -0
  55. data/lib/rufio/script_path_manager.rb +9 -43
  56. data/lib/rufio/script_runner.rb +13 -50
  57. data/lib/rufio/search_controller.rb +1 -1
  58. data/lib/rufio/shell_command_completion.rb +1 -5
  59. data/lib/rufio/shell_init.rb +90 -0
  60. data/lib/rufio/task_status.rb +5 -1
  61. data/lib/rufio/terminal_ui.rb +84 -298
  62. data/lib/rufio/text_utils.rb +26 -3
  63. data/lib/rufio/ui_renderer.rb +73 -45
  64. data/lib/rufio/version.rb +1 -1
  65. data/lib/rufio/zip_archive.rb +314 -0
  66. data/lib/rufio/zip_extractor.rb +346 -0
  67. data/lib/rufio/zip_writer.rb +268 -0
  68. data/lib/rufio/zoxide_integration.rb +5 -43
  69. data/lib/rufio.rb +6 -0
  70. data/lib_zig/rufio_native/Makefile +54 -17
  71. data/lib_zig/rufio_native/bench.sh +36 -0
  72. data/lib_zig/rufio_native/build.zig +32 -35
  73. data/lib_zig/rufio_native/src/cli.zig +99 -0
  74. data/lib_zig/rufio_native/src/main.zig +187 -83
  75. data/lib_zig/rufio_native/src/os.zig +889 -0
  76. data/lib_zig/rufio_native/src/search.zig +919 -0
  77. data/lib_zig/rufio_native/src/search_test.zig +88 -0
  78. data/lib_zig/rufio_native/src/zip.zig +482 -0
  79. metadata +128 -3
  80. data/lib/rufio/native/rufio_zig.bundle +0 -0
data/README_ja.md CHANGED
@@ -32,8 +32,10 @@ rufio は、ファイルを探すところからスクリプトの実行、結
32
32
  ### ファイルマネージャーとして
33
33
 
34
34
  - **Vimライクなキーバインド**: 直感的なナビゲーション
35
- - **リアルタイムプレビュー**: ファイル内容を即座に表示(`bat` によるシンタックスハイライト対応)
36
- - **高速検索**: fzf/rgaとの連携
35
+ - **リアルタイムプレビュー**: ファイル内容を即座に表示(`bat` によるシンタックスハイライト対応、`.zip` はエントリ一覧)
36
+ - **高速検索**: fzf/rgaとの連携に加え、PDF・Excel・PowerPointも読める全ブックマーク横断検索を内蔵
37
+ - **ZIPの展開・圧縮**: 選択したアイテムをバックグラウンドで展開・圧縮
38
+ - **パスへ移動**: コピーしたパスを `:cd` に貼り付けて移動
37
39
  - **ブックマーク**: よく使うディレクトリに素早くアクセス
38
40
  - **zoxide連携**: スマートなディレクトリ履歴
39
41
 
@@ -78,277 +80,38 @@ rufio /path/to # 指定したディレクトリで起動
78
80
 
79
81
  ## キーバインド
80
82
 
81
- ### 基本操作
83
+ よく使うキーです。すべてのキーとモードは **[操作ガイド](./docs/guide_ja.md)** を参照するか、rufio の中で `?` を押してください。
82
84
 
83
- | キー | 機能 |
85
+ | キー | 操作 |
84
86
  |------|------|
85
- | `j/k` | 上下移動 |
86
- | `h/l` | 親/子ディレクトリ |
87
- | `g/}` | 先頭/末尾 |
88
- | `Enter` | ディレクトリに入る/ファイルを開く |
89
- | `q` | 終了 |
90
-
91
- ### ファイル操作
92
-
93
- | キー | 機能 |
94
- |------|------|
95
- | `Space` | 選択/選択解除 |
96
- | `o` | 外部エディタで開く |
97
- | `a/A` | ファイル/ディレクトリ作成 |
98
- | `r` | リネーム |
99
- | `d` | 削除 |
100
- | `m/c/x` | 移動/コピー/削除(選択済み) |
101
-
102
- ### 検索・フィルター
103
-
104
- | キー | 機能 |
105
- |------|------|
106
- | `f` | フィルターモード |
107
- | `s` | fzfでファイル検索 |
108
- | `F` | rgaでファイル内容検索 |
109
-
110
- ### ナビゲーション
111
-
112
- | キー | 機能 |
113
- |------|------|
114
- | `b` | ブックマーク追加 |
115
- | `B` | ブックマークメニュー |
116
- | `0` | 起動ディレクトリに戻る |
117
- | `1-9` | ブックマークにジャンプ |
118
- | `Tab` | 次のブックマークへ循環移動(Filesモード限定) |
119
- | `z` | zoxide履歴 |
120
-
121
- ### ツールランタイム
122
-
123
- | キー | 機能 |
124
- |------|------|
125
- | `:` | コマンドモード |
126
- | `J` | ジョブモード |
127
- | `L` | 実行ログ表示 |
87
+ | `j/k`、`h/l` | 上下移動 / 親ディレクトリ / ディレクトリに入る |
88
+ | `g/}` | 先頭 / 末尾 |
89
+ | `P` | 全画面プレビュー |
90
+ | `Space` | 選択 |
91
+ | `m/c/x` | 選択したアイテムの移動 / コピー / 削除 |
92
+ | `z` | 選択したアイテムの展開・圧縮(`.zip`)。未選択時は zoxide 履歴 |
93
+ | `a/A`、`r`、`d` | ファイル / ディレクトリ作成、リネーム、削除 |
94
+ | `f`、`s`、`F` | フィルター、fzf でファイル名検索、rga で内容検索 |
95
+ | `G` | 全ブックマーク配下のファイル名・内容(PDF・Excel・PowerPoint も)を横断検索 |
96
+ | `b`、`B`、`1-9`、`Tab` | ブックマーク追加、メニュー、ジャンプ、次のブックマーク |
97
+ | `:` | コマンドモード(`@スクリプト`、`rake:タスク`、`!シェル`、`cd <パス>`) |
98
+ | `J`、`L`、`C` | ジョブ、ログ、設定ディレクトリ |
128
99
  | `?` | ヘルプ |
129
- | `G` | 全ブックマーク配下のファイル名・ファイル内容を横断検索 |
130
- | `C` | ~/.config/rufio/を開く(Configモード、操作はFilesと共通) |
131
- | `Shift+Tab` | モード切り替え(逆順) |
132
-
133
- ### Gモード(横断検索)
134
-
135
- `G` を押すと、ブックマークした全ディレクトリの配下を再帰的に検索します。
136
- Tabで **File names(ファイル名)** と **Content(ファイル内容)** を切り替えます。
137
- 大文字・小文字を区別しない文字列の部分一致で検索し、内容検索ではファイル名・行番号・一致した行を表示します。
138
- テキストはUTF-8として読み込み、先頭8 KBにNULバイトがあるファイルはバイナリとしてスキップします。
139
- 上下矢印または `Ctrl+N/P` で選択、
140
- Enterで選んだファイルの場所へ移動します。`Ctrl+U` で検索語をクリアし、
141
- Escで元の表示に戻ります。検索はバックグラウンドで進みます。
142
- 検索対象は `b` で登録したブックマークです。外部検索ツールは不要です。
143
- アクセスできないディレクトリはスキップし、ディレクトリのシンボリックリンクは辿りません。
144
- 検索中は黄色の `Searching...` と動くスピナーを表示します。
145
- 結果がまだ見つかっていない間も表示が動き、完了すると緑の `Search complete` に切り替わります。
146
-
147
- ファイル名検索・内容検索から特定の拡張子を除外するには、
148
- `~/.config/rufio/config.rb` に以下を追加してRufioを再起動します。
149
-
150
- ```ruby
151
- GLOBAL_SEARCH_EXCLUDED_EXTENSIONS = %w[.png .jpg .zip .tar.gz .log].freeze
152
- ```
153
-
154
- 大文字・小文字は区別せず、先頭のドットは省略できます。デフォルトは `[]`(拡張子の除外なし)です。
155
- ディレクトリ名には適用しません。
156
-
157
- ### Configモード(C)
158
-
159
- `C` で `~/.config/rufio/` を開きます。ディレクトリがなければ作成します。
160
- 移動・プレビュー・ファイル操作はFilesモードと共通で、設定やスクリプトを直接編集できます。
161
- 設定ディレクトリの外へ移動すると、モード表示がFilesに戻ります。
162
-
163
- ## コマンドモード
164
-
165
- `:` でコマンドモードを起動し、様々なコマンドを実行できます。
166
-
167
- ### スクリプト実行
168
-
169
- ```
170
- :@build # @で始まるとスクリプト補完
171
- :@deploy.rb # 登録済みスクリプトを実行
172
- ```
173
-
174
- ### シェルコマンド
175
-
176
- ```
177
- :!git status # !で始まるとシェルコマンド
178
- :!ls -la # バックグラウンドで実行
179
- ```
180
-
181
- ### 組み込みコマンド
182
-
183
- ```
184
- :hello # 挨拶メッセージ
185
- :stop # rufioを終了
186
- ```
187
-
188
- ## スクリプトパス
189
-
190
- ### スクリプトパスとは
191
-
192
- スクリプトファイルを配置するディレクトリを登録する機能です。登録したディレクトリ内のスクリプトは、コマンドモードで `@` プレフィックスを使って実行できます。
193
-
194
- ### 管理方法
195
-
196
- `B` → `3` でスクリプトパス管理メニューを開きます:
197
-
198
- - 登録済みパスの一覧表示
199
- - `d`: パスを削除
200
- - `Enter`: ディレクトリにジャンプ
201
- - `ESC`: メニューを閉じる
202
-
203
- ### 対応スクリプト
204
-
205
- | 拡張子 | 言語 |
206
- |--------|------|
207
- | `.rb` | Ruby |
208
- | `.py` | Python |
209
- | `.ps1` | PowerShell |
210
- | `.sh` | Shell (bash/zsh) |
211
-
212
- ## DSLコマンド
213
-
214
- `~/.config/rufio/commands.rb` でカスタムコマンドを定義できます:
215
-
216
- ```ruby
217
- command "hello" do
218
- ruby { "Hello from rufio!" }
219
- description "挨拶コマンド"
220
- end
221
-
222
- command "status" do
223
- shell "git status"
224
- description "Gitステータス"
225
- end
226
-
227
- command "build" do
228
- script "~/.config/rufio/scripts/build.rb"
229
- description "ビルド実行"
230
- end
231
- ```
232
-
233
- ## 設定
234
-
235
- ### 設定ファイル構成
236
-
237
- ```
238
- ~/.config/rufio/
239
- ├── config.rb # メイン設定(スクリプトパス、カラー等)
240
- ├── commands.rb # DSLコマンド定義
241
- ├── bookmarks.json # ブックマーク
242
- ├── scripts/ # スクリプトファイル
243
- └── logs/ # 実行ログ
244
- ```
245
-
246
- ### 設定例
247
-
248
- ```ruby
249
- # ~/.config/rufio/config.rb
250
-
251
- # スクリプトパス - スクリプトを配置するディレクトリ
252
- SCRIPT_PATHS = [
253
- '~/.config/rufio/scripts',
254
- '~/my-scripts',
255
- './scripts'
256
- ].freeze
257
-
258
- # カラー設定(HSL形式推奨)
259
- COLORS = {
260
- directory: { hsl: [220, 80, 60] },
261
- file: { hsl: [0, 0, 90] },
262
- executable: { hsl: [120, 70, 50] },
263
- selected: { hsl: [50, 90, 70] },
264
- preview: { hsl: [180, 60, 65] }
265
- }.freeze
266
- ```
100
+ | `q`、`T` | 終了、終了してシェルを現在のディレクトリへ移動 |
267
101
 
268
- ### ローカル設定
102
+ Gモードでは `Ctrl+J` / `Ctrl+K` で次/前の検索結果を選択し、`Enter` で開きます。
269
103
 
270
- プロジェクトルートに `rufio.rb` を配置すると、そのプロジェクト専用のスクリプトパスを設定できます:
271
-
272
- ```ruby
273
- # ./rufio.rb(プロジェクトルート)
274
- SCRIPT_PATHS = [
275
- './scripts',
276
- './bin'
277
- ].freeze
278
- ```
279
-
280
- ## 外部ツール連携
281
-
282
- rufioは以下の外部ツールと連携して機能を拡張します。
283
- すべて**オプション**です。インストールなしでも rufio は正常動作します。
284
-
285
- | ツール | 用途 | キー | 必須 |
286
- |--------|------|------|------|
287
- | fzf | ファイル名検索 | `s` | オプション |
288
- | rga | ファイル内容検索 | `F` | オプション |
289
- | zoxide | ディレクトリ履歴 | `z` | オプション |
290
- | bat | プレビューのシンタックスハイライト | — | オプション |
291
-
292
- ### bat — シンタックスハイライト
293
-
294
- `bat` をインストールすると、プレビューペインでコードファイルを開いた際に
295
- シンタックスハイライトが表示されます(Ruby、Python、Go、Rust、TypeScript など15言語以上対応)。
296
-
297
- ハイライトはバックグラウンドで非同期に読み込まれるため、大きなソースツリーでも
298
- カーソル移動が重くなりません。
299
-
300
- ```bash
301
- # macOS
302
- brew install bat
104
+ ## ドキュメント
303
105
 
304
- # Ubuntu/Debian
305
- apt install bat
306
- ```
307
-
308
- `rufio -c` を実行すると bat が正しく認識されているか確認できます。
309
-
310
- ### インストール(全ツール)
311
-
312
- ```bash
313
- # macOS
314
- brew install fzf bat rga zoxide
315
-
316
- # Ubuntu/Debian
317
- apt install fzf bat zoxide
318
- # rgaは別途インストール: https://github.com/phiresky/ripgrep-all
319
- ```
320
-
321
- ## 高度な機能
322
-
323
- ### ネイティブスキャナー(実験的)
324
-
325
- 高速なディレクトリスキャンのためのネイティブ実装をサポート:
326
-
327
- ```bash
328
- rufio --native # 自動検出
329
- rufio --native=zig # Zig実装
330
- ```
331
-
332
- ### JITコンパイラ
333
-
334
- ```bash
335
- rufio --yjit # Ruby 3.1+ YJIT
336
- rufio --zjit # Ruby 3.4+ ZJIT
337
- ```
338
-
339
- ### ヘルスチェック
340
-
341
- ```bash
342
- rufio -c # システム依存関係をチェック
343
- rufio --check-health # 同上
344
- ```
106
+ - **[操作ガイド](./docs/guide_ja.md)**: キー・モード・コマンドモード・スクリプト・DSL コマンド・設定・シェル連携・ネイティブエンジン
107
+ - **[CHANGELOG](./CHANGELOG.md)**: リリースノート
108
+ - **[SECURITY.md](./SECURITY.md)**: セキュリティ設計
345
109
 
346
110
  ## 開発
347
111
 
348
112
  ### 必要な環境
349
113
 
350
114
  - Ruby 2.7.0以上
351
- - io-console, pastel, tty-cursor, tty-screen gems
352
115
 
353
116
  ### 開発版の実行
354
117
 
data/Rakefile CHANGED
@@ -14,10 +14,23 @@ end
14
14
  # Default task
15
15
  task default: :test
16
16
 
17
+ # Zig native library tasks
18
+ namespace :native do
19
+ desc 'Cross-compile the Zig library for every bundled platform (requires zig)'
20
+ task :build do
21
+ sh 'make -C lib_zig/rufio_native dist'
22
+ end
23
+
24
+ desc 'Verify bundled Zig binaries for macOS, Linux and Windows'
25
+ task :check do
26
+ ruby '-I test -I lib test/test_native_platform_binaries.rb'
27
+ end
28
+ end
29
+
17
30
  # Gem build and push tasks
18
31
  namespace :gem do
19
32
  desc 'Build the gem file'
20
- task :build do
33
+ task :build => 'native:check' do
21
34
  puts "Building rufio v#{Rufio::VERSION}..."
22
35
 
23
36
  # Clean old gem files
data/bin/rufio CHANGED
@@ -19,6 +19,8 @@ start_directory = nil
19
19
  test_mode = false
20
20
  yjit_mode = false
21
21
  zjit_mode = false
22
+ choosedir = nil
23
+ shell_init = nil
22
24
  skip_next = false
23
25
 
24
26
  ARGV.each_with_index do |arg, idx|
@@ -49,6 +51,21 @@ ARGV.each_with_index do |arg, idx|
49
51
  end
50
52
  when /^--native=(rust|go|auto|ruby)$/
51
53
  native_mode = $1
54
+ when '--choosedir'
55
+ choosedir = ARGV[idx + 1]
56
+ skip_next = true
57
+ when /^--choosedir=(.+)$/
58
+ choosedir = $1
59
+ when '--shell-init'
60
+ # 次の引数がシェル名なら使い、なければ OS から判定する
61
+ if Rufio::ShellInit::SHELLS.include?(ARGV[idx + 1]) || ARGV[idx + 1] == 'pwsh'
62
+ shell_init = ARGV[idx + 1]
63
+ skip_next = true
64
+ else
65
+ shell_init = Rufio::ShellInit.detect
66
+ end
67
+ when /^--shell-init=(.+)$/
68
+ shell_init = $1
52
69
  when '-c', '--check-health', '--help', '-h', '--yjit', '--zjit'
53
70
  # これらは後で処理
54
71
  when /^--/
@@ -70,7 +87,15 @@ if ENV['RUFIO_NATIVE'] && defined?(Rufio::NativeScanner)
70
87
  end
71
88
 
72
89
  # コマンド処理
73
- if ARGV.include?('--check-health') || ARGV.include?('-c')
90
+ if shell_init
91
+ begin
92
+ print Rufio::ShellInit.script(shell_init)
93
+ exit(0)
94
+ rescue ArgumentError => e
95
+ warn e.message
96
+ exit(1)
97
+ end
98
+ elsif ARGV.include?('--check-health') || ARGV.include?('-c')
74
99
  # ヘルスチェック実行
75
100
  health_checker = Rufio::HealthChecker.new
76
101
  success = health_checker.run_check
@@ -87,6 +112,9 @@ elsif ARGV.include?('--help') || ARGV.include?('-h')
87
112
  puts " --test Show FPS counter in footer (for performance testing)"
88
113
  puts " --yjit Enable YJIT JIT compiler (Ruby 3.1+)"
89
114
  puts " --zjit Enable ZJIT JIT compiler (Ruby 3.4+)"
115
+ puts " --shell-init [SHELL] Print the rf shell function (cd on T quit)"
116
+ puts " SHELL: bash|zsh|fish|powershell (default: by OS)"
117
+ puts " --choosedir FILE Write the directory to FILE when quitting with T"
90
118
  if defined?(Rufio::NativeScanner)
91
119
  puts " --native[=MODE] Enable native scanner (experimental)"
92
120
  puts " MODE: auto|rust|go (default: auto)"
@@ -117,11 +145,12 @@ elsif ARGV.include?('--help') || ARGV.include?('-h')
117
145
  puts " RUFIO_NATIVE=rust rufio # Use Rust via environment variable"
118
146
  end
119
147
  puts " rufio -c # Check dependencies"
148
+ puts " eval \"$(rufio --shell-init)\" # Add rf to bash/zsh (T quits and cd)"
120
149
  exit(0)
121
150
  else
122
151
  start_directory ||= Dir.pwd
123
152
 
124
153
  # アプリケーション開始
125
- app = Rufio::Application.new(start_directory)
154
+ app = Rufio::Application.new(start_directory, choosedir: choosedir)
126
155
  app.run(test_mode: test_mode)
127
156
  end
@@ -0,0 +1,167 @@
1
+ # FastZip 実装検討(rufio 1.3)
2
+
3
+ ブランチ: `v1.3.0`
4
+ ステータス: Phase 1〜5 実装済み(Pure Ruby の一覧・展開・作成、`z` キー、Zig エンジンの並列展開・圧縮、8 プラットフォームのバイナリ同梱)
5
+
6
+ 「FastZip 開発構想メモ(Ruby × Zig ネイティブ拡張アーカイバ)」を rufio 1.3 の機能として取り込む前提で、既存コードとの整合と実装方針を整理する。
7
+
8
+ ---
9
+
10
+ ## 1. 結論
11
+
12
+ | 項目 | 構想メモ | 本検討での方針 |
13
+ |------|----------|----------------|
14
+ | 置き場所 | 独立Gem `fast_zip` | まず rufio 内の機能として実装。ABIが Ruby 非依存なので、後から Gem に切り出せる |
15
+ | Ruby との接続 | C拡張(`Init_fast_zip` + `rb_thread_call_without_gvl`) | **既存の流儀に合わせて Fiddle + Ruby ABI 非依存の C 関数** |
16
+ | GVL 解放 | 自前で `rb_thread_call_without_gvl` | Fiddle が既定で解放する(下記 2.3)。長時間処理は既存スキャナと同じ「ハンドル+ポーリング」型にしてキャンセル可能にする |
17
+ | ネイティブ非搭載環境 | 記載なし | **Pure Ruby 実装を必須のフォールバック兼リファレンス**とする(Phase 1 で実装済み) |
18
+ | 最初のユーザー価値 | — | `.zip` をプレビューペインで一覧表示(`unzip -l` 相当)※実装済み |
19
+
20
+ ### 1.1 決定事項(2026-10-31)
21
+ - C 拡張にはしない。既存の Zig 連携(Fiddle + Ruby ABI 非依存の C 関数)で実装する
22
+ - Pure Ruby 版は残す(ネイティブ非搭載環境のフォールバック兼、互換性の基準)
23
+ - 展開時の安全対策を入れる(4.2)
24
+ - ネイティブバイナリはビルド済みのものを Gem に同梱して配る(1.2 の `make dist` の 8 プラットフォーム)
25
+ - 操作は Files モードで選択後に `z`(すべて .zip なら展開、それ以外は圧縮)。暗号化エントリはスキップのまま
26
+ - 展開上限は現在の値(合計 16GiB / 10 万エントリ / 1MiB 超で 1000:1)で確定。対応方式は Stored と Deflate のみ
27
+
28
+ ---
29
+
30
+ ## 2. 現状調査(事実)
31
+
32
+ ### 2.1 rufio のアーカイブ対応
33
+ - `FilePreview#determine_file_type` は `.zip` 等を `archive` と判定するが、実際はバイナリ判定が先に走り「Binary file / Cannot preview」と表示されていた。
34
+ - 圧縮・展開機能はない。
35
+
36
+ ### 2.2 既存の Zig ネイティブ拡張(1.2.0 時点)
37
+ - ソース: `lib_zig/rufio_native/src/`(`main.zig` がエクスポート、`search.zig` がコンテンツ検索、`os.zig` が OS 差分の吸収)。Ruby ヘッダは include せず、`core_async_*` / `core_search_*` / `core_cache_*` という **Ruby ABI 非依存の C 関数**をエクスポートしている。
38
+ - Ruby 側: `lib/rufio/native_scanner_zig.rb` が `Fiddle::Importer` でロード。関数グループごとに `search_available?` のような判定があり、古いバイナリでも Ruby 実装に切り替わる。
39
+ - ビルド: Zig 0.17。`make -C lib_zig/rufio_native dist` で macOS(arm64/x86_64)・Linux(x86_64/aarch64、glibc 2.17+ と musl)・Windows(x64/arm64)の 8 種類をクロスコンパイルし、`lib/rufio/native/<platform>/` に同梱する。
40
+ - `test/test_native_platform_binaries.rb` が各バイナリの形式・CPU・必須エクスポート(`REQUIRED_EXPORTS`)を検証している。
41
+ - 1.2.0 で `rubyzip`(`>= 2.4, < 3.0`)が G モードの `.pptx` 読み取り用に依存関係に入った。ただし rubyzip 2.4 はファイル名を ASCII-8BIT で保持し、UTF-8 文字列で引けない(このブランチのテストで確認)。プレビュー・展開・作成は本実装を使い、rubyzip は相互運用テストの相手として使う。
42
+
43
+ ### 2.3 Fiddle と GVL
44
+ - 本環境の fiddle 1.1.8 の `ext/fiddle/function.c` では、`need_gvl` の既定値が `Qfalse` で、その場合 `rb_thread_call_without_gvl` 経由で関数を呼ぶ。`Fiddle::Importer#extern` は `need_gvl` を指定しないので、**既存の Zig 関数呼び出しは GVL を解放して実行されている**。
45
+ - したがって構想メモの「GVL 解放のための C 拡張化」は、Fiddle 経由でも同じ効果が得られる。C 拡張にすると Ruby のマイナーバージョンごとにバイナリが必要になるデメリットの方が大きい。
46
+ - ただし Fiddle の GVL 解放ではキャンセル(unblock 関数)が指定できない。TUI から中断できるようにするため、展開・圧縮は既存スキャナと同じ非同期ハンドル方式を採る。
47
+
48
+ ### 2.4 Ruby 標準 zlib でできること
49
+ - 構想メモの「zlib は ZIP 構造を扱えない」は正しいが、ZIP のコンテナ構造(EOCD / Central Directory / Local Header)は数百行の Ruby で扱える(Phase 1 で実装)。
50
+ - `Zlib::Inflate.new(-Zlib::MAX_WBITS)` / `Zlib::Deflate.new(level, -Zlib::MAX_WBITS)` で raw DEFLATE を扱え、`Zlib.crc32` もある。つまり **Pure Ruby + zlib だけで Store / Deflate の展開・作成まで実装できる**。ネイティブ実装は「速度のための加速器」という位置づけになる。
51
+
52
+ ---
53
+
54
+ ## 3. 実装内容(このブランチ)
55
+
56
+ ### 3.1 `Rufio::ZipArchive`(`lib/rufio/zip_archive.rb`)
57
+ 読み取り専用。ファイル本体は読まず、末尾と Central Directory だけを読む。
58
+
59
+ - EOCD の後方探索(末尾 22 + 65535 バイト)。コメント長が整合し、かつ指す先に Central Directory シグネチャがある候補のみ採用(コメント内の偽シグネチャ対策)
60
+ - ZIP64(ZIP64 EOCD Locator / ZIP64 EOCD / ZIP64 拡張フィールド 0x0001)
61
+ - ファイル名のエンコーディング: 汎用フラグ bit 11 → UTF-8、Info-ZIP Unicode Path 拡張(0x7075、CRC 一致時のみ)、UTF-8 として不正なら CP932(Windows 日本語環境で作られた ZIP)として解釈、それも失敗したら置換文字
62
+ - DOS 日時の変換、ディレクトリ・暗号化フラグの判定
63
+ - `limit:` で先頭 N 件だけ読む(プレビュー用。総数は EOCD から得る)
64
+ - 範囲外オフセット・切り詰められたファイルは `ZipArchive::FormatError`
65
+ - マルチディスク(分割)ZIP は非対応
66
+
67
+ ### 3.2 プレビュー統合
68
+ - `FilePreview#preview_file` で拡張子 `.zip` または先頭が `PK\x03\x04` / `PK\x05\x06` のファイルを `type: "archive"` として一覧表示(最大 200 件、超過分は件数のみ)
69
+ - 壊れた ZIP は `Invalid ZIP archive: <理由>` をエラー表示
70
+ - `UIRenderer#extract_preview_lines` が `archive` を表示対象に追加
71
+
72
+ ### 3.3 テスト(`test/test_zip_archive.rb`)
73
+ フィクスチャはテスト内の最小 ZIP ライタで生成し、外部コマンドに依存しない。加えて `zip` / `unzip` がある環境では `unzip -Z1` の一覧と完全一致することを確認する(ない環境では skip)。
74
+
75
+ ### 3.4 計測(参考値)
76
+ 環境: このクラウドコンテナ(Linux x86_64, Ruby 3.3.6)、`zip -qr` で作成した 20,001 エントリ / 3.2MB の ZIP。各 5 回平均。
77
+
78
+ | 処理 | 時間 |
79
+ |------|------|
80
+ | `ZipArchive.list`(全 20,001 件) | 176 ms |
81
+ | `ZipArchive.list(limit: 200)` | 1.9 ms |
82
+ | `FilePreview#preview_file`(200 件表示) | 1.9 ms |
83
+ | 参考: `unzip -l`(全件を標準出力へ、`time` の real) | 55 ms |
84
+
85
+ プレビュー用途では Pure Ruby で十分速い。ネイティブ化の効果が出るのは「全件走査」と「展開・圧縮(データ本体の処理)」である。
86
+
87
+ ---
88
+
89
+ ### 3.5 Phase 2: 展開・作成(Pure Ruby)
90
+ - `Rufio::ZipExtractor`(`lib/rufio/zip_extractor.rb`): Stored / Deflate の展開。安全対策は 4.2 をすべて実装。書き込み前に全エントリを検証し、危険なエントリが 1 件でもあれば何も書かない。進捗コールバックとキャンセルに対応。
91
+ - `Rufio::ZipWriter`(`lib/rufio/zip_writer.rb`): ファイル・ディレクトリから ZIP を作成。UTF-8 名、縮まないファイルは Stored、ZIP64(4GiB 超・65535 件超)、シンボリックリンクと出力先自身はスキップ、一時ファイル経由で失敗時に残骸を残さない。
92
+ - テスト: `test/test_zip_extractor.rb` / `test/test_zip_writer.rb`。`zip` コマンド製(データディスクリプタ付きを含む)の展開、作成物の `unzip -t` 通過、rubyzip での読み取り、70,000 件での ZIP64 を確認。
93
+
94
+ ## 4. 計画
95
+
96
+ | Phase | 内容 | 実装 | 備考 |
97
+ |-------|------|------|------|
98
+ | 2 | Store / Deflate の展開・作成 | Pure Ruby + zlib | **実装済み**。ネイティブ版のテストオラクルにする |
99
+ | 3 | Zig コア(展開・圧縮のデータ処理) | Zig + Fiddle | **実装済み**。同じテストを両実装で回す |
100
+ | 4 | 並列展開・並列圧縮 | Zig(ワーカースレッド) | **実装済み**。最大 16 スレッド、既定は CPU 数 |
101
+ | 5 | 配布・ベンチマーク | — | **実装済み**。`make dist` で 8 プラットフォーム分を同梱、`REQUIRED_EXPORTS` に `core_zip_*` を追加 |
102
+
103
+ ### 4.1 TUI への組み込み(実装済み)
104
+ - Files モードで `Space` で選択してから `z`。選択がすべて `.zip` なら展開、それ以外は圧縮(`lib/rufio/archive_controller.rb`)
105
+ - 展開: 確認ダイアログの後、各 ZIP を現在のディレクトリの `<名前>/` に展開(既にあれば `<名前> 2/` …)。失敗・キャンセル時は作った展開先を削除する
106
+ - 圧縮: アーカイブ名を入力(空 Enter で 1 件なら `<名前>.zip`、複数なら `Archive.zip`)。同名ファイルがあればエラー通知
107
+ - `..` は対象外
108
+ - 選択がなければ `z` は従来どおり zoxide(キー設定 `zoxide` を共用)
109
+ - 処理は `JobManager` のジョブとしてバックグラウンドスレッドで実行。ジョブ名に 5% 刻みで進捗を表示し、Jobs モードのキャンセルで中断できる。完了時に通知し、表示中のディレクトリなら一覧を再読み込みする
110
+ - 暗号化エントリはスキップ(決定事項)
111
+
112
+ ### 4.2 安全性要件(実装済み。Ruby と Zig で共通)
113
+ - **Zip Slip**: `..`、絶対パス、Windows のドライブレター・`\` 区切りを含むエントリを拒否し、展開先ディレクトリ外に書かない
114
+ - **シンボリックリンク**: 外部属性で symlink を示すエントリは展開しない。展開先に既にあるシンボリックリンクも辿らない
115
+ - **ZIP 爆弾**: 展開後合計サイズ・圧縮率・エントリ数の上限。Central Directory 上のサイズを信用せず、実際に書いたバイト数で打ち切る。Local Header の範囲が重なるエントリ(重複参照型の爆弾)を拒否
116
+ - **CRC-32 検証**: 不一致なら展開したファイルを削除してエラー
117
+ - **既存ファイルの上書き**: 既定は拒否(`overwrite: true` で上書き)。UI では確認ダイアログを出す想定
118
+ - **ファイルモード**: 実行権限だけを引き継ぎ、setuid / setgid / sticky と他者書き込み権限は付けない
119
+ - 暗号化エントリ(ZipCrypto / AES)は初期は非対応としてスキップ
120
+
121
+ ### 4.3 Zig エンジン(実装済み)
122
+ `lib_zig/rufio_native/src/zip.zig`。役割分担は次のとおりで、安全性の検証は Ruby 実装と共通にした。
123
+
124
+ | 処理 | 担当 |
125
+ |------|------|
126
+ | Central Directory の解析、パス正規化、上限・重複範囲・シンボリックリンクの検証、ディレクトリ作成 | Ruby |
127
+ | ZIP 内のデータ範囲を読み、伸長し、宣言サイズ超過で打ち切り、CRC-32 を検証して一時ファイルに書く | Zig(エントリ単位で並列) |
128
+ | 一時ファイルの rename、権限・日時の設定、失敗時の一時ファイル削除 | Ruby |
129
+ | ファイルを raw DEFLATE で一時ファイルへ圧縮し、CRC-32 とサイズを返す(1MiB 以上のファイルのみ) | Zig(ファイル単位で並列) |
130
+ | ローカルヘッダ・Central Directory・ZIP64 を書いてアーカイブを組み立てる | Ruby |
131
+
132
+ - エクスポート: `core_zip_extract_create` / `core_zip_deflate_create` / `core_zip_state` / `core_zip_progress` / `core_zip_status` / `core_zip_deflate_result` / `core_zip_cancel` / `core_zip_destroy`
133
+ - Ruby 側は `NativeScannerZigFFI.zip_available?` で関数グループの有無を判定し、ない場合(古いバイナリ)や `RUFIO_NATIVE_ZIP=0` では Pure Ruby を使う
134
+ - DEFLATE は Zig 標準ライブラリの `std.compress.flate`、CRC-32 は `std.hash.Crc32`。libdeflate は同梱していない
135
+ - ファイル操作は `os.zig` に追加(Linux は直接システムコール、macOS は libc、Windows は Win32。位置指定読み込み・排他作成・書き込み)
136
+ - `flate.Compress` は約 230KB あり初期化時にスタックへ載るため、ワーカースレッドのスタックは 4MB
137
+ - 1MiB 未満のファイルの圧縮は Ruby の zlib で行う。小さいファイルを Zig に渡すと、一時ファイルの作成・コピー・削除の分だけ遅くなった(2 万ファイルで 0.98 秒 → 5.26 秒)ため
138
+ - テスト: `test/test_zip_extractor.rb` / `test/test_zip_writer.rb` の全テストを `TestZipExtractorNative` / `TestZipWriterNative` で Zig 実装でも実行。Debug ビルド(安全チェック有効)でも全件通過を確認
139
+
140
+ ### 4.4 計測(このクラウドコンテナ、4 CPU、Linux x86_64、ReleaseFast、各 3 回の最小値)
141
+
142
+ | データ | 処理 | Ruby | Zig | zip / unzip |
143
+ |--------|------|------|-----|-------------|
144
+ | 8 ファイル × 64MB のテキスト | 圧縮 | 15.36 秒 | 3.34 秒 | 20.20 秒 |
145
+ | 同上 | 展開 | 3.03 秒 | 0.94 秒 | 4.11 秒 |
146
+ | 2 万ファイル・計 79MB | 圧縮 | 0.93 秒 | 0.90 秒 | 0.30 秒 |
147
+ | 同上 | 展開 | 5.82 秒 | 5.00 秒 | 4.62 秒 |
148
+
149
+ - 圧縮後サイズは Zig の方がやや大きい(512MB → Ruby/zlib 67MB、Zig 71MB)。同じレベル指定でも実装が異なるため
150
+ - 小さいファイル大量の展開はファイル作成が律速で、計測ごとのばらつきが大きい(`sync` を挟まない計測では unzip 1.4〜1.8 秒、Ruby/Zig 3〜6 秒)。ファイルごとの Ruby 側処理(rename・chmod・utime)が残るため、unzip との差は縮め切れていない
151
+
152
+ ## 5. 技術的な注意点(構想メモへのコメント)
153
+
154
+ - **libdeflate はストリーミング API を持たない**(バッファ全体を一度に処理する設計)。巨大エントリを扱うにはエントリ全体をメモリに載せるか mmap が必要。メモリ上限の設計とセットで検討する。ライセンスは MIT。
155
+ - **CRC-32**: 自前の SIMD 実装より、libdeflate の `libdeflate_crc32`(CPU 機能に応じた高速版を内部で選択)を使う方が保守コストが低い。
156
+ - **mmap**: POSIX と Windows で API が異なる。Windows 対応を続けるなら抽象化が必要(rufio は Windows 対応の修正を継続的に入れている)。
157
+ - **Zig のバージョン固定**: 既存 `build.zig` は `addExecutable(.{ .root_source_file = ... })` の書式。Zig はリリースごとにビルド API・標準ライブラリ(`std.compress.flate` 含む)が変わっているため、採用前に対象バージョンを決めて動作確認する必要がある(本検討では未確認)。
158
+ - **圧縮率**: 構想メモの通り DEFLATE は 32KB 窓・エントリ単位圧縮なので 7z には勝てない。比較軸は速度と OS 標準互換性に絞る。
159
+
160
+ ---
161
+
162
+ ## 6. 要判断事項
163
+ 1. **独立 Gem 化のタイミング**: rufio 内で安定させてからか、`fast_zip` として切り出すか
164
+
165
+ ## 7. 未確認事項
166
+ - Zig 版は Linux x86_64(このコンテナ)でのみ実行・計測した。macOS / Windows / aarch64 向けはクロスコンパイルとバイナリ形式・エクスポートの検証(`rake native:check`)までで、実機では動かしていない。特に Windows のファイル操作(`CreateFileW` / 位置指定 `ReadFile`)は未実行
167
+ - 計測は 1 環境・1 種類のデータのみ。CPU 数やストレージによって倍率は変わる