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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +96 -194
  4. data/bin/rufio +0 -3
  5. data/docs/CHANGELOG_v0.50.0.md +128 -0
  6. data/examples/config.yml +8 -0
  7. data/lib/rufio/builtin_commands.rb +34 -0
  8. data/lib/rufio/command_completion.rb +20 -5
  9. data/lib/rufio/command_mode.rb +157 -46
  10. data/lib/rufio/config_loader.rb +37 -0
  11. data/lib/rufio/dsl_command.rb +120 -0
  12. data/lib/rufio/dsl_command_loader.rb +177 -0
  13. data/lib/rufio/interpreter_resolver.rb +79 -0
  14. data/lib/rufio/job_manager.rb +128 -0
  15. data/lib/rufio/job_mode.rb +146 -0
  16. data/lib/rufio/keybind_handler.rb +243 -232
  17. data/lib/rufio/notification_manager.rb +77 -0
  18. data/lib/rufio/script_config_loader.rb +101 -0
  19. data/lib/rufio/script_executor.rb +253 -0
  20. data/lib/rufio/script_path_manager.rb +386 -0
  21. data/lib/rufio/script_runner.rb +216 -0
  22. data/lib/rufio/task_status.rb +118 -0
  23. data/lib/rufio/terminal_ui.rb +181 -495
  24. data/lib/rufio/version.rb +1 -1
  25. data/lib/rufio.rb +15 -11
  26. data/scripts/test_jobs/build_simulation.sh +29 -0
  27. data/scripts/test_jobs/deploy_simulation.sh +37 -0
  28. data/scripts/test_jobs/quick.sh +11 -0
  29. data/scripts/test_jobs/random_result.sh +23 -0
  30. data/scripts/test_jobs/slow_fail.sh +10 -0
  31. data/scripts/test_jobs/slow_success.sh +10 -0
  32. data/scripts/test_jobs/very_slow.sh +19 -0
  33. metadata +23 -13
  34. data/docs/file-preview-optimization-analysis.md +0 -759
  35. data/docs/file-preview-performance-issue-FIXED.md +0 -547
  36. data/lib/rufio/plugin.rb +0 -89
  37. data/lib/rufio/plugin_config.rb +0 -59
  38. data/lib/rufio/plugin_manager.rb +0 -84
  39. data/lib/rufio/plugins/file_operations.rb +0 -44
  40. data/lib/rufio/plugins/hello.rb +0 -30
  41. data/lib/rufio/plugins/stop.rb +0 -32
  42. data/lib/rufio/project_command.rb +0 -147
  43. data/lib/rufio/project_log.rb +0 -68
  44. data/lib/rufio/project_mode.rb +0 -58
@@ -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
- load_plugin_commands
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
- def execute(command_string)
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
- unless @commands.key?(command_name)
43
- return "⚠️ コマンドが見つかりません: #{command_name}"
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 @background_executor
48
- command_method = @commands[command_name][:method]
49
- command_display_name = command_name.to_s
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
- return nil unless @commands.key?(command_name)
101
+ command = @commands[command_name]
102
+ return nil unless command
77
103
 
78
104
  {
79
105
  name: command_name,
80
- plugin: @commands[command_name][:plugin],
81
- description: @commands[command_name][: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
- def load_plugin_commands
117
- # 有効なプラグインを取得
118
- enabled_plugins = PluginManager.enabled_plugins
119
-
120
- # 各プラグインからコマンドを取得
121
- enabled_plugins.each do |plugin|
122
- plugin_name = plugin.name
123
- plugin_commands = plugin.commands
124
-
125
- # 各コマンドを登録
126
- plugin_commands.each do |command_name, command_method|
127
- @commands[command_name] = {
128
- method: command_method,
129
- plugin: plugin_name,
130
- description: plugin.description
131
- }
132
- end
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
@@ -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