tty-command 0.7.0 → 0.10.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.
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,3 +1 @@
1
- # encoding: utf-8
2
-
3
- require 'tty/command'
1
+ require_relative "tty/command"
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rbconfig'
4
-
5
- require_relative 'command/cmd'
6
- require_relative 'command/exit_error'
7
- require_relative 'command/dry_runner'
8
- require_relative 'command/process_runner'
9
- require_relative 'command/printers/null'
10
- require_relative 'command/printers/pretty'
11
- require_relative 'command/printers/progress'
12
- require_relative 'command/printers/quiet'
13
- require_relative 'command/version'
3
+ require "rbconfig"
4
+
5
+ require_relative "command/cmd"
6
+ require_relative "command/exit_error"
7
+ require_relative "command/dry_runner"
8
+ require_relative "command/process_runner"
9
+ require_relative "command/printers/null"
10
+ require_relative "command/printers/pretty"
11
+ require_relative "command/printers/progress"
12
+ require_relative "command/printers/quiet"
13
+ require_relative "command/version"
14
14
 
15
15
  module TTY
16
16
  class Command
@@ -19,9 +19,9 @@ module TTY
19
19
  TimeoutExceeded = Class.new(StandardError)
20
20
 
21
21
  # Path to the current Ruby
22
- RUBY = ENV['RUBY'] || ::File.join(
23
- RbConfig::CONFIG['bindir'],
24
- RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']
22
+ RUBY = ENV["RUBY"] || ::File.join(
23
+ RbConfig::CONFIG["bindir"],
24
+ RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]
25
25
  )
26
26
 
27
27
  WIN_PLATFORMS = /cygwin|mswin|mingw|bccwin|wince|emx/.freeze
@@ -35,7 +35,7 @@ module TTY
35
35
  end
36
36
 
37
37
  def self.windows?
38
- !!(RbConfig::CONFIG['host_os'] =~ WIN_PLATFORMS)
38
+ !!(RbConfig::CONFIG["host_os"] =~ WIN_PLATFORMS)
39
39
  end
40
40
 
41
41
  attr_reader :printer
@@ -59,6 +59,7 @@ module TTY
59
59
  @dry_run = options.fetch(:dry_run) { false }
60
60
  @printer = use_printer(@printer_name, color: @color, uuid: @uuid)
61
61
  @cmd_options = {}
62
+ @cmd_options[:verbose] = options.fetch(:verbose, true)
62
63
  @cmd_options[:pty] = true if options[:pty]
63
64
  @cmd_options[:binmode] = true if options[:binmode]
64
65
  @cmd_options[:timeout] = options[:timeout] if options[:timeout]
@@ -127,7 +128,7 @@ module TTY
127
128
  def wait(*args)
128
129
  pattern = args.pop
129
130
  unless pattern
130
- raise ArgumentError, 'Please provide condition to wait for'
131
+ raise ArgumentError, "Please provide condition to wait for"
131
132
  end
132
133
 
133
134
  run(*args) do |out, _|
@@ -173,7 +174,7 @@ module TTY
173
174
  # @api private
174
175
  def command(*args)
175
176
  cmd = Cmd.new(*args)
176
- cmd.update(@cmd_options)
177
+ cmd.update(**@cmd_options)
177
178
  cmd
178
179
  end
179
180
 
@@ -203,7 +204,7 @@ module TTY
203
204
  #
204
205
  # @api private
205
206
  def find_printer_class(name)
206
- const_name = name.to_s.capitalize.to_sym
207
+ const_name = name.to_s.split("_").map(&:capitalize).join.to_sym
207
208
  if const_name.empty? || !TTY::Command::Printers.const_defined?(const_name)
208
209
  raise ArgumentError, %(Unknown printer type "#{name}")
209
210
  end
@@ -1,9 +1,8 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'tempfile'
5
- require 'securerandom'
6
- require 'io/console'
3
+ require "tempfile"
4
+ require "securerandom"
5
+ require "io/console"
7
6
 
8
7
  module TTY
9
8
  class Command
@@ -25,14 +24,15 @@ module TTY
25
24
  process_opts = normalize_redirect_options(cmd.options)
26
25
  binmode = cmd.options[:binmode] || false
27
26
  pty = cmd.options[:pty] || false
27
+ verbose = cmd.options[:verbose]
28
28
 
