tty-command 0.7.0 → 0.10.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 +5 -5
- data/CHANGELOG.md +49 -0
- data/LICENSE.txt +1 -1
- data/README.md +134 -64
- data/lib/tty-command.rb +1 -3
- data/lib/tty/command.rb +19 -18
- data/lib/tty/command/child_process.rb +23 -16
- data/lib/tty/command/cmd.rb +16 -12
- data/lib/tty/command/dry_runner.rb +4 -5
- data/lib/tty/command/exit_error.rb +1 -2
- data/lib/tty/command/printers/abstract.rb +11 -9
- data/lib/tty/command/printers/null.rb +1 -4
- data/lib/tty/command/printers/pretty.rb +29 -22
- data/lib/tty/command/printers/progress.rb +3 -8
- data/lib/tty/command/printers/quiet.rb +18 -8
- data/lib/tty/command/process_runner.rb +90 -67
- data/lib/tty/command/result.rb +0 -1
- data/lib/tty/command/truncator.rb +3 -4
- data/lib/tty/command/version.rb +2 -2
- metadata +24 -64
- data/.gitignore +0 -9
- data/.rspec +0 -4
- data/.travis.yml +0 -28
- data/CODE_OF_CONDUCT.md +0 -49
- data/Gemfile +0 -14
- data/Rakefile +0 -10
- data/appveyor.yml +0 -24
- data/benchmarks/memory.rb +0 -11
- data/bin/console +0 -6
- data/bin/setup +0 -6
- data/examples/bash.rb +0 -12
- data/examples/basic.rb +0 -9
- data/examples/cli +0 -4
- data/examples/env.rb +0 -9
- data/examples/logger.rb +0 -10
- data/examples/output.rb +0 -10
- data/examples/pty.rb +0 -7
- data/examples/redirect_stderr.rb +0 -10
- data/examples/redirect_stdin.rb +0 -16
- data/examples/redirect_stdout.rb +0 -10
- data/examples/stdin_input.rb +0 -10
- data/examples/threaded.rb +0 -12
- data/examples/timeout.rb +0 -7
- data/examples/wait.rb +0 -21
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-command.gemspec +0 -27
@@ -1,14 +1,9 @@
|
|
1
|
-
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'pastel'
|
5
|
-
require_relative 'abstract'
|
1
|
+
require_relative "abstract"
|
6
2
|
|
7
3
|
module TTY
|
8
4
|
class Command
|
9
5
|
module Printers
|
10
6
|
class Progress < Abstract
|
11
|
-
|
12
7
|
def print_command_exit(cmd, status, runtime, *args)
|
13
8
|
output.print(success_or_failure(status))
|
14
9
|
end
|
@@ -21,9 +16,9 @@ module TTY
|
|
21
16
|
# @api private
|
22
17
|
def success_or_failure(status)
|
23
18
|
if status == 0
|
24
|
-
decorate(
|
19
|
+
decorate(".", :green)
|
25
20
|
else
|
26
|
-
decorate(
|
21
|
+
decorate("F", :red)
|
27
22
|
end
|
28
23
|
end
|
29
24
|
end # Progress
|
@@ -1,23 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require_relative 'abstract'
|
1
|
+
require_relative "abstract"
|
4
2
|
|
5
3
|
module TTY
|
6
4
|
class Command
|
7
5
|
module Printers
|
8
6
|
class Quiet < Abstract
|
9
|
-
attr_reader :output, :options
|
10
|
-
|
11
7
|
def print_command_start(cmd)
|
12
8
|
# quiet
|
13
9
|
end
|
14
10
|
|
15
|
-
def
|
11
|
+
def print_command_out_data(cmd, *args)
|
12
|
+
write(cmd, args.join(" "), out_data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def print_command_err_data(cmd, *args)
|
16
|
+
write(cmd, args.join(" "), err_data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def print_command_exit(cmd, status, *args)
|
20
|
+
unless !cmd.only_output_on_error || status.zero?
|
21
|
+
output << out_data
|
22
|
+
output << err_data
|
23
|
+
end
|
24
|
+
|
16
25
|
# quiet
|
17
26
|
end
|
18
27
|
|
19
|
-
def write(message)
|
20
|
-
|
28
|
+
def write(cmd, message, data = nil)
|
29
|
+
target = (cmd.only_output_on_error && !data.nil?) ? data : output
|
30
|
+
target << message
|
21
31
|
end
|
22
32
|
end # Progress
|
23
33
|
end # Printers
|
@@ -1,11 +1,10 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require
|
3
|
+
require "thread"
|
5
4
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
5
|
+
require_relative "child_process"
|
6
|
+
require_relative "result"
|
7
|
+
require_relative "truncator"
|
9
8
|
|
10
9
|
module TTY
|
11
10
|
class Command
|
@@ -23,7 +22,8 @@ module TTY
|
|
23
22
|
@cmd = cmd
|
24
23
|
@timeout = cmd.options[:timeout]
|
25
24
|
@input = cmd.options[:input]
|
26
|
-
@signal = cmd.options[:signal] ||
|
25
|
+
@signal = cmd.options[:signal] || "SIGKILL"
|
26
|
+
@binmode = cmd.options[:binmode]
|
27
27
|
@printer = printer
|
28
28
|
@block = block
|
29
29
|
end
|
@@ -40,22 +40,10 @@ module TTY
|
|
40
40
|
def run!
|
41
41
|
@printer.print_command_start(cmd)
|
42
42
|
start = Time.now
|
43
|
-
runtime = 0.0
|
44
43
|
|
45
44
|
pid, stdin, stdout, stderr = ChildProcess.spawn(cmd)
|
46
45
|
|
47
|
-
|
48
|
-
stdin.close if (@input.nil? || @input.empty?) && !stdin.nil?
|
49
|
-
|
50
|
-
readers = [stdout, stderr]
|
51
|
-
writers = [@input && stdin].compact
|
52
|
-
|
53
|
-
while writers.any?
|
54
|
-
ready_readers, ready_writers = IO.select(readers, writers, [], @timeout)
|
55
|
-
raise TimeoutExceeded if ready_readers.nil? || ready_writers.nil?
|
56
|
-
|
57
|
-
write_stream(ready_writers, writers)
|
58
|
-
end
|
46
|
+
write_stream(stdin, @input)
|
59
47
|
|
60
48
|
stdout_data, stderr_data = read_streams(stdout, stderr)
|
61
49
|
|
@@ -67,6 +55,10 @@ module TTY
|
|
67
55
|
Result.new(status, stdout_data, stderr_data, runtime)
|
68
56
|
ensure
|
69
57
|
[stdin, stdout, stderr].each { |fd| fd.close if fd && !fd.closed? }
|
58
|
+
if pid # Ensure no zombie processes
|
59
|
+
::Process.detach(pid)
|
60
|
+
terminate(pid)
|
61
|
+
end
|
70
62
|
end
|
71
63
|
|
72
64
|
# Stop a process marked by pid
|
@@ -80,6 +72,9 @@ module TTY
|
|
80
72
|
|
81
73
|
private
|
82
74
|
|
75
|
+
# The buffer size for reading stdout and stderr
|
76
|
+
BUFSIZE = 16 * 1024
|
77
|
+
|
83
78
|
# @api private
|
84
79
|
def handle_timeout(runtime)
|
85
80
|
return unless @timeout
|
@@ -91,28 +86,35 @@ module TTY
|
|
91
86
|
# Write the input to the process stdin
|
92
87
|
#
|
93
88
|
# @api private
|
94
|
-
def write_stream(
|
89
|
+
def write_stream(stream, input)
|
95
90
|
start = Time.now
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
91
|
+
writers = [input && stream].compact
|
92
|
+
|
93
|
+
while writers.any?
|
94
|
+
ready = IO.select(nil, writers, writers, @timeout)
|
95
|
+
raise TimeoutExceeded if ready.nil?
|
96
|
+
|
97
|
+
ready[1].each do |writer|
|
98
|
+
begin
|
99
|
+
err = nil
|
100
|
+
size = writer.write(@input)
|
101
|
+
input = input.byteslice(size..-1)
|
102
|
+
rescue IO::WaitWritable
|
103
|
+
rescue Errno::EPIPE => err
|
104
|
+
# The pipe closed before all input written
|
105
|
+
# Probably process exited prematurely
|
106
|
+
writer.close
|
107
|
+
writers.delete(writer)
|
108
|
+
end
|
109
|
+
if err || input.bytesize == 0
|
110
|
+
writer.close
|
111
|
+
writers.delete(writer)
|
112
|
+
end
|
112
113
|
|
113
|
-
|
114
|
-
|
115
|
-
|
114
|
+
# control total time spent writing
|
115
|
+
runtime = Time.now - start
|
116
|
+
handle_timeout(runtime)
|
117
|
+
end
|
116
118
|
end
|
117
119
|
end
|
118
120
|
|
@@ -126,53 +128,74 @@ module TTY
|
|
126
128
|
stdout_data = []
|
127
129
|
stderr_data = Truncator.new
|
128
130
|
|
129
|
-
|
130
|
-
stdout_data <<
|
131
|
-
@printer.print_command_out_data(cmd,
|
132
|
-
@block.(
|
131
|
+
out_handler = ->(data) {
|
132
|
+
stdout_data << data
|
133
|
+
@printer.print_command_out_data(cmd, data)
|
134
|
+
@block.(data, nil) if @block
|
133
135
|
}
|
134
136
|
|
135
|
-
|
136
|
-
stderr_data <<
|
137
|
-
@printer.print_command_err_data(cmd,
|
138
|
-
@block.(nil,
|
137
|
+
err_handler = ->(data) {
|
138
|
+
stderr_data << data
|
139
|
+
@printer.print_command_err_data(cmd, data)
|
140
|
+
@block.(nil, data) if @block
|
139
141
|
}
|
140
142
|
|
141
|
-
stdout_thread = read_stream(stdout,
|
142
|
-
stderr_thread = read_stream(stderr,
|
143
|
+
stdout_thread = read_stream(stdout, out_handler)
|
144
|
+
stderr_thread = read_stream(stderr, err_handler)
|
143
145
|
|
144
146
|
stdout_thread.join
|
145
147
|
stderr_thread.join
|
146
148
|
|
147
|
-
|
149
|
+
encoding = @binmode ? Encoding::BINARY : Encoding::UTF_8
|
150
|
+
|
151
|
+
[
|
152
|
+
stdout_data.join.force_encoding(encoding),
|
153
|
+
stderr_data.read.dup.force_encoding(encoding)
|
154
|
+
]
|
148
155
|
end
|
149
156
|
|
150
|
-
|
157
|
+
# Read stream and invoke handler when data becomes available
|
158
|
+
#
|
159
|
+
# @param [IO] stream
|
160
|
+
# the stream to read data from
|
161
|
+
# @param [Proc] handler
|
162
|
+
# the handler to call when data becomes available
|
163
|
+
#
|
164
|
+
# @api private
|
165
|
+
def read_stream(stream, handler)
|
151
166
|
Thread.new do
|
167
|
+
if Thread.current.respond_to?(:report_on_exception)
|
168
|
+
Thread.current.report_on_exception = false
|
169
|
+
end
|
152
170
|
Thread.current[:cmd_start] = Time.now
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
171
|
+
readers = [stream]
|
172
|
+
|
173
|
+
while readers.any?
|
174
|
+
ready = IO.select(readers, nil, readers, @timeout)
|
175
|
+
raise TimeoutExceeded if ready.nil?
|
176
|
+
|
177
|
+
ready[0].each do |reader|
|
178
|
+
begin
|
179
|
+
chunk = reader.readpartial(BUFSIZE)
|
180
|
+
handler.(chunk)
|
181
|
+
|
182
|
+
# control total time spent reading
|
183
|
+
runtime = Time.now - Thread.current[:cmd_start]
|
184
|
+
handle_timeout(runtime)
|
185
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
186
|
+
rescue EOFError, Errno::EPIPE, Errno::EIO # thrown by PTY
|
187
|
+
readers.delete(reader)
|
188
|
+
reader.close
|
189
|
+
end
|
160
190
|
end
|
161
|
-
rescue Errno::EIO
|
162
|
-
# GNU/Linux `gets` raises when PTY slave is closed
|
163
|
-
nil
|
164
|
-
rescue => err
|
165
|
-
raise err
|
166
|
-
ensure
|
167
|
-
stream.close
|
168
191
|
end
|
169
192
|
end
|
170
193
|
end
|
171
194
|
|
172
195
|
# @api private
|
173
196
|
def waitpid(pid)
|
174
|
-
::Process.
|
175
|
-
|
197
|
+
_pid, status = ::Process.waitpid2(pid, ::Process::WUNTRACED)
|
198
|
+
status.exitstatus || status.termsig if _pid
|
176
199
|
rescue Errno::ECHILD
|
177
200
|
# In JRuby, waiting on a finished pid raises.
|
178
201
|
end
|
data/lib/tty/command/result.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module TTY
|
@@ -18,8 +17,8 @@ module TTY
|
|
18
17
|
# @api public
|
19
18
|
def initialize(options = {})
|
20
19
|
@max_size = options.fetch(:max_size) { DEFAULT_SIZE }
|
21
|
-
@prefix =
|
22
|
-
@suffix =
|
20
|
+
@prefix = ""
|
21
|
+
@suffix = ""
|
23
22
|
@skipped = 0
|
24
23
|
end
|
25
24
|
|
@@ -93,7 +92,7 @@ module TTY
|
|
93
92
|
# @api private
|
94
93
|
def append(value, dst)
|
95
94
|
remain = @max_size - dst.bytesize
|
96
|
-
remaining =
|
95
|
+
remaining = ""
|
97
96
|
if remain > 0
|
98
97
|
value_bytes = value.to_s.bytesize
|
99
98
|
offset = value_bytes < remain ? value_bytes : remain
|
data/lib/tty/command/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tty-command
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Murach
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pastel
|
@@ -16,98 +16,57 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: '0.8'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: '0.8'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
- - "<"
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: '2.0'
|
33
|
+
version: '0'
|
37
34
|
type: :development
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
40
37
|
requirements:
|
41
38
|
- - ">="
|
42
39
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
44
|
-
- - "<"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '2.0'
|
47
|
-
- !ruby/object:Gem::Dependency
|
48
|
-
name: rake
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
requirements:
|
51
|
-
- - "~>"
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '10.0'
|
54
|
-
type: :development
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
requirements:
|
58
|
-
- - "~>"
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '10.0'
|
40
|
+
version: '0'
|
61
41
|
- !ruby/object:Gem::Dependency
|
62
42
|
name: rspec
|
63
43
|
requirement: !ruby/object:Gem::Requirement
|
64
44
|
requirements:
|
65
|
-
- - "
|
45
|
+
- - ">="
|
66
46
|
- !ruby/object:Gem::Version
|
67
47
|
version: '3.0'
|
68
48
|
type: :development
|
69
49
|
prerelease: false
|
70
50
|
version_requirements: !ruby/object:Gem::Requirement
|
71
51
|
requirements:
|
72
|
-
- - "
|
52
|
+
- - ">="
|
73
53
|
- !ruby/object:Gem::Version
|
74
54
|
version: '3.0'
|
75
55
|
description: Execute shell commands with pretty output logging and capture their stdout,
|
76
56
|
stderr and exit status. Redirect stdin, stdout and stderr of each command to a file
|
77
57
|
or a string.
|
78
58
|
email:
|
79
|
-
-
|
59
|
+
- piotr@piotrmurach.com
|
80
60
|
executables: []
|
81
61
|
extensions: []
|
82
|
-
extra_rdoc_files:
|
62
|
+
extra_rdoc_files:
|
63
|
+
- README.md
|
64
|
+
- CHANGELOG.md
|
65
|
+
- LICENSE.txt
|
83
66
|
files:
|
84
|
-
- ".gitignore"
|
85
|
-
- ".rspec"
|
86
|
-
- ".travis.yml"
|
87
67
|
- CHANGELOG.md
|
88
|
-
- CODE_OF_CONDUCT.md
|
89
|
-
- Gemfile
|
90
68
|
- LICENSE.txt
|
91
69
|
- README.md
|
92
|
-
- Rakefile
|
93
|
-
- appveyor.yml
|
94
|
-
- benchmarks/memory.rb
|
95
|
-
- bin/console
|
96
|
-
- bin/setup
|
97
|
-
- examples/bash.rb
|
98
|
-
- examples/basic.rb
|
99
|
-
- examples/cli
|
100
|
-
- examples/env.rb
|
101
|
-
- examples/logger.rb
|
102
|
-
- examples/output.rb
|
103
|
-
- examples/pty.rb
|
104
|
-
- examples/redirect_stderr.rb
|
105
|
-
- examples/redirect_stdin.rb
|
106
|
-
- examples/redirect_stdout.rb
|
107
|
-
- examples/stdin_input.rb
|
108
|
-
- examples/threaded.rb
|
109
|
-
- examples/timeout.rb
|
110
|
-
- examples/wait.rb
|
111
70
|
- lib/tty-command.rb
|
112
71
|
- lib/tty/command.rb
|
113
72
|
- lib/tty/command/child_process.rb
|
@@ -123,14 +82,16 @@ files:
|
|
123
82
|
- lib/tty/command/result.rb
|
124
83
|
- lib/tty/command/truncator.rb
|
125
84
|
- lib/tty/command/version.rb
|
126
|
-
|
127
|
-
- tasks/coverage.rake
|
128
|
-
- tasks/spec.rake
|
129
|
-
- tty-command.gemspec
|
130
|
-
homepage: https://piotrmurach.github.io/tty
|
85
|
+
homepage: https://ttytoolkit.org
|
131
86
|
licenses:
|
132
87
|
- MIT
|
133
|
-
metadata:
|
88
|
+
metadata:
|
89
|
+
allowed_push_host: https://rubygems.org
|
90
|
+
bug_tracker_uri: https://github.com/piotrmurach/tty-command/issues
|
91
|
+
changelog_uri: https://github.com/piotrmurach/tty-command/blob/master/CHANGELOG.md
|
92
|
+
documentation_uri: https://www.rubydoc.info/gems/tty-command
|
93
|
+
homepage_uri: https://ttytoolkit.org
|
94
|
+
source_code_uri: https://github.com/piotrmurach/tty-command
|
134
95
|
post_install_message:
|
135
96
|
rdoc_options: []
|
136
97
|
require_paths:
|
@@ -139,15 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
100
|
requirements:
|
140
101
|
- - ">="
|
141
102
|
- !ruby/object:Gem::Version
|
142
|
-
version:
|
103
|
+
version: 2.0.0
|
143
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
105
|
requirements:
|
145
106
|
- - ">="
|
146
107
|
- !ruby/object:Gem::Version
|
147
108
|
version: '0'
|
148
109
|
requirements: []
|
149
|
-
|
150
|
-
rubygems_version: 2.5.1
|
110
|
+
rubygems_version: 3.1.2
|
151
111
|
signing_key:
|
152
112
|
specification_version: 4
|
153
113
|
summary: Execute shell commands with pretty output logging and capture their stdout,
|