rufio 0.9.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 +7 -0
- data/CHANGELOG.md +188 -0
- data/CHANGELOG_v0.4.0.md +146 -0
- data/CHANGELOG_v0.5.0.md +26 -0
- data/CHANGELOG_v0.6.0.md +182 -0
- data/CHANGELOG_v0.7.0.md +280 -0
- data/CHANGELOG_v0.8.0.md +267 -0
- data/CHANGELOG_v0.9.0.md +279 -0
- data/README.md +631 -0
- data/README_EN.md +561 -0
- data/Rakefile +156 -0
- data/bin/rufio +34 -0
- data/config_example.rb +88 -0
- data/docs/PLUGIN_GUIDE.md +431 -0
- data/docs/plugin_example.rb +119 -0
- data/lib/rufio/application.rb +32 -0
- data/lib/rufio/bookmark.rb +115 -0
- data/lib/rufio/bookmark_manager.rb +173 -0
- data/lib/rufio/color_helper.rb +150 -0
- data/lib/rufio/command_mode.rb +72 -0
- data/lib/rufio/command_mode_ui.rb +168 -0
- data/lib/rufio/config.rb +199 -0
- data/lib/rufio/config_loader.rb +110 -0
- data/lib/rufio/dialog_renderer.rb +127 -0
- data/lib/rufio/directory_listing.rb +113 -0
- data/lib/rufio/file_opener.rb +140 -0
- data/lib/rufio/file_operations.rb +231 -0
- data/lib/rufio/file_preview.rb +200 -0
- data/lib/rufio/filter_manager.rb +114 -0
- data/lib/rufio/health_checker.rb +246 -0
- data/lib/rufio/keybind_handler.rb +828 -0
- data/lib/rufio/logger.rb +103 -0
- data/lib/rufio/plugin.rb +89 -0
- data/lib/rufio/plugin_config.rb +59 -0
- data/lib/rufio/plugin_manager.rb +84 -0
- data/lib/rufio/plugins/file_operations.rb +44 -0
- data/lib/rufio/selection_manager.rb +79 -0
- data/lib/rufio/terminal_ui.rb +630 -0
- data/lib/rufio/text_utils.rb +108 -0
- data/lib/rufio/version.rb +5 -0
- data/lib/rufio/zoxide_integration.rb +188 -0
- data/lib/rufio.rb +33 -0
- data/publish_gem.zsh +131 -0
- data/rufio.gemspec +40 -0
- data/test_delete/test1.txt +1 -0
- data/test_delete/test2.txt +1 -0
- metadata +189 -0
data/lib/rufio/logger.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# Unified logger for debug and error messages
|
|
5
|
+
# Only logs when BENIYA_DEBUG environment variable is set to '1'
|
|
6
|
+
class Logger
|
|
7
|
+
LOG_FILE = File.join(Dir.home, '.rufio_debug.log')
|
|
8
|
+
|
|
9
|
+
# Log levels
|
|
10
|
+
DEBUG = :debug
|
|
11
|
+
INFO = :info
|
|
12
|
+
WARN = :warn
|
|
13
|
+
ERROR = :error
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
# Log a debug message with optional context
|
|
17
|
+
# @param message [String] The log message
|
|
18
|
+
# @param context [Hash] Additional context information
|
|
19
|
+
def debug(message, context: {})
|
|
20
|
+
return unless debug_enabled?
|
|
21
|
+
|
|
22
|
+
write_log(DEBUG, message, context)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Log an info message
|
|
26
|
+
# @param message [String] The log message
|
|
27
|
+
# @param context [Hash] Additional context information
|
|
28
|
+
def info(message, context: {})
|
|
29
|
+
return unless debug_enabled?
|
|
30
|
+
|
|
31
|
+
write_log(INFO, message, context)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Log a warning message
|
|
35
|
+
# @param message [String] The log message
|
|
36
|
+
# @param context [Hash] Additional context information
|
|
37
|
+
def warn(message, context: {})
|
|
38
|
+
return unless debug_enabled?
|
|
39
|
+
|
|
40
|
+
write_log(WARN, message, context)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Log an error message with optional exception
|
|
44
|
+
# @param message [String] The error message
|
|
45
|
+
# @param exception [Exception, nil] Optional exception object
|
|
46
|
+
# @param context [Hash] Additional context information
|
|
47
|
+
def error(message, exception: nil, context: {})
|
|
48
|
+
return unless debug_enabled?
|
|
49
|
+
|
|
50
|
+
full_context = context.dup
|
|
51
|
+
if exception
|
|
52
|
+
full_context[:exception] = exception.message
|
|
53
|
+
full_context[:backtrace] = exception.backtrace&.first(5)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
write_log(ERROR, message, full_context)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Clear the log file
|
|
60
|
+
def clear_log
|
|
61
|
+
return unless debug_enabled?
|
|
62
|
+
|
|
63
|
+
File.open(LOG_FILE, 'w') { |f| f.puts "=== Rufio Debug Log Cleared at #{Time.now} ===" }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Check if debug logging is enabled
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def debug_enabled?
|
|
71
|
+
ENV['BENIYA_DEBUG'] == '1'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Write a log entry to the log file
|
|
75
|
+
# @param level [Symbol] Log level
|
|
76
|
+
# @param message [String] Log message
|
|
77
|
+
# @param context [Hash] Context information
|
|
78
|
+
def write_log(level, message, context)
|
|
79
|
+
File.open(LOG_FILE, 'a') do |f|
|
|
80
|
+
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
|
81
|
+
f.puts "[#{timestamp}] [#{level.to_s.upcase}] #{message}"
|
|
82
|
+
|
|
83
|
+
unless context.empty?
|
|
84
|
+
f.puts ' Context:'
|
|
85
|
+
context.each do |key, value|
|
|
86
|
+
if value.is_a?(Array) && value.length > 10
|
|
87
|
+
f.puts " #{key}: [#{value.length} items]"
|
|
88
|
+
else
|
|
89
|
+
f.puts " #{key}: #{value.inspect}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
f.puts ''
|
|
95
|
+
end
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
# Silently fail if we can't write to log file
|
|
98
|
+
# Don't want logging to break the application
|
|
99
|
+
warn "Failed to write to log file: #{e.message}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
data/lib/rufio/plugin.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
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
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# プラグインを管理するクラス
|
|
5
|
+
class PluginManager
|
|
6
|
+
class << self
|
|
7
|
+
# 登録済みプラグインクラスのリスト
|
|
8
|
+
def plugins
|
|
9
|
+
@plugins ||= []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# プラグインを登録する
|
|
13
|
+
def register(plugin_class)
|
|
14
|
+
@plugins ||= []
|
|
15
|
+
@plugins << plugin_class unless @plugins.include?(plugin_class)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# 全プラグインを読み込む(本体同梱 + ユーザープラグイン)
|
|
19
|
+
def load_all
|
|
20
|
+
load_builtin_plugins
|
|
21
|
+
load_user_plugins
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# 有効なプラグインインスタンスのリストを取得
|
|
25
|
+
def enabled_plugins
|
|
26
|
+
return @enabled_plugins if @enabled_plugins
|
|
27
|
+
|
|
28
|
+
@enabled_plugins = []
|
|
29
|
+
|
|
30
|
+
plugins.each do |plugin_class|
|
|
31
|
+
# プラグイン名を取得(クラス名から推測)
|
|
32
|
+
plugin_name = plugin_class.name.split('::').last
|
|
33
|
+
|
|
34
|
+
# PluginConfigで有効かチェック
|
|
35
|
+
next unless PluginConfig.plugin_enabled?(plugin_name)
|
|
36
|
+
|
|
37
|
+
# プラグインのインスタンスを作成
|
|
38
|
+
begin
|
|
39
|
+
plugin_instance = plugin_class.new
|
|
40
|
+
@enabled_plugins << plugin_instance
|
|
41
|
+
rescue Plugin::DependencyError => e
|
|
42
|
+
warn "⚠️ #{e.message}"
|
|
43
|
+
# プラグインは無効化されるが、rufioは起動継続
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
warn "⚠️ Failed to load plugin #{plugin_name}: #{e.message}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@enabled_plugins
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# 本体同梱プラグインを読み込む
|
|
55
|
+
def load_builtin_plugins
|
|
56
|
+
# plugin_manager.rbは/lib/rufio/にあるので、pluginsディレクトリは同じディレクトリ内
|
|
57
|
+
builtin_plugins_dir = File.join(__dir__, 'plugins')
|
|
58
|
+
return unless Dir.exist?(builtin_plugins_dir)
|
|
59
|
+
|
|
60
|
+
Dir.glob(File.join(builtin_plugins_dir, '*.rb')).sort.each do |file|
|
|
61
|
+
begin
|
|
62
|
+
require file
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
warn "⚠️ Failed to load builtin plugin #{File.basename(file)}: #{e.message}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ユーザープラグインを読み込む
|
|
70
|
+
def load_user_plugins
|
|
71
|
+
user_plugins_dir = File.expand_path('~/.rufio/plugins')
|
|
72
|
+
return unless Dir.exist?(user_plugins_dir)
|
|
73
|
+
|
|
74
|
+
Dir.glob(File.join(user_plugins_dir, '*.rb')).sort.each do |file|
|
|
75
|
+
begin
|
|
76
|
+
require file
|
|
77
|
+
rescue SyntaxError, StandardError => e
|
|
78
|
+
warn "⚠️ Failed to load user plugin #{File.basename(file)}: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
module Plugins
|
|
5
|
+
# 基本的なファイル操作を提供するプラグイン
|
|
6
|
+
class FileOperations < Plugin
|
|
7
|
+
def name
|
|
8
|
+
"FileOperations"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def description
|
|
12
|
+
"基本的なファイル操作(コピー、移動、削除)"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def commands
|
|
16
|
+
{
|
|
17
|
+
copy: method(:copy),
|
|
18
|
+
move: method(:move),
|
|
19
|
+
delete: method(:delete)
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# ファイルコピー(スタブ実装)
|
|
26
|
+
def copy
|
|
27
|
+
# 実装は将来追加
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# ファイル移動(スタブ実装)
|
|
32
|
+
def move
|
|
33
|
+
# 実装は将来追加
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# ファイル削除(スタブ実装)
|
|
38
|
+
def delete
|
|
39
|
+
# 実装は将来追加
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# Manages selected items (files/directories) for bulk operations
|
|
5
|
+
class SelectionManager
|
|
6
|
+
def initialize
|
|
7
|
+
@selected_items = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Toggle selection for an entry
|
|
11
|
+
# @param entry [Hash] Entry with :name key
|
|
12
|
+
# @return [Boolean] true if now selected, false if unselected
|
|
13
|
+
def toggle_selection(entry)
|
|
14
|
+
return false unless entry
|
|
15
|
+
|
|
16
|
+
if @selected_items.include?(entry[:name])
|
|
17
|
+
@selected_items.delete(entry[:name])
|
|
18
|
+
false
|
|
19
|
+
else
|
|
20
|
+
@selected_items << entry[:name]
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Check if an entry is selected
|
|
26
|
+
# @param entry_name [String] Entry name
|
|
27
|
+
# @return [Boolean]
|
|
28
|
+
def selected?(entry_name)
|
|
29
|
+
@selected_items.include?(entry_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Get all selected items
|
|
33
|
+
# @return [Array<String>] Copy of selected items
|
|
34
|
+
def selected_items
|
|
35
|
+
@selected_items.dup
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Clear all selections
|
|
39
|
+
def clear
|
|
40
|
+
@selected_items.clear
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if any items are selected
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
def any?
|
|
46
|
+
!@selected_items.empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get the count of selected items
|
|
50
|
+
# @return [Integer]
|
|
51
|
+
def count
|
|
52
|
+
@selected_items.length
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add an item to selection
|
|
56
|
+
# @param item_name [String] Item name
|
|
57
|
+
def add(item_name)
|
|
58
|
+
@selected_items << item_name unless @selected_items.include?(item_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Remove an item from selection
|
|
62
|
+
# @param item_name [String] Item name
|
|
63
|
+
def remove(item_name)
|
|
64
|
+
@selected_items.delete(item_name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Select multiple items
|
|
68
|
+
# @param item_names [Array<String>] Item names
|
|
69
|
+
def select_multiple(item_names)
|
|
70
|
+
item_names.each { |name| add(name) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check if selection is empty
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
def empty?
|
|
76
|
+
@selected_items.empty?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|