tty-command 0.7.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +49 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +134 -64
  5. data/lib/tty-command.rb +1 -3
  6. data/lib/tty/command.rb +19 -18
  7. data/lib/tty/command/child_process.rb +23 -16
  8. data/lib/tty/command/cmd.rb +16 -12
  9. data/lib/tty/command/dry_runner.rb +4 -5
  10. data/lib/tty/command/exit_error.rb +1 -2
  11. data/lib/tty/command/printers/abstract.rb +11 -9
  12. data/lib/tty/command/printers/null.rb +1 -4
  13. data/lib/tty/command/printers/pretty.rb +29 -22
  14. data/lib/tty/command/printers/progress.rb +3 -8
  15. data/lib/tty/command/printers/quiet.rb +18 -8
  16. data/lib/tty/command/process_runner.rb +90 -67
  17. data/lib/tty/command/result.rb +0 -1
  18. data/lib/tty/command/truncator.rb +3 -4
  19. data/lib/tty/command/version.rb +2 -2
  20. metadata +24 -64
  21. data/.gitignore +0 -9
  22. data/.rspec +0 -4
  23. data/.travis.yml +0 -28
  24. data/CODE_OF_CONDUCT.md +0 -49
  25. data/Gemfile +0 -14
  26. data/Rakefile +0 -10
  27. data/appveyor.yml +0 -24
  28. data/benchmarks/memory.rb +0 -11
  29. data/bin/console +0 -6
  30. data/bin/setup +0 -6
  31. data/examples/bash.rb +0 -12
  32. data/examples/basic.rb +0 -9
  33. data/examples/cli +0 -4
  34. data/examples/env.rb +0 -9
  35. data/examples/logger.rb +0 -10
  36. data/examples/output.rb +0 -10
  37. data/examples/pty.rb +0 -7
  38. data/examples/redirect_stderr.rb +0 -10
  39. data/examples/redirect_stdin.rb +0 -16
  40. data/examples/redirect_stdout.rb +0 -10
  41. data/examples/stdin_input.rb +0 -10
  42. data/examples/threaded.rb +0 -12
  43. data/examples/timeout.rb +0 -7
  44. data/examples/wait.rb +0 -21
  45. data/tasks/console.rake +0 -11
  46. data/tasks/coverage.rake +0 -11
  47. data/tasks/spec.rake +0 -29
  48. data/tty-command.gemspec +0 -27
@@ -1,14 +1,9 @@
1
- # encoding: utf-8
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('.', :green)
19
+ decorate(".", :green)
25
20
  else
26
- decorate('F', :red)
21
+ decorate("F", :red)
27
22
  end
28
23
  end
29
24
  end # Progress
@@ -1,23 +1,33 @@
1
- # encoding: utf-8
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 print_command_exit(cmd, *args)
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
- output << message
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 'thread'
3
+ require "thread"
5
4
 
6
- require_relative 'child_process'
7
- require_relative 'result'
8
- require_relative 'truncator'
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] || :TERM
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
- # no input to write, close child's stdin pipe
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(ready_writers, writers)
89
+ def write_stream(stream, input)
95
90
  start = Time.now
96
- ready_writers.each do |fd|
97
- begin
98
- err = nil
99
- size = fd.write(@input)
100
- @input = @input.byteslice(size..-1)
101
- rescue IO::WaitWritable
102
- rescue Errno::EPIPE => err
103
- # The pipe closed before all input written
104
- # Probably process exited prematurely
105
- fd.close
106
- writers.delete(fd)
107
- end
108
- if err || @input.bytesize == 0
109
- fd.close
110
- writers.delete(fd)
111
- end
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
- # control total time spent writing
114
- runtime = Time.now - start
115
- handle_timeout(runtime)
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
- out_buffer = -> (line) {
130
- stdout_data << line
131
- @printer.print_command_out_data(cmd, line)
132
- @block.(line, nil) if @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
- err_buffer = -> (line) {
136
- stderr_data << line
137
- @printer.print_command_err_data(cmd, line)
138
- @block.(nil, line) if @block
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, out_buffer)
142
- stderr_thread = read_stream(stderr, err_buffer)
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
- [stdout_data.join, stderr_data.read]
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
- def read_stream(stream, buffer)
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
- begin
154
- while (line = stream.gets)
155
- buffer.(line)
156
-
157
- # control total time spent reading
158
- runtime = Time.now - Thread.current[:cmd_start]
159
- handle_timeout(runtime)
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.waitpid(pid, Process::WUNTRACED)
175
- $?.exitstatus
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
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module TTY
@@ -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
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module TTY
4
4
  class Command
5
- VERSION = '0.7.0'
5
+ VERSION = "0.10.0"
6
6
  end # Command
7
7
  end # TTY
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.7.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: 2017-11-19 00:00:00.000000000 Z
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.7.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.7.0
26
+ version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.5.0
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: 1.5.0
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
- - tasks/console.rake
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: '0'
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
- rubyforge_project:
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,