29
- pty = try_loading_pty if pty
30
- require('pty') if pty # load within this scope
29
+ pty = try_loading_pty(verbose) if pty
30
+ require("pty") if pty # load within this scope
31
31
 
32
32
  # Create pipes
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
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
36
36
  in_wr.sync = true
37
37
 
38
38
  if binmode
@@ -65,8 +65,8 @@ module TTY
65
65
 
66
66
  pid = Process.spawn(cmd.to_command, opts)
67
67
 
68
- # close in parent process
69
- [in_rd, out_wr, err_wr].each { |fd| fd.close if fd }
68
+ # close streams in parent process talking to the child
69
+ close_fds(in_rd, out_wr, err_wr)
70
70
 
71
71
  tuple = [pid, in_wr, out_rd, err_rd]
72
72
 
@@ -75,7 +75,7 @@ module TTY
75
75
  return yield(*tuple)
76
76
  ensure
77
77
  # ensure parent pipes are closed
78
- [in_wr, out_rd, err_rd].each { |fd| fd.close if fd && !fd.closed? }
78
+ close_fds(in_wr, out_rd, err_rd)
79
79
  end
80
80
  else
81
81
  tuple
@@ -83,16 +83,23 @@ module TTY
83
83
  end
84
84
  module_function :spawn
85
85
 
86
+ # Close all streams
87
+ # @api private
88
+ def close_fds(*fds)
89
+ fds.each { |fd| fd && !fd.closed? && fd.close }
90
+ end
91
+ module_function :close_fds
92
+
86
93
  # Try loading pty module
87
94
  #
88
95
  # @return [Boolean]
89
96
  #
90
97
  # @api private
91
- def try_loading_pty
98
+ def try_loading_pty(verbose = false)
92
99
  require 'pty'
93
100
  true
94
101
  rescue LoadError
95
- warn("Requested PTY device but the system doesn't support it.")
102
+ warn("Requested PTY device but the system doesn't support it.") if verbose
96
103
  false
97
104
  end
98
105
  module_function :try_loading_pty
@@ -125,7 +132,7 @@ module TTY
125
132
  key = fd_to_process_key(spawn_key)
126
133
  value = spawn_value
127
134
 
128
- if key.to_s == 'in'
135
+ if key.to_s == "in"
129
136
  value = convert_to_fd(spawn_value)
130
137
  end
131
138
 
@@ -188,7 +195,7 @@ module TTY
188
195
  return object
189
196
  end
190
197
 
191
- tmp = ::Tempfile.new(::SecureRandom.uuid.split('-')[0])
198
+ tmp = ::Tempfile.new(::SecureRandom.uuid.split("-")[0])
192
199
  content = try_reading(object)
193
200
  tmp.write(content)
194
201
  tmp.rewind
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
4
- require 'shellwords'
3
+ require "securerandom"
4
+ require "shellwords"
5
5
 
6
6
  module TTY
7
7
  class Command
@@ -22,6 +22,9 @@ module TTY
22
22
  # @api public
23
23
  attr_reader :uuid
24
24
 
25
+ # Flag that controls whether to print the output only on error or not
26
+ attr_reader :only_output_on_error
27
+
25
28
  # Initialize a new Cmd object
26
29
  #
27
30
  # @api private
@@ -30,14 +33,14 @@ module TTY
30
33
  if env_or_cmd.respond_to?(:to_hash)
31
34
  @env = env_or_cmd
32
35
  unless command = args.shift
33
- raise ArgumentError, 'Cmd requires command argument'
36
+ raise ArgumentError, "Cmd requires command argument"
34
37
  end
35
38
  else
36
39
  command = env_or_cmd
37
40
  end
38
41
 
39
42
  if args.empty? && cmd = command.to_s
40
- raise ArgumentError, 'No command provided' if cmd.empty?
43
+ raise ArgumentError, "No command provided" if cmd.empty?
41
44
  @command = sanitize(cmd)
42
45
  @argv = []
43
46
  else
@@ -52,14 +55,15 @@ module TTY
52
55
  @env ||= {}
53
56
  @options = opts
54
57
 
55
- @uuid = SecureRandom.uuid.split('-')[0]
58
+ @uuid = SecureRandom.uuid.split("-")[0]
59
+ @only_output_on_error = opts.fetch(:only_output_on_error) { false }
56
60
  freeze
57
61
  end
58
62
 
59
63
  # Extend command options if keys don't already exist
