rufio 0.33.0 → 0.34.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.
@@ -0,0 +1,205 @@
1
+ const std = @import("std");
2
+ const c = @cImport({
3
+ @cInclude("dirent.h");
4
+ @cInclude("sys/stat.h");
5
+ @cInclude("string.h");
6
+ @cInclude("stdlib.h");
7
+ });
8
+
9
+ /// ディレクトリエントリ情報(内部保持用)
10
+ const DirEntry = struct {
11
+ name: []u8,
12
+ is_dir: bool,
13
+ size: u64,
14
+ mtime: i64,
15
+ executable: bool,
16
+ hidden: bool,
17
+
18
+ fn deinit(self: *DirEntry) void {
19
+ c.free(self.name.ptr);
20
+ }
21
+ };
22
+
23
+ /// スキャナーコア(状態を保持)
24
+ const Scanner = struct {
25
+ entries: []DirEntry,
26
+ capacity: usize,
27
+ count: usize,
28
+
29
+ fn init() !*Scanner {
30
+ const self = @as(?*Scanner, @ptrCast(@alignCast(c.malloc(@sizeOf(Scanner))))) orelse return error.OutOfMemory;
31
+ self.*.capacity = 64;
32
+ const entries_size = self.*.capacity * @sizeOf(DirEntry);
33
+ const entries_ptr = c.malloc(entries_size) orelse {
34
+ c.free(self);
35
+ return error.OutOfMemory;
36
+ };
37
+ self.*.entries = @as([*]DirEntry, @ptrCast(@alignCast(entries_ptr)))[0..self.*.capacity];
38
+ self.*.count = 0;
39
+ return self;
40
+ }
41
+
42
+ fn deinit(self: *Scanner) void {
43
+ // 各エントリの名前を解放
44
+ for (self.entries[0..self.count]) |*entry| {
45
+ entry.deinit();
46
+ }
47
+ // エントリ配列を解放
48
+ c.free(self.entries.ptr);
49
+ // 自身を解放
50
+ c.free(self);
51
+ }
52
+
53
+ fn clear(self: *Scanner) void {
54
+ // 既存のエントリをクリア
55
+ for (self.entries[0..self.count]) |*entry| {
56
+ entry.deinit();
57
+ }
58
+ self.count = 0;
59
+ }
60
+
61
+ fn ensureCapacity(self: *Scanner, needed: usize) !void {
62
+ if (needed <= self.capacity) return;
63
+
64
+ const new_capacity = @max(needed, self.capacity * 2);
65
+ const new_size = new_capacity * @sizeOf(DirEntry);
66
+ const new_ptr = c.realloc(self.entries.ptr, new_size) orelse return error.OutOfMemory;
67
+ self.entries = @as([*]DirEntry, @ptrCast(@alignCast(new_ptr)))[0..new_capacity];
68
+ self.capacity = new_capacity;
69
+ }
70
+
71
+ fn scan(self: *Scanner, path: [*:0]const u8, max_entries: usize) !void {
72
+ // 既存のエントリをクリア
73
+ self.clear();
74
+
75
+ const dir = c.opendir(path) orelse return error.CannotOpenDir;
76
+ defer _ = c.closedir(dir);
77
+
78
+ const path_len = c.strlen(path);
79
+ const path_slice = path[0..path_len];
80
+
81
+ while (c.readdir(dir)) |entry| {
82
+ // エントリ数制限チェック
83
+ if (max_entries > 0 and self.count >= max_entries) break;
84
+
85
+ const name_ptr = @as([*:0]const u8, @ptrCast(&entry.*.d_name));
86
+ const name_len = c.strlen(name_ptr);
87
+ const name_slice = name_ptr[0..name_len];
88
+
89
+ // "." と ".." をスキップ
90
+ if (std.mem.eql(u8, name_slice, ".") or std.mem.eql(u8, name_slice, "..")) {
91
+ continue;
92
+ }
93
+
94
+ // 容量確保
95
+ try self.ensureCapacity(self.count + 1);
96
+
97
+ // フルパスを構築
98
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
99
+ const full_path = std.fmt.bufPrintZ(&path_buf, "{s}/{s}", .{ path_slice, name_slice }) catch continue;
100
+
101
+ // ファイル情報を取得
102
+ var stat_buf: c.struct_stat = undefined;
103
+ if (c.lstat(full_path.ptr, &stat_buf) != 0) {
104
+ continue;
105
+ }
106
+
107
+ // 名前をコピー
108
+ const name_copy_ptr = c.malloc(name_len + 1) orelse return error.OutOfMemory;
109
+ const name_copy_bytes = @as([*]u8, @ptrCast(@alignCast(name_copy_ptr)));
110
+ const name_copy = name_copy_bytes[0..name_len];
111
+ @memcpy(name_copy, name_slice);
112
+ name_copy_bytes[name_len] = 0;
113
+
114
+ // エントリを追加
115
+ self.entries[self.count] = DirEntry{
116
+ .name = name_copy,
117
+ .is_dir = (stat_buf.st_mode & c.S_IFMT) == c.S_IFDIR,
118
+ .size = @intCast(stat_buf.st_size),
119
+ .mtime = @intCast(stat_buf.st_mtimespec.tv_sec),
120
+ .executable = (stat_buf.st_mode & c.S_IXUSR) != 0,
121
+ .hidden = name_slice[0] == '.',
122
+ };
123
+ self.count += 1;
124
+ }
125
+ }
126
+ };
127
+
128
+ // ============================================================
129
+ // ABI Boundary: Ruby ABI非依存・ハンドルベース
130
+ // ============================================================
131
+
132
+ /// スキャナーを作成(ハンドル返却)
133
+ export fn core_create() u64 {
134
+ const scanner = Scanner.init() catch return 0;
135
+ return @intFromPtr(scanner);
136
+ }
137
+
138
+ /// ディレクトリをスキャン
139
+ export fn core_scan(handle: u64, path: [*:0]const u8) i32 {
140
+ if (handle == 0) return -1;
141
+
142
+ const scanner = @as(*Scanner, @ptrFromInt(handle));
143
+ scanner.scan(path, 0) catch return -1;
144
+ return 0;
145
+ }
146
+
147
+ /// 高速スキャン(エントリ数制限付き)
148
+ export fn core_scan_fast(handle: u64, path: [*:0]const u8, max_entries: usize) i32 {
149
+ if (handle == 0) return -1;
150
+
151
+ const scanner = @as(*Scanner, @ptrFromInt(handle));
152
+ scanner.scan(path, max_entries) catch return -1;
153
+ return 0;
154
+ }
155
+
156
+ /// エントリ数を取得
157
+ export fn core_get_count(handle: u64) usize {
158
+ if (handle == 0) return 0;
159
+
160
+ const scanner = @as(*Scanner, @ptrFromInt(handle));
161
+ return scanner.count;
162
+ }
163
+
164
+ /// 指定インデックスのエントリ名を取得
165
+ export fn core_get_name(handle: u64, index: usize, buf: [*]u8, buf_size: usize) usize {
166
+ if (handle == 0) return 0;
167
+
168
+ const scanner = @as(*Scanner, @ptrFromInt(handle));
169
+ if (index >= scanner.count) return 0;
170
+
171
+ const entry = &scanner.entries[index];
172
+ const copy_len = @min(entry.name.len, buf_size - 1);
173
+ @memcpy(buf[0..copy_len], entry.name[0..copy_len]);
174
+ buf[copy_len] = 0;
175
+ return copy_len;
176
+ }
177
+
178
+ /// 指定インデックスのエントリ属性を取得
179
+ export fn core_get_attrs(handle: u64, index: usize, is_dir: *u8, size: *u64, mtime: *i64, executable: *u8, hidden: *u8) i32 {
180
+ if (handle == 0) return -1;
181
+
182
+ const scanner = @as(*Scanner, @ptrFromInt(handle));
183
+ if (index >= scanner.count) return -1;
184
+
185
+ const entry = &scanner.entries[index];
186
+ is_dir.* = if (entry.is_dir) 1 else 0;
187
+ size.* = entry.size;
188
+ mtime.* = entry.mtime;
189
+ executable.* = if (entry.executable) 1 else 0;
190
+ hidden.* = if (entry.hidden) 1 else 0;
191
+ return 0;
192
+ }
193
+
194
+ /// スキャナーを破棄
195
+ export fn core_destroy(handle: u64) void {
196
+ if (handle == 0) return;
197
+
198
+ const scanner = @as(*Scanner, @ptrFromInt(handle));
199
+ scanner.deinit();
200
+ }
201
+
202
+ /// バージョン情報
203
+ export fn core_version() [*:0]const u8 {
204
+ return "3.0.0-handle-based";
205
+ }
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.33.0
4
+ version: 0.34.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - masisz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-03 00:00:00.000000000 Z
11
+ date: 2026-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -129,7 +129,7 @@ files:
129
129
  - docs/CHANGELOG_v0.30.0.md
