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 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