rufio 0.41.0 → 0.60.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 +26 -0
- data/README.md +96 -194
- data/bin/rufio +0 -3
- data/docs/CHANGELOG_v0.50.0.md +128 -0
- data/examples/config.yml +8 -0
- data/lib/rufio/builtin_commands.rb +34 -0
- data/lib/rufio/command_completion.rb +20 -5
- data/lib/rufio/command_mode.rb +157 -46
- data/lib/rufio/config_loader.rb +37 -0
- data/lib/rufio/dsl_command.rb +120 -0
- data/lib/rufio/dsl_command_loader.rb +177 -0
- data/lib/rufio/interpreter_resolver.rb +79 -0
- data/lib/rufio/job_manager.rb +128 -0
- data/lib/rufio/job_mode.rb +146 -0
- data/lib/rufio/keybind_handler.rb +243 -232
- data/lib/rufio/notification_manager.rb +77 -0
- data/lib/rufio/script_config_loader.rb +101 -0
- data/lib/rufio/script_executor.rb +253 -0
- data/lib/rufio/script_path_manager.rb +386 -0
- data/lib/rufio/script_runner.rb +216 -0
- data/lib/rufio/task_status.rb +118 -0
- data/lib/rufio/terminal_ui.rb +181 -495
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +15 -11
- data/scripts/test_jobs/build_simulation.sh +29 -0
- data/scripts/test_jobs/deploy_simulation.sh +37 -0
- data/scripts/test_jobs/quick.sh +11 -0
- data/scripts/test_jobs/random_result.sh +23 -0
- data/scripts/test_jobs/slow_fail.sh +10 -0
- data/scripts/test_jobs/slow_success.sh +10 -0
- data/scripts/test_jobs/very_slow.sh +19 -0
- metadata +23 -13
- data/docs/file-preview-optimization-analysis.md +0 -759
- data/docs/file-preview-performance-issue-FIXED.md +0 -547
- 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
- data/lib/rufio/project_command.rb +0 -147
- data/lib/rufio/project_log.rb +0 -68
- data/lib/rufio/project_mode.rb +0 -58
data/lib/rufio/command_mode.rb
CHANGED
|
@@ -3,21 +3,58 @@
|
|
|
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
|
|
10
|
+
attr_reader :script_runner, :script_path_manager
|
|
9
11
|
|
|
10
12
|
def initialize(background_executor = nil)
|
|
11
13
|
@commands = {}
|
|
12
14
|
@background_executor = background_executor
|
|
13
|
-
|
|
15
|
+
@script_runner = nil
|
|
16
|
+
@script_path_manager = nil
|
|
17
|
+
@job_manager = nil
|
|
18
|
+
load_builtin_commands
|
|
19
|
+
load_dsl_commands
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# ScriptRunnerを設定する
|
|
23
|
+
# @param script_paths [Array<String>] スクリプトパス
|
|
24
|
+
# @param job_manager [JobManager] ジョブマネージャー
|
|
25
|
+
def setup_script_runner(script_paths:, job_manager:)
|
|
26
|
+
@job_manager = job_manager
|
|
27
|
+
@script_runner = ScriptRunner.new(
|
|
28
|
+
script_paths: script_paths,
|
|
29
|
+
job_manager: job_manager
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ScriptPathManagerを設定する(設定ファイルベース)
|
|
34
|
+
# @param config_file [String] 設定ファイルのパス
|
|
35
|
+
# @param job_manager [JobManager] ジョブマネージャー
|
|
36
|
+
def setup_script_path_manager(config_file:, job_manager:)
|
|
37
|
+
@job_manager = job_manager
|
|
38
|
+
@script_path_manager = ScriptPathManager.new(config_file)
|
|
39
|
+
# ScriptRunnerも設定(ScriptPathManagerのパスを使用)
|
|
40
|
+
@script_runner = ScriptRunner.new(
|
|
41
|
+
script_paths: @script_path_manager.paths,
|
|
42
|
+
job_manager: job_manager
|
|
43
|
+
)
|
|
14
44
|
end
|
|
15
45
|
|
|
16
46
|
# コマンドを実行する
|
|
17
|
-
|
|
47
|
+
# @param command_string [String] コマンド文字列
|
|
48
|
+
# @param working_dir [String, nil] 作業ディレクトリ(スクリプト実行時に使用)
|
|
49
|
+
def execute(command_string, working_dir: nil)
|
|
18
50
|
# 空のコマンドは無視
|
|
19
51
|
return nil if command_string.nil? || command_string.strip.empty?
|
|
20
52
|
|
|
53
|
+
# スクリプト実行 (@ で始まる場合)
|
|
54
|
+
if command_string.strip.start_with?('@')
|
|
55
|
+
return execute_script(command_string.strip[1..-1], working_dir)
|
|
56
|
+
end
|
|
57
|
+
|
|
21
58
|
# シェルコマンドの実行 (! で始まる場合)
|
|
22
59
|
if command_string.strip.start_with?('!')
|
|
23
60
|
shell_command = command_string.strip[1..-1]
|
|
@@ -38,32 +75,20 @@ module Rufio
|
|
|
38
75
|
# コマンド名を取得 (前後の空白を削除)
|
|
39
76
|
command_name = command_string.strip.to_sym
|
|
40
77
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
78
|
+
# 統一されたコマンドストアから検索
|
|
79
|
+
command = @commands[command_name]
|
|
80
|
+
if command
|
|
81
|
+
# 内部コマンドを実行
|
|
82
|
+
return execute_unified_command(command_name, command)
|
|
44
83
|
end
|
|
45
84
|
|
|
46
|
-
#
|
|
47
|
-
if @
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if @background_executor.execute_ruby_async(command_display_name) do
|
|
52
|
-
command_method.call
|
|
53
|
-
end
|
|
54
|
-
return "🔄 バックグラウンドで実行中: #{command_display_name}"
|
|
55
|
-
else
|
|
56
|
-
return "⚠️ 既にコマンドが実行中です"
|
|
57
|
-
end
|
|
85
|
+
# 内部コマンドが見つからない場合、スクリプトパスから検索
|
|
86
|
+
if @script_path_manager || @script_runner
|
|
87
|
+
script_result = try_execute_script_from_paths(command_string.strip, working_dir)
|
|
88
|
+
return script_result if script_result
|
|
58
89
|
end
|
|
59
90
|
|
|
60
|
-
#
|
|
61
|
-
begin
|
|
62
|
-
command_method = @commands[command_name][:method]
|
|
63
|
-
command_method.call
|
|
64
|
-
rescue StandardError => e
|
|
65
|
-
"⚠️ コマンド実行エラー: #{e.message}"
|
|
66
|
-
end
|
|
91
|
+
"⚠️ コマンドが見つかりません: #{command_name}"
|
|
67
92
|
end
|
|
68
93
|
|
|
69
94
|
# 利用可能なコマンドのリストを取得
|
|
@@ -73,17 +98,83 @@ module Rufio
|
|
|
73
98
|
|
|
74
99
|
# コマンドの情報を取得
|
|
75
100
|
def command_info(command_name)
|
|
76
|
-
|
|
101
|
+
command = @commands[command_name]
|
|
102
|
+
return nil unless command
|
|
77
103
|
|
|
78
104
|
{
|
|
79
105
|
name: command_name,
|
|
80
|
-
plugin:
|
|
81
|
-
description:
|
|
106
|
+
plugin: command[:source] || "dsl",
|
|
107
|
+
description: command[:command].description
|
|
82
108
|
}
|
|
83
109
|
end
|
|
84
110
|
|
|
111
|
+
# DSLコマンドをロードする
|
|
112
|
+
# @param paths [Array<String>, nil] 設定ファイルのパス配列(nilの場合はデフォルトパス)
|
|
113
|
+
def load_dsl_commands(paths = nil)
|
|
114
|
+
loader = DslCommandLoader.new
|
|
115
|
+
|
|
116
|
+
commands = if paths
|
|
117
|
+
loader.load_from_paths(paths)
|
|
118
|
+
else
|
|
119
|
+
loader.load
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# ユーザーDSLコマンドは既存のコマンドを上書きする(優先度が高い)
|
|
123
|
+
commands.each do |cmd|
|
|
124
|
+
@commands[cmd.name.to_sym] = {
|
|
125
|
+
command: cmd,
|
|
126
|
+
source: "dsl"
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# スクリプト名を補完する
|
|
132
|
+
# @param prefix [String] 入力中の文字列(@を含む)
|
|
133
|
+
# @return [Array<String>] 補完候補(@付き)
|
|
134
|
+
def complete_script(prefix)
|
|
135
|
+
return [] unless @script_runner
|
|
136
|
+
|
|
137
|
+
# @を除去して検索
|
|
138
|
+
search_prefix = prefix.sub(/^@/, '')
|
|
139
|
+
@script_runner.complete(search_prefix).map { |name| "@#{name}" }
|
|
140
|
+
end
|
|
141
|
+
|
|
85
142
|
private
|
|
86
143
|
|
|
144
|
+
# 組み込みコマンドをロードする
|
|
145
|
+
def load_builtin_commands
|
|
146
|
+
builtin = BuiltinCommands.load
|
|
147
|
+
builtin.each do |name, cmd|
|
|
148
|
+
@commands[name] = {
|
|
149
|
+
command: cmd,
|
|
150
|
+
source: "builtin"
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# 統一されたコマンド実行
|
|
156
|
+
# @param command_name [Symbol] コマンド名
|
|
157
|
+
# @param command [Hash] コマンド情報 { command: DslCommand, source: String }
|
|
158
|
+
# @return [Hash] 実行結果
|
|
159
|
+
def execute_unified_command(command_name, command)
|
|
160
|
+
dsl_cmd = command[:command]
|
|
161
|
+
|
|
162
|
+
# バックグラウンドエグゼキュータが利用可能な場合は非同期実行
|
|
163
|
+
if @background_executor
|
|
164
|
+
command_display_name = command_name.to_s
|
|
165
|
+
if @background_executor.execute_ruby_async(command_display_name) do
|
|
166
|
+
ScriptExecutor.execute_command(dsl_cmd)
|
|
167
|
+
end
|
|
168
|
+
return "🔄 バックグラウンドで実行中: #{command_display_name}"
|
|
169
|
+
else
|
|
170
|
+
return "⚠️ 既にコマンドが実行中です"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# 同期実行
|
|
175
|
+
ScriptExecutor.execute_command(dsl_cmd)
|
|
176
|
+
end
|
|
177
|
+
|
|
87
178
|
# シェルコマンドを実行する
|
|
88
179
|
def execute_shell_command(shell_command)
|
|
89
180
|
# コマンドが空の場合
|
|
@@ -112,24 +203,44 @@ module Rufio
|
|
|
112
203
|
end
|
|
113
204
|
end
|
|
114
205
|
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
206
|
+
# スクリプトを実行する(@プレフィックス用)
|
|
207
|
+
# @param script_name [String] スクリプト名
|
|
208
|
+
# @param working_dir [String, nil] 作業ディレクトリ
|
|
209
|
+
# @return [String] 実行結果メッセージ
|
|
210
|
+
def execute_script(script_name, working_dir)
|
|
211
|
+
unless @script_runner
|
|
212
|
+
return "⚠️ スクリプトランナーが設定されていません"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
working_dir ||= Dir.pwd
|
|
216
|
+
|
|
217
|
+
job = @script_runner.run(script_name, working_dir: working_dir)
|
|
218
|
+
|
|
219
|
+
if job
|
|
220
|
+
"🚀 ジョブを開始: #{script_name}"
|
|
221
|
+
else
|
|
222
|
+
"⚠️ スクリプトが見つかりません: #{script_name}"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# スクリプトパスからスクリプトを検索して実行を試みる
|
|
227
|
+
# @param command_name [String] コマンド名
|
|
228
|
+
# @param working_dir [String, nil] 作業ディレクトリ
|
|
229
|
+
# @return [String, nil] 実行結果メッセージ、見つからない場合nil
|
|
230
|
+
def try_execute_script_from_paths(command_name, working_dir)
|
|
231
|
+
return nil unless @script_runner
|
|
232
|
+
|
|
233
|
+
script = @script_runner.find_script(command_name)
|
|
234
|
+
return nil unless script
|
|
235
|
+
|
|
236
|
+
working_dir ||= Dir.pwd
|
|
237
|
+
|
|
238
|
+
job = @script_runner.run(command_name, working_dir: working_dir)
|
|
239
|
+
|
|
240
|
+
if job
|
|
241
|
+
"🚀 ジョブを開始: #{script[:name]}"
|
|
242
|
+
else
|
|
243
|
+
nil
|
|
133
244
|
end
|
|
134
245
|
end
|
|
135
246
|
end
|
data/lib/rufio/config_loader.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'yaml'
|
|
3
4
|
require_relative 'config'
|
|
4
5
|
|
|
5
6
|
module Rufio
|
|
6
7
|
class ConfigLoader
|
|
7
8
|
CONFIG_PATH = File.expand_path('~/.config/rufio/config.rb').freeze
|
|
9
|
+
YAML_CONFIG_PATH = File.expand_path('~/.config/rufio/config.yml').freeze
|
|
8
10
|
|
|
9
11
|
class << self
|
|
10
12
|
def load_config
|
|
@@ -60,6 +62,41 @@ module Rufio
|
|
|
60
62
|
load_config[:command_history_size] || 1000
|
|
61
63
|
end
|
|
62
64
|
|
|
65
|
+
# スクリプトパスの配列を取得
|
|
66
|
+
# @return [Array<String>] 展開済みのスクリプトパス
|
|
67
|
+
def script_paths
|
|
68
|
+
yaml_config = load_yaml_config
|
|
69
|
+
paths = yaml_config[:script_paths] || default_script_paths
|
|
70
|
+
expand_script_paths(paths)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# デフォルトのスクリプトパス
|
|
74
|
+
# @return [Array<String>] デフォルトパス
|
|
75
|
+
def default_script_paths
|
|
76
|
+
[File.expand_path('~/.config/rufio/scripts')]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# スクリプトパスを展開
|
|
80
|
+
# @param paths [Array<String>] パスの配列
|
|
81
|
+
# @return [Array<String>] 展開済みのパス
|
|
82
|
+
def expand_script_paths(paths)
|
|
83
|
+
paths.map { |p| File.expand_path(p) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# YAML設定ファイルを読み込む
|
|
87
|
+
# @param path [String, nil] 設定ファイルのパス(nilの場合はデフォルト)
|
|
88
|
+
# @return [Hash] 設定内容
|
|
89
|
+
def load_yaml_config(path = nil)
|
|
90
|
+
config_path = path || YAML_CONFIG_PATH
|
|
91
|
+
return {} unless File.exist?(config_path)
|
|
92
|
+
|
|
93
|
+
yaml = YAML.safe_load(File.read(config_path), symbolize_names: true)
|
|
94
|
+
yaml || {}
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
warn "Failed to load YAML config: #{e.message}"
|
|
97
|
+
{}
|
|
98
|
+
end
|
|
99
|
+
|
|
63
100
|
private
|
|
64
101
|
|
|
65
102
|
def load_config_file
|
|
@@ -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
|
|
@@ -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
|