130
130
  - docs/CHANGELOG_v0.31.0.md
131
131
  - docs/CHANGELOG_v0.32.0.md
132
- - docs/CHANGELOG_v0.33.0.md
132
+ - docs/CHANGELOG_v0.34.0.md
133
133
  - docs/CHANGELOG_v0.4.0.md
134
134
  - docs/CHANGELOG_v0.5.0.md
135
135
  - docs/CHANGELOG_v0.6.0.md
@@ -143,6 +143,8 @@ files:
143
143
  - info/welcome.md
144
144
  - lib/rufio.rb
145
145
  - lib/rufio/application.rb
146
+ - lib/rufio/async_scanner_fiber.rb
147
+ - lib/rufio/async_scanner_promise.rb
146
148
  - lib/rufio/background_command_executor.rb
147
149
  - lib/rufio/bookmark.rb
148
150
  - lib/rufio/bookmark_manager.rb
@@ -164,11 +166,10 @@ files:
164
166
  - lib/rufio/info_notice.rb
165
167
  - lib/rufio/keybind_handler.rb
166
168
  - lib/rufio/logger.rb
167
- - lib/rufio/native/rufio_native.bundle
168
169
  - lib/rufio/native/rufio_zig.bundle
169
170
  - lib/rufio/native_scanner.rb
