test-cmd.rb 2.2.0 → 2.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e57955db2ea9f64997167878e30449b920c1746751e493b54c1ff549b852d55d
4
- data.tar.gz: 3e2527cb8deedc6205726ff8900aa2e23c15b69c9662debfb9322ebbc38e7ee3
3
+ metadata.gz: a0790a7e8e68709204aeb42611543db79e3575615d6ed77ee0b166fff7827825
4
+ data.tar.gz: 7965cd9baf53560ff8648f0652876b3d31a969528b1419b5b975406ec37c13fc
5
5
  SHA512:
6
- metadata.gz: 782b41654883c063415e13191a28fdaf719123db4c4980a1f69102d23ad2026a61e8ce0b2c927cde4e12b321426cdcf1ecd25fce528fca5d586fe627160c8373
7
- data.tar.gz: 442fb73c9759f458c448729a2893b507ab0c8e6db2d5d8bea4d465b2b117c01794f9b501bb910460dbbc9a56f627c8147dea53a7e0814c1ae503133b4cc2774a
6
+ metadata.gz: f0f7c63d381cbbd8c603c8fc7a1c22e7c73509b0a51965107c2fe2c23adf3c2b8cf5f243712b4f297f90aeb0535c01477ddd4518c10be6afd6a29b248c30af18
7
+ data.tar.gz: aee5d21c5948e17b29f0df43636bb18d396d6919efdc3a0fe6c03628ba85d5f3ae0e04cd03e61e51c33ad30aa88a2e7a840051cc704d3ac384957ea75d6f1cd1
data/LICENSE CHANGED
@@ -1,15 +1,21 @@
1
- Copyright (C) 2023 by 0x1eef <0x1eef@protonmail.com>
2
-
3
- Permission to use, copy, modify, and/or distribute this
4
- software for any purpose with or without fee is hereby
5
- granted.
6
-
7
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
8
- ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
9
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
10
- EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
12
- RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
14
- ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
15
- OF THIS SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robert Gleeson <robert@r.uby.dev>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -10,16 +10,19 @@
10
10
  </a>
11
11
  </p>
12
12
 
