tty-command 0.6.0 → 0.7.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 +4 -4
- data/.travis.yml +4 -3
- data/CHANGELOG.md +25 -0
- data/Gemfile +6 -0
- data/README.md +78 -21
- data/appveyor.yml +24 -0
- data/benchmarks/memory.rb +11 -0
- data/examples/pty.rb +7 -0
- data/examples/threaded.rb +12 -0
- data/lib/tty/command.rb +16 -6
- data/lib/tty/command/{execute.rb → child_process.rb} +57 -10
- data/lib/tty/command/cmd.rb +9 -15
- data/lib/tty/command/printers/null.rb +2 -1
- data/lib/tty/command/printers/pretty.rb +12 -8
- data/lib/tty/command/printers/progress.rb +2 -1
- data/lib/tty/command/printers/quiet.rb +1 -1
- data/lib/tty/command/process_runner.rb +68 -57
- data/lib/tty/command/version.rb +1 -1
- data/tasks/console.rake +2 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb721a5620ec91bddc77643358783a38fb76631f
|
4
|
+
data.tar.gz: b0f3485e21f80e9172d1651eb661360fb3972d6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3184987586dff4f957bbeee34cf0811f60c10db0f533fe48e957f171a3a1b22485f1e4970a7b4867466deb73ad95e9b3ae30c4194753724471756aece30446de
|
7
|
+
data.tar.gz: 1500b854c1a69a788e49226d28403f92ee28258b608939596e1da92296ff815623ef70885e0c54751e3a8769539fc5f6c8dfd973f69c43c87f112cbf575707ce
|
data/.travis.yml
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
language: ruby
|
3
3
|
sudo: false
|
4
4
|
cache: bundler
|
5
|
+
before_install: "gem update bundler"
|
5
6
|
script: "bundle exec rake ci"
|
6
7
|
rvm:
|
7
8
|
- 2.0.0
|
8
9
|
- 2.1.10
|
9
|
-
- 2.2.
|
10
|
-
- 2.3.
|
11
|
-
- 2.4.
|
10
|
+
- 2.2.8
|
11
|
+
- 2.3.5
|
12
|
+
- 2.4.2
|
12
13
|
- ruby-head
|
13
14
|
- jruby-9.1.1.0
|
14
15
|
- jruby-head
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## [v0.7.0] - 2017-11-19
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Add :binmode option to allow configuring input & ouput as binary
|
7
|
+
* Add :pty option to allow runnig commands in PTY(pseudo terminal)
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
* Change Command to remove threads synchronization to leave it up to client to handle
|
11
|
+
* Change Cmd to allow updating options
|
12
|
+
* Change Command to accept options for all commands such as :timeout, :binmode etc...
|
13
|
+
* Change Execute to ChildProcess module
|
14
|
+
* Change ChildProcess to skip spawn redirect close options on Windows platform
|
15
|
+
* Change to enforce UTF-8 encoding for process pipes to be cross platform
|
16
|
+
* Change ProcessRunner to stop rescuing runtime failures
|
17
|
+
* Change to stop mutating String instances
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
* Fix ProcessRunner threads deadlocking on exclusive mutex
|
21
|
+
* Fix :timeout option to raise TimeoutExceeded error
|
22
|
+
* Fix test suite to work on Windows
|
23
|
+
* Fix Cmd arguments escaping
|
24
|
+
|
3
25
|
## [v0.6.0] - 2017-07-22
|
4
26
|
|
5
27
|
### Added
|
@@ -74,6 +96,9 @@
|
|
74
96
|
|
75
97
|
* Initial implementation and release
|
76
98
|
|
99
|
+
[v0.7.0]: https://github.com/piotrmurach/tty-command/compare/v0.6.0...v0.7.0
|
100
|
+
[v0.6.0]: https://github.com/piotrmurach/tty-command/compare/v0.5.0...v0.6.0
|
101
|
+
[v0.5.0]: https://github.com/piotrmurach/tty-command/compare/v0.4.0...v0.5.0
|
77
102
|
[v0.4.0]: https://github.com/piotrmurach/tty-command/compare/v0.3.3...v0.4.0
|
78
103
|
[v0.3.3]: https://github.com/piotrmurach/tty-command/compare/v0.3.2...v0.3.3
|
79
104
|
[v0.3.2]: https://github.com/piotrmurach/tty-command/compare/v0.3.1...v0.3.2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# TTY::Command [][gitter]
|
2
|
+
|
2
3
|
[][gem]
|
3
4
|
[][travis]
|
5
|
+
[][appveyor]
|
4
6
|
[][codeclimate]
|
5
7
|
[][coverage]
|
6
8
|
[][inchpages]
|
@@ -8,6 +10,7 @@
|
|
8
10
|
[gitter]: https://gitter.im/piotrmurach/tty
|
9
11
|
[gem]: http://badge.fury.io/rb/tty-command
|
10
12
|
[travis]: http://travis-ci.org/piotrmurach/tty-command
|
13
|
+
[appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-command
|
11
14
|
[codeclimate]: https://codeclimate.com/github/piotrmurach/tty-command
|
12
15
|
[coverage]: https://coveralls.io/github/piotrmurach/tty-command
|
13
16
|
[inchpages]: http://inch-ci.org/github/piotrmurach/tty-command
|
@@ -56,14 +59,16 @@ Or install it yourself as:
|
|
56
59
|
* [3. Advanced Interface](#3-advanced-interface)
|
57
60
|
* [3.1. Environment variables](#31-environment-variables)
|
58
61
|
* [3.2. Options](#32-options)
|
59
|
-
* [3.2.1.
|
60
|
-
* [3.2.2.
|
61
|
-
* [3.2.3.
|
62
|
-
* [3.2.4.
|
62
|
+
* [3.2.1. Redirection](#321-redirection)
|
63
|
+
* [3.2.2. Handling input](#322-handling-input)
|
64
|
+
* [3.2.3. Timeout](#323-timeout)
|
65
|
+
* [3.2.4. Binary mode](#324-binary-mode)
|
63
66
|
* [3.2.5. Signal](#325-signal)
|
64
|
-
* [3.2.6.
|
65
|
-
* [3.2.7.
|
66
|
-
* [3.2.8.
|
67
|
+
* [3.2.6. PTY(pseudo-terminal)](#326-ptypseudo-terminal)
|
68
|
+
* [3.2.7. Current directory](#327-current-directory)
|
69
|
+
* [3.2.8. User](#328-user)
|
70
|
+
* [3.2.9. Group](#329-group)
|
71
|
+
* [3.2.10. Umask](#3210-umask)
|
67
72
|
* [3.3. Result](#33-result)
|
68
73
|
* [3.3.1. success?](#331-success)
|
69
74
|
* [3.3.2. failure?](#332-failure)
|
@@ -266,15 +271,7 @@ cmd.run(:echo, 'hello', env: {foo: 'bar', baz: nil})
|
|
266
271
|
|
267
272
|
When a hash is given in the last argument (options), it allows to specify a current directory, umask, user, group and and zero or more fd redirects for the child process.
|
268
273
|
|
269
|
-
#### 3.2.1
|
270
|
-
|
271
|
-
To change directory in which the command is run pass the `:chidir` option:
|
272
|
-
|
273
|
-
```ruby
|
274
|
-
cmd.run(:echo, 'hello', chdir: '/var/tmp')
|
275
|
-
```
|
276
|
-
|
277
|
-
#### 3.2.2 Redirection
|
274
|
+
#### 3.2.1 Redirection
|
278
275
|
|
279
276
|
There are few ways you can redirect commands output.
|
280
277
|
|
@@ -328,7 +325,7 @@ You can, for example, read data from one source and output to another:
|
|
328
325
|
cmd.run("cat", :in => "Gemfile", :out => 'gemfile.log')
|
329
326
|
```
|
330
327
|
|
331
|
-
#### 3.2.
|
328
|
+
#### 3.2.2 Handling Input
|
332
329
|
|
333
330
|
You can provide input to stdin stream using the `:input` key. For instance, given the following executable called `cli` that expects name from `stdin`:
|
334
331
|
|
@@ -357,7 +354,7 @@ in_stream.rewind
|
|
357
354
|
cmd.run("my_cli_program", "login", in: in_stream).out
|
358
355
|
```
|
359
356
|
|
360
|
-
#### 3.2.
|
357
|
+
#### 3.2.3 Timeout
|
361
358
|
|
362
359
|
You can timeout command execuation by providing the `:timeout` option in seconds:
|
363
360
|
|
@@ -365,8 +362,28 @@ You can timeout command execuation by providing the `:timeout` option in seconds
|
|
365
362
|
cmd.run("while test 1; sleep 1; done", timeout: 5)
|
366
363
|
```
|
367
364
|
|
365
|
+
And to set it for all commands do:
|
366
|
+
|
367
|
+
```ruby
|
368
|
+
cmd = TTY::Command.new(timeout: 5)
|
369
|
+
```
|
370
|
+
|
368
371
|
Please run `examples/timeout.rb` to see timeout in action.
|
369
372
|
|
373
|
+
#### 3.2.4 Binary mode
|
374
|
+
|
375
|
+
By default the standard input, output and error are non-binary. However, you can change to read and write in binary mode by using the `:binmode` option like so:
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
cmd.run("echo 'hello'", binmode: true)
|
379
|
+
```
|
380
|
+
|
381
|
+
To set all commands to be run in binary mode do:
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
cmd = TTY::Command.new(binmode: true)
|
385
|
+
```
|
386
|
+
|
370
387
|
#### 3.2.5 Signal
|
371
388
|
|
372
389
|
You can specify process termination signal other than the defaut `SIGTERM`:
|
@@ -375,7 +392,47 @@ You can specify process termination signal other than the defaut `SIGTERM`:
|
|
375
392
|
cmd.run("whilte test1; sleep1; done", timeout: 5, signal: :KILL)
|
376
393
|
```
|
377
394
|
|
378
|
-
#### 3.2.6
|
395
|
+
#### 3.2.6 PTY(pseudo terminal)
|
396
|
+
|
397
|
+
The `:pty` configuration option causes the command to be executed in subprocess where each stream is a pseudo terminal. By default this options is set to `false`. However, some comamnds may require a terminal like device to work correctly. For example, a command may emit colored output only if it is running via terminal.
|
398
|
+
|
399
|
+
In order to run command in pseudo terminal, either set the flag globally for all commands:
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
cmd = TTY::Command.new(pty: true)
|
403
|
+
```
|
404
|
+
|
405
|
+
or for each executed command individually:
|
406
|
+
|
407
|
+
```ruby
|
408
|
+
cmd.run("echo 'hello'", pty: true)
|
409
|
+
```
|
410
|
+
|
411
|
+
Please note though, that setting `:pty` to `true` may change how the command behaves. For instance, on unix like systems the line feed character `\n` in output will be prefixed with carriage return `\r`:
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
out, _ = cmd.run("echo 'hello'")
|
415
|
+
out # => "hello\n"
|
416
|
+
```
|
417
|
+
|
418
|
+
and with `:pty` option:
|
419
|
+
|
420
|
+
```ruby
|
421
|
+
out, _ = cmd.run("echo 'hello'", pty: true)
|
422
|
+
out # => "hello\r\n"
|
423
|
+
```
|
424
|
+
|
425
|
+
In addition, any input to command may be echoed to the standard output.
|
426
|
+
|
427
|
+
#### 3.2.7 Current directory
|
428
|
+
|
429
|
+
To change directory in which the command is run pass the `:chdir` option:
|
430
|
+
|
431
|
+
```ruby
|
432
|
+
cmd.run(:echo, 'hello', chdir: '/var/tmp')
|
433
|
+
```
|
434
|
+
|
435
|
+
#### 3.2.8 User
|
379
436
|
|
380
437
|
To run command as a given user do:
|
381
438
|
|
@@ -383,7 +440,7 @@ To run command as a given user do:
|
|
383
440
|
cmd.run(:echo, 'hello', user: 'piotr')
|
384
441
|
```
|
385
442
|
|
386
|
-
#### 3.2.
|
443
|
+
#### 3.2.9 Group
|
387
444
|
|
388
445
|
To run command as part of group do:
|
389
446
|
|
@@ -391,7 +448,7 @@ To run command as part of group do:
|
|
391
448
|
cmd.run(:echo, 'hello', group: 'devs')
|
392
449
|
```
|
393
450
|
|
394
|
-
#### 3.2.
|
451
|
+
#### 3.2.10 Umask
|
395
452
|
|
396
453
|
To run command with umask do:
|
397
454
|
|
data/appveyor.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
install:
|
3
|
+
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
|
4
|
+
- ruby --version
|
5
|
+
- gem --version
|
6
|
+
- bundle install
|
7
|
+
build: off
|
8
|
+
test_script:
|
9
|
+
- bundle exec rake ci
|
10
|
+
environment:
|
11
|
+
matrix:
|
12
|
+
- ruby_version: "200"
|
13
|
+
- ruby_version: "200-x64"
|
14
|
+
- ruby_version: "21"
|
15
|
+
- ruby_version: "21-x64"
|
16
|
+
- ruby_version: "22"
|
17
|
+
- ruby_version: "22-x64"
|
18
|
+
- ruby_version: "23"
|
19
|
+
- ruby_version: "23-x64"
|
20
|
+
- ruby_version: "24"
|
21
|
+
- ruby_version: "24-x64"
|
22
|
+
matrix:
|
23
|
+
allow_failures:
|
24
|
+
- ruby_version: "193"
|
data/examples/pty.rb
ADDED
data/lib/tty/command.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'rbconfig'
|
@@ -25,6 +24,8 @@ module TTY
|
|
25
24
|
RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']
|
26
25
|
)
|
27
26
|
|
27
|
+
WIN_PLATFORMS = /cygwin|mswin|mingw|bccwin|wince|emx/.freeze
|
28
|
+
|
28
29
|
def self.record_separator
|
29
30
|
@record_separator ||= $/
|
30
31
|
end
|
@@ -33,6 +34,10 @@ module TTY
|
|
33
34
|
@record_separator = sep
|
34
35
|
end
|
35
36
|
|
37
|
+
def self.windows?
|
38
|
+
!!(RbConfig::CONFIG['host_os'] =~ WIN_PLATFORMS)
|
39
|
+
end
|
40
|
+
|
36
41
|
attr_reader :printer
|
37
42
|
|
38
43
|
# Initialize a Command object
|
@@ -46,13 +51,17 @@ module TTY
|
|
46
51
|
# the mode for executing command
|
47
52
|
#
|
48
53
|
# @api public
|
49
|
-
def initialize(options
|
54
|
+
def initialize(**options)
|
50
55
|
@output = options.fetch(:output) { $stdout }
|
51
56
|
@color = options.fetch(:color) { true }
|
52
57
|
@uuid = options.fetch(:uuid) { true }
|
53
58
|
@printer_name = options.fetch(:printer) { :pretty }
|
54
59
|
@dry_run = options.fetch(:dry_run) { false }
|
55
60
|
@printer = use_printer(@printer_name, color: @color, uuid: @uuid)
|
61
|
+
@cmd_options = {}
|
62
|
+
@cmd_options[:pty] = true if options[:pty]
|
63
|
+
@cmd_options[:binmode] = true if options[:binmode]
|
64
|
+
@cmd_options[:timeout] = options[:timeout] if options[:timeout]
|
56
65
|
end
|
57
66
|
|
58
67
|
# Start external executable in a child process
|
@@ -163,15 +172,16 @@ module TTY
|
|
163
172
|
|
164
173
|
# @api private
|
165
174
|
def command(*args)
|
166
|
-
Cmd.new(*args)
|
175
|
+
cmd = Cmd.new(*args)
|
176
|
+
cmd.update(@cmd_options)
|
177
|
+
cmd
|
167
178
|
end
|
168
179
|
|
169
180
|
# @api private
|
170
181
|
def execute_command(cmd, &block)
|
171
|
-
mutex = Mutex.new
|
172
182
|
dry_run = @dry_run || cmd.options[:dry_run] || false
|
173
|
-
@runner = select_runner(dry_run).new(cmd, @printer)
|
174
|
-
|
183
|
+
@runner = select_runner(dry_run).new(cmd, @printer, &block)
|
184
|
+
@runner.run!
|
175
185
|
end
|
176
186
|
|
177
187
|
# @api private
|
@@ -3,10 +3,11 @@
|
|
3
3
|
|
4
4
|
require 'tempfile'
|
5
5
|
require 'securerandom'
|
6
|
+
require 'io/console'
|
6
7
|
|
7
8
|
module TTY
|
8
9
|
class Command
|
9
|
-
module
|
10
|
+
module ChildProcess
|
10
11
|
# Execute command in a child process with all IO streams piped
|
11
12
|
# in and out. The interface is similar to Process.spawn
|
12
13
|
#
|
@@ -22,19 +23,45 @@ module TTY
|
|
22
23
|
# @api public
|
23
24
|
def spawn(cmd)
|
24
25
|
process_opts = normalize_redirect_options(cmd.options)
|
26
|
+
binmode = cmd.options[:binmode] || false
|
27
|
+
pty = cmd.options[:pty] || false
|
28
|
+
|
29
|
+
pty = try_loading_pty if pty
|
30
|
+
require('pty') if pty # load within this scope
|
25
31
|
|
26
32
|
# Create pipes
|
27
|
-
in_rd, in_wr = IO.pipe # reading
|
28
|
-
out_rd, out_wr = IO.pipe # writing
|
29
|
-
err_rd, err_wr = IO.pipe # error
|
33
|
+
in_rd, in_wr = pty ? PTY.open : IO.pipe('utf-8') # reading
|
34
|
+
out_rd, out_wr = pty ? PTY.open : IO.pipe('utf-8') # writing
|
35
|
+
err_rd, err_wr = pty ? PTY.open : IO.pipe('utf-8') # error
|
30
36
|
in_wr.sync = true
|
31
37
|
|
38
|
+
if binmode
|
39
|
+
in_wr.binmode
|
40
|
+
out_rd.binmode
|
41
|
+
err_rd.binmode
|
42
|
+
end
|
43
|
+
|
44
|
+
if pty
|
45
|
+
in_wr.raw!
|
46
|
+
out_wr.raw!
|
47
|
+
err_wr.raw!
|
48
|
+
end
|
49
|
+
|
32
50
|
# redirect fds
|
33
51
|
opts = {
|
34
|
-
:
|
35
|
-
:
|
36
|
-
:
|
37
|
-
}
|
52
|
+
in: in_rd,
|
53
|
+
out: out_wr,
|
54
|
+
err: err_wr
|
55
|
+
}
|
56
|
+
unless TTY::Command.windows?
|
57
|
+
close_child_fds = {
|
58
|
+
in_wr => :close,
|
59
|
+
out_rd => :close,
|
60
|
+
err_rd => :close
|
61
|
+
}
|
62
|
+
opts.merge!(close_child_fds)
|
63
|
+
end
|
64
|
+
opts.merge!(process_opts)
|
38
65
|
|
39
66
|
pid = Process.spawn(cmd.to_command, opts)
|
40
67
|
|
@@ -47,14 +74,28 @@ module TTY
|
|
47
74
|
begin
|
48
75
|
return yield(*tuple)
|
49
76
|
ensure
|
77
|
+
# ensure parent pipes are closed
|
50
78
|
[in_wr, out_rd, err_rd].each { |fd| fd.close if fd && !fd.closed? }
|
51
79
|
end
|
52
80
|
else
|
53
81
|
tuple
|
54
82
|
end
|
55
83
|
end
|
84
|
+
module_function :spawn
|
56
85
|
|
57
|
-
|
86
|
+
# Try loading pty module
|
87
|
+
#
|
88
|
+
# @return [Boolean]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def try_loading_pty
|
92
|
+
require 'pty'
|
93
|
+
true
|
94
|
+
rescue LoadError
|
95
|
+
warn("Requested PTY device but the system doesn't support it.")
|
96
|
+
false
|
97
|
+
end
|
98
|
+
module_function :try_loading_pty
|
58
99
|
|
59
100
|
# Normalize spawn fd into :in, :out, :err keys.
|
60
101
|
#
|
@@ -75,6 +116,7 @@ module TTY
|
|
75
116
|
opts
|
76
117
|
end
|
77
118
|
end
|
119
|
+
module_function :normalize_redirect_options
|
78
120
|
|
79
121
|
# Convert option pari to recognized spawn option pair
|
80
122
|
#
|
@@ -93,6 +135,7 @@ module TTY
|
|
93
135
|
end
|
94
136
|
[key, value]
|
95
137
|
end
|
138
|
+
module_function :convert
|
96
139
|
|
97
140
|
# Determine if object is a fd
|
98
141
|
#
|
@@ -110,6 +153,7 @@ module TTY
|
|
110
153
|
respond_to?(:to_i) && !object.to_io.nil?
|
111
154
|
end
|
112
155
|
end
|
156
|
+
module_function :fd?
|
113
157
|
|
114
158
|
# Convert fd to name :in, :out, :err
|
115
159
|
#
|
@@ -132,6 +176,7 @@ module TTY
|
|
132
176
|
raise ExecuteError, "Wrong execute redirect: #{object.inspect}"
|
133
177
|
end
|
134
178
|
end
|
179
|
+
module_function :fd_to_process_key
|
135
180
|
|
136
181
|
# Convert file name to file handle
|
137
182
|
#
|
@@ -149,6 +194,7 @@ module TTY
|
|
149
194
|
tmp.rewind
|
150
195
|
tmp
|
151
196
|
end
|
197
|
+
module_function :convert_to_fd
|
152
198
|
|
153
199
|
# Attempts to read object content
|
154
200
|
#
|
@@ -162,6 +208,7 @@ module TTY
|
|
162
208
|
object
|
163
209
|
end
|
164
210
|
end
|
165
|
-
|
211
|
+
module_function :try_reading
|
212
|
+
end # ChildProcess
|
166
213
|
end # Command
|
167
214
|
end # TTY
|
data/lib/tty/command/cmd.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'securerandom'
|
4
|
+
require 'shellwords'
|
4
5
|
|
5
6
|
module TTY
|
6
7
|
class Command
|
@@ -46,7 +47,7 @@ module TTY
|
|
46
47
|
else
|
47
48
|
@command = sanitize(command)
|
48
49
|
end
|
49
|
-
@argv = args.map { |i|
|
50
|
+
@argv = args.map { |i| Shellwords.escape(i) }
|
50
51
|
end
|
51
52
|
@env ||= {}
|
52
53
|
@options = opts
|
@@ -55,6 +56,13 @@ module TTY
|
|
55
56
|
freeze
|
56
57
|
end
|
57
58
|
|
59
|
+
# Extend command options if keys don't already exist
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def update(**options)
|
63
|
+
@options.update(options.update(@options))
|
64
|
+
end
|
65
|
+
|
58
66
|
# The shell environment variables
|
59
67
|
#
|
60
68
|
# @api public
|
@@ -131,20 +139,6 @@ module TTY
|
|
131
139
|
def sanitize(value)
|
132
140
|
value.to_s.dup
|
133
141
|
end
|
134
|
-
|
135
|
-
# Enclose argument in quotes if it contains
|
136
|
-
# characters that require escaping
|
137
|
-
#
|
138
|
-
# @param [String] arg
|
139
|
-
# the argument to escape
|
140
|
-
#
|
141
|
-
# @api private
|
142
|
-
def shell_escape(arg)
|
143
|
-
str = arg.to_s.dup
|
144
|
-
return str if str =~ /^[0-9A-Za-z+,.\/:=@_-]+$/
|
145
|
-
str.gsub!("'", "'\\''")
|
146
|
-
"'#{str}'"
|
147
|
-
end
|
148
142
|
end # Cmd
|
149
143
|
end # Command
|
150
144
|
end # TTY
|
@@ -1,16 +1,20 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'pastel'
|
4
|
-
|
5
|
+
|
6
|
+
require_relative 'abstract'
|
5
7
|
|
6
8
|
module TTY
|
7
9
|
class Command
|
8
10
|
module Printers
|
9
11
|
class Pretty < Abstract
|
12
|
+
TIME_FORMAT = "%5.3f %s".freeze
|
13
|
+
|
10
14
|
def print_command_start(cmd, *args)
|
11
|
-
message = "Running #{decorate(cmd.to_command, :yellow, :bold)}"
|
15
|
+
message = ["Running #{decorate(cmd.to_command, :yellow, :bold)}"]
|
12
16
|
message << args.map(&:chomp).join(' ') unless args.empty?
|
13
|
-
write(message, cmd.uuid)
|
17
|
+
write(message.join, cmd.uuid)
|
14
18
|
end
|
15
19
|
|
16
20
|
def print_command_out_data(cmd, *args)
|
@@ -24,11 +28,11 @@ module TTY
|
|
24
28
|
end
|
25
29
|
|
26
30
|
def print_command_exit(cmd, status, runtime, *args)
|
27
|
-
runtime =
|
28
|
-
message = "Finished in #{runtime}"
|
31
|
+
runtime = TIME_FORMAT % [runtime, pluralize(runtime, 'second')]
|
32
|
+
message = ["Finished in #{runtime}"]
|
29
33
|
message << " with exit status #{status}" if status
|
30
34
|
message << " (#{success_or_failure(status)})"
|
31
|
-
write(message, cmd.uuid)
|
35
|
+
write(message.join, cmd.uuid)
|
32
36
|
end
|
33
37
|
|
34
38
|
# Write message out to output
|
@@ -36,12 +40,12 @@ module TTY
|
|
36
40
|
# @api private
|
37
41
|
def write(message, uuid = nil)
|
38
42
|
uuid_needed = options.fetch(:uuid) { true }
|
39
|
-
out =
|
43
|
+
out = []
|
40
44
|
if uuid_needed
|
41
45
|
out << "[#{decorate(uuid, :green)}] " unless uuid.nil?
|
42
46
|
end
|
43
47
|
out << "#{message}\n"
|
44
|
-
output << out
|
48
|
+
output << out.join
|
45
49
|
end
|
46
50
|
|
47
51
|
private
|
@@ -3,15 +3,13 @@
|
|
3
3
|
|
4
4
|
require 'thread'
|
5
5
|
|
6
|
-
require_relative '
|
6
|
+
require_relative 'child_process'
|
7
7
|
require_relative 'result'
|
8
8
|
require_relative 'truncator'
|
9
9
|
|
10
10
|
module TTY
|
11
11
|
class Command
|
12
12
|
class ProcessRunner
|
13
|
-
include Execute
|
14
|
-
|
15
13
|
# the command to be spawned
|
16
14
|
attr_reader :cmd
|
17
15
|
|
@@ -21,28 +19,45 @@ module TTY
|
|
21
19
|
# the printer to use for logging
|
22
20
|
#
|
23
21
|
# @api private
|
24
|
-
def initialize(cmd, printer)
|
22
|
+
def initialize(cmd, printer, &block)
|
25
23
|
@cmd = cmd
|
26
24
|
@timeout = cmd.options[:timeout]
|
27
25
|
@input = cmd.options[:input]
|
28
26
|
@signal = cmd.options[:signal] || :TERM
|
29
27
|
@printer = printer
|
30
|
-
@
|
31
|
-
@lock = Mutex.new
|
28
|
+
@block = block
|
32
29
|
end
|
33
30
|
|
34
31
|
# Execute child process
|
32
|
+
#
|
33
|
+
# Write the input if provided to the child's stdin and read
|
34
|
+
# the contents of both the stdout and stderr.
|
35
|
+
#
|
36
|
+
# If a block is provided then yield the stdout and stderr content
|
37
|
+
# as its being read.
|
38
|
+
#
|
35
39
|
# @api public
|
36
|
-
def run!
|
40
|
+
def run!
|
37
41
|
@printer.print_command_start(cmd)
|
38
42
|
start = Time.now
|
39
43
|
runtime = 0.0
|
40
44
|
|
41
|
-
pid, stdin, stdout, stderr = spawn(cmd)
|
45
|
+
pid, stdin, stdout, stderr = ChildProcess.spawn(cmd)
|
46
|
+
|
47
|
+
# no input to write, close child's stdin pipe
|
48
|
+
stdin.close if (@input.nil? || @input.empty?) && !stdin.nil?
|
42
49
|
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
59
|
+
|
60
|
+
stdout_data, stderr_data = read_streams(stdout, stderr)
|
46
61
|
|
47
62
|
status = waitpid(pid)
|
48
63
|
runtime = Time.now - start
|
@@ -50,9 +65,6 @@ module TTY
|
|
50
65
|
@printer.print_command_exit(cmd, status, runtime)
|
51
66
|
|
52
67
|
Result.new(status, stdout_data, stderr_data, runtime)
|
53
|
-
rescue
|
54
|
-
terminate(pid)
|
55
|
-
Result.new(-1, stdout_data, stderr_data)
|
56
68
|
ensure
|
57
69
|
[stdin, stdout, stderr].each { |fd| fd.close if fd && !fd.closed? }
|
58
70
|
end
|
@@ -76,32 +88,31 @@ module TTY
|
|
76
88
|
raise TimeoutExceeded if t < 0.0
|
77
89
|
end
|
78
90
|
|
91
|
+
# Write the input to the process stdin
|
92
|
+
#
|
79
93
|
# @api private
|
80
|
-
def write_stream(
|
81
|
-
return unless @input
|
82
|
-
writers = [stdin]
|
94
|
+
def write_stream(ready_writers, writers)
|
83
95
|
start = Time.now
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
# control total time spent writing
|
102
|
-
runtime = Time.now - start
|
103
|
-
handle_timeout(runtime)
|
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)
|
104
111
|
end
|
112
|
+
|
113
|
+
# control total time spent writing
|
114
|
+
runtime = Time.now - start
|
115
|
+
handle_timeout(runtime)
|
105
116
|
end
|
106
117
|
end
|
107
118
|
|
@@ -111,45 +122,45 @@ module TTY
|
|
111
122
|
# @param [IO] stderr
|
112
123
|
#
|
113
124
|
# @api private
|
114
|
-
def read_streams(stdout, stderr
|
125
|
+
def read_streams(stdout, stderr)
|
115
126
|
stdout_data = []
|
116
127
|
stderr_data = Truncator.new
|
117
128
|
|
118
|
-
|
119
|
-
|
129
|
+
out_buffer = -> (line) {
|
130
|
+
stdout_data << line
|
131
|
+
@printer.print_command_out_data(cmd, line)
|
132
|
+
@block.(line, nil) if @block
|
133
|
+
}
|
120
134
|
|
121
|
-
|
122
|
-
|
135
|
+
err_buffer = -> (line) {
|
136
|
+
stderr_data << line
|
137
|
+
@printer.print_command_err_data(cmd, line)
|
138
|
+
@block.(nil, line) if @block
|
139
|
+
}
|
123
140
|
|
124
|
-
|
125
|
-
|
141
|
+
stdout_thread = read_stream(stdout, out_buffer)
|
142
|
+
stderr_thread = read_stream(stderr, err_buffer)
|
126
143
|
|
127
|
-
|
128
|
-
|
129
|
-
if result.nil?
|
130
|
-
@threads[0].raise
|
131
|
-
@threads[1].raise
|
132
|
-
end
|
133
|
-
end
|
144
|
+
stdout_thread.join
|
145
|
+
stderr_thread.join
|
134
146
|
|
135
147
|
[stdout_data.join, stderr_data.read]
|
136
148
|
end
|
137
149
|
|
138
|
-
def read_stream(stream,
|
150
|
+
def read_stream(stream, buffer)
|
139
151
|
Thread.new do
|
140
152
|
Thread.current[:cmd_start] = Time.now
|
141
153
|
begin
|
142
154
|
while (line = stream.gets)
|
143
|
-
|
144
|
-
data << line
|
145
|
-
callback.(line)
|
146
|
-
print_callback.(cmd, line)
|
147
|
-
end
|
155
|
+
buffer.(line)
|
148
156
|
|
149
157
|
# control total time spent reading
|
150
158
|
runtime = Time.now - Thread.current[:cmd_start]
|
151
159
|
handle_timeout(runtime)
|
152
160
|
end
|
161
|
+
rescue Errno::EIO
|
162
|
+
# GNU/Linux `gets` raises when PTY slave is closed
|
163
|
+
nil
|
153
164
|
rescue => err
|
154
165
|
raise err
|
155
166
|
ensure
|
data/lib/tty/command/version.rb
CHANGED
data/tasks/console.rake
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.7.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
|
+
date: 2017-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pastel
|
@@ -90,6 +90,8 @@ files:
|
|
90
90
|
- LICENSE.txt
|
91
91
|
- README.md
|
92
92
|
- Rakefile
|
93
|
+
- appveyor.yml
|
94
|
+
- benchmarks/memory.rb
|
93
95
|
- bin/console
|
94
96
|
- bin/setup
|
95
97
|
- examples/bash.rb
|
@@ -98,17 +100,19 @@ files:
|
|
98
100
|
- examples/env.rb
|
99
101
|
- examples/logger.rb
|
100
102
|
- examples/output.rb
|
103
|
+
- examples/pty.rb
|
101
104
|
- examples/redirect_stderr.rb
|
102
105
|
- examples/redirect_stdin.rb
|
103
106
|
- examples/redirect_stdout.rb
|
104
107
|
- examples/stdin_input.rb
|
108
|
+
- examples/threaded.rb
|
105
109
|
- examples/timeout.rb
|
106
110
|
- examples/wait.rb
|
107
111
|
- lib/tty-command.rb
|
108
112
|
- lib/tty/command.rb
|
113
|
+
- lib/tty/command/child_process.rb
|
109
114
|
- lib/tty/command/cmd.rb
|
110
115
|
- lib/tty/command/dry_runner.rb
|
111
|
-
- lib/tty/command/execute.rb
|
112
116
|
- lib/tty/command/exit_error.rb
|
113
117
|
- lib/tty/command/printers/abstract.rb
|
114
118
|
- lib/tty/command/printers/null.rb
|