tty-command 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b9086c13127af2f7d61b0577426a34ea6e71929c
4
- data.tar.gz: fbb089e7cc7d6937571ab84c3700856bb6f7eac7
3
+ metadata.gz: c3d696d9fa5255f95622561988eddfcb26650824
4
+ data.tar.gz: cf0fe3027e2e2958b55a1c99ce1f9769ccbd717a
5
5
  SHA512:
6
- metadata.gz: 3eeb7cc3fc245a9bc126ee2948c9d02ebae2e88df4f83a4c3ead72e4ee8b1597ac087b039448f843ab4f305575957dbde65f86ff7813101678a801aabc3f37a3
7
- data.tar.gz: 028e2be35a74088e0015bb60577a4cbdf462b31e727221aa7e3d72a7c2530c68f47bdff8ed1593e55d1af249f1e006d6078d15d55dbbc59d839fc35a5fac4a6b
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 facility like so:
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 the streams 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. The pair value can be a filename for redirection.
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, :in => "/dev/null") # read mode
291
- cmd.run(:ls, :out => "/dev/null") # write mode
292
- cmd.run(:ls, :err => "log") # write mode
293
- cmd.run(:ls, [:out, :err] => "/dev/null") # write mode
294
- cmd.run(:ls, 3 => "/dev/null") # read mode
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
- You can also provide actual file descriptor for redirection:
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
- For example, to merge stderr into stdout you would do:
325
+ You can, for example, read data from one source and output to another:
304
326
 
305
327
  ```ruby
306
- cmd.run(:ls, '-la', :stderr => :stdout)
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
@@ -4,7 +4,7 @@ require 'tty-command'
4
4
 
5
5
  cmd = TTY::Command.new
6
6
 
7
- out, err = cmd.run("echo 'hello' 1>& 2")
7
+ out, err = cmd.run("echo 'hello'", :out => :err)
8
8
 
9
9
  puts "out: #{out}"
10
10
  puts "err: #{err}"
@@ -4,4 +4,7 @@ require 'tty-command'
4
4
 
5
5
  cmd = TTY::Command.new
6
6
 
7
- cmd.run(:ls, :out => 'ls_sample')
7
+ out, err = cmd.run(:ls, :out => 'ls.log')
8
+
9
+ puts "OUT>> #{out}"
10
+ puts "ERR>> #{err}"
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 'rbconfig'
4
5
 
@@ -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!(&block)
19
+ def run!(*)
19
20
  cmd.to_command
20
- message = "#{@printer.decorate('(dry run)', :blue)} "
21
- message << @printer.decorate(cmd.to_command, :yellow, :bold)
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
@@ -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
- }).merge(process_opts)
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
- process_key = fd_to_process_key(key)
62
- if process_key.to_s == 'in'
63
- value = convert_to_fd(value)
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
- true
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
- false
110
+ respond_to?(:to_i) && !object.to_io.nil?
86
111
  end
87
112
  end
88
113
 
89
- def try_reading(object)
90
- if object.respond_to?(:read)
91
- object.read
92
- elsif object.respond_to?(:to_s)
93
- object.to_s
94
- else
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
- def fd_to_process_key(object)
115
- case object
116
- when STDIN, $stdin, :in, :stdin, 0
117
- :in
118
- when STDOUT, $stdout, :out, :stdout, 1
119
- :out
120
- when STDERR, $stderr, :err, :stderr, 2
121
- :err
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
- raise ExecuteError, "Wrong execute redirect: #{object.inspect}"
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
- message = ''
21
- message << "Running `#{cmd_name}` failed with\n"
22
- message << " exit status: #{result.exit_status}\n"
23
- message << " stdout: #{(result.out || '').strip.empty? ? 'Nothing written' : result.out.strip}\n"
24
- message << " stderr: #{(result.err || '').strip.empty? ? 'Nothing written' : result.err.strip}\n"
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) # do |pid, stdin, stdout, stderr|
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)
@@ -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
- def initialize(status, out, err)
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
- res = ''
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 = @max_size - dst.bytesize
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
- dst << value.byteslice(0...offset)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Command
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.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.5.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-16 00:00:00.000000000 Z
11
+ date: 2017-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pastel