rufio 1.1.0 → 1.3.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +74 -3
  4. data/README_ja.md +71 -2
  5. data/Rakefile +14 -1
  6. data/bin/rufio +31 -2
  7. data/docs/fast_zip_design_ja.md +167 -0
  8. data/info/help.md +2 -0
  9. data/info/keybindings.md +3 -1
  10. data/info/welcome.md +1 -1
  11. data/lib/rufio/application.rb +12 -1
  12. data/lib/rufio/archive_controller.rb +211 -0
  13. data/lib/rufio/background_command_executor.rb +9 -0
  14. data/lib/rufio/background_status.rb +49 -0
  15. data/lib/rufio/bookmark_controller.rb +38 -121
  16. data/lib/rufio/bookmark_manager.rb +77 -331
  17. data/lib/rufio/command_completion.rb +6 -1
  18. data/lib/rufio/command_history.rb +5 -2
  19. data/lib/rufio/command_mode.rb +40 -134
  20. data/lib/rufio/command_mode_ui.rb +4 -42
  21. data/lib/rufio/config.rb +88 -136
  22. data/lib/rufio/config_loader.rb +3 -11
  23. data/lib/rufio/dialog_renderer.rb +64 -152
  24. data/lib/rufio/dialog_support.rb +69 -0
  25. data/lib/rufio/document_text_extractor.rb +167 -0
  26. data/lib/rufio/file_operation_controller.rb +49 -222
  27. data/lib/rufio/file_preview.rb +46 -2
  28. data/lib/rufio/global_search.rb +121 -9
  29. data/lib/rufio/help_menu.rb +8 -2
  30. data/lib/rufio/interpreter_resolver.rb +6 -11
  31. data/lib/rufio/job_manager.rb +2 -5
  32. data/lib/rufio/key_input.rb +77 -0
  33. data/lib/rufio/keybind_handler.rb +81 -115
  34. data/lib/rufio/local_script_scanner.rb +6 -18
  35. data/lib/rufio/native/aarch64-linux-gnu/librufio_zig.so +0 -0
  36. data/lib/rufio/native/aarch64-linux-musl/librufio_zig.so +0 -0
  37. data/lib/rufio/native/aarch64-macos/librufio_zig.dylib +0 -0
  38. data/lib/rufio/native/aarch64-windows/rufio_zig.dll +0 -0
  39. data/lib/rufio/native/x86_64-linux-gnu/librufio_zig.so +0 -0
  40. data/lib/rufio/native/x86_64-linux-musl/librufio_zig.so +0 -0
  41. data/lib/rufio/native/x86_64-macos/librufio_zig.dylib +0 -0
  42. data/lib/rufio/native/x86_64-windows/rufio_zig.dll +0 -0
  43. data/lib/rufio/native_content_search.rb +153 -0
  44. data/lib/rufio/native_scanner.rb +13 -68
  45. data/lib/rufio/native_scanner_zig.rb +106 -56
  46. data/lib/rufio/native_zip.rb +121 -0
  47. data/lib/rufio/navigation_controller.rb +18 -20
  48. data/lib/rufio/screen.rb +2 -12
  49. data/lib/rufio/script_executor.rb +6 -73
  50. data/lib/rufio/script_file.rb +44 -0
  51. data/lib/rufio/script_path_manager.rb +9 -43
  52. data/lib/rufio/script_runner.rb +13 -50
  53. data/lib/rufio/search_controller.rb +1 -1
  54. data/lib/rufio/shell_command_completion.rb +1 -5
  55. data/lib/rufio/shell_init.rb +90 -0
  56. data/lib/rufio/task_status.rb +5 -1
  57. data/lib/rufio/terminal_ui.rb +55 -294
  58. data/lib/rufio/text_utils.rb +26 -3
  59. data/lib/rufio/ui_renderer.rb +63 -40
  60. data/lib/rufio/version.rb +1 -1
  61. data/lib/rufio/zip_archive.rb +314 -0
  62. data/lib/rufio/zip_extractor.rb +346 -0
  63. data/lib/rufio/zip_writer.rb +268 -0
  64. data/lib/rufio/zoxide_integration.rb +5 -43
  65. data/lib/rufio.rb +5 -0
  66. data/lib_zig/rufio_native/Makefile +54 -17
  67. data/lib_zig/rufio_native/bench.sh +36 -0
  68. data/lib_zig/rufio_native/build.zig +32 -35
  69. data/lib_zig/rufio_native/src/cli.zig +99 -0
  70. data/lib_zig/rufio_native/src/main.zig +187 -83
  71. data/lib_zig/rufio_native/src/os.zig +889 -0
  72. data/lib_zig/rufio_native/src/search.zig +919 -0
  73. data/lib_zig/rufio_native/src/search_test.zig +88 -0
  74. data/lib_zig/rufio_native/src/zip.zig +482 -0
  75. metadata +125 -3
  76. data/lib/rufio/native/rufio_zig.bundle +0 -0
