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.
@@ -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
- @config = load_config
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
- # --- Phase 4: キャッシュ ---
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
- @config['script_paths'] = @paths
240
- File.write(@config_path, YAML.dump(@config))
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 # マッチしない場合は0
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rufio
4
- VERSION = '0.61.0'
4
+ VERSION = '0.62.0'
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.61.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-25 00:00:00.000000000 Z
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
- - README_EN.md
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/config.yml
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