rufio 0.61.0 → 0.62.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -18
- data/README.md +191 -150
- data/README_ja.md +331 -0
- data/examples/bookmarks.yml +14 -0
- data/examples/config.rb +68 -0
- data/examples/script_paths.yml +11 -0
- data/lib/rufio/application.rb +1 -1
- data/lib/rufio/bookmark_manager.rb +3 -3
- data/lib/rufio/bookmark_storage.rb +61 -60
- data/lib/rufio/config.rb +214 -0
- data/lib/rufio/config_loader.rb +144 -37
- data/lib/rufio/keybind_handler.rb +14 -9
- data/lib/rufio/script_config_loader.rb +27 -12
- data/lib/rufio/script_path_manager.rb +50 -89
- data/lib/rufio/version.rb +1 -1
- metadata +6 -14
- data/README_EN.md +0 -610
- data/config_example.rb +0 -88
- data/examples/config.yml +0 -8
- data/scripts/test_jobs/build_simulation.sh +0 -29
- data/scripts/test_jobs/deploy_simulation.sh +0 -37
- data/scripts/test_jobs/quick.sh +0 -11
- data/scripts/test_jobs/random_result.sh +0 -23
- data/scripts/test_jobs/slow_fail.sh +0 -10
- data/scripts/test_jobs/slow_success.sh +0 -10
- data/scripts/test_jobs/very_slow.sh +0 -19
- data/test_delete/test1.txt +0 -1
- data/test_delete/test2.txt +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'yaml'
|
|
4
|
+
require_relative 'config'
|
|
4
5
|
|
|
5
6
|
module Rufio
|
|
6
7
|
# スクリプトパスを管理するクラス
|
|
@@ -14,22 +15,43 @@ module Rufio
|
|
|
14
15
|
# 履歴の最大サイズ
|
|
15
16
|
MAX_HISTORY_SIZE = 100
|
|
16
17
|
|
|
17
|
-
# @param config_path [String]
|
|
18
|
-
def initialize(config_path)
|
|
19
|
-
@config_path = config_path
|
|
20
|
-
@
|
|
21
|
-
@paths = expand_paths(@config['script_paths'] || [])
|
|
18
|
+
# @param config_path [String] 設定ファイルのパス(script_paths.yml または config.yml)
|
|
19
|
+
def initialize(config_path = nil)
|
|
20
|
+
@config_path = config_path || Config::SCRIPT_PATHS_YML
|
|
21
|
+
@paths = load_paths_from_config
|
|
22
22
|
@cache = {}
|
|
23
23
|
@scripts_cache = nil
|
|
24
24
|
@execution_history = []
|
|
25
25
|
@execution_count = Hash.new(0)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# 設定ファイルからパスを読み込む(新旧形式対応)
|
|
31
|
+
def load_paths_from_config
|
|
32
|
+
# 新形式: script_paths.yml(リスト形式)
|
|
33
|
+
if @config_path.end_with?('script_paths.yml')
|
|
34
|
+
return Config.load_script_paths(@config_path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 後方互換: 古いconfig.yml形式
|
|
38
|
+
return [] unless File.exist?(@config_path)
|
|
39
|
+
|
|
40
|
+
yaml = YAML.safe_load(File.read(@config_path), symbolize_names: false)
|
|
41
|
+
return [] unless yaml.is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
paths = yaml['script_paths'] || []
|
|
44
|
+
paths.map { |p| File.expand_path(p) }
|
|
45
|
+
rescue StandardError
|
|
46
|
+
[]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
public
|
|
50
|
+
|
|
28
51
|
# スクリプト名で解決
|
|
29
52
|
# @param command_name [String] スクリプト名(拡張子あり/なし)
|
|
30
53
|
# @return [String, nil] スクリプトのフルパス、見つからない場合はnil
|
|
31
54
|
def resolve(command_name)
|
|
32
|
-
# キャッシュをチェック
|
|
33
55
|
return @cache[command_name] if @cache.key?(command_name)
|
|
34
56
|
|
|
35
57
|
scripts = find_scripts(command_name)
|
|
@@ -40,7 +62,6 @@ module Rufio
|
|
|
40
62
|
when 1
|
|
41
63
|
@cache[command_name] = scripts.first
|
|
42
64
|
else
|
|
43
|
-
# 複数見つかった場合は最初のものを返す(on_multiple_match: 'first')
|
|
44
65
|
@cache[command_name] = scripts.first
|
|
45
66
|
end
|
|
46
67
|
end
|
|
@@ -98,20 +119,12 @@ module Rufio
|
|
|
98
119
|
!!result
|
|
99
120
|
end
|
|
100
121
|
|
|
101
|
-
# --- Phase 4: 複数マッチ ---
|
|
102
|
-
|
|
103
122
|
# すべてのマッチを取得(複数マッチ対応)
|
|
104
|
-
# @param command_name [String] コマンド名
|
|
105
|
-
# @return [Array<String>] マッチしたスクリプトのパス
|
|
106
123
|
def find_all_matches(command_name)
|
|
107
124
|
find_scripts_all_paths(command_name)
|
|
108
125
|
end
|
|
109
126
|
|
|
110
|
-
# --- Phase 4: タブ補完 ---
|
|
111
|
-
|
|
112
127
|
# スクリプト名を補完
|
|
113
|
-
# @param prefix [String] 入力中の文字列
|
|
114
|
-
# @return [Array<String>] 補完候補(拡張子なし)
|
|
115
128
|
def complete(prefix)
|
|
116
129
|
scripts = all_scripts
|
|
117
130
|
return scripts if prefix.empty?
|
|
@@ -119,87 +132,63 @@ module Rufio
|
|
|
119
132
|
scripts.select { |name| name.downcase.start_with?(prefix.downcase) }
|
|
120
133
|
end
|
|
121
134
|
|
|
122
|
-
# --- Phase 4: fuzzy matching ---
|
|
123
|
-
|
|
124
135
|
# fuzzy matchingで候補を取得
|
|
125
|
-
# @param query [String] 検索クエリ
|
|
126
|
-
# @return [Array<String>] マッチしたスクリプト名
|
|
127
136
|
def fuzzy_match(query)
|
|
128
137
|
return all_scripts if query.empty?
|
|
129
138
|
|
|
130
139
|
scripts = all_scripts
|
|
131
140
|
query_chars = query.downcase.chars
|
|
132
141
|
|
|
133
|
-
# スコア計算してソート
|
|
134
142
|
scored = scripts.map do |name|
|
|
135
143
|
score = fuzzy_score(name.downcase, query_chars)
|
|
136
144
|
[name, score]
|
|
137
145
|
end
|
|
138
146
|
|
|
139
|
-
# スコアが0より大きいものをスコア降順で返す
|
|
140
147
|
scored.select { |_, score| score > 0 }
|
|
141
148
|
.sort_by { |_, score| -score }
|
|
142
149
|
.map { |name, _| name }
|
|
143
150
|
end
|
|
144
151
|
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
# キャッシュを無効化(public)
|
|
152
|
+
# キャッシュを無効化
|
|
148
153
|
def invalidate_cache
|
|
149
154
|
@cache.clear
|
|
150
155
|
@scripts_cache = nil
|
|
151
156
|
end
|
|
152
157
|
|
|
153
|
-
# --- Phase 4: 実行履歴 ---
|
|
154
|
-
|
|
155
158
|
# 実行を記録
|
|
156
|
-
# @param script_name [String] スクリプト名
|
|
157
159
|
def record_execution(script_name)
|
|
158
|
-
# 履歴の先頭に追加
|
|
159
160
|
@execution_history.unshift(script_name)
|
|
160
161
|
@execution_history = @execution_history.take(MAX_HISTORY_SIZE)
|
|
161
|
-
|
|
162
|
-
# カウントを増やす
|
|
163
162
|
@execution_count[script_name] += 1
|
|
164
163
|
end
|
|
165
164
|
|
|
166
165
|
# 実行履歴を取得
|
|
167
|
-
# @return [Array<String>] 最近実行したスクリプト名(新しい順)
|
|
168
166
|
def execution_history
|
|
169
167
|
@execution_history.dup
|
|
170
168
|
end
|
|
171
169
|
|
|
172
170
|
# 実行頻度順にスクリプトを取得
|
|
173
|
-
# @return [Array<String>] スクリプト名(頻度順)
|
|
174
171
|
def scripts_by_frequency
|
|
175
172
|
scripts = all_scripts
|
|
176
173
|
scripts.sort_by { |name| -@execution_count[name] }
|
|
177
174
|
end
|
|
178
175
|
|
|
179
|
-
# --- セクション9: エラーハンドリング ---
|
|
180
|
-
|
|
181
176
|
# 類似スクリプトの候補を取得
|
|
182
|
-
# @param query [String] 検索クエリ(typoを含む可能性あり)
|
|
183
|
-
# @return [Array<String>] 類似スクリプト名
|
|
184
177
|
def suggest(query)
|
|
185
178
|
scripts = all_scripts
|
|
186
179
|
return [] if scripts.empty?
|
|
187
180
|
|
|
188
|
-
# レーベンシュタイン距離でソート
|
|
189
181
|
scored = scripts.map do |name|
|
|
190
182
|
distance = levenshtein_distance(query.downcase, name.downcase)
|
|
191
183
|
[name, distance]
|
|
192
184
|
end
|
|
193
185
|
|
|
194
|
-
# 距離が3以下のものを距離順で返す
|
|
195
186
|
scored.select { |_, dist| dist <= 3 }
|
|
196
187
|
.sort_by { |_, dist| dist }
|
|
197
188
|
.map { |name, _| name }
|
|
198
189
|
end
|
|
199
190
|
|
|
200
191
|
# ファイルが実行可能かどうかをチェック
|
|
201
|
-
# @param path [String] ファイルパス
|
|
202
|
-
# @return [Boolean]
|
|
203
192
|
def executable?(path)
|
|
204
193
|
return false unless File.exist?(path)
|
|
205
194
|
|
|
@@ -207,13 +196,11 @@ module Rufio
|
|
|
207
196
|
end
|
|
208
197
|
|
|
209
198
|
# ファイルに実行権限を付与
|
|
210
|
-
# @param path [String] ファイルパス
|
|
211
|
-
# @return [Boolean] 成功した場合true
|
|
212
199
|
def fix_permissions(path)
|
|
213
200
|
return false unless File.exist?(path)
|
|
214
201
|
|
|
215
202
|
current_mode = File.stat(path).mode
|
|
216
|
-
new_mode = current_mode | 0111
|
|
203
|
+
new_mode = current_mode | 0111
|
|
217
204
|
File.chmod(new_mode, path)
|
|
218
205
|
true
|
|
219
206
|
rescue StandardError
|
|
@@ -222,34 +209,29 @@ module Rufio
|
|
|
222
209
|
|
|
223
210
|
private
|
|
224
211
|
|
|
225
|
-
#
|
|
226
|
-
# @return [Hash] 設定内容
|
|
227
|
-
def load_config
|
|
228
|
-
return {} unless File.exist?(@config_path)
|
|
229
|
-
|
|
230
|
-
yaml = YAML.safe_load(File.read(@config_path), symbolize_names: false)
|
|
231
|
-
yaml || {}
|
|
232
|
-
rescue StandardError => e
|
|
233
|
-
warn "Failed to load config: #{e.message}"
|
|
234
|
-
{}
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# 設定ファイルを保存
|
|
212
|
+
# 設定ファイルにスクリプトパスを保存(新旧形式対応)
|
|
238
213
|
def save_config
|
|
239
|
-
|
|
240
|
-
|
|
214
|
+
# 新形式: script_paths.yml
|
|
215
|
+
if @config_path.end_with?('script_paths.yml')
|
|
216
|
+
Config.save_script_paths(@config_path, @paths)
|
|
217
|
+
return
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# 後方互換: 古いconfig.yml形式
|
|
221
|
+
existing = if File.exist?(@config_path)
|
|
222
|
+
YAML.safe_load(File.read(@config_path), symbolize_names: false) || {}
|
|
223
|
+
else
|
|
224
|
+
{}
|
|
225
|
+
end
|
|
226
|
+
existing['script_paths'] = @paths
|
|
227
|
+
FileUtils.mkdir_p(File.dirname(@config_path))
|
|
228
|
+
File.write(@config_path, YAML.dump(existing))
|
|
241
229
|
end
|
|
242
230
|
|
|
243
|
-
# パスを展開(チルダ展開)
|
|
244
|
-
# @param paths [Array<String>] パスの配列
|
|
245
|
-
# @return [Array<String>] 展開済みのパス
|
|
246
231
|
def expand_paths(paths)
|
|
247
232
|
paths.map { |p| File.expand_path(p) }
|
|
248
233
|
end
|
|
249
234
|
|
|
250
|
-
# スクリプトを検索
|
|
251
|
-
# @param command_name [String] コマンド名
|
|
252
|
-
# @return [Array<String>] 見つかったスクリプトのパス
|
|
253
235
|
def find_scripts(command_name)
|
|
254
236
|
scripts = []
|
|
255
237
|
basename_without_ext = command_name.sub(/\.[^.]+$/, '')
|
|
@@ -266,25 +248,19 @@ module Rufio
|
|
|
266
248
|
next unless executable_script?(file)
|
|
267
249
|
|
|
268
250
|
if has_extension
|
|
269
|
-
# 拡張子付きで完全一致
|
|
270
251
|
scripts << file if file_basename.downcase == command_name.downcase
|
|
271
252
|
else
|
|
272
|
-
# 拡張子なしで比較
|
|
273
253
|
file_name_without_ext = File.basename(file, '.*')
|
|
274
254
|
scripts << file if file_name_without_ext.downcase == basename_without_ext.downcase
|
|
275
255
|
end
|
|
276
256
|
end
|
|
277
257
|
|
|
278
|
-
# 最初のパスで見つかったらそれを優先(on_multiple_match: 'first'相当)
|
|
279
258
|
return scripts unless scripts.empty?
|
|
280
259
|
end
|
|
281
260
|
|
|
282
261
|
scripts
|
|
283
262
|
end
|
|
284
263
|
|
|
285
|
-
# 実行可能なスクリプトかどうかを判定
|
|
286
|
-
# @param path [String] ファイルパス
|
|
287
|
-
# @return [Boolean]
|
|
288
264
|
def executable_script?(path)
|
|
289
265
|
ext = File.extname(path).downcase
|
|
290
266
|
return true if SUPPORTED_EXTENSIONS.include?(ext)
|
|
@@ -292,9 +268,6 @@ module Rufio
|
|
|
292
268
|
File.executable?(path)
|
|
293
269
|
end
|
|
294
270
|
|
|
295
|
-
# すべてのパスからスクリプトを検索(最初のパスで止まらない)
|
|
296
|
-
# @param command_name [String] コマンド名
|
|
297
|
-
# @return [Array<String>] 見つかったスクリプトのパス
|
|
298
271
|
def find_scripts_all_paths(command_name)
|
|
299
272
|
scripts = []
|
|
300
273
|
basename_without_ext = command_name.sub(/\.[^.]+$/, '')
|
|
@@ -322,17 +295,12 @@ module Rufio
|
|
|
322
295
|
scripts
|
|
323
296
|
end
|
|
324
297
|
|
|
325
|
-
# レーベンシュタイン距離を計算
|
|
326
|
-
# @param s1 [String] 文字列1
|
|
327
|
-
# @param s2 [String] 文字列2
|
|
328
|
-
# @return [Integer] 編集距離
|
|
329
298
|
def levenshtein_distance(s1, s2)
|
|
330
299
|
m = s1.length
|
|
331
300
|
n = s2.length
|
|
332
301
|
return n if m == 0
|
|
333
302
|
return m if n == 0
|
|
334
303
|
|
|
335
|
-
# 動的計画法でテーブルを構築
|
|
336
304
|
d = Array.new(m + 1) { Array.new(n + 1, 0) }
|
|
337
305
|
|
|
338
306
|
(0..m).each { |i| d[i][0] = i }
|
|
@@ -342,9 +310,9 @@ module Rufio
|
|
|
342
310
|
(1..n).each do |j|
|
|
343
311
|
cost = s1[i - 1] == s2[j - 1] ? 0 : 1
|
|
344
312
|
d[i][j] = [
|
|
345
|
-
d[i - 1][j] + 1,
|
|
346
|
-
d[i][j - 1] + 1,
|
|
347
|
-
d[i - 1][j - 1] + cost
|
|
313
|
+
d[i - 1][j] + 1,
|
|
314
|
+
d[i][j - 1] + 1,
|
|
315
|
+
d[i - 1][j - 1] + cost
|
|
348
316
|
].min
|
|
349
317
|
end
|
|
350
318
|
end
|
|
@@ -352,27 +320,20 @@ module Rufio
|
|
|
352
320
|
d[m][n]
|
|
353
321
|
end
|
|
354
322
|
|
|
355
|
-
# fuzzy matchingのスコアを計算
|
|
356
|
-
# @param text [String] 対象テキスト
|
|
357
|
-
# @param query_chars [Array<String>] クエリの文字配列
|
|
358
|
-
# @return [Integer] スコア(マッチしない場合は0)
|
|
359
323
|
def fuzzy_score(text, query_chars)
|
|
360
324
|
score = 0
|
|
361
325
|
text_index = 0
|
|
362
326
|
|
|
363
327
|
query_chars.each do |char|
|
|
364
|
-
# テキスト内で文字を探す
|
|
365
328
|
found_index = text.index(char, text_index)
|
|
366
|
-
return 0 unless found_index
|
|
329
|
+
return 0 unless found_index
|
|
367
330
|
|
|
368
|
-
# 連続していればボーナス
|
|
369
331
|
if found_index == text_index
|
|
370
332
|
score += 2
|
|
371
333
|
else
|
|
372
334
|
score += 1
|
|
373
335
|
end
|
|
374
336
|
|
|
375
|
-
# 単語の先頭ならボーナス
|
|
376
337
|
if found_index == 0 || text[found_index - 1] == '_' || text[found_index - 1] == '-'
|
|
377
338
|
score += 1
|
|
378
339
|
end
|
data/lib/rufio/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.62.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-
|
|
11
|
+
date: 2026-01-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: io-console
|
|
@@ -119,10 +119,9 @@ extra_rdoc_files: []
|
|
|
119
119
|
files:
|
|
120
120
|
- CHANGELOG.md
|
|
121
121
|
- README.md
|
|
122
|
-
-
|
|
122
|
+
- README_ja.md
|
|
123
123
|
- Rakefile
|
|
124
124
|
- bin/rufio
|
|
125
|
-
- config_example.rb
|
|
126
125
|
- docs/CHANGELOG_v0.10.0.md
|
|
127
126
|
- docs/CHANGELOG_v0.20.0.md
|
|
128
127
|
- docs/CHANGELOG_v0.21.0.md
|
|
@@ -139,7 +138,9 @@ files:
|
|
|
139
138
|
- docs/CHANGELOG_v0.7.0.md
|
|
140
139
|
- docs/CHANGELOG_v0.8.0.md
|
|
141
140
|
- docs/CHANGELOG_v0.9.0.md
|
|
142
|
-
- examples/
|
|
141
|
+
- examples/bookmarks.yml
|
|
142
|
+
- examples/config.rb
|
|
143
|
+
- examples/script_paths.yml
|
|
143
144
|
- info/help.md
|
|
144
145
|
- info/keybindings.md
|
|
145
146
|
- info/welcome.md
|
|
@@ -198,15 +199,6 @@ files:
|
|
|
198
199
|
- lib_zig/rufio_native/src/main.zig
|
|
199
200
|
- lib_zig/rufio_native/src/main.zig.sync
|
|
200
201
|
- retag.sh
|
|
201
|
-
- scripts/test_jobs/build_simulation.sh
|
|
202
|
-
- scripts/test_jobs/deploy_simulation.sh
|
|
203
|
-
- scripts/test_jobs/quick.sh
|
|
204
|
-
- scripts/test_jobs/random_result.sh
|
|
205
|
-
- scripts/test_jobs/slow_fail.sh
|
|
206
|
-
- scripts/test_jobs/slow_success.sh
|
|
207
|
-
- scripts/test_jobs/very_slow.sh
|
|
208
|
-
- test_delete/test1.txt
|
|
209
|
-
- test_delete/test2.txt
|
|
210
202
|
homepage: https://github.com/masisz/rufio
|
|
211
203
|
licenses:
|
|
212
204
|
- MIT
|