data/lib/rufio/screen.rb CHANGED
@@ -229,12 +229,7 @@ module Rufio
229
229
  return if out_of_bounds?(x, y)
230
230
 
231
231
  char_width = width || TextUtils.display_width(char)
232
- @overlay_cells[y][x] = {
233
- char: char,
234
- fg: fg,
235
- bg: bg,
236
- width: char_width
237
- }
232
+ @overlay_cells[y][x] = Cell.new(char, fg, bg, char_width)
238
233
 
239
234
  @dirty_rows.add(y)
240
235
 
@@ -243,12 +238,7 @@ module Rufio
243
238
  (char_width - 1).times do |offset|
244
239
  next_x = x + 1 + offset
245
240
  break if next_x >= @width
246
- @overlay_cells[y][next_x] = {
247
- char: '',
248
- fg: nil,
249
- bg: nil,
250
- width: 0
251
- }
241
+ @overlay_cells[y][next_x] = Cell.new('', nil, nil, 0)
252
242
  end
253
243
  end
254
244
  end
@@ -83,11 +83,7 @@ module Rufio
83
83
  options[:chdir] = chdir if chdir
84
84
  spawn_env = env || {}
85
85
 
86
- if timeout
87
- execute_shell_with_timeout(dsl_command.shell_command, spawn_env, options, timeout)
88
- else
89
- execute_shell_without_timeout(dsl_command.shell_command, spawn_env, options)
90
- end
86
+ execute_with_options([dsl_command.shell_command], spawn_env, options, timeout)
91
87
  rescue StandardError => e
92
88
  build_error_result(e)
93
89
  end
@@ -133,8 +129,11 @@ module Rufio
133
129
  # プロセスを終了
134
130
  if pid
135
131
  begin
136
- Process.kill("TERM", pid)
137
- sleep 0.1
132
+ # Windows は TERM を送れない(Errno::EINVAL)ため KILL だけで終了させる
133
+ unless Gem.win_platform?
134
+ Process.kill("TERM", pid)
135
+ sleep 0.1
136
+ end
138
137
  Process.kill("KILL", pid)
139
138
  rescue Errno::ESRCH, Errno::EPERM
140
139
  # プロセスが既に終了している、または権限がない
@@ -176,72 +175,6 @@ module Rufio
176
175
  }
177
176
  end
178
177
 
179
- # シェルコマンドをタイムアウト付きで実行
180
- def execute_shell_with_timeout(shell_command, env, options, timeout_sec)
181
- stdout = ""
182
- stderr = ""
183
- status = nil
184
- timed_out = false
185
- pid = nil
186
-
187
- begin
188
- Timeout.timeout(timeout_sec) do
189
- args = env.empty? ? [shell_command] : [env, shell_command]
190
- stdin, stdout_io, stderr_io, wait_thread = Open3.popen3(*args, **options)
191
- pid = wait_thread.pid
192
- stdin.close
193
- stdout = stdout_io.read
194
- stderr = stderr_io.read
195
- stdout_io.close
196
- stderr_io.close
197
- status = wait_thread.value
198
- end
199
- rescue Timeout::Error
200
- timed_out = true
201
- if pid
202
- begin
203
- Process.kill("TERM", pid)
204
- sleep 0.1
205
- Process.kill("KILL", pid)
206
- rescue Errno::ESRCH, Errno::EPERM
207
- # プロセスが既に終了している、または権限がない
208
- end
209
- end
210
- end
211
-
212
- if timed_out
213
- {
214
- success: false,
215
- exit_code: nil,
216
- stdout: stdout,
217
- stderr: stderr,
218
- timeout: true
219
- }
220
- else
221
- {
222
- success: status&.success? || false,
223
- exit_code: status&.exitstatus || 1,
224
- stdout: stdout,
225
- stderr: stderr,
226
- timeout: false
227
- }
228
- end
229
- end
230
-
231
- # シェルコマンドをタイムアウトなしで実行
232
- def execute_shell_without_timeout(shell_command, env, options)
233
- args = env.empty? ? [shell_command] : [env, shell_command]
234
- stdout, stderr, status = Open3.capture3(*args, **options)
235
-
236
- {
237
- success: status.success?,
238
- exit_code: status.exitstatus,
239
- stdout: stdout,
240
- stderr: stderr,
241
- timeout: false
242
- }
243
- end
244
-
245
178
  # エラー結果を構築
