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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +49 -0
- data/LICENSE.txt +1 -1
- data/README.md +134 -64
- data/lib/tty-command.rb +1 -3
- data/lib/tty/command.rb +19 -18
- data/lib/tty/command/child_process.rb +23 -16
- data/lib/tty/command/cmd.rb +16 -12
- data/lib/tty/command/dry_runner.rb +4 -5
- data/lib/tty/command/exit_error.rb +1 -2
- data/lib/tty/command/printers/abstract.rb +11 -9
- data/lib/tty/command/printers/null.rb +1 -4
- data/lib/tty/command/printers/pretty.rb +29 -22
- data/lib/tty/command/printers/progress.rb +3 -8
- data/lib/tty/command/printers/quiet.rb +18 -8
- data/lib/tty/command/process_runner.rb +90 -67
- data/lib/tty/command/result.rb +0 -1
- data/lib/tty/command/truncator.rb +3 -4
- data/lib/tty/command/version.rb +2 -2
- metadata +24 -64
- data/.gitignore +0 -9
- data/.rspec +0 -4
- data/.travis.yml +0 -28
- data/CODE_OF_CONDUCT.md +0 -49
- data/Gemfile +0 -14
- data/Rakefile +0 -10
- data/appveyor.yml +0 -24
- data/benchmarks/memory.rb +0 -11
- data/bin/console +0 -6
- data/bin/setup +0 -6
- data/examples/bash.rb +0 -12
- data/examples/basic.rb +0 -9
- data/examples/cli +0 -4
- data/examples/env.rb +0 -9
- data/examples/logger.rb +0 -10
- data/examples/output.rb +0 -10
- data/examples/pty.rb +0 -7
- data/examples/redirect_stderr.rb +0 -10
- data/examples/redirect_stdin.rb +0 -16
- data/examples/redirect_stdout.rb +0 -10
- data/examples/stdin_input.rb +0 -10
- data/examples/threaded.rb +0 -12
- data/examples/timeout.rb +0 -7
- data/examples/wait.rb +0 -21
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-command.gemspec +0 -27
data/lib/tty-command.rb
CHANGED
data/lib/tty/command.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative
|
11
|
-
require_relative
|
12
|
-
require_relative
|
13
|
-
require_relative
|
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[
|
23
|
-
RbConfig::CONFIG[
|
24
|
-
RbConfig::CONFIG[
|
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[
|
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,
|
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(
|
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
|
5
|
-
require
|
6
|
-
require
|
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(
|
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(
|
34
|
-
out_rd, out_wr = pty ? PTY.open : IO.pipe(
|
35
|
-
err_rd, err_wr = pty ? PTY.open : IO.pipe(
|
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
|
-
|
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
|
-
|
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 ==
|
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(
|
198
|
+
tmp = ::Tempfile.new(::SecureRandom.uuid.split("-")[0])
|
192
199
|
content = try_reading(object)
|
193
200
|
tmp.write(content)
|
194
201
|
tmp.rewind
|
data/lib/tty/command/cmd.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
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,
|
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,
|
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(
|
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(
|
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
|
-
|
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]} &&
|
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
|
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(
|
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 ||
|
27
|
+
(value || "").strip.empty? ? "Nothing written" : value.strip
|
29
28
|
end
|
30
29
|
end # ExitError
|
31
30
|
end # Command
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
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
|
25
|
-
@color = ::Pastel.new(
|
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,51 +1,58 @@
|
|
1
|
-
|
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"
|
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(
|
17
|
-
write(message.join
|
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}",
|
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),
|
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
|
-
|
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
|
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,
|
42
|
-
|
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
|
-
|
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.
|
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(
|
70
|
+
decorate("successful", :green, :bold)
|
64
71
|
else
|
65
|
-
decorate(
|
72
|
+
decorate("failed", :red, :bold)
|
66
73
|
end
|
67
74
|
end
|
68
75
|
end # Pretty
|