rufio 0.40.1 → 0.50.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 +25 -0
- data/README.md +64 -158
- data/bin/rufio +11 -4
- data/docs/CHANGELOG_v0.41.0.md +533 -0
- data/docs/CHANGELOG_v0.50.0.md +128 -0
- data/lib/rufio/background_command_executor.rb +64 -2
- data/lib/rufio/builtin_commands.rb +34 -0
- data/lib/rufio/command_mode.rb +67 -35
- data/lib/rufio/dsl_command.rb +120 -0
- data/lib/rufio/dsl_command_loader.rb +177 -0
- data/lib/rufio/file_preview.rb +22 -6
- data/lib/rufio/interpreter_resolver.rb +79 -0
- data/lib/rufio/renderer.rb +15 -2
- data/lib/rufio/script_executor.rb +253 -0
- data/lib/rufio/terminal_ui.rb +145 -47
- data/lib/rufio/text_utils.rb +42 -19
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +7 -7
- metadata +9 -8
- data/lib/rufio/plugin.rb +0 -89
- data/lib/rufio/plugin_config.rb +0 -59
- data/lib/rufio/plugin_manager.rb +0 -84
- data/lib/rufio/plugins/file_operations.rb +0 -44
- data/lib/rufio/plugins/hello.rb +0 -30
- data/lib/rufio/plugins/stop.rb +0 -32
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'open3'
|
|
4
4
|
|
|
5
5
|
module Rufio
|
|
6
|
-
#
|
|
6
|
+
# バックグラウンドでシェルコマンドまたはRubyコードを実行するクラス
|
|
7
7
|
class BackgroundCommandExecutor
|
|
8
8
|
attr_reader :command_logger
|
|
9
9
|
|
|
@@ -13,11 +13,12 @@ module Rufio
|
|
|
13
13
|
@command_logger = command_logger
|
|
14
14
|
@thread = nil
|
|
15
15
|
@command = nil
|
|
16
|
+
@command_type = nil # :shell または :ruby
|
|
16
17
|
@completed = false
|
|
17
18
|
@completion_message = nil
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
#
|
|
21
|
+
# シェルコマンドを非同期で実行
|
|
21
22
|
# @param command [String] 実行するコマンド
|
|
22
23
|
# @return [Boolean] 実行を開始した場合はtrue、既に実行中の場合はfalse
|
|
23
24
|
def execute_async(command)
|
|
@@ -25,6 +26,7 @@ module Rufio
|
|
|
25
26
|
return false if running?
|
|
26
27
|
|
|
27
28
|
@command = command
|
|
29
|
+
@command_type = :shell
|
|
28
30
|
@completed = false
|
|
29
31
|
@completion_message = nil
|
|
30
32
|
|
|
@@ -73,6 +75,54 @@ module Rufio
|
|
|
73
75
|
true
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
# Rubyコード(プラグインコマンド)を非同期で実行
|
|
79
|
+
# @param command_name [String] コマンド名(表示用)
|
|
80
|
+
# @param block [Proc] 実行するコードブロック
|
|
81
|
+
# @return [Boolean] 実行を開始した場合はtrue、既に実行中の場合はfalse
|
|
82
|
+
def execute_ruby_async(command_name, &block)
|
|
83
|
+
# 既に実行中の場合は新しいコマンドを開始しない
|
|
84
|
+
return false if running?
|
|
85
|
+
|
|
86
|
+
@command = command_name
|
|
87
|
+
@command_type = :ruby
|
|
88
|
+
@completed = false
|
|
89
|
+
@completion_message = nil
|
|
90
|
+
|
|
91
|
+
@thread = Thread.new do
|
|
92
|
+
begin
|
|
93
|
+
# Rubyコードを実行
|
|
94
|
+
result = block.call
|
|
95
|
+
|
|
96
|
+
# 結果をログに保存
|
|
97
|
+
output = result.to_s
|
|
98
|
+
|
|
99
|
+
@command_logger.log(
|
|
100
|
+
command_name,
|
|
101
|
+
output,
|
|
102
|
+
success: true,
|
|
103
|
+
error: nil
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# 完了メッセージを生成
|
|
107
|
+
@completion_message = "✓ #{command_name} 完了"
|
|
108
|
+
@completed = true
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
# エラーが発生した場合もログに記録
|
|
111
|
+
@command_logger.log(
|
|
112
|
+
command_name,
|
|
113
|
+
"",
|
|
114
|
+
success: false,
|
|
115
|
+
error: e.message
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@completion_message = "✗ #{command_name} エラー: #{e.message}"
|
|
119
|
+
@completed = true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
|
|
76
126
|
# コマンドが実行中かどうか
|
|
77
127
|
# @return [Boolean] 実行中の場合はtrue
|
|
78
128
|
def running?
|
|
@@ -85,6 +135,18 @@ module Rufio
|
|
|
85
135
|
@completion_message
|
|
86
136
|
end
|
|
87
137
|
|
|
138
|
+
# 現在実行中のコマンド名を取得
|
|
139
|
+
# @return [String, nil] コマンド名(実行中でない場合はnil)
|
|
140
|
+
def current_command
|
|
141
|
+
running? ? @command : nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# コマンドタイプを取得
|
|
145
|
+
# @return [Symbol, nil] :shell または :ruby(実行中でない場合はnil)
|
|
146
|
+
def command_type
|
|
147
|
+
running? ? @command_type : nil
|
|
148
|
+
end
|
|
149
|
+
|
|
88
150
|
private
|
|
89
151
|
|
|
90
152
|
# コマンド文字列からコマンド名を抽出
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# 組み込みコマンドを定義するモジュール
|
|
5
|
+
# DSL形式で定義されたコマンドをDslCommandインスタンスとして提供する
|
|
6
|
+
module BuiltinCommands
|
|
7
|
+
class << self
|
|
8
|
+
# 組み込みコマンドをロードする
|
|
9
|
+
# @return [Hash{Symbol => DslCommand}] コマンド名をキーとしたハッシュ
|
|
10
|
+
def load
|
|
11
|
+
commands = {}
|
|
12
|
+
|
|
13
|
+
# hello コマンド
|
|
14
|
+
commands[:hello] = DslCommand.new(
|
|
15
|
+
name: "hello",
|
|
16
|
+
ruby_block: -> { "Hello, World!\n\nこのコマンドはDSLで定義されています。" },
|
|
17
|
+
description: "挨拶メッセージを返す"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# stop コマンド
|
|
21
|
+
commands[:stop] = DslCommand.new(
|
|
22
|
+
name: "stop",
|
|
23
|
+
ruby_block: lambda {
|
|
24
|
+
sleep 5
|
|
25
|
+
"done"
|
|
26
|
+
},
|
|
27
|
+
description: "5秒待機してdoneを返す"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
commands
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/rufio/command_mode.rb
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
require 'open3'
|
|
4
4
|
|
|
5
5
|
module Rufio
|
|
6
|
-
# コマンドモード -
|
|
6
|
+
# コマンドモード - DSLコマンドを実行するための統一インターフェース
|
|
7
|
+
# すべてのコマンドはDslCommandとして扱われる
|
|
7
8
|
class CommandMode
|
|
8
9
|
attr_accessor :background_executor
|
|
9
10
|
|
|
10
11
|
def initialize(background_executor = nil)
|
|
11
12
|
@commands = {}
|
|
12
13
|
@background_executor = background_executor
|
|
13
|
-
|
|
14
|
+
load_builtin_commands
|
|
15
|
+
load_dsl_commands
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
# コマンドを実行する
|
|
@@ -38,18 +40,14 @@ module Rufio
|
|
|
38
40
|
# コマンド名を取得 (前後の空白を削除)
|
|
39
41
|
command_name = command_string.strip.to_sym
|
|
40
42
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
+
# 統一されたコマンドストアから検索
|
|
44
|
+
command = @commands[command_name]
|
|
45
|
+
unless command
|
|
43
46
|
return "⚠️ コマンドが見つかりません: #{command_name}"
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
command_method = @commands[command_name][:method]
|
|
49
|
-
command_method.call
|
|
50
|
-
rescue StandardError => e
|
|
51
|
-
"⚠️ コマンド実行エラー: #{e.message}"
|
|
52
|
-
end
|
|
49
|
+
# 統一された実行パス
|
|
50
|
+
execute_unified_command(command_name, command)
|
|
53
51
|
end
|
|
54
52
|
|
|
55
53
|
# 利用可能なコマンドのリストを取得
|
|
@@ -59,17 +57,72 @@ module Rufio
|
|
|
59
57
|
|
|
60
58
|
# コマンドの情報を取得
|
|
61
59
|
def command_info(command_name)
|
|
62
|
-
|
|
60
|
+
command = @commands[command_name]
|
|
61
|
+
return nil unless command
|
|
63
62
|
|
|
64
63
|
{
|
|
65
64
|
name: command_name,
|
|
66
|
-
plugin:
|
|
67
|
-
description:
|
|
65
|
+
plugin: command[:source] || "dsl",
|
|
66
|
+
description: command[:command].description
|
|
68
67
|
}
|
|
69
68
|
end
|
|
70
69
|
|
|
70
|
+
# DSLコマンドをロードする
|
|
71
|
+
# @param paths [Array<String>, nil] 設定ファイルのパス配列(nilの場合はデフォルトパス)
|
|
72
|
+
def load_dsl_commands(paths = nil)
|
|
73
|
+
loader = DslCommandLoader.new
|
|
74
|
+
|
|
75
|
+
commands = if paths
|
|
76
|
+
loader.load_from_paths(paths)
|
|
77
|
+
else
|
|
78
|
+
loader.load
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# ユーザーDSLコマンドは既存のコマンドを上書きする(優先度が高い)
|
|
82
|
+
commands.each do |cmd|
|
|
83
|
+
@commands[cmd.name.to_sym] = {
|
|
84
|
+
command: cmd,
|
|
85
|
+
source: "dsl"
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
71
90
|
private
|
|
72
91
|
|
|
92
|
+
# 組み込みコマンドをロードする
|
|
93
|
+
def load_builtin_commands
|
|
94
|
+
builtin = BuiltinCommands.load
|
|
95
|
+
builtin.each do |name, cmd|
|
|
96
|
+
@commands[name] = {
|
|
97
|
+
command: cmd,
|
|
98
|
+
source: "builtin"
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# 統一されたコマンド実行
|
|
104
|
+
# @param command_name [Symbol] コマンド名
|
|
105
|
+
# @param command [Hash] コマンド情報 { command: DslCommand, source: String }
|
|
106
|
+
# @return [Hash] 実行結果
|
|
107
|
+
def execute_unified_command(command_name, command)
|
|
108
|
+
dsl_cmd = command[:command]
|
|
109
|
+
|
|
110
|
+
# バックグラウンドエグゼキュータが利用可能な場合は非同期実行
|
|
111
|
+
if @background_executor
|
|
112
|
+
command_display_name = command_name.to_s
|
|
113
|
+
if @background_executor.execute_ruby_async(command_display_name) do
|
|
114
|
+
ScriptExecutor.execute_command(dsl_cmd)
|
|
115
|
+
end
|
|
116
|
+
return "🔄 バックグラウンドで実行中: #{command_display_name}"
|
|
117
|
+
else
|
|
118
|
+
return "⚠️ 既にコマンドが実行中です"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# 同期実行
|
|
123
|
+
ScriptExecutor.execute_command(dsl_cmd)
|
|
124
|
+
end
|
|
125
|
+
|
|
73
126
|
# シェルコマンドを実行する
|
|
74
127
|
def execute_shell_command(shell_command)
|
|
75
128
|
# コマンドが空の場合
|
|
@@ -97,26 +150,5 @@ module Rufio
|
|
|
97
150
|
{ success: false, error: "コマンド実行エラー: #{e.message}" }
|
|
98
151
|
end
|
|
99
152
|
end
|
|
100
|
-
|
|
101
|
-
# プラグインからコマンドを読み込む
|
|
102
|
-
def load_plugin_commands
|
|
103
|
-
# 有効なプラグインを取得
|
|
104
|
-
enabled_plugins = PluginManager.enabled_plugins
|
|
105
|
-
|
|
106
|
-
# 各プラグインからコマンドを取得
|
|
107
|
-
enabled_plugins.each do |plugin|
|
|
108
|
-
plugin_name = plugin.name
|
|
109
|
-
plugin_commands = plugin.commands
|
|
110
|
-
|
|
111
|
-
# 各コマンドを登録
|
|
112
|
-
plugin_commands.each do |command_name, command_method|
|
|
113
|
-
@commands[command_name] = {
|
|
114
|
-
method: command_method,
|
|
115
|
-
plugin: plugin_name,
|
|
116
|
-
description: plugin.description
|
|
117
|
-
}
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
153
|
end
|
|
122
154
|
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# DSLで定義されたコマンドを表すクラス
|
|
5
|
+
class DslCommand
|
|
6
|
+
attr_reader :name, :script, :description, :interpreter, :errors
|
|
7
|
+
attr_reader :ruby_block, :shell_command
|
|
8
|
+
|
|
9
|
+
# コマンドを初期化する
|
|
10
|
+
# @param name [String] コマンド名
|
|
11
|
+
# @param script [String, nil] スクリプトパス
|
|
12
|
+
# @param description [String] コマンドの説明
|
|
13
|
+
# @param interpreter [String, nil] インタープリタ(nilの場合は自動検出)
|
|
14
|
+
# @param ruby_block [Proc, nil] inline Rubyブロック
|
|
15
|
+
# @param shell_command [String, nil] inline シェルコマンド
|
|
16
|
+
def initialize(name:, script: nil, description: "", interpreter: nil,
|
|
17
|
+
ruby_block: nil, shell_command: nil)
|
|
18
|
+
@name = name.to_s
|
|
19
|
+
@script = script ? normalize_path(script.to_s) : nil
|
|
20
|
+
@description = description.to_s
|
|
21
|
+
@interpreter = interpreter || auto_resolve_interpreter
|
|
22
|
+
@ruby_block = ruby_block
|
|
23
|
+
@shell_command = shell_command
|
|
24
|
+
@errors = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# コマンドタイプを返す
|
|
28
|
+
# @return [Symbol] :ruby, :shell, :script のいずれか
|
|
29
|
+
def command_type
|
|
30
|
+
if @ruby_block
|
|
31
|
+
:ruby
|
|
32
|
+
elsif @shell_command
|
|
33
|
+
:shell
|
|
34
|
+
else
|
|
35
|
+
:script
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# コマンドが有効かどうかを検証する
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
def valid?
|
|
42
|
+
@errors = []
|
|
43
|
+
validate_name
|
|
44
|
+
validate_execution_source
|
|
45
|
+
@errors.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 実行用の引数配列を返す
|
|
49
|
+
# @return [Array<String>] [インタープリタ, スクリプトパス]
|
|
50
|
+
def to_execution_args
|
|
51
|
+
[@interpreter, @script]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ハッシュ表現を返す
|
|
55
|
+
# @return [Hash]
|
|
56
|
+
def to_h
|
|
57
|
+
hash = {
|
|
58
|
+
name: @name,
|
|
59
|
+
script: @script,
|
|
60
|
+
description: @description,
|
|
61
|
+
interpreter: @interpreter
|
|
62
|
+
}
|
|
63
|
+
hash[:has_ruby_block] = true if @ruby_block
|
|
64
|
+
hash[:shell_command] = @shell_command if @shell_command
|
|
65
|
+
hash
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# パスを正規化する(チルダ展開、パストラバーサル解決)
|
|
71
|
+
# @param path [String] 入力パス
|
|
72
|
+
# @return [String] 正規化されたパス
|
|
73
|
+
def normalize_path(path)
|
|
74
|
+
return "" if path.empty?
|
|
75
|
+
|
|
76
|
+
# チルダを展開
|
|
77
|
+
expanded = File.expand_path(path)
|
|
78
|
+
|
|
79
|
+
# ファイルが存在する場合は実際のパスを取得(パストラバーサル解決)
|
|
80
|
+
if File.exist?(expanded)
|
|
81
|
+
File.realpath(expanded)
|
|
82
|
+
else
|
|
83
|
+
# ファイルが存在しない場合は展開されたパスをそのまま返す
|
|
84
|
+
expanded
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# 拡張子からインタープリタを自動検出する
|
|
89
|
+
# @return [String, nil]
|
|
90
|
+
def auto_resolve_interpreter
|
|
91
|
+
return nil if @script.nil? || @script.empty?
|
|
92
|
+
|
|
93
|
+
InterpreterResolver.resolve_from_path(@script)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# コマンド名のバリデーション
|
|
97
|
+
def validate_name
|
|
98
|
+
if @name.empty?
|
|
99
|
+
@errors << "Command name is required"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# 実行ソースのバリデーション
|
|
104
|
+
# ruby_block, shell_command, script のいずれかが必要
|
|
105
|
+
def validate_execution_source
|
|
106
|
+
# ruby_block または shell_command がある場合はスクリプト不要
|
|
107
|
+
return if @ruby_block || @shell_command
|
|
108
|
+
|
|
109
|
+
# スクリプトパスのバリデーション
|
|
110
|
+
if @script.nil? || @script.empty?
|
|
111
|
+
@errors << "Script path is required"
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
unless File.exist?(@script)
|
|
116
|
+
@errors << "Script not found: #{@script}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# DSL設定ファイルを読み込んでDslCommandを生成するクラス
|
|
5
|
+
class DslCommandLoader
|
|
6
|
+
attr_reader :errors, :warnings
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@errors = []
|
|
10
|
+
@warnings = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# 文字列からDSLを解析してコマンドをロードする
|
|
14
|
+
# @param dsl_string [String] DSL文字列
|
|
15
|
+
# @return [Array<DslCommand>] 有効なコマンドの配列
|
|
16
|
+
def load_from_string(dsl_string)
|
|
17
|
+
@errors = []
|
|
18
|
+
@warnings = []
|
|
19
|
+
|
|
20
|
+
context = DslContext.new
|
|
21
|
+
begin
|
|
22
|
+
context.instance_eval(dsl_string)
|
|
23
|
+
rescue SyntaxError, StandardError => e
|
|
24
|
+
@errors << "DSL parse error: #{e.message}"
|
|
25
|
+
return []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
validate_commands(context.commands)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# ファイルからDSLをロードする
|
|
32
|
+
# @param file_path [String] 設定ファイルのパス
|
|
33
|
+
# @return [Array<DslCommand>] 有効なコマンドの配列
|
|
34
|
+
def load_from_file(file_path)
|
|
35
|
+
@errors = []
|
|
36
|
+
@warnings = []
|
|
37
|
+
|
|
38
|
+
expanded_path = File.expand_path(file_path)
|
|
39
|
+
unless File.exist?(expanded_path)
|
|
40
|
+
return []
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
content = File.read(expanded_path)
|
|
44
|
+
load_from_string(content)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# 複数のパスからDSLをロードする
|
|
48
|
+
# @param paths [Array<String>] 設定ファイルのパス配列
|
|
49
|
+
# @return [Array<DslCommand>] 有効なコマンドの配列
|
|
50
|
+
def load_from_paths(paths)
|
|
51
|
+
commands = []
|
|
52
|
+
|
|
53
|
+
paths.each do |path|
|
|
54
|
+
commands.concat(load_from_file(path))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
commands
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# デフォルトのパスからDSLをロードする
|
|
61
|
+
# @return [Array<DslCommand>] 有効なコマンドの配列
|
|
62
|
+
def load
|
|
63
|
+
load_from_paths(default_config_paths)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# デフォルトの設定ファイルパスを返す
|
|
67
|
+
# @return [Array<String>] 設定ファイルパスの配列
|
|
68
|
+
def default_config_paths
|
|
69
|
+
home = Dir.home
|
|
70
|
+
[
|
|
71
|
+
File.join(home, ".rufio", "commands.rb"),
|
|
72
|
+
File.join(home, ".config", "rufio", "commands.rb")
|
|
73
|
+
]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# コマンドをバリデーションし、有効なもののみを返す
|
|
79
|
+
# @param commands [Array<DslCommand>] コマンドの配列
|
|
80
|
+
# @return [Array<DslCommand>] 有効なコマンドの配列
|
|
81
|
+
def validate_commands(commands)
|
|
82
|
+
valid_commands = []
|
|
83
|
+
|
|
84
|
+
commands.each do |cmd|
|
|
85
|
+
if cmd.valid?
|
|
86
|
+
valid_commands << cmd
|
|
87
|
+
else
|
|
88
|
+
@warnings << "Command '#{cmd.name}' is invalid: #{cmd.errors.join(', ')}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
valid_commands
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# DSL評価用の独立したコンテキスト
|
|
96
|
+
class DslContext < BasicObject
|
|
97
|
+
attr_reader :commands
|
|
98
|
+
|
|
99
|
+
def initialize
|
|
100
|
+
@commands = []
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# コマンドを定義する
|
|
104
|
+
# @param name [String] コマンド名
|
|
105
|
+
# @yield コマンドの設定ブロック
|
|
106
|
+
def command(name, &block)
|
|
107
|
+
builder = CommandBuilder.new(name)
|
|
108
|
+
builder.instance_eval(&block) if block
|
|
109
|
+
@commands << builder.build
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 安全でないメソッドをブロック
|
|
113
|
+
def method_missing(method_name, *_args)
|
|
114
|
+
::Kernel.raise ::NoMethodError, "Method '#{method_name}' is not allowed in DSL"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# コマンドビルダー
|
|
123
|
+
class CommandBuilder
|
|
124
|
+
def initialize(name)
|
|
125
|
+
@name = name
|
|
126
|
+
@script = nil
|
|
127
|
+
@description = ""
|
|
128
|
+
@interpreter = nil
|
|
129
|
+
@ruby_block = nil
|
|
130
|
+
@shell_command = nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def script(path)
|
|
134
|
+
@script = path
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def description(desc)
|
|
138
|
+
@description = desc
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def interpreter(interp)
|
|
142
|
+
@interpreter = interp
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# inline Rubyブロックを定義
|
|
146
|
+
def ruby(&block)
|
|
147
|
+
@ruby_block = block
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# inline シェルコマンドを定義
|
|
151
|
+
def shell(command)
|
|
152
|
+
@shell_command = command
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def build
|
|
156
|
+
DslCommand.new(
|
|
157
|
+
name: @name,
|
|
158
|
+
script: @script,
|
|
159
|
+
description: @description,
|
|
160
|
+
interpreter: @interpreter,
|
|
161
|
+
ruby_block: @ruby_block,
|
|
162
|
+
shell_command: @shell_command
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# 未知のメソッドは無視
|
|
167
|
+
def method_missing(_method_name, *_args)
|
|
168
|
+
# DSL内で未知のメソッドが呼ばれた場合は無視
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
173
|
+
true
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/rufio/file_preview.rb
CHANGED
|
@@ -67,12 +67,20 @@ module Rufio
|
|
|
67
67
|
file.each_line.with_index do |line, index|
|
|
68
68
|
break if index >= max_lines
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
line = line
|
|
73
|
-
|
|
70
|
+
begin
|
|
71
|
+
# Ensure line is properly encoded as UTF-8
|
|
72
|
+
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
73
|
+
|
|
74
|
+
# truncate too long lines
|
|
75
|
+
if line.length > MAX_LINE_LENGTH
|
|
76
|
+
line = line[0...MAX_LINE_LENGTH] + "..."
|
|
77
|
+
end
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
lines << line.chomp
|
|
80
|
+
rescue EncodingError, ArgumentError => e
|
|
81
|
+
# If encoding fails, add placeholder
|
|
82
|
+
lines << "[encoding error in line #{index + 1}]"
|
|
83
|
+
end
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
# check if there are more lines to read
|
|
@@ -91,7 +99,15 @@ module Rufio
|
|
|
91
99
|
File.open(file_path, "r:Shift_JIS:UTF-8", invalid: :replace, undef: :replace, replace: '�') do |file|
|
|
92
100
|
file.each_line.with_index do |line, index|
|
|
93
101
|
break if index >= max_lines
|
|
94
|
-
|
|
102
|
+
|
|
103
|
+
begin
|
|
104
|
+
# Ensure line is properly encoded as UTF-8
|
|
105
|
+
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
106
|
+
lines << line.chomp
|
|
107
|
+
rescue EncodingError, ArgumentError => e
|
|
108
|
+
# If encoding fails, add placeholder
|
|
109
|
+
lines << "[encoding error in line #{index + 1}]"
|
|
110
|
+
end
|
|
95
111
|
end
|
|
96
112
|
truncated = !file.eof?
|
|
97
113
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# 拡張子からインタープリタを解決するクラス
|
|
5
|
+
class InterpreterResolver
|
|
6
|
+
# デフォルトの拡張子とインタープリタのマッピング
|
|
7
|
+
DEFAULT_EXTENSIONS = {
|
|
8
|
+
".rb" => "ruby",
|
|
9
|
+
".py" => "python3",
|
|
10
|
+
".sh" => "bash",
|
|
11
|
+
".js" => "node",
|
|
12
|
+
".pl" => "perl",
|
|
13
|
+
".lua" => "lua",
|
|
14
|
+
".ts" => "ts-node",
|
|
15
|
+
".php" => "php",
|
|
16
|
+
".ps1" => nil # プラットフォーム依存
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# 拡張子からインタープリタを解決する
|
|
21
|
+
# @param extension [String] 拡張子(ドット付きまたはなし)
|
|
22
|
+
# @return [String, nil] インタープリタ名、解決できない場合はnil
|
|
23
|
+
def resolve(extension)
|
|
24
|
+
# ドットで始まらない場合は追加
|
|
25
|
+
ext = extension.start_with?(".") ? extension : ".#{extension}"
|
|
26
|
+
ext = ext.downcase
|
|
27
|
+
|
|
28
|
+
# PowerShellはプラットフォーム依存
|
|
29
|
+
return resolve_powershell if ext == ".ps1"
|
|
30
|
+
|
|
31
|
+
DEFAULT_EXTENSIONS[ext]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# ファイルパスから拡張子を取得してインタープリタを解決する
|
|
35
|
+
# @param path [String] ファイルパス
|
|
36
|
+
# @return [String, nil] インタープリタ名、解決できない場合はnil
|
|
37
|
+
def resolve_from_path(path)
|
|
38
|
+
ext = File.extname(path)
|
|
39
|
+
return nil if ext.empty?
|
|
40
|
+
|
|
41
|
+
resolve(ext)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 全ての拡張子マッピングを取得する
|
|
45
|
+
# @return [Hash] 拡張子とインタープリタのマッピング
|
|
46
|
+
def all_extensions
|
|
47
|
+
# PowerShellのプラットフォーム依存を解決した状態で返す
|
|
48
|
+
DEFAULT_EXTENSIONS.merge(".ps1" => resolve_powershell)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Windowsプラットフォームかどうか
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def windows?
|
|
54
|
+
RUBY_PLATFORM =~ /mingw|mswin|cygwin/ ? true : false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# macOSプラットフォームかどうか
|
|
58
|
+
# @return [Boolean]
|
|
59
|
+
def macos?
|
|
60
|
+
RUBY_PLATFORM =~ /darwin/ ? true : false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Linuxプラットフォームかどうか
|
|
64
|
+
# @return [Boolean]
|
|
65
|
+
def linux?
|
|
66
|
+
RUBY_PLATFORM =~ /linux/ ? true : false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# PowerShellのインタープリタを解決する
|
|
72
|
+
# WindowsではpowershellとWindowsではpwsh
|
|
73
|
+
# @return [String]
|
|
74
|
+
def resolve_powershell
|
|
75
|
+
windows? ? "powershell" : "pwsh"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|