246
179
  def build_error_result(error)
247
180
  {
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module Rufio
6
+ # 検出対象の拡張子・名前解決・ファイル実行方法を一か所で定義する。
7
+ module ScriptFile
8
+ # .sh は shebang を尊重して直接実行する。PowerShell は既存の pwsh を維持する。
9
+ INTERPRETERS = {
10
+ '.sh' => nil, '.rb' => 'ruby', '.py' => 'python3', '.pl' => 'perl',
11
+ '.js' => 'node', '.ts' => 'ts-node', '.ps1' => 'pwsh'
12
+ }.freeze
13
+ EXTENSIONS = INTERPRETERS.keys.freeze
14
+
15
+ def self.supported?(path)
16
+ INTERPRETERS.key?(File.extname(path).downcase)
17
+ end
18
+
19
+ def self.executable?(path)
20
+ supported?(path) || File.executable?(path)
21
+ end
22
+
23
+ def self.find(scripts, name)
24
+ exact = scripts.find { |script| script[:name] == name }
25
+ return exact if exact
26
+
27
+ EXTENSIONS.each do |extension|
28
+ match = scripts.find { |script| script[:name] == "#{name}#{extension}" }
29
+ return match if match
30
+ end
31
+ nil
32
+ end
33
+
34
+ def self.complete(scripts, prefix)
35
+ scripts.map { |script| script[:name] }.select { |name| name.start_with?(prefix) }.uniq.sort
36
+ end
37
+
38
+ def self.command(path, args: nil)
39
+ interpreter = INTERPRETERS[File.extname(path).downcase]
40
+ command = [interpreter, path.shellescape].compact.join(' ')
41
+ args && !args.empty? ? "#{command} #{args}" : command
42
+ end
43
+ end
44
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'script_file'
4
+
3
5
  require 'yaml'
6
+ require 'set'
4
7
  require_relative 'config'
5
8
 
6
9
  module Rufio
@@ -10,7 +13,7 @@ module Rufio
10
13
  attr_reader :paths
11
14
 
12
15
  # サポートするスクリプト拡張子
13
- SUPPORTED_EXTENSIONS = %w[.sh .rb .py .pl .js .ts .ps1].freeze
16
+ SUPPORTED_EXTENSIONS = ScriptFile::EXTENSIONS
14
17
 
15
18
  # 履歴の最大サイズ
16
19
  MAX_HISTORY_SIZE = 100
@@ -56,14 +59,7 @@ module Rufio
56
59
 
57
60
  scripts = find_scripts(command_name)
58
61
 
59
- case scripts.size
60
- when 0
61
- nil
62
- when 1
63
- @cache[command_name] = scripts.first
64
- else
65
- @cache[command_name] = scripts.first
66
- end
62
+ @cache[command_name] = scripts.first unless scripts.empty?
67
63
  end
68
64
 
69
65
  # 全スクリプト一覧を取得(タブ補完用)
@@ -121,7 +117,7 @@ module Rufio
121
117
 
122
118
  # すべてのマッチを取得(複数マッチ対応)
123
119
  def find_all_matches(command_name)
124
- find_scripts_all_paths(command_name)
120
+ find_scripts(command_name, all_paths: true)
125
121
  end
126
122
 
127
123
  # スクリプト名を補完
@@ -232,7 +228,7 @@ module Rufio
232
228
  paths.map { |p| File.expand_path(p) }
233
229
  end
234
230
 
235
- def find_scripts(command_name)
231
+ def find_scripts(command_name, all_paths: false)
236
232
  scripts = []
237
233
  basename_without_ext = command_name.sub(/\.[^.]+$/, '')
238
234
  has_extension = command_name.include?('.')
@@ -255,44 +251,14 @@ module Rufio
255
251
  end
256
252
  end
257
253
 
258
- return scripts unless scripts.empty?
254
+ return scripts if !all_paths && !scripts.empty?
259
255
  end
260
256
 
261
257
  scripts
262
258
  end
263
259
 
264
260
  def executable_script?(path)
265
- ext = File.extname(path).downcase
266
- return true if SUPPORTED_EXTENSIONS.include?(ext)
267
-
268
- File.executable?(path)
269
- end
270
-
271
- def find_scripts_all_paths(command_name)
272
- scripts = []
273
- basename_without_ext = command_name.sub(/\.[^.]+$/, '')
274
- has_extension = command_name.include?('.')
275
-
276
- @paths.each do |path|
277
- next unless Dir.exist?(path)
278
-
279
- Dir.glob(File.join(path, '**', '*')).each do |file|
280
- next unless File.file?(file)
281
-
282
- file_basename = File.basename(file)
283
- next if file_basename.start_with?('.')
284
- next unless executable_script?(file)
285
-
286
- if has_extension
287
- scripts << file if file_basename.downcase == command_name.downcase
288
- else
289
- file_name_without_ext = File.basename(file, '.*')
290
- scripts << file if file_name_without_ext.downcase == basename_without_ext.downcase
291
- end
292
- end
293
- end
294
-
295
- scripts
261
+ ScriptFile.executable?(path)
296
262
  end
297
263
 
298
264
  def levenshtein_distance(s1, s2)
@@ -1,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'script_file'
4
+
3
5
  require 'open3'
6
+ require 'set'
4
7
 
5
8
  module Rufio
6
9
  # スクリプトパスからスクリプトを検索・実行するクラス
7
10
  # ジョブマネージャーと連携してバックグラウンド実行を管理
8
11
  class ScriptRunner
9
12
  # サポートするスクリプト拡張子
10
- SUPPORTED_EXTENSIONS = %w[.sh .rb .py .pl .js .ts .ps1].freeze
13
+ SUPPORTED_EXTENSIONS = ScriptFile::EXTENSIONS
11
14
 
12
15
  # @param script_paths [Array<String>] スクリプトを検索するディレクトリのリスト
13
16
  # @param job_manager [JobManager, nil] ジョブマネージャー(nilの場合は同期実行)
@@ -29,28 +32,14 @@ module Rufio
29
32
  # @param name [String] スクリプト名(拡張子あり/なし)
30
33
  # @return [Hash, nil] スクリプト情報 { name:, path:, dir: } または nil
31
34
  def find_script(name)
32
- # 完全一致を優先
33
- script = available_scripts.find { |s| s[:name] == name }
34
- return script if script
35
-
36
- # 拡張子なしで検索
37
- SUPPORTED_EXTENSIONS.each do |ext|
38
- script = available_scripts.find { |s| s[:name] == "#{name}#{ext}" }
39
- return script if script
40
- end
41
-
42
- nil
35
+ ScriptFile.find(available_scripts, name)
43
36
  end
44
37
 
45
38
  # スクリプト名の補完候補を取得
46
39
  # @param prefix [String] 入力中の文字列
47
40
  # @return [Array<String>] 補完候補のスクリプト名
48
41
  def complete(prefix)
49
- available_scripts
50
- .map { |s| s[:name] }
51
- .select { |name| name.start_with?(prefix) }
52
- .uniq
53
- .sort
42
+ ScriptFile.complete(available_scripts, prefix)
54
43
  end
55
44
 
56
45
  # スクリプトをジョブとして実行
@@ -64,6 +53,11 @@ module Rufio
64
53
  script = find_script(name)
65
54
  return nil unless script
66
55
 
56
+ execute(script, working_dir: working_dir, args: args, selected_file: selected_file, selected_dir: selected_dir)
57
+ end
58
+
59
+ # 検出済みスクリプトの実行。検索元によらず同じジョブ・ログ処理を使う。
60
+ def execute(script, working_dir:, args: nil, selected_file: nil, selected_dir: nil)
67
61
  env = build_environment(working_dir, selected_file, selected_dir)
68
62
  execute_script(script, working_dir, env, args)
69
63
  end
@@ -108,11 +102,7 @@ module Rufio
108
102
  # @param path [String] ファイルパス
109
103
  # @return [Boolean]
110
104
  def executable_script?(path)
111
- ext = File.extname(path).downcase
112
- return true if SUPPORTED_EXTENSIONS.include?(ext)
113
-
114
- # 拡張子がなくても実行可能なら対象
115
- File.executable?(path)
105
+ ScriptFile.executable?(path)
116
106
  end
117
107
 
118
108
  # 環境変数を構築
@@ -136,8 +126,7 @@ module Rufio
136
126
  # @param args [String, nil] スクリプトに渡す引数
137
127
  # @return [TaskStatus] 作成されたジョブ
138
128
  def execute_script(script, working_dir, env = {}, args = nil)
139
- command = build_command(script)
140
- command = "#{command} #{args}" if args && !args.empty?
129
+ command = ScriptFile.command(script[:path], args: args)
141
130
 
142
131
  if @job_manager
143
132
  # ジョブマネージャーにジョブを追加
@@ -166,32 +155,6 @@ module Rufio
166
155
  end
167
156
  end
168
157
 
169
- # スクリプトの実行コマンドを構築
170
- # @param script [Hash] スクリプト情報
171
- # @return [String] 実行コマンド
172
- def build_command(script)
173
- path = script[:path]
174
- ext = File.extname(path).downcase
175
-
176
- case ext
177
- when '.rb'
178
- "ruby #{path.shellescape}"
179
- when '.py'
180
- "python3 #{path.shellescape}"
181
- when '.js'
182
- "node #{path.shellescape}"
183
- when '.ts'
184
- "ts-node #{path.shellescape}"
185
- when '.pl'
186
- "perl #{path.shellescape}"
187
- when '.ps1'
188
- "pwsh #{path.shellescape}"
189
- else
190
- # shスクリプトまたは実行可能ファイル
191
- path.shellescape
192
- end
193
- end
194
-
195
158
  # バックグラウンドでスクリプトを実行
196
159
  # @param job [TaskStatus] ジョブ
197
160
  # @param command [String] 実行コマンド
@@ -64,7 +64,7 @@ module Rufio
64
64
  if search_results.empty?
65
65
  puts "\n#{ConfigLoader.message('keybind.no_matches')}"
66
66
  print ConfigLoader.message('keybind.press_any_key')
67
- STDIN.getch
67
+ KeyInput.getch
68
68
  return true
69
69
  end
70
70
 
@@ -108,12 +108,8 @@ module Rufio
108
108
  # @param history [CommandHistory] 履歴オブジェクト
109
109
  # @return [Array<String>] コマンドのリスト
110
110
  def get_history_commands(history)
111
- # CommandHistoryの内部データにアクセスするため、
112
- # instance_variable_getを使用(本来はpublicメソッドを追加すべき)
113
- history_array = history.instance_variable_get(:@history) || []
114
-
115
111
  # ! を除去したコマンドを返す
116
- history_array.map do |cmd|
112
+ history.entries.map do |cmd|
117
113
  cmd.start_with?('!') ? cmd[1..-1] : cmd
118
114
  end
119
115
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rufio
4
+ # `rufio --shell-init` が出力するシェル関数。
5
+ # rufio は子プロセスなので親シェルの作業ディレクトリを変えられない。
6
+ # T キーで終了した時に rufio が --choosedir のファイルへ書いたディレクトリへ、関数側で cd する。
7
+ module ShellInit
8
+ FUNCTION_NAME = 'rf'
9
+ SHELLS = %w[bash zsh fish powershell].freeze
10
+
11
+ class << self
12
+ # OS とログインシェルから出力するシェルを決める。
13
+ # Windows は PowerShell。ただし Git Bash など SHELL が設定された環境ではそのシェルに合わせる。
14
+ def detect(platform: RUBY_PLATFORM, env: ENV)
15
+ shell = env['SHELL'].to_s.split(%r{[/\\]}).last.to_s.sub(/\.exe\z/i, '')
16
+ return shell if SHELLS.include?(shell)
17
+
18
+ platform =~ /mswin|mingw/ ? 'powershell' : 'bash'
19
+ end
20
+
21
+ def script(shell)
22
+ case shell
23
+ when 'bash', 'zsh' then posix_script(shell)
24
+ when 'fish' then fish_script
25
+ when 'powershell', 'pwsh' then powershell_script
26
+ else
27
+ raise ArgumentError, "Unsupported shell: #{shell} (#{SHELLS.join(', ')})"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def posix_script(shell)
34
+ <<~SH
35
+ # rufio shell integration (#{shell})
36
+ # Add to ~/.#{shell}rc: eval "$(rufio --shell-init #{shell})"
37
+ # Run `#{FUNCTION_NAME}` and press T to quit rufio and cd to the last directory.
38
+ #{FUNCTION_NAME}() {
39
+ local tmp dir
40
+ tmp="$(mktemp -t rufio-cwd.XXXXXX)" || return
41
+ command rufio --choosedir "$tmp" "$@"
42
+ dir="$(cat -- "$tmp" 2>/dev/null)"
43
+ rm -f -- "$tmp"
44
+ if [ -n "$dir" ] && [ -d "$dir" ] && [ "$dir" != "$PWD" ]; then
45
+ cd -- "$dir"
46
+ fi
47
+ }
48
+ SH
49
+ end
50
+
51
+ def fish_script
52
+ <<~FISH
53
+ # rufio shell integration (fish)
54
+ # Add to ~/.config/fish/config.fish: rufio --shell-init fish | source
55
+ # Run `#{FUNCTION_NAME}` and press T to quit rufio and cd to the last directory.
56
+ function #{FUNCTION_NAME}
57
+ set -l tmp (mktemp -t rufio-cwd.XXXXXX); or return
58
+ command rufio --choosedir $tmp $argv
59
+ set -l dir (cat -- $tmp 2>/dev/null)
60
+ rm -f -- $tmp
61
+ if test -n "$dir"; and test -d "$dir"; and test "$dir" != "$PWD"
62
+ cd -- $dir
63
+ end
64
+ end
65
+ FISH
66
+ end
67
+
68
+ def powershell_script
69
+ <<~PS1
70
+ # rufio shell integration (PowerShell)
71
+ # Add to $PROFILE: Invoke-Expression (& rufio --shell-init powershell | Out-String)
72
+ # Run `#{FUNCTION_NAME}` and press T to quit rufio and cd to the last directory.
73
+ function #{FUNCTION_NAME} {
74
+ $tmp = New-TemporaryFile
75
+ try {
76
+ rufio --choosedir $tmp.FullName @args
77
+ $dir = Get-Content -LiteralPath $tmp.FullName -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
78
+ if ($dir) { $dir = $dir.Trim() }
79
+ if ($dir -and (Test-Path -LiteralPath $dir -PathType Container)) {
80
+ Set-Location -LiteralPath $dir
81
+ }
82
+ } finally {
83
+ Remove-Item -LiteralPath $tmp.FullName -ErrorAction SilentlyContinue
84
+ }
85
+ }
86
+ PS1
87
+ end
88
+ end
89
+ end
90
+ end
@@ -6,14 +6,18 @@ module Rufio
6
6
  class TaskStatus
7
7
  attr_accessor :id, :name, :path, :status, :start_time, :end_time, :logs, :exit_code
8
8
 
9
+ attr_reader :command
10
+
9
11
  # 初期化
10
12
  # @param id [Integer] タスクID
11
13
  # @param name [String] タスク名(スクリプト名)
12
14
  # @param path [String] 実行ディレクトリ
13
- def initialize(id:, name:, path:)
15
+ # @param command [String, nil] 実行コマンド
16
+ def initialize(id:, name:, path:, command: nil)
14
17
  @id = id
15
18
  @name = name
16
19
  @path = path
20
+ @command = command
17
21
  @status = :waiting
18
22
  @start_time = nil
19
23
  @end_time = nil