13
- > A [r.uby.dev](https://r.uby.dev) project.
13
+ > [r.uby.dev](https://r.uby.dev) project.
14
14
 
15
15
  Welcome to the canonical test-cmd.rb repository.
16
16
 
17
17
  test-cmd.rb is a Go-inspired, object-oriented interface for running
18
18
  commands on UNIX-like systems, in the spirit of Go's `os/exec`. A
19
- command is built up and then spawned so its standard output and
20
- error streams, process ID, and exit status are captured in the
21
- background. Predicates, callbacks, and process control cover the
22
- common cases with a small and dependency-free API.
19
+ command can be built by chaining multiple method calls that impact
20
+ different command attributes. The environment the command executes
21
+ with can be set, its standard input stream can be written to, and
22
+ the standard output and standard error streams are captured so the
23
+ parent process can read them.
24
+
25
+ It has zero runtime dependencies (stdlib-only).
23
26
 
24
27
  ## Install
25
28
 
@@ -31,37 +34,51 @@ test-cmd.rb can be installed via rubygems.org:
31
34
 
32
35
  ### Commands
33
36
 
34
- A command is created with
35
- [`Test::Command#initialize`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#initialize-instance_method)
36
- which takes the name or path of a command, and given additional
37
- arguments with
38
- [`Test::Command#argv`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#argv-instance_method).
39
- Environment variables can be set for the spawned process with
40
- [`Test::Command#env`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#env-instance_method),
41
- and standard input with
42
- [`Test::Command#stdin`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#stdin-instance_method).
43
- The instance has access to the command's process ID, exit status,
44
- standard output stream, and standard error stream.
45
37
 
46
38
  ```ruby
47
39
  require "test-cmd"
48
- puts Test::Command.new("ls").argv("-l").stdout
49
- puts Test::Command.new("env").env("FOO" => "bar").stdout
50
- test_cmd = Test::Command.new("tr", "a-z", "A-Z").stdin("hello").stdout # => "HELLO"
40
+
41
+ ##
42
+ # Call the 'ls' command
43
+ p Test::Command
44
+ .new("ls")
45
+ .argv("-l")
46
+ .stdout
47
+
48
+ ##
49
+ # Call the env command with the given environment
50
+ # variables set.
51
+ p Test::Command
52
+ .new("env")
53
+ .env("FOO" => "bar")
54
+ .stdout
55
+
56
+ ##
57
+ # Write to the stdin stream of the command that
58
+ # is executed.
59
+ p Test::Command
60
+ .new("tr", "a-z", "A-Z")
61
+ .stdin("hello")
62
+ .stdout # => "HELLO"
63
+
64
+ ##
65
+ # Limit how much data is read from stdout and
66
+ # stderr. At most 20k chars is read from both:
67
+ p Test::Command
68
+ .new("git")
69
+ .limit(stdout: 20_000, stderr: 20_000)
70
+ .argv("diff", "--cached")
71
+ .stdout
51
72
  ```
52
73
 
53
74
  <details>
54
75
  <summary>Environment</summary>
55
76
  <br>
56
77
 
57
- Environment variables for the spawned process are set with
58
- [`Test::Command#env`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#env-instance_method),
59
- which merges the given variables into the child's environment and
60
- returns the command for chaining.
61
-
62
78
  ```ruby
63
79
  require "test-cmd"
64
- puts Test::Command
80
+
81
+ p Test::Command
65
82
  .new("ruby", "-e", "puts ENV['FOO']")
66
83
  .env("FOO" => "42")
67
84
  .stdout # => "42\n"
@@ -72,18 +89,16 @@ puts Test::Command
72
89
  <summary>Standard input</summary>
73
90
  <br>
74
91
 
75
- Standard input for the spawned process is set with
76
- [`Test::Command#stdin`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#stdin-instance_method).
77
- Pass a String to write it to the child's standard input, or another
78
- [Test::Command](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html)
79
- whose standard output will be used as the standard input.
80
-
81
92
  ```ruby
82
93
  require "test-cmd"
83
- puts Test::Command.new("cat").stdin("hello world").stdout # => "hello world"
84
94
 
85
- # Pipe one command's standard output into another's standard input
86
- puts Test::Command.new("tr", "a-z", "A-Z")
95
+ p Test::Command
96
+ .new("cat")
97
+ .stdin("hello world")
98
+ .stdout # => "hello world"
99
+
100
+ p Test::Command
101
+ .new("tr", "a-z", "A-Z")
87
102
  .stdin(Test::Command.new("echo", "hello"))
88
103
  .stdout # => "HELLO\n"
89
104
  ```
@@ -93,13 +108,6 @@ puts Test::Command.new("tr", "a-z", "A-Z")
93
108
  <summary>Callbacks</summary>
94
109
  <br>
95
110
 
96
- The success and failure callbacks provide hooks for when
97
- a command exits successfully or unsuccessfully. The callbacks
98
- are passed an instance of
99
- [Test::Command](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html)
100
- that has access to the command's process ID, exit status,
101
- standard output stream, and standard error stream.
102
-
103
111
  ```ruby
104
112
  require "test-cmd"
105
113
  Test::Command.new("ruby", "-e", "exit 0")
@@ -121,7 +129,7 @@ stream of the spawned ruby process:
121
129
 
122
130
  ```ruby
123
131
  require "test/unit"
124
- require "test/cmd"
132
+ require "test-cmd"
125
133
 
126
134
  class CmdTest < Test::Unit::TestCase
127
135
  def test_ruby_stdout
@@ -149,12 +157,7 @@ end
149
157
  ```
150
158
  </details>
151
159
 
152
- ## Documentation
153
-
154
- A complete API reference is available at
155
- [r.uby.dev/api-docs/test-cmd.rb](https://r.uby.dev/api-docs/test-cmd.rb)
156
-
157
160
  ## License
158
161
 
159
- This software is released under the terms of the BSD Zero Clause license. <br>
160
- See [LICENSE](./LICENSE) for details.
162
+ This software is released under the MIT license. <br>
163
+ See [LICENSE](LICENSE) for details.
@@ -0,0 +1,8 @@
1
+ module Test
2
+ end unless defined?(Test)
3
+
4
+ class Test::Command
5
+ ##
6
+ # @string [String]
7
+ VERSION = "2.5.0"
8
+ end
@@ -5,6 +5,8 @@ end unless defined?(Test)
5
5
  # test-cmd.rb provides an object oriented interface
6
6
  # for spawning a command
7
7
  class Test::Command
8
+ require_relative "command/version"
9
+
8
10
  ##
9
11
  # @api private
10
12
  class Pipe < Struct.new(:r, :w)
@@ -32,6 +34,7 @@ class Test::Command
32
34
  @stdout = ""
33
35
  @stderr = ""
34
36
  @enoent = false
37
+ @limit = {stdout: nil, stderr: nil}
35
38
  end
36
39
 
37
40
  ##
@@ -65,6 +68,19 @@ class Test::Command
65
68
  tap { @stdin = data }
66
69
  end
67
70
 
71
+ ##
72
+ # Limits how many bytes of each stream are captured.
73
+ # A stream output beyond the limit is discarded, keeping
74
+ # memory bounded even for a command that streams a lot.
75
+ # @param [Integer, nil] stdout
76
+ # Max bytes of stdout to read, or nil for unlimited
77
+ # @param [Integer, nil] stderr
78
+ # Max bytes of stderr to read, or nil for unlimited
79
+ # @return [Test::Command]
80
+ def limit(stdout: nil, stderr: nil)
81
+ tap { @limit = {stdout:, stderr:} }
82
+ end
83
+
68
84
  ##
69
85
  # Spawns a command
70
86
  # @return [Test::Command]
@@ -76,15 +92,15 @@ class Test::Command
76
92
  @out, @err = Pipe.pair, Pipe.pair
77
93
  @pid = Process.spawn(
78
94
  @env,
79
- @cmd, *@argv,
95
+ @cmd, *@argv.map(&:to_s),
80
96
  {in: @in_r, out: @out.w, err: @err.w}
81
97
  )
82
98
  @in_r.close unless @in_r.equal?(IO::NULL)
83
99
  @out.w.close
84
100
  @err.w.close
85
101
  @producer = Thread.new do
86
- @stdout = @out.r.read
87
- @stderr = @err.r.read
102
+ @stdout = read_limited(@out.r, @limit[:stdout])
103
+ @stderr = read_limited(@err.r, @limit[:stderr])
88
104
  Process.wait
89
105
  @status = $?
90
106
  ensure
@@ -230,6 +246,31 @@ class Test::Command
230
246
 
231
247
  private
232
248
 
249
+ ##
250
+ # Reads up to +max+ bytes from a stream, then drains
251
+ # the remainder so a child writing more than the limit
252
+ # does not block on a full pipe. When +max+ is nil the
253
+ # entire stream is read.
254
+ # @param [IO] io
255
+ # @param [Integer, nil] max
256
+ # @return [String]
257
+ def read_limited(io, max)
258
+ return io.read if max.nil?
259
+ buf = +""
260
+ remaining = max
261
+ while remaining > 0 && (chunk = io.read(remaining))
262
+ buf << chunk
263
+ remaining -= chunk.bytesize
264
+ end
265
+ ##
266
+ # Drain whatever is left so the child can exit:
267
+ # the drained bytes are discarded, keeping memory
268
+ # bounded.
269
+ while (chunk = io.read(16_384))
270
+ end
271
+ buf
272
+ end
273
+
233
274
  ##
234
275
  # Returns a pipe pair used for the command's
235
276
  # standard input. When no input is set, the child
@@ -256,4 +297,4 @@ class Test::Command
256
297
  return unless @producer&.alive?
257
298
  @producer.join
258
299
  end
259
- end
300
+ end
data/lib/test-cmd.rb CHANGED
@@ -1 +1 @@
1
- require_relative "test/cmd"
1
+ require_relative "test/command"
data/rubydev.svg CHANGED
@@ -1,30 +1,78 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 220" role="img" aria-labelledby="title desc">
2
2
  <title id="title">r.uby.dev</title>
3
- <desc id="desc">A wide r.uby.dev wordmark with three small ruby gemstones moving around the text.</desc>
3
+ <desc id="desc">A wide r.uby.dev wordmark with ruby, sapphire, and emerald gemstones moving around the text.</desc>
4
4
  <defs>
5
5
  <linearGradient id="body" x1="45" y1="35" x2="211" y2="224" gradientUnits="userSpaceOnUse">
6
- <stop offset="0" stop-color="#ff7468"/>
7
- <stop offset="0.42" stop-color="#cc342d"/>
6
+ <stop offset="0" stop-color="#e0796f"/>
7
+ <stop offset="0.42" stop-color="#a7312b"/>
8
8
  <stop offset="1" stop-color="#5b090d"/>
9
9
  </linearGradient>
10
10
  <linearGradient id="table" x1="75" y1="31" x2="166" y2="104" gradientUnits="userSpaceOnUse">
11
11
  <stop offset="0" stop-color="#fff3ec"/>
12
- <stop offset="0.26" stop-color="#ff8f84"/>
13
- <stop offset="1" stop-color="#d62f2c"/>
12
+ <stop offset="0.26" stop-color="#d9867d"/>
13
+ <stop offset="1" stop-color="#a32d2a"/>
14
14
  </linearGradient>
15
15
  <linearGradient id="right" x1="150" y1="59" x2="219" y2="195" gradientUnits="userSpaceOnUse">
16
- <stop offset="0" stop-color="#bb2026"/>
16
+ <stop offset="0" stop-color="#911b20"/>
17
17
  <stop offset="1" stop-color="#3c0407"/>
18
18
  </linearGradient>
19
19
  <linearGradient id="center" x1="99" y1="104" x2="146" y2="226" gradientUnits="userSpaceOnUse">
20
- <stop offset="0" stop-color="#ff6c61"/>
21
- <stop offset="0.48" stop-color="#c82d2a"/>
22
- <stop offset="1" stop-color="#8d1618"/>
20
+ <stop offset="0" stop-color="#c8554f"/>
21
+ <stop offset="0.48" stop-color="#992522"/>
22
+ <stop offset="1" stop-color="#6d1315"/>
23
23
  </linearGradient>
24
24
  <radialGradient id="glow" cx="38%" cy="22%" r="66%">
25
25
  <stop offset="0" stop-color="#fff8ed" stop-opacity="0.68"/>
26
- <stop offset="0.42" stop-color="#ffb0a7" stop-opacity="0.18"/>
27
- <stop offset="1" stop-color="#ffb0a7" stop-opacity="0"/>
26
+ <stop offset="0.42" stop-color="#d89a92" stop-opacity="0.18"/>
27
+ <stop offset="1" stop-color="#d89a92" stop-opacity="0"/>
28
+ </radialGradient>
29
+ <linearGradient id="body-blue" x1="45" y1="35" x2="211" y2="224" gradientUnits="userSpaceOnUse">
30
+ <stop offset="0" stop-color="#7aa5d9"/>
31
+ <stop offset="0.42" stop-color="#35669e"/>
32
+ <stop offset="1" stop-color="#162f4a"/>
33
+ </linearGradient>
34
+ <linearGradient id="table-blue" x1="75" y1="31" x2="166" y2="104" gradientUnits="userSpaceOnUse">
35
+ <stop offset="0" stop-color="#edf4ff"/>
36
+ <stop offset="0.26" stop-color="#7d9ed9"/>
37
+ <stop offset="1" stop-color="#2a5ca3"/>
38
+ </linearGradient>
39
+ <linearGradient id="right-blue" x1="150" y1="59" x2="219" y2="195" gradientUnits="userSpaceOnUse">
40
+ <stop offset="0" stop-color="#1f4a91"/>
41
+ <stop offset="1" stop-color="#071b3c"/>
42
+ </linearGradient>
43
+ <linearGradient id="center-blue" x1="99" y1="104" x2="146" y2="226" gradientUnits="userSpaceOnUse">
44
+ <stop offset="0" stop-color="#4f78c8"/>
45
+ <stop offset="0.48" stop-color="#254a99"/>
46
+ <stop offset="1" stop-color="#132a6d"/>
47
+ </linearGradient>
48
+ <radialGradient id="glow-blue" cx="38%" cy="22%" r="66%">
49
+ <stop offset="0" stop-color="#edf6ff" stop-opacity="0.68"/>
50
+ <stop offset="0.42" stop-color="#92aed9" stop-opacity="0.18"/>
51
+ <stop offset="1" stop-color="#92aed9" stop-opacity="0"/>
52
+ </radialGradient>
53
+ <linearGradient id="body-green" x1="45" y1="35" x2="211" y2="224" gradientUnits="userSpaceOnUse">
54
+ <stop offset="0" stop-color="#79d9a0"/>
55
+ <stop offset="0.42" stop-color="#359e66"/>
56
+ <stop offset="1" stop-color="#164a2f"/>
57
+ </linearGradient>
58
+ <linearGradient id="table-green" x1="75" y1="31" x2="166" y2="104" gradientUnits="userSpaceOnUse">
59
+ <stop offset="0" stop-color="#edfff4"/>
60
+ <stop offset="0.26" stop-color="#7dd9a5"/>
61
+ <stop offset="1" stop-color="#2aa360"/>
62
+ </linearGradient>
63
+ <linearGradient id="right-green" x1="150" y1="59" x2="219" y2="195" gradientUnits="userSpaceOnUse">
64
+ <stop offset="0" stop-color="#1b9147"/>
65
+ <stop offset="1" stop-color="#043c1c"/>
66
+ </linearGradient>
67
+ <linearGradient id="center-green" x1="99" y1="104" x2="146" y2="226" gradientUnits="userSpaceOnUse">
68
+ <stop offset="0" stop-color="#4fc87d"/>
69
+ <stop offset="0.48" stop-color="#259950"/>
70
+ <stop offset="1" stop-color="#136d30"/>
71
+ </linearGradient>
72
+ <radialGradient id="glow-green" cx="38%" cy="22%" r="66%">
73
+ <stop offset="0" stop-color="#edfff4" stop-opacity="0.68"/>
74
+ <stop offset="0.42" stop-color="#92d9b0" stop-opacity="0.18"/>
75
+ <stop offset="1" stop-color="#92d9b0" stop-opacity="0"/>
28
76
  </radialGradient>
29
77
  <linearGradient id="wordmark" x1="0" y1="76" x2="0" y2="138" gradientUnits="userSpaceOnUse">
30
78
  <stop offset="0" stop-color="#ffffff"/>
@@ -33,7 +81,7 @@
33
81
  </linearGradient>
34
82
  <linearGradient id="accentLine" x1="90" y1="0" x2="470" y2="0" gradientUnits="userSpaceOnUse">
35
83
  <stop offset="0" stop-color="#30363d"/>
36
- <stop offset="0.5" stop-color="#cc342d"/>
84
+ <stop offset="0.5" stop-color="#a7312b"/>
37
85
  <stop offset="1" stop-color="#30363d"/>
38
86
  </linearGradient>
39
87
  <filter id="shadow" x="-18%" y="-18%" width="136%" height="146%">
@@ -45,17 +93,47 @@
45
93
  <symbol id="gemstone" viewBox="0 0 256 256">
46
94
  <path d="M33 91 78 30h100l45 61-95 134Z" fill="url(#body)" stroke="#250507" stroke-width="7" stroke-linejoin="round"/>
47
95
  <path d="M78 30h100l-50 61Z" fill="url(#table)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
48
- <path d="M33 91h95l-52 44Z" fill="#e93c35" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
96
+ <path d="M33 91h95l-52 44Z" fill="#b03a33" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
49
97
  <path d="M223 91h-95l52 44Z" fill="url(#right)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
50
- <path d="M76 135 128 225 33 91Z" fill="#9f1b1d" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
98
+ <path d="M76 135 128 225 33 91Z" fill="#7a1a1b" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
51
99
  <path d="M180 135 128 225 223 91Z" fill="#62080d" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
52
100
  <path d="M76 135h104l-52 90Z" fill="url(#center)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
53
- <path d="M78 30 128 91 76 135 33 91Z" fill="#d9322d" opacity="0.72"/>
54
- <path d="M178 30 128 91 180 135 223 91Z" fill="#8f1519" opacity="0.72"/>
55
- <path d="M128 91 76 135h104Z" fill="#ff6257" opacity="0.5"/>
101
+ <path d="M78 30 128 91 76 135 33 91Z" fill="#a32d29" opacity="0.72"/>
102
+ <path d="M178 30 128 91 180 135 223 91Z" fill="#6f1416" opacity="0.72"/>
103
+ <path d="M128 91 76 135h104Z" fill="#c44f47" opacity="0.5"/>
56
104
  <path d="M49 91 84 43h43L86 96Z" fill="url(#glow)"/>
57
105
  <path d="M85 62 101 43h35L93 87Z" fill="#fff6eb" opacity="0.52"/>
58
- <path d="M97 153h62" fill="none" stroke="#ffaba2" stroke-width="4" stroke-linecap="round" opacity="0.25"/>
106
+ <path d="M97 153h62" fill="none" stroke="#cf8d85" stroke-width="4" stroke-linecap="round" opacity="0.25"/>
107
+ </symbol>
108
+ <symbol id="gemstone-blue" viewBox="0 0 256 256">
109
+ <path d="M33 91 78 30h100l45 61-95 134Z" fill="url(#body-blue)" stroke="#250507" stroke-width="7" stroke-linejoin="round"/>
110
+ <path d="M78 30h100l-50 61Z" fill="url(#table-blue)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
111
+ <path d="M33 91h95l-52 44Z" fill="#336fb0" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
112
+ <path d="M223 91h-95l52 44Z" fill="url(#right-blue)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
113
+ <path d="M76 135 128 225 33 91Z" fill="#1a3a7a" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
114
+ <path d="M180 135 128 225 223 91Z" fill="#081a62" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
115
+ <path d="M76 135h104l-52 90Z" fill="url(#center-blue)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
116
+ <path d="M78 30 128 91 76 135 33 91Z" fill="#2d5ba3" opacity="0.72"/>
117
+ <path d="M178 30 128 91 180 135 223 91Z" fill="#142d6f" opacity="0.72"/>
118
+ <path d="M128 91 76 135h104Z" fill="#4774c4" opacity="0.5"/>
119
+ <path d="M49 91 84 43h43L86 96Z" fill="url(#glow-blue)"/>
120
+ <path d="M85 62 101 43h35L93 87Z" fill="#edf4ff" opacity="0.52"/>
121
+ <path d="M97 153h62" fill="none" stroke="#85a5cf" stroke-width="4" stroke-linecap="round" opacity="0.25"/>
122
+ </symbol>
123
+ <symbol id="gemstone-green" viewBox="0 0 256 256">
124
+ <path d="M33 91 78 30h100l45 61-95 134Z" fill="url(#body-green)" stroke="#250507" stroke-width="7" stroke-linejoin="round"/>
125
+ <path d="M78 30h100l-50 61Z" fill="url(#table-green)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
126
+ <path d="M33 91h95l-52 44Z" fill="#33b070" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
127
+ <path d="M223 91h-95l52 44Z" fill="url(#right-green)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
128
+ <path d="M76 135 128 225 33 91Z" fill="#1a7a4b" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
129
+ <path d="M180 135 128 225 223 91Z" fill="#086220" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
130
+ <path d="M76 135h104l-52 90Z" fill="url(#center-green)" stroke="#250507" stroke-width="4" stroke-linejoin="round"/>
131
+ <path d="M78 30 128 91 76 135 33 91Z" fill="#2da36a" opacity="0.72"/>
132
+ <path d="M178 30 128 91 180 135 223 91Z" fill="#146f3d" opacity="0.72"/>
133
+ <path d="M128 91 76 135h104Z" fill="#47c478" opacity="0.5"/>
134
+ <path d="M49 91 84 43h43L86 96Z" fill="url(#glow-green)"/>
135
+ <path d="M85 62 101 43h35L93 87Z" fill="#edfff4" opacity="0.52"/>
136
+ <path d="M97 153h62" fill="none" stroke="#85cf9f" stroke-width="4" stroke-linecap="round" opacity="0.25"/>
59
137
  </symbol>
60
138
  </defs>
61
139
  <style>
@@ -99,22 +177,22 @@
99
177
  stroke: var(--accent-shadow);
100
178
  }
101
179
  .gem {
102
- animation-duration: 8.5s;
180
+ animation-duration: 6.5s;
103
181
  animation-iteration-count: infinite;
104
182
  animation-timing-function: ease-in-out;
105
183
  }
106
184
  .gem-a { animation-name: gem-a; }
107
- .gem-b { animation-name: gem-b; animation-duration: 9.5s; }
108
- .gem-c { animation-name: gem-c; animation-duration: 10s; }
185
+ .gem-b { animation-name: gem-b; animation-duration: 7.5s; }
186
+ .gem-c { animation-name: gem-c; animation-duration: 8s; }
109
187
  .gem-front {
110
188
  opacity: 0;
111
- animation-duration: 18s;
189
+ animation-duration: 12s;
112
190
  animation-iteration-count: infinite;
113
191
  animation-timing-function: ease-in-out;
114
192
  }
115
193
  .gem-front-a { animation-name: gem-front-a; }
116
- .gem-front-b { animation-name: gem-front-b; animation-delay: -6s; }
117
- .gem-front-c { animation-name: gem-front-c; animation-delay: -12s; }
194
+ .gem-front-b { animation-name: gem-front-b; animation-delay: -4s; }
195
+ .gem-front-c { animation-name: gem-front-c; animation-delay: -8s; }
118
196
  @keyframes gem-a {
119
197
  0%, 100% { transform: translate(42px, 82px) rotate(-10deg); }
120
198
  35% { transform: translate(66px, 45px) rotate(5deg); }
@@ -151,13 +229,10 @@
151
229
  }
152
230
  </style>
153
231
 
154
- <path class="rule" d="M32 38h496M32 182h496" stroke-width="1" opacity="0.62"/>
155
- <path class="rule-strong" d="M90 32h380" stroke-width="2" stroke-linecap="round" opacity="0.72"/>
156
-
157
- <g filter="url(#shadow)" opacity="0.92">
232
+ <g opacity="0.92">
158
233
  <g class="gem gem-a"><use href="#gemstone" width="78" height="78"/></g>
159
- <g class="gem gem-b"><use href="#gemstone" width="84" height="84"/></g>
160
- <g class="gem gem-c"><use href="#gemstone" width="78" height="78"/></g>
234
+ <g class="gem gem-b"><use href="#gemstone-blue" width="84" height="84"/></g>
235
+ <g class="gem gem-c"><use href="#gemstone-green" width="78" height="78"/></g>
161
236
  </g>
162
237
 
163
238
  <g font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" text-anchor="middle">
@@ -170,15 +245,12 @@
170
245
  font-size="72" font-weight="800">r.uby.dev</text>
171
246
  </g>
172
247
 
173
- <g filter="url(#shadow)">
248
+ <g>
174
249
  <g class="gem gem-a"><use class="gem-front gem-front-a" href="#gemstone" width="78" height="78"/></g>
175
- <g class="gem gem-b"><use class="gem-front gem-front-b" href="#gemstone" width="84" height="84"/></g>
176
- <g class="gem gem-c"><use class="gem-front gem-front-c" href="#gemstone" width="78" height="78"/></g>
250
+ <g class="gem gem-b"><use class="gem-front gem-front-b" href="#gemstone-blue" width="84" height="84"/></g>
251
+ <g class="gem gem-c"><use class="gem-front gem-front-c" href="#gemstone-green" width="78" height="78"/></g>
177
252
  </g>
178
253
 
179
- <path class="accent-shadow" d="M90 176h380" stroke-width="8" stroke-linecap="round"/>
180
- <path d="M90 176h380" stroke="url(#accentLine)" stroke-width="4" stroke-linecap="round"/>
181
- <circle cx="280" cy="176" r="5" fill="#cc342d"/>
182
254
  <text class="tagline" x="280" y="204"
183
255
  font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
184
256
  text-anchor="middle" font-size="18" font-weight="650">Building cool stuff with Ruby</text>
data/test/cmd_test.rb CHANGED
@@ -19,6 +19,14 @@ class Test::Command
19
19
  .argv("-e", "warn 42")
20
20
  .stderr
21
21
  end
22
+
23
+ def test_ruby_argv_to_s
24
+ arg = Object.new
25
+ def arg.to_s = "-e"
26
+ assert_equal "42\n", ::Test::Command.new("ruby")
27
+ .argv(arg, "warn 42")
28
+ .stderr
29
+ end
22
30
  end
23
31
 
24
32
  ##
@@ -50,6 +58,43 @@ class Test::Command
50
58
  end
51
59
  end
52
60
 
61
+ ##
62
+ # Test::Command#limit
63
+ class LimitTest < Test
64
+ def test_ruby_limit_stdout
65
+ assert_equal "happy",
66
+ ::Test::Command.new("printf", "%s", "happy days")
67
+ .limit(stdout: 5)
68
+ .stdout
69
+ end
70
+
71
+ def test_ruby_limit_stderr
72
+ assert_equal "smile",
73
+ ::Test::Command.new("sh", "-c", "echo smile >&2")
74
+ .limit(stderr: 5)
75
+ .stderr
76
+ end
77
+
78
+ def test_ruby_limit_nil_is_unlimited
79
+ expect = "a" * 20_000
80
+ assert_equal expect,
81
+ ::Test::Command.new("ruby", "-e", "print 'a' * 20_000")
82
+ .stdout
83
+ end
84
+
85
+ def test_ruby_limit_drains_remaining_output
86
+ cmd = ::Test::Command.new("sh", "-c", "printf 'happy days'")
87
+ .limit(stdout: 5)
88
+ .spawn
89
+ assert_equal true, cmd.spawned?
90
+ ##
91
+ # A full pipe would block forever: the
92
+ # drain lets the child finish and capture
93
+ # its status
94
+ assert_equal 0, cmd.exit_status
95
+ end
96
+ end
97
+
53
98
  ##
54
99
  # Test::Command#{exit_status, status, success?}
55
100
  class ExitStatusTest < Test
data/test/setup.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require "bundler/setup"
2
2
  require "test/unit"
3
- require "test/cmd"
3
+ require "test-cmd"
data/test-cmd.rb.gemspec CHANGED
@@ -1,16 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require File.join(__dir__, "lib", "test", "command", "version.rb")
4
+
3
5
  Gem::Specification.new do |gem|
4
6
  gem.name = "test-cmd.rb"
5
- gem.authors = ["robert"]
7
+ gem.authors = ["Robert Gleeson"]
6
8
  gem.email = ["robert@r.uby.dev"]
7
9
  gem.homepage = "https://github.com/0x1eef/test-cmd.rb#readme"
8
- gem.version = "2.2.0"
10
+ gem.version = Test::Command::VERSION
9
11
  gem.required_ruby_version = ">= 3.0"
10
- gem.licenses = ["0BSD"]
12
+ gem.licenses = ["MIT"]
11
13
  gem.files = `git ls-files`.split($/)
12
14
  gem.require_paths = ["lib"]
13
- gem.summary = "test-cmd.rb provides an object-oriented interface for spawning a command"
15
+ gem.summary = <<~SUMMARY
16
+ test-cmd.rb is a Go-inspired, object-oriented interface for running
17
+ commands on UNIX-like systems, in the spirit of Go's `os/exec`. A
18
+ command can be built by chaining multiple method calls that impact
19
+ different command attributes. The environment the command executes
20
+ with can be set, its standard input stream can be written to, and
21
+ the standard output and standard error streams are captured so the
22
+ parent process can read them.
23
+ SUMMARY
14
24
  gem.description = gem.summary
15
25
  gem.add_development_dependency "test-unit", "~> 3.5.7"
16
26
  gem.add_development_dependency "yard", "~> 0.9"
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-cmd.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
- - robert
7
+ - Robert Gleeson
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -79,7 +79,12 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '13.1'
82
- description: test-cmd.rb provides an object-oriented interface for spawning a command
82
+ description: test-cmd.rb is a Go-inspired, object-oriented interface for running commands
83
+ on UNIX-like systems, in the spirit of Go's `os/exec`. A command can be built by
84
+ chaining multiple method calls that impact different command attributes. The environment
85
+ the command executes with can be set, its standard input stream can be written to,
86
+ and the standard output and standard error streams are captured so the parent process
87
+ can read them.
83
88
  email:
84
89
  - robert@r.uby.dev
85
90
  executables: []
@@ -95,14 +100,15 @@ files:
95
100
  - README.md
96
101
  - Rakefile.rb
97
102
  - lib/test-cmd.rb
98
- - lib/test/cmd.rb
103
+ - lib/test/command.rb
104
+ - lib/test/command/version.rb
99
105
  - rubydev.svg
100
106
  - test-cmd.rb.gemspec
101
107
  - test/cmd_test.rb
102
108
  - test/setup.rb
103
109
  homepage: https://github.com/0x1eef/test-cmd.rb#readme
104
110
  licenses:
105
- - 0BSD
111
+ - MIT
106
112
  metadata: {}
107
113
  rdoc_options: []
108
114
  require_paths:
@@ -120,5 +126,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
126
  requirements: []
121
127
  rubygems_version: 4.0.16
122
128
  specification_version: 4
123
- summary: test-cmd.rb provides an object-oriented interface for spawning a command
129
+ summary: test-cmd.rb is a Go-inspired, object-oriented interface for running commands
130
+ on UNIX-like systems, in the spirit of Go's `os/exec`. A command can be built by
131
+ chaining multiple method calls that impact different command attributes. The environment
132
+ the command executes with can be set, its standard input stream can be written to,
133
+ and the standard output and standard error streams are captured so the parent process
134
+ can read them.
124
135
  test_files: []