tty-command 0.5.0 → 0.6.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/CHANGELOG.md +13 -0
- data/README.md +33 -12
- data/examples/redirect_stderr.rb +1 -1
- data/examples/redirect_stdout.rb +4 -1
- data/lib/tty/command.rb +1 -0
- data/lib/tty/command/dry_runner.rb +4 -3
- data/lib/tty/command/execute.rb +69 -36
- data/lib/tty/command/exit_error.rb +9 -5
- data/lib/tty/command/process_runner.rb +6 -5
- data/lib/tty/command/result.rb +9 -1
- data/lib/tty/command/truncator.rb +8 -10
- data/lib/tty/command/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3d696d9fa5255f95622561988eddfcb26650824
|
4
|
+
data.tar.gz: cf0fe3027e2e2958b55a1c99ce1f9769ccbd717a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06bdcfbea782615ef663e866bc11862f7cc9046a98ce6748418d83c4d539597e93f6d31bd928477b91a08370c9db9cef33094252110e08439a06cc02bac7b60d
|
7
|
+
data.tar.gz: 94da004a191721e5d45e26f62587d5bfadb767fc083af00fa3eeac4d7e5520b7ec354578597b6c563bfb423adcb6d018e41d9215e8cf592099f79475dedf1e2c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## [v0.6.0] - 2017-07-22
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Add runtime property to command result
|
7
|
+
* Add ability to merge multiple redirects
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
* Change to make all strings immutable
|
11
|
+
* Change waiting for pid to recover when already dead
|
12
|
+
|
13
|
+
### Fix
|
14
|
+
* Fix redirection to instead of redirecting to parent process, redirect to child process. And hence allow for :out => :err redirection to work with output logging.
|
15
|
+
|
3
16
|
## [v0.5.0] - 2017-07-16
|
4
17
|
|
5
18
|
### Added
|
data/README.md
CHANGED
@@ -278,33 +278,54 @@ cmd.run(:echo, 'hello', chdir: '/var/tmp')
|
|
278
278
|
|
279
279
|
There are few ways you can redirect commands output.
|
280
280
|
|
281
|
-
You can directly use shell redirection
|
281
|
+
You can directly use shell redirection like so:
|
282
282
|
|
283
283
|
```ruby
|
284
|
-
cmd.run("ls 1&>2")
|
284
|
+
out, err = cmd.run("ls 1&>2")
|
285
|
+
puts err
|
286
|
+
# =>
|
287
|
+
# CHANGELOG.md
|
288
|
+
# CODE_OF_CONDUCT.md
|
289
|
+
# Gemfile
|
290
|
+
# ...
|
285
291
|
```
|
286
292
|
|
287
|
-
You can provide
|
293
|
+
You can provide redirection as additional hash options where the key is one of `:in`, `:out`, `:err`, an integer (a file descriptor for the child process), an IO or array. For example, `stderr` can be merged into stdout as follows:
|
288
294
|
|
289
295
|
```ruby
|
290
|
-
cmd.run(:ls, :
|
291
|
-
cmd.run(:ls, :
|
292
|
-
cmd.run(:ls,
|
293
|
-
cmd.run(:ls,
|
294
|
-
cmd.run(:ls,
|
296
|
+
cmd.run(:ls, :err => :out)
|
297
|
+
cmd.run(:ls, :stderr => :stdout)
|
298
|
+
cmd.run(:ls, 2 => 1)
|
299
|
+
cmd.run(:ls, STDERR => :out)
|
300
|
+
cmd.run(:ls, STDERR => STDOUT)
|
295
301
|
```
|
296
302
|
|
297
|
-
|
303
|
+
The hash key and value specify a file descriptor in the child process (stderr & stdout in the examples).
|
304
|
+
|
305
|
+
You can also redirect to a file:
|
298
306
|
|
299
307
|
```ruby
|
308
|
+
cmd.run(:cat, :in => 'file')
|
300
309
|
cmd.run(:cat, :in => open('/etc/passwd'))
|
310
|
+
cmd.run(:ls, :out => 'log')
|
311
|
+
cmd.run(:ls, :out => "/dev/null")
|
312
|
+
cmd.run(:ls, :out => 'out.log', :err => "err.log")
|
313
|
+
cmd.run(:ls, [:out, :err] => "log")
|
314
|
+
cmd.run("ls 1>&2", :err => 'log')
|
315
|
+
```
|
316
|
+
|
317
|
+
It is possible to specify flags and permissions of file creation explicitly by passing an array value:
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
cmd.run(:ls, :out => ['log', 'w']) # 0664 assumed
|
321
|
+
cmd.run(:ls, :out => ['log', 'w', 0600])
|
322
|
+
cmd.run(:ls, :out => ['log', File::WRONLY|File::EXCL|File::CREAT, 0600])
|
301
323
|
```
|
302
324
|
|
303
|
-
|
325
|
+
You can, for example, read data from one source and output to another:
|
304
326
|
|
305
327
|
```ruby
|
306
|
-
cmd.run(
|
307
|
-
cmd.run(:ls, '-la', 2 => 1)
|
328
|
+
cmd.run("cat", :in => "Gemfile", :out => 'gemfile.log')
|
308
329
|
```
|
309
330
|
|
310
331
|
#### 3.2.3 Handling Input
|
data/examples/redirect_stderr.rb
CHANGED
data/examples/redirect_stdout.rb
CHANGED
data/lib/tty/command.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative 'result'
|
4
5
|
|
@@ -15,10 +16,10 @@ module TTY
|
|
15
16
|
# Show command without running
|
16
17
|
#
|
17
18
|
# @api public
|
18
|
-
def run!(
|
19
|
+
def run!(*)
|
19
20
|
cmd.to_command
|
20
|
-
message = "#{@printer.decorate('(dry run)', :blue)} "
|
21
|
-
|
21
|
+
message = "#{@printer.decorate('(dry run)', :blue)} " +
|
22
|
+
@printer.decorate(cmd.to_command, :yellow, :bold)
|
22
23
|
@printer.write(message, cmd.uuid)
|
23
24
|
Result.new(0, '', '')
|
24
25
|
end
|
data/lib/tty/command/execute.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'tempfile'
|
4
5
|
require 'securerandom'
|
@@ -29,11 +30,11 @@ module TTY
|
|
29
30
|
in_wr.sync = true
|
30
31
|
|
31
32
|
# redirect fds
|
32
|
-
opts =
|
33
|
+
opts = {
|
33
34
|
:in => in_rd, in_wr => :close,
|
34
35
|
:out => out_wr, out_rd => :close,
|
35
36
|
:err => err_wr, err_rd => :close
|
36
|
-
}
|
37
|
+
}.merge(process_opts)
|
37
38
|
|
38
39
|
pid = Process.spawn(cmd.to_command, opts)
|
39
40
|
|
@@ -55,47 +56,86 @@ module TTY
|
|
55
56
|
|
56
57
|
private
|
57
58
|
|
59
|
+
# Normalize spawn fd into :in, :out, :err keys.
|
60
|
+
#
|
61
|
+
# @return [Hash]
|
62
|
+
#
|
63
|
+
# @api private
|
58
64
|
def normalize_redirect_options(options)
|
59
65
|
options.reduce({}) do |opts, (key, value)|
|
60
66
|
if fd?(key)
|
61
|
-
|
62
|
-
|
63
|
-
|
67
|
+
spawn_key, spawn_value = convert(key, value)
|
68
|
+
opts[spawn_key] = spawn_value
|
69
|
+
elsif key.is_a?(Array) && key.all?(&method(:fd?))
|
70
|
+
key.each do |k|
|
71
|
+
spawn_key, spawn_value = convert(k, value)
|
72
|
+
opts[spawn_key] = spawn_value
|
64
73
|
end
|
65
|
-
opts[process_key] = value
|
66
74
|
end
|
67
75
|
opts
|
68
76
|
end
|
69
77
|
end
|
70
78
|
|
79
|
+
# Convert option pari to recognized spawn option pair
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
def convert(spawn_key, spawn_value)
|
83
|
+
key = fd_to_process_key(spawn_key)
|
84
|
+
value = spawn_value
|
85
|
+
|
86
|
+
if key.to_s == 'in'
|
87
|
+
value = convert_to_fd(spawn_value)
|
88
|
+
end
|
89
|
+
|
90
|
+
if fd?(spawn_value)
|
91
|
+
value = fd_to_process_key(spawn_value)
|
92
|
+
value = [:child, value] # redirect in child process
|
93
|
+
end
|
94
|
+
[key, value]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Determine if object is a fd
|
98
|
+
#
|
99
|
+
# @return [Boolean]
|
100
|
+
#
|
71
101
|
# @api private
|
72
102
|
def fd?(object)
|
73
103
|
case object
|
74
|
-
when :stdin, :stdout, :stderr, :in, :out, :err
|
75
|
-
|
76
|
-
when STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr, ::IO
|
77
|
-
true
|
78
|
-
when ::IO
|
104
|
+
when :stdin, :stdout, :stderr, :in, :out, :err,
|
105
|
+
STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr, ::IO
|
79
106
|
true
|
80
107
|
when ::Integer
|
81
108
|
object >= 0
|
82
|
-
when respond_to?(:to_i) && !object.to_io.nil?
|
83
|
-
true
|
84
109
|
else
|
85
|
-
|
110
|
+
respond_to?(:to_i) && !object.to_io.nil?
|
86
111
|
end
|
87
112
|
end
|
88
113
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
114
|
+
# Convert fd to name :in, :out, :err
|
115
|
+
#
|
116
|
+
# @api private
|
117
|
+
def fd_to_process_key(object)
|
118
|
+
case object
|
119
|
+
when STDIN, $stdin, :in, :stdin, 0
|
120
|
+
:in
|
121
|
+
when STDOUT, $stdout, :out, :stdout, 1
|
122
|
+
:out
|
123
|
+
when STDERR, $stderr, :err, :stderr, 2
|
124
|
+
:err
|
125
|
+
when Integer
|
126
|
+
object >= 0 ? IO.for_fd(object) : nil
|
127
|
+
when IO
|
95
128
|
object
|
129
|
+
when respond_to?(:to_io)
|
130
|
+
object.to_io
|
131
|
+
else
|
132
|
+
raise ExecuteError, "Wrong execute redirect: #{object.inspect}"
|
96
133
|
end
|
97
134
|
end
|
98
135
|
|
136
|
+
# Convert file name to file handle
|
137
|
+
#
|
138
|
+
# @api private
|
99
139
|
def convert_to_fd(object)
|
100
140
|
return object if fd?(object)
|
101
141
|
|
@@ -104,29 +144,22 @@ module TTY
|
|
104
144
|
end
|
105
145
|
|
106
146
|
tmp = ::Tempfile.new(::SecureRandom.uuid.split('-')[0])
|
107
|
-
|
108
147
|
content = try_reading(object)
|
109
148
|
tmp.write(content)
|
110
149
|
tmp.rewind
|
111
150
|
tmp
|
112
151
|
end
|
113
152
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
when Integer
|
123
|
-
object >= 0 ? IO.for_fd(object) : nil
|
124
|
-
when IO
|
125
|
-
object
|
126
|
-
when respond_to?(:to_io)
|
127
|
-
object.to_io
|
153
|
+
# Attempts to read object content
|
154
|
+
#
|
155
|
+
# @api private
|
156
|
+
def try_reading(object)
|
157
|
+
if object.respond_to?(:read)
|
158
|
+
object.read
|
159
|
+
elsif object.respond_to?(:to_s)
|
160
|
+
object.to_s
|
128
161
|
else
|
129
|
-
|
162
|
+
object
|
130
163
|
end
|
131
164
|
end
|
132
165
|
end # Execute
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module TTY
|
4
5
|
class Command
|
@@ -17,11 +18,14 @@ module TTY
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def info(cmd_name, result)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
"Running `#{cmd_name}` failed with\n" \
|
22
|
+
" exit status: #{result.exit_status}\n" \
|
23
|
+
" stdout: #{extract_output(result.out)}\n" \
|
24
|
+
" stderr: #{extract_output(result.err)}\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
def extract_output(value)
|
28
|
+
(value || '').strip.empty? ? 'Nothing written' : value.strip
|
25
29
|
end
|
26
30
|
end # ExitError
|
27
31
|
end # Command
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'thread'
|
4
5
|
|
@@ -37,7 +38,7 @@ module TTY
|
|
37
38
|
start = Time.now
|
38
39
|
runtime = 0.0
|
39
40
|
|
40
|
-
pid, stdin, stdout, stderr = spawn(cmd)
|
41
|
+
pid, stdin, stdout, stderr = spawn(cmd)
|
41
42
|
|
42
43
|
# write and read streams
|
43
44
|
write_stream(stdin)
|
@@ -48,7 +49,7 @@ module TTY
|
|
48
49
|
|
49
50
|
@printer.print_command_exit(cmd, status, runtime)
|
50
51
|
|
51
|
-
Result.new(status, stdout_data, stderr_data)
|
52
|
+
Result.new(status, stdout_data, stderr_data, runtime)
|
52
53
|
rescue
|
53
54
|
terminate(pid)
|
54
55
|
Result.new(-1, stdout_data, stderr_data)
|
@@ -62,7 +63,7 @@ module TTY
|
|
62
63
|
#
|
63
64
|
# @api public
|
64
65
|
def terminate(pid)
|
65
|
-
::Process.kill(@signal, pid)
|
66
|
+
::Process.kill(@signal, pid) rescue nil
|
66
67
|
end
|
67
68
|
|
68
69
|
private
|
@@ -111,7 +112,7 @@ module TTY
|
|
111
112
|
#
|
112
113
|
# @api private
|
113
114
|
def read_streams(stdout, stderr, &block)
|
114
|
-
stdout_data =
|
115
|
+
stdout_data = []
|
115
116
|
stderr_data = Truncator.new
|
116
117
|
|
117
118
|
print_out = -> (cmd, line) { @printer.print_command_out_data(cmd, line) }
|
@@ -131,7 +132,7 @@ module TTY
|
|
131
132
|
end
|
132
133
|
end
|
133
134
|
|
134
|
-
[stdout_data, stderr_data.read]
|
135
|
+
[stdout_data.join, stderr_data.read]
|
135
136
|
end
|
136
137
|
|
137
138
|
def read_stream(stream, data, print_callback, callback)
|
data/lib/tty/command/result.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module TTY
|
4
5
|
class Command
|
@@ -16,10 +17,17 @@ module TTY
|
|
16
17
|
attr_reader :err
|
17
18
|
alias stderr err
|
18
19
|
|
19
|
-
|
20
|
+
# Total command execution time
|
21
|
+
attr_reader :runtime
|
22
|
+
|
23
|
+
# Create a result
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def initialize(status, out, err, runtime = 0.0)
|
20
27
|
@status = status
|
21
28
|
@out = out
|
22
29
|
@err = err
|
30
|
+
@runtime = runtime
|
23
31
|
end
|
24
32
|
|
25
33
|
# Enumerate over output lines
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module TTY
|
4
5
|
class Command
|
@@ -33,14 +34,14 @@ module TTY
|
|
33
34
|
def write(content)
|
34
35
|
content = content.to_s.dup
|
35
36
|
|
36
|
-
content = append(content, @prefix)
|
37
|
+
content, @prefix = append(content, @prefix)
|
37
38
|
|
38
39
|
if (over = (content.bytesize - @max_size)) > 0
|
39
40
|
content = content.byteslice(over..-1)
|
40
41
|
@skipped += over
|
41
42
|
end
|
42
43
|
|
43
|
-
content = append(content, @suffix)
|
44
|
+
content, @suffix = append(content, @suffix)
|
44
45
|
|
45
46
|
# suffix is full but we still have content to write
|
46
47
|
while content.bytesize > 0
|
@@ -61,11 +62,7 @@ module TTY
|
|
61
62
|
return @prefix << @suffix
|
62
63
|
end
|
63
64
|
|
64
|
-
|
65
|
-
res << @prefix
|
66
|
-
res << "\n... omitting #{@skipped} bytes ...\n"
|
67
|
-
res << @suffix
|
68
|
-
res
|
65
|
+
@prefix + "\n... omitting #{@skipped} bytes ...\n" + @suffix
|
69
66
|
end
|
70
67
|
alias to_s read
|
71
68
|
|
@@ -95,14 +92,15 @@ module TTY
|
|
95
92
|
#
|
96
93
|
# @api private
|
97
94
|
def append(value, dst)
|
98
|
-
remain
|
95
|
+
remain = @max_size - dst.bytesize
|
96
|
+
remaining = ''
|
99
97
|
if remain > 0
|
100
98
|
value_bytes = value.to_s.bytesize
|
101
99
|
offset = value_bytes < remain ? value_bytes : remain
|
102
|
-
|
100
|
+
remaining = value.byteslice(0...offset)
|
103
101
|
value = value.byteslice(offset..-1)
|
104
102
|
end
|
105
|
-
value
|
103
|
+
[value, dst + remaining]
|
106
104
|
end
|
107
105
|
end # Truncator
|
108
106
|
end # Command
|
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.6.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-07-
|
11
|
+
date: 2017-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pastel
|