60
64
  #
61
65
  # @api public
62
- def update(**options)
66
+ def update(options)
63
67
  @options.update(options.update(@options))
64
68
  end
65
69
 
@@ -75,12 +79,12 @@ module TTY
75
79
  converted_key = key.is_a?(Symbol) ? key.to_s.upcase : key.to_s
76
80
  escaped_val = val.to_s.gsub(/"/, '\"')
77
81
  %(#{converted_key}="#{escaped_val}")
78
- end.join(' ')
82
+ end.join(" ")
79
83
  end
80
84
 
81
85
  def evars(value, &block)
82
86
  return (value || block) unless environment.any?
83
- %(( export #{environment_string} ; %s )) % [value || block.call]
87
+ "( export #{environment_string} ; #{value || block.call} )"
84
88
  end
85
89
 
86
90
  def umask(value)
@@ -90,12 +94,12 @@ module TTY
90
94
 
91
95
  def chdir(value)
92
96
  return value unless options[:chdir]
93
- %(cd #{options[:chdir]} && %s) % [value]
97
+ %(cd #{Shellwords.escape(options[:chdir])} && #{value})
94
98
  end
95
99
 
96
100
  def user(value)
97
101
  return value unless options[:user]
98
- vars = environment.any? ? "#{environment_string} " : ''
102
+ vars = environment.any? ? "#{environment_string} " : ""
99
103
  %(sudo -u #{options[:user]} #{vars}-- sh -c '%s') % [value]
100
104
  end
101
105
 
@@ -119,7 +123,7 @@ module TTY
119
123
 
120
124
  # @api public
121
125
  def to_s
122
- [command.to_s, *Array(argv)].join(' ')
126
+ [command.to_s, *Array(argv)].join(" ")
123
127
  end
124
128
 
125
129
  # @api public
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require_relative 'result'
3
+ require_relative "result"
5
4
 
6
5
  module TTY
7
6
  class Command
@@ -18,10 +17,10 @@ module TTY
18
17
  # @api public
19
18
  def run!(*)
20
19
  cmd.to_command
21
- message = "#{@printer.decorate('(dry run)', :blue)} " +
20
+ message = "#{@printer.decorate("(dry run)", :blue)} " +
22
21
  @printer.decorate(cmd.to_command, :yellow, :bold)
23
- @printer.write(message, cmd.uuid)
24
- Result.new(0, '', '')
22
+ @printer.write(cmd, message, cmd.uuid)
23
+ Result.new(0, "", "")
25
24
  end
26
25
  end # DryRunner
27
26
  end # Command
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module TTY
@@ -25,7 +24,7 @@ module TTY
25
24
  end
26
25
 
27
26
  def extract_output(value)
28
- (value || '').strip.empty? ? 'Nothing written' : value.strip
27
+ (value || "").strip.empty? ? "Nothing written" : value.strip
29
28
  end
30
29
  end # ExitError
31
30
  end # Command
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- require 'pastel'
1
+ require "pastel"
4
2
 
5
3
  module TTY
6
4
  class Command
@@ -11,6 +9,7 @@ module TTY
11
9
  def_delegators :@color, :decorate
12
10
 
13
11
  attr_reader :output, :options
12
+ attr_accessor :out_data, :err_data
14
13
 
15
14
  # Initialize a Printer object
16
15
  #
@@ -21,8 +20,11 @@ module TTY
21
20
  def initialize(output, options = {})
22
21
  @output = output
23
22
  @options = options
24
- @enabled = options.fetch(:color) { true }
25
- @color = ::Pastel.new(output: output, enabled: @enabled)
23
+ @enabled = options.fetch(:color, true)
24
+ @color = ::Pastel.new(enabled: @enabled)
25
+
26
+ @out_data = ""
27
+ @err_data = ""
26
28
  end
27
29
 
28
30
  def print_command_start(cmd, *args)
@@ -30,18 +32,18 @@ module TTY
30
32
  end
31
33
 
32
34
  def print_command_out_data(cmd, *args)
33
- write(args.join(' '))
35
+ write(args.join(" "))
34
36
  end
35
37
 
36
38
  def print_command_err_data(cmd, *args)
37
- write(args.join(' '))
39
+ write(args.join(" "))
38
40
  end
39
41
 
40
42
  def print_command_exit(cmd, *args)
41
- write(args.join(' '))
43
+ write(args.join(" "))
42
44
  end
43
45
 
44
- def write(message)
46
+ def write(cmd, message)
45
47
  raise NotImplemented, "Abstract printer cannot be used"
46
48
  end
47
49
  end # Abstract
@@ -1,7 +1,4 @@
1
- # encoding: utf-8
2
- # frozen_string_literal: true
3
-
4
- require_relative 'abstract'
1
+ require_relative "abstract"
5
2
 
6
3
  module TTY
7
4
  class Command
@@ -1,51 +1,58 @@
1
- # encoding: utf-8
2
- # frozen_string_literal: true
3
-
4
- require 'pastel'
5
-
6
- require_relative 'abstract'
1
+ require_relative "abstract"
7
2
 
8
3
  module TTY
9
4
  class Command
10
5
  module Printers
11
6
  class Pretty < Abstract
12
- TIME_FORMAT = "%5.3f %s".freeze
7
+ TIME_FORMAT = "%5.3f %s"
8
+
9
+ def initialize(*)
10
+ super
11
+ @uuid = options.fetch(:uuid, true)
12
+ end
13
13
 
14
14
  def print_command_start(cmd, *args)
15
15
  message = ["Running #{decorate(cmd.to_command, :yellow, :bold)}"]
16
- message << args.map(&:chomp).join(' ') unless args.empty?
17
- write(message.join, cmd.uuid)
16
+ message << args.map(&:chomp).join(" ") unless args.empty?
17
+ write(cmd, message.join)
18
18
  end
19
19
 
20
20
  def print_command_out_data(cmd, *args)
21
- message = args.map(&:chomp).join(' ')
22
- write("\t#{message}", cmd.uuid)
21
+ message = args.map(&:chomp).join(" ")
22
+ write(cmd, "\t#{message}", out_data)
23
23
  end
24
24
 
25
25
  def print_command_err_data(cmd, *args)
26
- message = args.map(&:chomp).join(' ')
27
- write("\t" + decorate(message, :red), cmd.uuid)
26
+ message = args.map(&:chomp).join(" ")
27
+ write(cmd, "\t" + decorate(message, :red), err_data)
28
28
  end
29
29
 
30
30
  def print_command_exit(cmd, status, runtime, *args)
31
- runtime = TIME_FORMAT % [runtime, pluralize(runtime, 'second')]
31
+ if cmd.only_output_on_error && !status.zero?
32
+ output << out_data
33
+ output << err_data
34
+ end
35
+
36
+ runtime = TIME_FORMAT % [runtime, pluralize(runtime, "second")]
32
37
  message = ["Finished in #{runtime}"]
33
38
  message << " with exit status #{status}" if status
34
39
  message << " (#{success_or_failure(status)})"
35
- write(message.join, cmd.uuid)
40
+ write(cmd, message.join)
36
41
  end
37
42
 
38
43
  # Write message out to output
39
44
  #
40
45
  # @api private
41
- def write(message, uuid = nil)
42
- uuid_needed = options.fetch(:uuid) { true }
46
+ def write(cmd, message, data = nil)
47
+ cmd_set_uuid = cmd.options.fetch(:uuid, true)
48
+ uuid_needed = cmd.options[:uuid].nil? ? @uuid : cmd_set_uuid
43
49
  out = []
44
50
  if uuid_needed
45
- out << "[#{decorate(uuid, :green)}] " unless uuid.nil?
51
+ out << "[#{decorate(cmd.uuid, :green)}] " unless cmd.uuid.nil?
46
52
  end
47
53
  out << "#{message}\n"
48
- output << out.join
54
+ target = (cmd.only_output_on_error && !data.nil?) ? data : output
55
+ target << out.join
49
56
  end
50
57
 
51
58
  private
@@ -54,15 +61,15 @@ module TTY
54
61
  #
55
62
  # @api private
56
63
  def pluralize(count, word)
57
- "#{word}#{'s' unless count.to_f == 1}"
64
+ "#{word}#{'s' unless count.to_i == 1}"
58
65
  end
59
66
 
60
67
  # @api private
61
68
  def success_or_failure(status)
62
69
  if status == 0
63
- decorate('successful', :green, :bold)
70
+ decorate("successful", :green, :bold)
64
71
  else
65
- decorate('failed', :red, :bold)
72
+ decorate("failed", :red, :bold)
66
73
  end
67
74
  end
68
75
  end # Pretty