test-cmd.rb 2.1.0 → 2.4.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: 92d0cf20a1d63472f4ac178bcb7b74b05d2e5875fe05bccb56f9decbcccd691a
4
- data.tar.gz: '0318fb0281b874accf7f44c6f4a3c2320d3aa4a3405829a36430347f8d65a9c4'
3
+ metadata.gz: b091ee25f1e890969728e516747d3104c20076d2a71973e02ccc346a9eb1cc8e
4
+ data.tar.gz: 4dbb449b245f30e89285b7cae84de42564e352a4f0790fcc3a8a048e21b3ede2
5
5
  SHA512:
6
- metadata.gz: 4daa335326b92120051a40666c07d8b731f447b45e01a73bc5e1cbb2eac65d53e4e4bb87d3ded462b6375b86e85bbe15419d1b8e64642c5f1a60659427456401
7
- data.tar.gz: '0859457b884abaafc8ea53ecc2fd238c4c111b2f6175f177d85ab48b1378de7bc81556160a06433ba900e59d0513b7e910e060944f99fb7d8db6b57182e5e15f'
6
+ metadata.gz: 43fb7bb3700e9f384c253c8ab36ec3a1ebb2ec5e807b3af1d57883a64bddb132838086fc15b242ce3a65b85092941d23a040b866b3e173bed4bf5254e2829690
7
+ data.tar.gz: c314163ab04470596296e8644414bec98414955b7423a1cc5affe202184befedffaff5f1f63427b18dd192b356fe491d3ec85e1b1c5140857c185b3a7ea37402
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,34 +34,42 @@ 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
- The instance has access to the command's process ID, exit status,
42
- standard output stream, and standard error stream.
43
37
 
44
38
  ```ruby
45
39
  require "test-cmd"
46
- puts Test::Command.new("ls").argv("-l").stdout
47
- puts Test::Command.new("env").env("FOO" => "bar").stdout
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"
48
63
  ```
49
64
 
50
65
  <details>
51
66
  <summary>Environment</summary>
52
67
  <br>
53
68
 
