Skip to content

Commit 7955049

Browse files
authored
Merge pull request #2692 from ksss/refine-open3
Update open3
2 parents a9c0577 + 98793d9 commit 7955049

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

stdlib/open3/0/open3.rbs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,106 @@
4848
# Options](rdoc-ref:Process@Execution+Options).
4949
#
5050
module Open3
51+
type env = Hash[String, String]
52+
53+
# <!--
54+
# rdoc-file=lib/open3.rb
55+
# - Open3.capture2([env, ] command_line, options = {}) -> [stdout_s, status]
56+
# - Open3.capture2([env, ] exe_path, *args, options = {}) -> [stdout_s, status]
57+
# -->
58+
# Basically a wrapper for Open3.popen3 that:
59+
#
60+
# * Creates a child process, by calling Open3.popen3 with the given arguments
61+
# (except for certain entries in hash `options`; see below).
62+
# * Returns as string `stdout_s` the standard output of the child process.
63+
# * Returns as `status` a `Process::Status` object that represents the exit
64+
# status of the child process.
65+
#
66+
# Returns the array `[stdout_s, status]`:
67+
#
68+
# stdout_s, status = Open3.capture2('echo "Foo"')
69+
# # => ["Foo\n", #<Process::Status: pid 2326047 exit 0>]
70+
#
71+
# Like Process.spawn, this method has potential security vulnerabilities if
72+
# called with untrusted input; see [Command
73+
# Injection](rdoc-ref:command_injection.rdoc@Command+Injection).
74+
#
75+
# Unlike Process.spawn, this method waits for the child process to exit before
76+
# returning, so the caller need not do so.
77+
#
78+
# If the first argument is a hash, it becomes leading argument `env` in the call
79+
# to Open3.popen3; see [Execution
80+
# Environment](rdoc-ref:Process@Execution+Environment).
81+
#
82+
# If the last argument is a hash, it becomes trailing argument `options` in the
83+
# call to Open3.popen3; see [Execution
84+
# Options](rdoc-ref:Process@Execution+Options).
85+
#
86+
# The hash `options` is given; two options have local effect in method
87+
# Open3.capture2:
88+
#
89+
# * If entry `options[:stdin_data]` exists, the entry is removed and its
90+
# string value is sent to the command's standard input:
91+
#
92+
# Open3.capture2('tee', stdin_data: 'Foo')
93+
#
94+
# # => ["Foo", #<Process::Status: pid 2326087 exit 0>]
95+
#
96+
# * If entry `options[:binmode]` exists, the entry is removed and the internal
97+
# streams are set to binary mode.
98+
#
99+
# The single required argument is one of the following:
100+
#
101+
# * `command_line` if it is a string, and if it begins with a shell reserved
102+
# word or special built-in, or if it contains one or more metacharacters.
103+
# * `exe_path` otherwise.
104+
#
105+
# **Argument `command_line`**
106+
#
107+
# String argument `command_line` is a command line to be passed to a shell; it
108+
# must begin with a shell reserved word, begin with a special built-in, or
109+
# contain meta characters:
110+
#
111+
# Open3.capture2('if true; then echo "Foo"; fi') # Shell reserved word.
112+
# # => ["Foo\n", #<Process::Status: pid 2326131 exit 0>]
113+
# Open3.capture2('echo') # Built-in.
114+
# # => ["\n", #<Process::Status: pid 2326139 exit 0>]
115+
# Open3.capture2('date > date.tmp') # Contains meta character.
116+
# # => ["", #<Process::Status: pid 2326174 exit 0>]
117+
#
118+
# The command line may also contain arguments and options for the command:
119+
#
120+
# Open3.capture2('echo "Foo"')
121+
# # => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
122+
#
123+
# **Argument `exe_path`**
124+
#
125+
# Argument `exe_path` is one of the following:
126+
#
127+
# * The string path to an executable to be called.
128+
# * A 2-element array containing the path to an executable and the string to
129+
# be used as the name of the executing process.
130+
#
131+
# Example:
132+
#
133+
# Open3.capture2('/usr/bin/date')
134+
# # => ["Fri Sep 29 01:00:39 PM CDT 2023\n", #<Process::Status: pid 2326222 exit 0>]
135+
#
136+
# Ruby invokes the executable directly, with no shell and no shell expansion:
137+
#
138+
# Open3.capture2('doesnt_exist') # Raises Errno::ENOENT
139+
#
140+
# If one or more `args` is given, each is an argument or option to be passed to
141+
# the executable:
142+
#
143+
# Open3.capture2('echo', 'C #')
144+
# # => ["C #\n", #<Process::Status: pid 2326267 exit 0>]
145+
# Open3.capture2('echo', 'hello', 'world')
146+
# # => ["hello world\n", #<Process::Status: pid 2326299 exit 0>]
147+
#
148+
def self?.capture2: (*String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status]
149+
| (env, *String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status]
150+
51151
# <!--
52152
# rdoc-file=lib/open3.rb
53153
# - Open3.capture2e([env, ] command_line, options = {}) -> [stdout_and_stderr_s, status]
@@ -143,5 +243,6 @@ module Open3
143243
# Open3.capture2e('echo', 'hello', 'world')
144244
# # => ["hello world\n", #<Process::Status: pid 2371894 exit 0>]
145245
#
146-
def self.capture2e: (*String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status]
246+
def self?.capture2e: (*String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status]
247+
| (env, *String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status]
147248
end

