1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
require "open3"
module Shellany
# The Guard sheller abstract the actual subshell
# calls and allow easier stubbing.
#
class Sheller
attr_reader :status
# Creates a new Guard::Sheller object.
#
# @param [String] args a command to run in a subshell
# @param [Array<String>] args an array of command parts to run in a subshell
# @param [*String] args a list of command parts to run in a subshell
#
def initialize(*args)
fail ArgumentError, "no command given" if args.empty?
@command = args
@ran = false
end
# Shortcut for new(command).run
#
def self.run(*args)
new(*args).run
end
# Shortcut for new(command).run.stdout
#
def self.stdout(*args)
new(*args).stdout
end
# Shortcut for new(command).run.stderr
#
def self.stderr(*args)
new(*args).stderr
end
# Runs the command.
#
# @return [Boolean] whether or not the command succeeded.
#
def run
unless ran?
status, output, errors = self.class._system_with_capture(*@command)
@ran = true
@stdout = output
@stderr = errors
@status = status
end
ok?
end
# Returns true if the command has already been run, false otherwise.
#
# @return [Boolean] whether or not the command has already been run
#
def ran?
@ran
end
# Returns true if the command succeeded, false otherwise.
#
# @return [Boolean] whether or not the command succeeded
#
def ok?
run unless ran?
@status && @status.success?
end
# Returns the command's output.
#
# @return [String] the command output
#
def stdout
run unless ran?
@stdout
end
# Returns the command's error output.
#
# @return [String] the command output
#
def stderr
run unless ran?
@stderr
end
# No output capturing
#
# NOTE: `$stdout.puts system('cls')` on Windows won't work like
# it does for on systems with ansi terminals, so we need to be
# able to call Kernel.system directly.
def self.system(*args)
_system_with_no_capture(*args)
end
def self._system_with_no_capture(*args)
Kernel.system(*args)
result = $?
errors = (result == 0) || "Guard failed to run: #{args.inspect}"
[result, nil, errors]
end
def self._system_with_capture(*args)
# We use popen3, because it started working on recent versions
# of JRuby, while JRuby doesn't handle options to Kernel.system
args = _shellize_if_needed(args)
stdout, stderr, status = nil
Open3.popen3(*args) do |_stdin, _stdout, _stderr, _thr|
stdout = _stdout.read
stderr = _stderr.read
status = _thr.value
end
[status, stdout, stderr]
rescue Errno::ENOENT, IOError => e
[nil, nil, "Guard::Sheller failed (#{e.inspect})"]
end
# Only needed on JRUBY, because MRI properly detects ';' and metachars
def self._shellize_if_needed(args)
return args unless RUBY_PLATFORM == "java"
return args unless args.size == 1
return args unless /[;<>]/ =~ args.first
# NOTE: Sheller was originally meant for Guard (which basically only uses
# UNIX commands anyway) and JRuby doesn't support options to
# Kernel.system (and doesn't automatically shell when there's a
# metacharacter in the command).
#
# So ... I'm assuming /bin/sh exists - if not, PRs are welcome,
# because I have no clue what to do if /bin/sh doesn't exist.
# (use ENV["RUBYSHELL"] ? Detect cmd.exe ?)
["/bin/sh", "-c", args.first]
end
end
end
|