170
- - lib/rufio/native_scanner_magnus.rb
171
171
  - lib/rufio/native_scanner_zig.rb
172
+ - lib/rufio/parallel_scanner.rb
172
173
  - lib/rufio/plugin.rb
173
174
  - lib/rufio/plugin_config.rb
174
175
  - lib/rufio/plugin_manager.rb
@@ -183,14 +184,10 @@ files:
183
184
  - lib/rufio/text_utils.rb
184
185
  - lib/rufio/version.rb
185
186
  - lib/rufio/zoxide_integration.rb
186
- - lib_rust/rufio_native/.cargo/config.toml
187
- - lib_rust/rufio_native/Cargo.lock
188
- - lib_rust/rufio_native/Cargo.toml
189
- - lib_rust/rufio_native/build.rs
190
- - lib_rust/rufio_native/src/lib.rs
191
187
  - lib_zig/rufio_native/Makefile
192
188
  - lib_zig/rufio_native/build.zig
193
189
  - lib_zig/rufio_native/src/main.zig
190
+ - lib_zig/rufio_native/src/main.zig.sync
194
191
  - publish_gem.zsh
195
192
  - retag.sh
196
193
  - test_delete/test1.txt
Binary file
@@ -1,194 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rufio
4
- # Magnus拡張のラッパー
5
- # FFIを使わずに直接Rubyオブジェクトとして扱える
6
- module NativeScannerMagnusLoader
7
- LIB_PATH = File.expand_path('native/rufio_native.bundle', __dir__)
8
-
9
- @loaded = false
10
- @available = false
11
-
12
- class << self
13
- def load!
14
- return @available if @loaded
15
-
16
- @loaded = true
17
-
18
- if File.exist?(LIB_PATH)
19
- begin
20
- # .bundleファイルを直接ロード
21
- # 拡張子を外してrequireする
22
- lib_path_without_ext = LIB_PATH.sub(/\.bundle$/, '')
23
- require lib_path_without_ext
24
- @available = defined?(Rufio::NativeScannerMagnus)
25
- rescue LoadError => e
26
- warn "Failed to load magnus extension: #{e.message}" if ENV['RUFIO_DEBUG']
27
- @available = false
28
- end
29
- else
30
- @available = false
31
- end
32
-
33
- @available
34
- end
35
-
36
- def available?
37
- load! unless @loaded
38
- @available
39
- end
40
- end
41
- end
42
-
43
- # Magnus拡張が利用可能な場合のみロード
44
- if NativeScannerMagnusLoader.load!
45
- # NativeScannerにmagnusモードを追加
46
- class NativeScanner
47
- class << self
48
- # magnusモードを追加
49
- alias_method :original_mode=, :mode=
50
-
51
- def mode=(value)
52
- case value
53
- when 'magnus'
54
- if NativeScannerMagnusLoader.available?
55
- @mode = 'magnus'
56
- @current_library = nil # magnus は FFI を使わない
57
- else
58
- @mode = 'ruby'
59
- @current_library = nil
60
- end
61
- else
62
- send(:original_mode=, value)
63
- end
64
- end
65
-
66
- # magnusスキャン
67
- def scan_directory_with_magnus(path)
68
- raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
69
-
70
- entries = NativeScannerMagnus.scan_directory(path)
71
-
72
- # 結果の形式を統一(すでに正しい形式だが念のため)
73
- entries.map do |entry|
74
- {
75
- name: entry[:name],
76
- type: entry[:is_dir] ? 'directory' : 'file',
77
- size: entry[:size],
78
- mtime: entry[:mtime],
79
- mode: 0,
80
- executable: entry[:executable],
81
- hidden: entry[:hidden]
82
- }
83
- end
84
- rescue StandardError => e
85
- raise StandardError, "Magnus scan failed: #{e.message}"
86
- end
87
-
88
- # magnusで高速スキャン
89
- def scan_directory_fast_with_magnus(path, max_entries)
90
- raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
91
-
92
- entries = NativeScannerMagnus.scan_directory_fast(path, max_entries)
93
-
94
- entries.map do |entry|
95
- {
96
- name: entry[:name],
97
- type: entry[:is_dir] ? 'directory' : 'file',
98
- size: entry[:size],
99
- mtime: entry[:mtime],
100
- mode: 0,
101
- executable: entry[:executable],
102
- hidden: entry[:hidden]
103
- }
104
- end
105
- rescue StandardError => e
106
- raise StandardError, "Magnus fast scan failed: #{e.message}"
107
- end
108
-
109
- # scan_directoryメソッドを拡張
110
- alias_method :original_scan_directory, :scan_directory
111
-
112
- def scan_directory(path)
113
- mode if @mode.nil?
114
-
115
- case @mode
116
- when 'magnus'
117
- scan_directory_with_magnus(path)
118
- else
119
- original_scan_directory(path)
120
- end
121
- end
122
-
123
- # scan_directory_fastメソッドを拡張
124
- alias_method :original_scan_directory_fast, :scan_directory_fast
125
-
126
- def scan_directory_fast(path, max_entries = 1000)
127
- mode if @mode.nil?
128
-
129
- case @mode
130
- when 'magnus'
131
- scan_directory_fast_with_magnus(path, max_entries)
132
- else
133
- original_scan_directory_fast(path, max_entries)
134
- end
135
- end
136
-
137
- # versionメソッドを拡張
138
- alias_method :original_version, :version
139
-
140
- def version
141
- mode if @mode.nil?
142
-
143
- case @mode
144
- when 'magnus'
145
- NativeScannerMagnus.version
146
- else
147
- original_version
148
- end
149
- end
150
-
151
- # available_librariesを更新
152
- alias_method :original_available_libraries, :available_libraries
153
-
154
- def available_libraries
155
- original = original_available_libraries
156
- original.merge(magnus: NativeScannerMagnusLoader.available?)
157
- end
158
-
159
- # autoモードの優先順位を更新(magnus > rust > go > ruby)
160
- alias_method :original_auto_mode, :mode=
161
-
162
- def mode=(value)
163
- case value
164
- when 'auto'
165
- # 優先順位: Magnus > Rust > Go > Ruby
166
- if NativeScannerMagnusLoader.available?
167
- @mode = 'magnus'
168
- @current_library = nil
169
- elsif RustLib.available?
170
- @mode = 'rust'
171
- @current_library = RustLib
172
- elsif GoLib.available?
173
- @mode = 'go'
174
- @current_library = GoLib
175
- else
176
- @mode = 'ruby'
177
- @current_library = nil
178
- end
179
- when 'magnus'
180
- if NativeScannerMagnusLoader.available?
181
- @mode = 'magnus'
182
- @current_library = nil
183
- else
184
- @mode = 'ruby'
185
- @current_library = nil
186
- end
187
- else
188
- send(:original_auto_mode, value)
189
- end
190
- end
191
- end
192
- end
193
- end
194
- end
@@ -1,2 +0,0 @@
1
- [env]
2
- RUBY = "/Users/miso/.rbenv/versions/3.4.2/bin/ruby"