54
- Environment variables for the spawned process are set with
55
- [`Test::Command#env`](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html#env-instance_method),
56
- which merges the given variables into the child's environment and
57
- returns the command for chaining.
58
-
59
69
  ```ruby
60
70
  require "test-cmd"
61
- puts Test::Command
71
+
72
+ p Test::Command
62
73
  .new("ruby", "-e", "puts ENV['FOO']")
63
74
  .env("FOO" => "42")
64
75
  .stdout # => "42\n"
@@ -66,15 +77,27 @@ puts Test::Command
66
77
  </details>
67
78
 
68
79
  <details>
69
- <summary>Callbacks</summary>
80
+ <summary>Standard input</summary>
70
81
  <br>
71
82
 
72
- The success and failure callbacks provide hooks for when
73
- a command exits successfully or unsuccessfully. The callbacks
74
- are passed an instance of
75
- [Test::Command](https://r.uby.dev/api-docs/test-cmd.rb/Test/Command.html)
76
- that has access to the command's process ID, exit status,
77
- standard output stream, and standard error stream.
83
+ ```ruby
84
+ require "test-cmd"
85
+
86
+ p Test::Command
87
+ .new("cat")
88
+ .stdin("hello world")
89
+ .stdout # => "hello world"
90
+
91
+ p Test::Command
92
+ .new("tr", "a-z", "A-Z")
93
+ .stdin(Test::Command.new("echo", "hello"))
94
+ .stdout # => "HELLO\n"
95
+ ```
96
+ </details>
97
+
98
+ <details>
99
+ <summary>Callbacks</summary>
100
+ <br>
78
101
 
79
102
  ```ruby
80
103
  require "test-cmd"
@@ -97,7 +120,7 @@ stream of the spawned ruby process:
97
120
 
98
121
  ```ruby
99
122
  require "test/unit"
100
- require "test/cmd"
123
+ require "test-cmd"
101
124
 
102
125
  class CmdTest < Test::Unit::TestCase
103
126
  def test_ruby_stdout
@@ -125,12 +148,7 @@ end
125
148
  ```
126
149
  </details>
127
150
 
128
- ## Documentation
129
-
130
- A complete API reference is available at
131
- [r.uby.dev/api-docs/test-cmd.rb](https://r.uby.dev/api-docs/test-cmd.rb)
132
-
133
151
  ## License
134
152
 
135
- This software is released under the terms of the BSD Zero Clause license. <br>
136
- See [LICENSE](./LICENSE) for details.
153
+ This software is released under the MIT license. <br>
154
+ 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.4.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)
@@ -50,26 +52,36 @@ class Test::Command
50
52
  tap { @env.merge!(env) }
51
53
  end
52
54
 
55
+ ##
56
+ # Presets the standard input that will be sent to the
57
+ # spawned process. Pass a String, or another
58
+ # {Test::Command Test::Command} whose standard output
59
+ # will be used as the standard input.
60
+ # @param [String, Test::Command] data
61
+ # The standard input of the spawned process
62
+ # @example
63
+ # cmd = Test::Command.new("cat").stdin("hello world")
64
+ # puts cmd.stdout
65
+ # @return [Test::Command]
66
+ def stdin(data)
67
+ tap { @stdin = data }
68
+ end
69
+
53
70
  ##
54
71
  # Spawns a command
55
72
  # @return [Test::Command]
56
73
  def spawn
57
74
  return self if @spawned
75
+ @in_r = input_pipe
58
76
  tap do
59
77
  @spawned = true
60
78
  @out, @err = Pipe.pair, Pipe.pair
61
- ##
62
- # Spawn in the calling thread so the command's fds are
63
- # wired up before the reader thread starts. We then close
64
- # our own copies of the write ends so the reader thread
65
- # observes EOF the moment the child exits, and reads both
66
- # streams with whole-buffer reads rather than a byte at
67
- # a time.
68
79
  @pid = Process.spawn(
69
80
  @env,
70
- @cmd, *@argv,
71
- {out: @out.w, err: @err.w, in: IO::NULL}
81
+ @cmd, *@argv.map(&:to_s),
82
+ {in: @in_r, out: @out.w, err: @err.w}
72
83
  )
84
+ @in_r.close unless @in_r.equal?(IO::NULL)
73
85
  @out.w.close
74
86
  @err.w.close
75
87
  @producer = Thread.new do
@@ -86,8 +98,9 @@ class Test::Command
86
98
  @stderr = ex.message
87
99
  @enoent = true
88
100
  ##
89
- # Capture a real non-zero status so predicates like
90
- # #success? work even though the command never spawned.
101
+ # Close the read end of any input pipe so a pending
102
+ # writer thread does not block forever.
103
+ @in_r.close unless @in_r.equal?(IO::NULL)
91
104
  @status = Process.waitpid2(Process.spawn("false")).last
92
105
  end
93
106
 
@@ -219,6 +232,23 @@ class Test::Command
219
232
 
220
233
  private
221
234
 
235
+ ##
236
+ # Returns a pipe pair used for the command's
237
+ # standard input. When no input is set, the child
238
+ # reads from IO::NULL. When the input is another
239
+ # command, the source command is spawned first and
240
+ # its stdout feeds the pipe.
241
+ # @return [IO]
242
+ # A read end (IO::NULL or a pipe read end)
243
+ def input_pipe
244
+ return IO::NULL if @stdin.nil?
245
+ data = self.class === @stdin ? @stdin.stdout : @stdin.to_s
246
+ r, w = IO.pipe
247
+ w.write(data)
248
+ w.close
249
+ r
250
+ end
251
+
222
252
  ##
223
253
  # Blocks until the spawned command has finished, its
224
254
  # output has been read, and its exit status captured.
@@ -228,4 +258,4 @@ class Test::Command
228
258
  return unless @producer&.alive?
229
259
  @producer.join
230
260
  end
231
- end
261
+ 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
  ##
@@ -32,6 +40,24 @@ class Test::Command
32
40
  end
33
41
  end
34
42
 
43
+ ##
44
+ # Test::Command#stdin
45
+ class StdinTest < Test
46
+ def test_ruby_stdin_string
47
+ assert_equal "hello world",
48
+ ::Test::Command.new("cat")
49
+ .stdin("hello world")
50
+ .stdout
51
+ end
52
+
53
+ def test_ruby_stdin_command
54
+ assert_equal "42\n",
55
+ ::Test::Command.new("cat")
56
+ .stdin(ruby("puts 42"))
57
+ .stdout
58
+ end
59
+ end
60
+
35
61
  ##
36
62
  # Test::Command#{exit_status, status, success?}
37
63
  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.1.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.1.0
4
+ version: 2.4.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: []