/
sheller.rb
143 lines (122 loc) · 3.57 KB
/
sheller.rb
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
require "open3"
module Guard
# 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: guard basically only uses UNIX commands anyway
# while 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