test/stdlib/Open3_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,46 @@ class Open3SingletonTest < Test::Unit::TestCase
66
library "open3"
77
testing "singleton(::Open3)"
88

9+
def test_capture2
10+
assert_send_type "(*::String) -> [ ::String, ::Process::Status ]",
11+
Open3, :capture2, 'echo "Foo"'
12+
assert_send_type "(*::String, binmode: boolish) -> [ ::String, ::Process::Status ]",
13+
Open3, :capture2, 'echo "Foo"', binmode: true
14+
assert_send_type "(*::String, stdin_data: ::String) -> [ ::String, ::Process::Status ]",
15+
Open3, :capture2, "#{RUBY_EXECUTABLE} -e 'puts STDIN.read'", stdin_data: 'Foo'
16+
assert_send_type "(::Hash[::String, ::String], *::String) -> [ ::String, ::Process::Status ]",
17+
Open3, :capture2, { 'FOO' => 'BAR' }, "echo $FOO"
18+
end
19+
920
def test_capture2e
1021
assert_send_type "(*::String) -> [ ::String, ::Process::Status ]",
1122
Open3, :capture2e, 'echo "Foo"'
1223
assert_send_type "(*::String, binmode: boolish) -> [ ::String, ::Process::Status ]",
1324
Open3, :capture2e, 'echo "Foo"', binmode: true
1425
assert_send_type "(*::String, stdin_data: ::String) -> [ ::String, ::Process::Status ]",
1526
Open3, :capture2e, "#{RUBY_EXECUTABLE} -e 'puts STDIN.read'", stdin_data: 'Foo'
27+
assert_send_type "(::Hash[::String, ::String], *::String) -> [ ::String, ::Process::Status ]",
28+
Open3, :capture2e, { 'FOO' => 'BAR' }, "echo $FOO"
29+
end
30+
end
31+
32+
class Open3InstanceTest < Test::Unit::TestCase
33+
include TestHelper
34+
35+
library "open3"
36+
testing "::Open3"
37+
38+
class CustomOpen3
39+
include Open3
40+
end
41+
42+
def test_capture2
43+
assert_send_type "(*::String) -> [ ::String, ::Process::Status ]",
44+
CustomOpen3.new, :capture2, 'echo "Foo"'
45+
end
46+
47+
def test_capture2e
48+
assert_send_type "(*::String) -> [ ::String, ::Process::Status ]",
49+
CustomOpen3.new, :capture2e, 'echo "Foo"'
1650
end
1751
end

0 commit comments

Comments
 (0)