diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index edbfee68fefd..abc359fdb862 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -216,6 +216,22 @@ describe Process do end end + describe ".run(args)" do + it "waits for the process" do + Process.run(to_ary(exit_code_command(0))).exit_code.should eq(0) + end + end + + describe ".run(args, &)" do + it "waits for the process" do + Process.run(to_ary(exit_code_command(0))) { }[0].exit_code.should eq(0) + end + + it "returns block result" do + Process.run(to_ary(exit_code_command(0))) { 42 }[1].should eq 42 + end + end + describe ".run" do it "waits for the process" do Process.run(*exit_code_command(0)).exit_code.should eq(0) diff --git a/src/process.cr b/src/process.cr index 2e67e5605fb9..18ce9eca0710 100644 --- a/src/process.cr +++ b/src/process.cr @@ -177,6 +177,28 @@ class Process alias ExecStdio = Redirect | IO::FileDescriptor alias Env = Nil | Hash(String, Nil) | Hash(String, String?) | Hash(String, String) + # Executes a child process and waits for it to complete, returning its status. + # + # See `Process.new` for the meaning of the parameters. + # + # Returns a `Process::Status` representing the child process' exit status. + # + # Raises `IO::Error` if the execution itself fails (for example because the + # executable does not exist or is not executable). + # + # Example: + # + # ``` + # io = IO::Memory.new + # status = Process.run(%w[echo hello], output: io) + # io.to_s # => "hello\n" + # status # => Process::Status[0] + # ``` + def self.run(args : Enumerable(String), *, env : Env = nil, clear_env : Bool = false, + input : Stdio = Redirect::Close, output : Stdio = Redirect::Close, error : Stdio = Redirect::Close, chdir : Path | String? = nil) : Process::Status + new(args, env: env, clear_env: clear_env, input: input, output: output, error: error, chdir: chdir).wait + end + # Executes a child process and waits for it to complete, returning its status. # # See `Process.new` for the meaning of the parameters. @@ -202,6 +224,42 @@ class Process status end + # Executes a child process, yields the block, and then waits for it to finish. + # + # See `Process.new` for the meaning of the parameters. + # + # By default the process is configured to use pipes for input, output and error. + # These will be closed automatically at the end of the block. + # + # Returns a tuple with the process' exit status and the block's output value. + # + # Raises `IO::Error` if the execution itself fails (for example because the + # executable does not exist or is not executable). + # + # Example: + # + # ``` + # status, result = Process.run(%w[echo hello]) do |process| + # process.output.gets_to_end + # end + # status # => Process::Status[0] + # result # => "hello\n" + # ``` + def self.run(args : Enumerable(String), *, env : Env = nil, clear_env : Bool = false, + input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : Path | String? = nil, & : Process -> _) + process = new(args, env: env, clear_env: clear_env, input: input, output: output, error: error, chdir: chdir) + begin + value = yield process + + process.close + status = process.wait + {status, value} + rescue ex + process.terminate + raise ex + end + end + # Executes a child process, yields the block, and then waits for it to finish. # # See `Process.new` for the meaning of the parameters.